initial site
This commit is contained in:
commit
da5b259d06
167 files changed
+34157
No files matched your search
@@ -0,0 +1,257 @@
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import useEmblaCarousel from "embla-carousel-react";
|
||||
import Autoplay from "embla-carousel-autoplay";
|
||||
import { ArrowRight, Bot, ChevronLeft, ChevronRight, Coffee, Flame, Sparkles } from "lucide-react";
|
||||
import { cn } from "../utils/cn";
|
||||
import { Reveal, SectionHeading } from "./shared";
|
||||
import { signatureMixes, strengthLabels, type SignatureMix } from "../data/catalog";
|
||||
|
||||
const items = signatureMixes;
|
||||
|
||||
function MenuCard({ item, index }: { item: SignatureMix; index: number }) {
|
||||
return (
|
||||
<article className="group relative h-full rounded-3xl bg-gradient-to-br from-neon-violet/50 via-white/10 to-neon-cyan/40 p-[1px] transition-all duration-500 hover:-translate-y-2 hover:shadow-[0_24px_70px_-18px_rgba(176,38,255,0.4)]">
|
||||
<div className="holo-sheen relative flex h-full flex-col rounded-[calc(1.5rem-1px)] bg-panel/85 p-5 backdrop-blur-xl sm:p-7">
|
||||
<span className="pointer-events-none absolute right-5 top-4 select-none font-display text-6xl font-extrabold text-white/[0.05]">
|
||||
0{index + 1}
|
||||
</span>
|
||||
|
||||
{item.badge && (
|
||||
<span
|
||||
className={cn(
|
||||
"inline-flex w-fit items-center rounded-full border px-3 py-1 text-[10px] font-bold uppercase tracking-[0.2em]",
|
||||
item.badge.hot
|
||||
? "border-neon-violet/60 bg-neon-violet/15 text-fuchsia-200 shadow-[0_0_18px_rgba(176,38,255,0.35)]"
|
||||
: "border-white/15 bg-white/5 text-mist/60"
|
||||
)}
|
||||
>
|
||||
{item.badge.label}
|
||||
</span>
|
||||
)}
|
||||
|
||||
<h3 className="mt-5 font-display text-lg font-bold leading-snug text-white transition-colors duration-300 group-hover:text-cyan-100 sm:text-xl">
|
||||
{item.name}
|
||||
</h3>
|
||||
<p className="mt-3 flex-1 text-sm leading-relaxed text-mist/65">{item.desc}</p>
|
||||
|
||||
{/* strength */}
|
||||
<div className="mt-6 flex items-center gap-2.5">
|
||||
<span className="text-[10px] font-bold uppercase tracking-[0.25em] text-mist/45">
|
||||
Крепость · {strengthLabels[item.strength]}
|
||||
</span>
|
||||
<span className="flex items-center gap-1.5">
|
||||
{[1, 2, 3].map((d) => (
|
||||
<span
|
||||
key={d}
|
||||
className={cn(
|
||||
"h-1.5 w-1.5 rounded-full",
|
||||
d <= item.strength
|
||||
? "bg-neon-cyan shadow-[0_0_8px_rgba(0,240,255,0.9)]"
|
||||
: "bg-white/15"
|
||||
)}
|
||||
/>
|
||||
))}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="mt-5 h-px bg-gradient-to-r from-neon-violet/40 via-white/10 to-neon-cyan/40" />
|
||||
|
||||
<div className="mt-5 flex items-center justify-between">
|
||||
<span className="font-display text-2xl font-bold text-white text-glow-cyan">
|
||||
{item.price}
|
||||
</span>
|
||||
<a
|
||||
href="#contacts"
|
||||
aria-label={`Забронировать и попробовать ${item.name}`}
|
||||
className="flex h-11 w-11 items-center justify-center rounded-full border border-white/15 text-mist/70 transition-all duration-300 group-hover:border-neon-cyan/60 group-hover:text-neon-cyan group-hover:shadow-[0_0_20px_rgba(0,240,255,0.45)]"
|
||||
>
|
||||
<ArrowRight size={18} />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
);
|
||||
}
|
||||
|
||||
export default function MenuCarousel() {
|
||||
const autoplay = useRef(
|
||||
Autoplay({ delay: 5200, stopOnInteraction: false, stopOnMouseEnter: true })
|
||||
);
|
||||
const [emblaRef, emblaApi] = useEmblaCarousel(
|
||||
{ loop: true, align: "start" },
|
||||
[autoplay.current]
|
||||
);
|
||||
const [canPrev, setCanPrev] = useState(true);
|
||||
const [canNext, setCanNext] = useState(true);
|
||||
|
||||
const prev = useCallback(() => emblaApi?.scrollPrev(), [emblaApi]);
|
||||
const next = useCallback(() => emblaApi?.scrollNext(), [emblaApi]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!emblaApi) return;
|
||||
const update = () => {
|
||||
setCanPrev(emblaApi.canScrollPrev());
|
||||
setCanNext(emblaApi.canScrollNext());
|
||||
};
|
||||
update();
|
||||
emblaApi.on("select", update);
|
||||
return () => {
|
||||
emblaApi.off("select", update);
|
||||
};
|
||||
}, [emblaApi]);
|
||||
|
||||
return (
|
||||
<section id="prices" className="relative overflow-hidden py-24 sm:py-32">
|
||||
{/* smoky backdrop */}
|
||||
<div className="absolute inset-0">
|
||||
<img
|
||||
src="./images/menu-bg.jpg"
|
||||
alt=""
|
||||
aria-hidden
|
||||
className="h-full w-full object-cover opacity-25"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-b from-void via-void/70 to-void" />
|
||||
</div>
|
||||
|
||||
<div className="relative mx-auto max-w-7xl px-5 sm:px-8">
|
||||
<div className="flex flex-col gap-8 sm:flex-row sm:items-end sm:justify-between">
|
||||
<Reveal>
|
||||
<SectionHeading
|
||||
align="left"
|
||||
tag="Меню"
|
||||
title={
|
||||
<>
|
||||
Что <span className="text-glow-cyan text-cyan-300">попробовать</span>
|
||||
</>
|
||||
}
|
||||
desc="Авторские миксы от наших кальянных мастеров. Не нашёл своё — соберём любой вкус под тебя. Цена единая — без сюрпризов в чеке."
|
||||
/>
|
||||
</Reveal>
|
||||
<Reveal delay={120} className="hidden sm:block">
|
||||
<div className="flex gap-3">
|
||||
<button
|
||||
onClick={prev}
|
||||
disabled={!canPrev}
|
||||
aria-label="Назад"
|
||||
className="glass flex h-12 w-12 items-center justify-center rounded-full text-white transition-all duration-300 hover:border-neon-violet/60 hover:shadow-[0_0_24px_rgba(176,38,255,0.45)] disabled:opacity-40 disabled:hover:shadow-none"
|
||||
>
|
||||
<ChevronLeft size={22} />
|
||||
</button>
|
||||
<button
|
||||
onClick={next}
|
||||
disabled={!canNext}
|
||||
aria-label="Вперёд"
|
||||
className="glass flex h-12 w-12 items-center justify-center rounded-full text-white transition-all duration-300 hover:border-neon-cyan/60 hover:shadow-[0_0_24px_rgba(0,240,255,0.45)] disabled:opacity-40 disabled:hover:shadow-none"
|
||||
>
|
||||
<ChevronRight size={22} />
|
||||
</button>
|
||||
</div>
|
||||
</Reveal>
|
||||
</div>
|
||||
|
||||
<Reveal delay={150}>
|
||||
<div className="mt-10 grid gap-3 sm:grid-cols-2 sm:gap-4">
|
||||
<div className="relative overflow-hidden rounded-3xl border border-neon-violet/40 bg-gradient-to-br from-neon-violet/25 via-panel/90 to-panel/90 p-5 shadow-[0_0_45px_-12px_rgba(176,38,255,0.6)] sm:p-7">
|
||||
<div className="pointer-events-none absolute -right-12 -top-12 h-40 w-40 rounded-full bg-neon-violet/30 blur-3xl" />
|
||||
<div className="relative flex flex-wrap items-center gap-x-4 gap-y-3">
|
||||
<div className="flex flex-1 basis-[190px] items-center gap-3 sm:gap-4">
|
||||
<span className="flex shrink-0 items-center justify-center rounded-2xl border border-neon-violet/50 bg-neon-violet/20 p-2.5 text-fuchsia-200 shadow-[0_0_22px_rgba(176,38,255,0.5)] sm:p-3.5">
|
||||
<Flame size={20} className="sm:hidden" />
|
||||
<Flame size={24} className="hidden sm:block" />
|
||||
</span>
|
||||
<div>
|
||||
<p className="font-display text-base font-bold text-white sm:text-xl">
|
||||
Кальян от
|
||||
</p>
|
||||
<p className="mt-1 text-[10px] font-semibold uppercase tracking-[0.18em] text-mist/55 sm:text-xs sm:tracking-[0.2em]">
|
||||
от 1 400 ₽
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<span className="text-glow-violet ml-auto shrink-0 whitespace-nowrap font-display text-2xl font-extrabold text-white sm:text-4xl">
|
||||
1 400 ₽
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative overflow-hidden rounded-3xl border border-neon-cyan/35 bg-gradient-to-br from-neon-cyan/15 via-panel/90 to-panel/90 p-5 shadow-[0_0_45px_-12px_rgba(0,240,255,0.45)] sm:p-7">
|
||||
<div className="pointer-events-none absolute -right-12 -top-12 h-40 w-40 rounded-full bg-neon-cyan/20 blur-3xl" />
|
||||
<div className="relative flex flex-wrap items-center gap-x-4 gap-y-3">
|
||||
<div className="flex flex-1 basis-[190px] items-center gap-3 sm:gap-4">
|
||||
<span className="flex shrink-0 items-center justify-center rounded-2xl border border-neon-cyan/50 bg-neon-cyan/10 p-2.5 text-neon-cyan shadow-[0_0_22px_rgba(0,240,255,0.4)] sm:p-3.5">
|
||||
<Coffee size={20} className="sm:hidden" />
|
||||
<Coffee size={24} className="hidden sm:block" />
|
||||
</span>
|
||||
<div>
|
||||
<p className="font-display text-base font-bold text-white sm:text-xl">
|
||||
Авторский чай
|
||||
</p>
|
||||
<p className="mt-1 text-[10px] font-semibold uppercase tracking-[0.18em] text-mist/55 sm:text-xs sm:tracking-[0.2em]">
|
||||
Чайник на компанию
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<span className="text-glow-cyan ml-auto shrink-0 whitespace-nowrap font-display text-2xl font-extrabold text-white sm:text-4xl">
|
||||
350 ₽
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Reveal>
|
||||
|
||||
<Reveal delay={180}>
|
||||
<div ref={emblaRef} className="mt-10 cursor-grab overflow-hidden active:cursor-grabbing">
|
||||
<div className="-ml-4 flex pb-3">
|
||||
{items.map((item, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="min-w-0 shrink-0 grow-0 basis-[88%] pl-4 pr-6 sm:basis-[58%] lg:basis-[36%] xl:basis-[31%]"
|
||||
>
|
||||
<MenuCard item={item} index={i} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</Reveal>
|
||||
|
||||
<Reveal delay={260}>
|
||||
<div className="mt-10 flex flex-col items-center justify-between gap-5 sm:flex-row">
|
||||
<p className="max-w-md text-center text-xs leading-relaxed text-mist/45 sm:text-left">
|
||||
* Кальян от <span className="font-bold text-mist/70">1 400 ₽</span>,
|
||||
чай — <span className="font-bold text-mist/70">350 ₽</span>.
|
||||
</p>
|
||||
<div className="flex flex-wrap justify-center gap-3 sm:justify-end">
|
||||
<a
|
||||
href="#/ai-mix"
|
||||
className="inline-flex items-center gap-2 rounded-full border border-neon-cyan/60 bg-gradient-to-r from-neon-violet/25 via-neon-cyan/25 to-neon-violet/20 px-6 py-3.5 text-sm font-bold text-white shadow-[0_0_25px_rgba(0,240,255,0.35)] transition-all duration-300 hover:-translate-y-0.5 hover:border-neon-cyan hover:shadow-[0_0_38px_rgba(0,240,255,0.6)]"
|
||||
>
|
||||
<Bot size={16} className="text-neon-cyan" />
|
||||
Автоподбор микса
|
||||
<Sparkles size={14} className="text-cyan-200" />
|
||||
</a>
|
||||
<a
|
||||
href="#/mixes"
|
||||
className="inline-flex items-center gap-2.5 rounded-full border border-neon-violet/50 bg-neon-violet/10 px-6 py-3.5 text-sm font-bold text-white transition-all duration-300 hover:-translate-y-0.5 hover:border-neon-cyan hover:shadow-[0_0_28px_rgba(0,240,255,0.4)]"
|
||||
>
|
||||
Все миксы
|
||||
<ArrowRight size={16} />
|
||||
</a>
|
||||
<a
|
||||
href="#/menu"
|
||||
className="inline-flex items-center gap-2.5 rounded-full bg-gradient-to-r from-neon-violet to-[#7a1fd6] px-7 py-3.5 text-sm font-bold text-white shadow-[0_0_25px_rgba(176,38,255,0.4)] transition-all duration-300 hover:-translate-y-0.5 hover:shadow-[0_0_40px_rgba(176,38,255,0.65)]"
|
||||
>
|
||||
Карта меню
|
||||
<ArrowRight size={16} />
|
||||
</a>
|
||||
<a
|
||||
href="#contacts"
|
||||
className="inline-flex items-center gap-2.5 rounded-full border border-neon-violet/50 bg-neon-violet/10 px-7 py-3.5 text-sm font-bold text-white transition-all duration-300 hover:-translate-y-0.5 hover:border-neon-cyan hover:shadow-[0_0_28px_rgba(0,240,255,0.4)]"
|
||||
>
|
||||
Забронировать
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</Reveal>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user