/* =========================================
   Home Page - Editorial Redesign
   ========================================= */

.home-page {
    --home-ink: #111216;
    --home-ink-soft: #3b4250;
    --home-muted: #667085;
    --home-line: rgba(17, 24, 39, 0.08);
    --home-line-strong: rgba(17, 24, 39, 0.16);
    --home-surface: rgba(255, 255, 255, 0.86);
    --home-surface-solid: #ffffff;
    --home-shadow-sm: 0 12px 28px rgba(17, 24, 39, 0.06);
    --home-shadow-md: 0 22px 54px rgba(17, 24, 39, 0.10);
    --home-shadow-lg: 0 36px 84px rgba(17, 24, 39, 0.14);
    --home-rose: #df5c98;
    --home-rose-deep: #bd3f7c;
    --home-blush: #fde9f3;
    --home-peach: #ffd7b4;
    --home-aqua: #89e0d8;
    --home-lime: #d7f28d;
    --home-cream: #fff9f2;
    --home-radius-xl: 30px;
    --home-radius-lg: 22px;
    --home-radius-md: 16px;
    --home-shell-max: 1228px;
    --home-shell-gutter: clamp(14px, 2.4vw, 28px);
    background:
        radial-gradient(820px 300px at 50% 12%, rgba(137, 224, 216, 0.06), transparent 70%),
        radial-gradient(900px 420px at 8% -3%, rgba(223, 92, 152, 0.10), transparent 62%),
        radial-gradient(760px 320px at 94% 5%, rgba(255, 215, 180, 0.14), transparent 64%),
        linear-gradient(180deg, #fff 0%, #fffafc 42%, #fff 100%);
    color: var(--home-ink);
    font-family: "Manrope", "Inter", "Segoe UI", "Prompt", sans-serif;
    overflow-x: clip;
}

.home-page a {
    text-decoration: none;
}

.home-page img {
    max-width: 100%;
}

.home-shell {
    width: min(var(--home-shell-max), 100%);
    margin-inline: auto;
    padding-inline: var(--home-shell-gutter);
    box-sizing: border-box;
    position: relative;
}


.home-featured.home-shell {
    max-width: 1228px;
}

/* =========================================
   HERO - "the strand strings itself"
   The thread draws, the category beads land on it, then the copy clears in.
   Beads are placed in percentages taken from the path's arc length, in the
   same 720x460 space as the SVG viewBox, so the two stay aligned at any width
   with no transform scaling anywhere.
   ========================================= */

.hero {
    --clay: #47231a;
    --clay-deep: #34180f;
    --clay-lift: #5c3125;
    --cream: #fff4ea;
    --cream-dim: rgba(255, 244, 234, 0.72);
    --ember: #ef8354;
    --thread: rgba(255, 244, 234, 0.34);
    position: relative;
    isolation: isolate;
    overflow: hidden;
    min-height: clamp(600px, 80vh, 800px);
    display: flex;
    align-items: center;
    background: var(--clay);
    color: var(--cream);
}

/* One light source, not five: a warm high-right glow plus a single soft shaft
   echoing the hard afternoon sun in the product photography. */
.hero__field {
    position: absolute;
    inset: 0;
    z-index: 0;
    pointer-events: none;
    background:
        /* The base layer below is tilted 168deg, which leaves the bottom edge a
           different colour on the left than on the right. The campaign section
           starts flat, so that mismatch showed up as a visible join. This layer
           settles the last stretch onto flat --clay-deep, which is exactly what
           the next section opens with. */
        linear-gradient(180deg, transparent 62%, var(--clay-deep) 100%),
        radial-gradient(1100px 620px at 82% 6%, rgba(239, 131, 84, 0.26), transparent 62%),
        linear-gradient(104deg, transparent 0 38%, rgba(255, 244, 234, 0.05) 44%, transparent 52%),
        linear-gradient(168deg, var(--clay-lift) 0%, var(--clay) 46%, var(--clay-deep) 100%);
}

.hero__inner {
    position: relative;
    z-index: 1;
    display: grid;
    grid-template-columns: minmax(0, 0.86fr) minmax(0, 1.14fr);
    align-items: center;
    gap: clamp(24px, 3vw, 56px);
    padding-block: clamp(96px, 11vh, 132px) clamp(64px, 8vh, 96px);
}

/* ---- copy ---------------------------------------------------------- */

.hero__copy > * {
    /* `backwards` (not `forwards`) so the resting state is the plain CSS.
       If the animation never runs, the copy is simply already visible. */
    animation: heroRise 0.9s cubic-bezier(0.16, 1, 0.3, 1) backwards;
}

.hero__title {
    margin: 0;
    max-width: 13ch;
    font-family: "Fraunces", "Cormorant Garamond", Georgia, "Prompt", serif;
    font-weight: 700;
    font-size: clamp(2.7rem, 5.4vw, 5.2rem);
    /* Thai stacks vowels and tone marks; 0.9 would clip them. */
    line-height: 1.1;
    /* Negative tracking collides Thai tone marks with their base glyphs. */
    letter-spacing: 0;
    text-wrap: balance;
    overflow-wrap: break-word;
    animation-delay: 0.1s;
}

.hero__subtitle {
    margin: 20px 0 0;
    max-width: 40ch;
    font-size: 1.02rem;
    line-height: 1.75;
    color: var(--cream-dim);
    animation-delay: 0.26s;
}

.hero__actions {
    margin-top: 32px;
    display: flex;
    flex-wrap: wrap;
    gap: 12px;
    animation-delay: 0.4s;
}

.hero__btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: 48px;
    padding: 0 26px;
    border-radius: 999px;
    font-size: 0.98rem;
    font-weight: 600;
    letter-spacing: 0;
    transition: transform 0.32s cubic-bezier(0.2, 0.8, 0.2, 1), background-color 0.32s ease, color 0.32s ease;
}

.hero__btn--solid {
    /* Dark ink on ember, not white: white on #EF8354 is only 2.4:1. */
    background: var(--ember);
    color: #2a1009;
    box-shadow: 0 14px 34px rgba(239, 131, 84, 0.24);
}

.hero__btn--solid:hover {
    background: #ff9668;
    transform: translateY(-2px);
}

.hero__btn--ghost {
    border: 1px solid rgba(255, 244, 234, 0.3);
    color: var(--cream);
}

.hero__btn--ghost:hover {
    background: rgba(255, 244, 234, 0.1);
    transform: translateY(-2px);
}

.hero__btn:focus-visible,
.bead:focus-visible,
.hero__scroll:focus-visible {
    outline: 2px solid var(--cream);
    outline-offset: 3px;
}

/* ---- the strand ----------------------------------------------------- */

/* The stage is a pure percentage space. Beads are placed with left/top taken
   from the path's arc length as a share of the 720x460 viewBox, and the stage
   holds that same aspect ratio, so the two stay aligned at every width. No
   transform scaling means labels keep their real size and nothing depends on
   offset-path or container-query units. */
.strand {
    justify-self: end;
    width: 100%;
    max-width: 720px;
}

.strand__stage {
    position: relative;
    width: 100%;
    aspect-ratio: 720 / 460;
}

.strand__thread {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    overflow: visible;
    /* Both ends of the path run past the viewBox. Fading them out is what
       makes the strand read as continuing rather than stopping in mid-air. */
    -webkit-mask-image: linear-gradient(90deg, transparent 0%, #000 9%, #000 88%, transparent 100%);
    mask-image: linear-gradient(90deg, transparent 0%, #000 9%, #000 88%, transparent 100%);
}

.strand__thread path {
    fill: none;
    stroke: var(--thread);
    stroke-width: 1.5;
    stroke-linecap: round;
    /* pathLength="1" on the element makes these unit-independent. The resting
       state is fully drawn and the animation runs `backwards` from hidden, so
       a throttled or disabled animation can never leave the thread unfinished. */
    stroke-dasharray: 1;
    stroke-dashoffset: 0;
    animation: threadDraw 1.6s cubic-bezier(0.16, 1, 0.3, 1) backwards;
}

/* The slot marks the point on the thread; the bead centres itself on it. */
.bead-slot {
    position: absolute;
    /* left/top come from the template, per bead. */
}

.bead {
    position: absolute;
    display: grid;
    place-items: center;
    width: 100%;
    translate: -50% -50%;
}

.bead__inner {
    display: block;
    position: relative;
    width: 100%;
    aspect-ratio: 1;
    border-radius: 50%;
    overflow: hidden;
    background: var(--clay-deep);
    box-shadow:
        0 0 0 1px rgba(255, 244, 234, 0.22),
        0 22px 44px rgba(0, 0, 0, 0.38);
    /* Resting state is the finished state; `backwards` holds the entry frame
       only until the delay elapses, so hover transitions work afterwards. */
    transform: scale(1);
    opacity: 1;
    transition: transform 0.45s cubic-bezier(0.2, 0.8, 0.2, 1), box-shadow 0.45s ease;
    animation: beadIn 0.8s cubic-bezier(0.16, 1, 0.3, 1) backwards;
    animation-delay: var(--delay);
}

/* The image is rendered `zoom` times the size of the bead and then shifted so
   that its focal point (fx, fy) sits in the middle of the circle. `scale()`
   cannot do this: it always magnifies around the centre of the picture, which
   on these flat-lays is bare cloth. Percentages on `translate` resolve against
   the image's own (already enlarged) box, so fx/fy are read in image space. */
.bead__inner img {
    position: absolute;
    left: 50%;
    top: 50%;
    width: calc(100% * var(--zoom, 1));
    height: calc(100% * var(--zoom, 1));
    max-width: none;
    object-fit: cover;
    display: block;
    translate: calc(-1 * var(--fx, 50%)) calc(-1 * var(--fy, 50%));
}

/* The width lives on the slot, not the bead: the slot is positioned against
   the stage, so a percentage resolves there. Percentages of 720 give 46, 58,
   74, 104 and 128 CSS px at full size, and the strand scales as one object. */
.bead-slot--xs { width: 6.4%; }
.bead-slot--sm { width: 8.1%; }
.bead-slot--md { width: 10.3%; }
.bead-slot--lg { width: 14.4%; }
.bead-slot--xl { width: 17.8%; }

.bead--quiet .bead__inner {
    opacity: 0.82;
    box-shadow:
        0 0 0 1px rgba(255, 244, 234, 0.14),
        0 14px 28px rgba(0, 0, 0, 0.3);
}

a.bead:hover .bead__inner {
    transform: scale(1.09);
    box-shadow:
        0 0 0 1px rgba(255, 244, 234, 0.5),
        0 28px 54px rgba(0, 0, 0, 0.44);
}

.bead__tag {
    position: absolute;
    top: calc(100% + 10px);
    left: 50%;
    translate: -50% -4px;
    padding: 5px 12px;
    border-radius: 999px;
    background: var(--cream);
    color: #2a1009;
    font-size: 0.8rem;
    font-weight: 600;
    white-space: nowrap;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.28s ease, translate 0.28s cubic-bezier(0.2, 0.8, 0.2, 1);
}

a.bead:hover .bead__tag,
a.bead:focus-visible .bead__tag {
    opacity: 1;
    translate: -50% 0;
}

/* ---- scroll cue ------------------------------------------------------ */

.hero__scroll {
    position: absolute;
    z-index: 2;
    left: 50%;
    bottom: 26px;
    /* Centring lives in `translate`, not `transform`. The old rule paired
       `transform: translateX(-50%)` with a keyframe that set `translateY(18px)`;
       with mismatched function lists the browser falls back to matrix
       interpolation, so the cue slid in diagonally from the left instead of
       rising straight up. */
    translate: -50% 0;
    display: inline-flex;
    flex-direction: column;
    align-items: center;
    gap: 10px;
    font-size: 0.8rem;
    color: var(--cream-dim);
    /* Enters last, after the strand has finished landing. */
    animation: scrollCueRise 1.05s cubic-bezier(0.16, 1, 0.3, 1) 1.35s backwards;
}

.hero__scroll-line {
    position: relative;
    width: 1px;
    height: 40px;
    overflow: hidden;
    background: linear-gradient(180deg, transparent, var(--thread));
}

/* A short highlight runs up the line on a loop, so the cue keeps repeating the
   "rises from below" gesture rather than animating once and going still.
   Transform only — no layout properties are animated. */
.hero__scroll-line::after {
    content: "";
    position: absolute;
    inset: 0 0 auto 0;
    height: 14px;
    background: linear-gradient(180deg, transparent, var(--cream));
    animation: scrollCueTravel 2.8s cubic-bezier(0.4, 0, 0.2, 1) 2.4s infinite;
}

.hero__scroll:hover {
    color: var(--cream);
}

.hero__scroll:hover .hero__scroll-line::after {
    animation-duration: 1.5s;
}

/* ---- motion ---------------------------------------------------------- */

@keyframes threadDraw {
    from { stroke-dashoffset: 1; }
}

@keyframes beadIn {
    from {
        opacity: 0;
        transform: scale(0.28) rotate(-20deg);
    }
}

@keyframes heroRise {
    from {
        opacity: 0;
        transform: translateY(18px);
    }
}

@keyframes scrollCueRise {
    from {
        opacity: 0;
        translate: -50% 52px;
    }
}

@keyframes scrollCueTravel {
    0% {
        translate: 0 40px;
        opacity: 0;
    }
    20% {
        opacity: 0.95;
    }
    75% {
        opacity: 0;
    }
    100% {
        translate: 0 -14px;
        opacity: 0;
    }
}

/* ---- utilities ------------------------------------------------------- */

.hero__sr {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}

/* =========================================
   CAMPAIGN — tags hanging from the strand
   The hero's thread carries on as a rail; each promotion is a paper tag hung
   from it by a cord, pivoting about the hole it is threaded through. The
   section keeps the hero's clay field so the two read as one surface.
   ========================================= */

.campaign {
    /* Picks up exactly where the hero's field ends, so there is no seam. */
    --clay: #47231a;
    --clay-deep: #34180f;
    --clay-lift: #5c3125;
    --cream: #fff4ea;
    --ink: #2a1009;
    --ember: #ef8354;
    --cord: rgba(255, 244, 234, 0.34);

    position: relative;
    /* The rail deliberately runs past the content shell. Once the shell fills
       the viewport there is no margin left to bleed into, so clip here rather
       than let it push the page into horizontal scroll. */
    overflow-x: clip;
    /* The page turns light again after this section, and cutting straight from
       clay to near-white was the harshest edge on the page. The clay dissolves
       instead — and it dissolves to *transparent*, not to white, so the join
       lands on the page's own background whatever colour that happens to be at
       this scroll position. Ending on #fff left a hairline against #fffafc.
       The alpha is front-loaded so the dark holds and the light floods up
       quickly, rather than washing the middle out to grey. */
    background: linear-gradient(180deg,
        rgba(52, 24, 15, 1) 0%,
        rgba(71, 35, 26, 1) 30%,
        rgba(71, 35, 26, 1) 60%,
        rgba(71, 35, 26, 0.93) 72%,
        rgba(71, 35, 26, 0.68) 82%,
        rgba(71, 35, 26, 0.32) 91%,
        rgba(71, 35, 26, 0) 100%);
    color: var(--cream);
    /* The bottom padding has to clear the dissolve below, or the last tag ends
       up sitting inside it. */
    padding-block: clamp(64px, 8vw, 104px) clamp(200px, 19vw, 260px);
}

.campaign__head {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    justify-content: space-between;
    gap: 16px;
    margin-bottom: clamp(48px, 6vw, 76px);
}

.campaign__title {
    margin: 0;
    font-family: "Fraunces", "Cormorant Garamond", Georgia, "Prompt", serif;
    font-weight: 700;
    font-size: clamp(1.9rem, 3.4vw, 2.9rem);
    line-height: 1.15;
    /* Zero, not negative: this heading renders Thai. */
    letter-spacing: 0;
}

.campaign__all {
    color: rgba(255, 244, 234, 0.78);
    font-size: 0.95rem;
    font-weight: 600;
    padding-bottom: 2px;
    border-bottom: 1px solid rgba(255, 244, 234, 0.28);
    transition: color 0.3s ease, border-color 0.3s ease;
}

.campaign__all:hover {
    color: var(--cream);
    border-color: var(--cream);
}

/* ---- the rail --------------------------------------------------------- */

.campaign__rail {
    position: relative;
    margin: 0;
    padding: 0;
    list-style: none;
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    align-items: start;
    gap: clamp(18px, 2.4vw, 34px);
}

/* One hairline across the top, fading at both ends the same way the hero
   thread does, so it reads as the same piece of string. */
.campaign__rail::before {
    content: "";
    position: absolute;
    top: 0;
    left: -6vw;
    right: -6vw;
    height: 1px;
    background: var(--cord);
    -webkit-mask-image: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent);
    mask-image: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent);
}

/* ---- a tag ------------------------------------------------------------ */

.campaign-tag {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    padding-top: 34px;
    /* The pivot is the point on the rail, which is what makes the swing read
       as a hanging object rather than a rotating box. */
    transform-origin: top center;
}

.campaign-tag__cord {
    position: absolute;
    top: 0;
    left: 50%;
    width: 1px;
    height: 34px;
    translate: -50% 0;
    background: var(--cord);
}

.campaign-tag__card {
    position: relative;
    isolation: isolate;
    overflow: hidden;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 12px;
    width: 100%;
    padding: 42px 26px 26px;
    border-radius: 14px;
    background: var(--cream);
    color: var(--ink);
    box-shadow: 0 26px 54px rgba(0, 0, 0, 0.34);
    transform-origin: top center;
    transition: translate 0.4s cubic-bezier(0.2, 0.8, 0.2, 1), box-shadow 0.4s ease;
}

/* The punched hole the cord runs through. */
.campaign-tag__hole {
    position: absolute;
    top: 15px;
    left: 50%;
    width: 11px;
    height: 11px;
    translate: -50% 0;
    border-radius: 50%;
    background: var(--clay);
    box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.5);
}

.campaign-tag__chip {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 5px 12px;
    border-radius: 999px;
    background: rgba(42, 16, 9, 0.08);
    font-size: 0.78rem;
    font-weight: 600;
    letter-spacing: 0;
}

.campaign-tag__stamp {
    padding: 2px 8px;
    border-radius: 999px;
    background: var(--ember);
    color: var(--ink);
    font-weight: 700;
}

.campaign-tag__offer {
    font-size: clamp(1.15rem, 1.7vw, 1.5rem);
    font-weight: 700;
    line-height: 1.35;
    letter-spacing: 0;
    text-wrap: balance;
}

.campaign-tag__desc {
    font-size: 0.92rem;
    /* Thai stacks vowels and tone marks; this is a legibility floor. */
    line-height: 1.7;
    color: rgba(42, 16, 9, 0.72);
}

.campaign-tag__cta {
    margin-top: 6px;
    display: inline-flex;
    align-items: center;
    gap: 8px;
    min-height: 44px;
    padding: 0 20px;
    border-radius: 999px;
    background: var(--ink);
    color: var(--cream);
    font-size: 0.9rem;
    font-weight: 600;
    transition: background-color 0.3s ease;
}

.campaign-tag__card:hover .campaign-tag__cta {
    background: var(--ember);
    color: var(--ink);
}

/* The mystery tag is the odd one out on purpose: darker stock, and the photo
   behind it is blurred because not seeing the contents is the offer. */
.campaign-tag--mystery .campaign-tag__card {
    background: var(--clay-lift);
    color: var(--cream);
}

.campaign-tag--mystery .campaign-tag__hole {
    background: var(--clay-deep);
}

.campaign-tag--mystery .campaign-tag__chip {
    background: rgba(255, 244, 234, 0.14);
    color: var(--cream);
}

.campaign-tag--mystery .campaign-tag__desc {
    color: rgba(255, 244, 234, 0.76);
}

.campaign-tag--mystery .campaign-tag__cta {
    background: var(--cream);
    color: var(--ink);
}

.campaign-tag__veil {
    position: absolute;
    inset: 0;
    z-index: -1;
    width: 100%;
    height: 100%;
    object-fit: cover;
    filter: blur(13px) saturate(0.85);
    opacity: 0.58;
    scale: 1.2;
}

.campaign-tag__card:focus-visible {
    outline: 2px solid var(--cream);
    outline-offset: 4px;
}

/* ---- motion ------------------------------------------------------------ */

/* Resting state is the finished state. `is-armed` is added by script only when
   IntersectionObserver exists and reduced motion is off, so with no JS the
   tags are simply already hanging. */
.campaign__rail.is-armed .campaign-tag {
    opacity: 0;
    translate: 0 -58px;
    rotate: -7deg;
}

.campaign__rail.is-armed .campaign-tag.is-hung {
    opacity: 1;
    translate: 0 0;
    rotate: 0deg;
    animation: tagSettle 1.15s cubic-bezier(0.22, 1, 0.3, 1) var(--drop-delay) backwards;
}

/* Drops, overshoots, then swings itself still — a pendulum losing energy. */
@keyframes tagSettle {
    0% {
        opacity: 0;
        translate: 0 -58px;
        rotate: -7deg;
    }
    45% {
        opacity: 1;
        translate: 0 0;
        rotate: 4.5deg;
    }
    65% { rotate: -2.4deg; }
    80% { rotate: 1.2deg; }
    91% { rotate: -0.5deg; }
    100% { rotate: 0deg; }
}

/* Hover nudges the card on its cord; the lift and the swing are separate
   properties so neither overwrites the other. */
.campaign-tag__card:hover {
    translate: 0 -4px;
    box-shadow: 0 34px 66px rgba(0, 0, 0, 0.42);
    animation: tagNudge 1s ease-out;
}

@keyframes tagNudge {
    0%, 100% { rotate: 0deg; }
    22% { rotate: 1.8deg; }
    55% { rotate: -1deg; }
    82% { rotate: 0.4deg; }
}

/* =========================================
   CATEGORIES — full-bleed horizontal strip
   The section breaks out of the content shell; only the header and the
   progress line stay on the grid. The first card lines up with the shell edge
   so the break-out reads as deliberate rather than as a stray margin.
   ========================================= */

.cats {
    /* Distance from the viewport edge to the shell's text column. Uses 100%
       rather than 100vw so a visible scrollbar cannot push the row sideways. */
    --cats-inset: max(var(--home-shell-gutter),
                      calc((100% - var(--home-shell-max)) / 2 + var(--home-shell-gutter)));
    --cats-card: clamp(248px, 27vw, 384px);

    position: relative;
    padding-block: clamp(56px, 7vw, 92px) clamp(48px, 6vw, 76px);
}

.cats__head {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    justify-content: space-between;
    gap: 16px;
    margin-bottom: clamp(24px, 3vw, 38px);
}

.cats__title {
    margin: 0;
    font-family: "Fraunces", "Cormorant Garamond", Georgia, "Prompt", serif;
    font-weight: 700;
    font-size: clamp(1.8rem, 3.2vw, 2.7rem);
    line-height: 1.15;
    /* Zero, not negative: this heading renders Thai. */
    letter-spacing: 0;
    color: var(--home-ink);
}

.cats__tools {
    display: flex;
    align-items: center;
    gap: 18px;
}

.cats__all {
    color: var(--home-ink-soft);
    font-size: 0.95rem;
    font-weight: 600;
    padding-bottom: 2px;
    border-bottom: 1px solid var(--home-line-strong);
    transition: color 0.3s ease, border-color 0.3s ease;
}

.cats__all:hover {
    color: var(--home-ink);
    border-color: var(--home-ink);
}

.cats__arrows {
    display: flex;
    gap: 8px;
}

.cats__arrow {
    width: 44px;
    height: 44px;
    display: grid;
    place-items: center;
    border-radius: 50%;
    border: 1px solid var(--home-line-strong);
    background: transparent;
    color: var(--home-ink);
    font-size: 1rem;
    cursor: pointer;
    transition: background-color 0.3s ease, border-color 0.3s ease, opacity 0.3s ease;
}

.cats__arrow:hover:not(:disabled) {
    background: var(--home-ink);
    border-color: var(--home-ink);
    color: #fff;
}

.cats__arrow:disabled {
    opacity: 0.3;
    cursor: default;
}

.cats__arrow:focus-visible {
    outline: 2px solid var(--home-ink);
    outline-offset: 3px;
}

/* ---- the strip -------------------------------------------------------- */

.cats__track {
    margin: 0;
    padding: 4px var(--cats-inset) 18px;
    list-style: none;
    display: flex;
    gap: clamp(12px, 1.6vw, 22px);
    overflow-x: auto;
    overscroll-behavior-x: contain;
    scroll-snap-type: x mandatory;
    /* Snapped cards stop level with the header, not against the viewport edge. */
    scroll-padding-inline-start: var(--cats-inset);
    /* Three other affordances exist (arrows, progress line, a peeking card),
       so the native bar is hidden to keep the strip clean. */
    scrollbar-width: none;
}

.cats__track::-webkit-scrollbar {
    display: none;
}

.cats__item {
    flex: 0 0 var(--cats-card);
    scroll-snap-align: start;
}

.cat-card {
    display: flex;
    flex-direction: column;
    gap: 14px;
}

.cat-card__frame {
    position: relative;
    display: block;
    overflow: hidden;
    border-radius: 14px;
    aspect-ratio: 4 / 5;
    background: var(--home-light-gray, #f4f1ee);
    box-shadow: var(--home-shadow-sm);
}

.cat-card__frame img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
    transition: scale 0.7s cubic-bezier(0.2, 0.8, 0.2, 1);
}

.cat-card:hover .cat-card__frame img {
    scale: 1.05;
}

.cat-card__foot {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    /* The name sits under the photo rather than on a capsule over it, so the
       product is never covered by its own label. */
    padding-bottom: 8px;
    border-bottom: 1px solid var(--home-line);
    transition: border-color 0.35s ease;
}

.cat-card:hover .cat-card__foot {
    border-color: var(--home-ink);
}

.cat-card__name {
    font-size: 1.02rem;
    font-weight: 600;
    letter-spacing: 0;
    color: var(--home-ink);
    transition: translate 0.35s cubic-bezier(0.2, 0.8, 0.2, 1);
}

.cat-card:hover .cat-card__name {
    translate: 4px 0;
}

.cat-card__go {
    color: var(--home-muted);
    transition: translate 0.35s cubic-bezier(0.2, 0.8, 0.2, 1), color 0.35s ease;
}

.cat-card:hover .cat-card__go {
    translate: 5px 0;
    color: var(--home-ink);
}

.cat-card:focus-visible {
    outline: 2px solid var(--home-ink);
    outline-offset: 5px;
    border-radius: 16px;
}

/* ---- progress --------------------------------------------------------- */

.cats__progress {
    height: 2px;
    border-radius: 999px;
    background: var(--home-line);
    overflow: hidden;
}

.cats__progress-fill {
    display: block;
    height: 100%;
    border-radius: inherit;
    background: var(--home-ink);
    transform: scaleX(0);
    transform-origin: left center;
    transition: transform 0.18s linear;
}

/* ---- motion ------------------------------------------------------------ */

/* Cards rest in place; `is-armed` is added by script only when the observer
   exists and reduced motion is off, so with no JS the strip is simply there. */
/* The transform goes on the card, never on the snap item: moving a snap item
   makes `scroll-snap-type: mandatory` re-snap, which scrolled the strip by the
   offset distance and knocked the first card out of line with the heading. */
.cats__track.is-armed .cat-card {
    opacity: 0;
    translate: 42px 0;
}

.cats__track.is-armed .cats__item.is-in .cat-card {
    opacity: 1;
    translate: 0 0;
    transition: opacity 0.7s ease, translate 0.7s cubic-bezier(0.16, 1, 0.3, 1);
    transition-delay: var(--in-delay);
}

/* As a card travels across the strip its photo drifts inside its frame — the
   depth cue that makes a scroller feel built rather than listed. Chrome and
   Safari only; everywhere else the photo simply sits still. */
@supports (animation-timeline: view()) {
    @media (prefers-reduced-motion: no-preference) {
        .cat-card__frame img {
            animation: catDrift linear both;
            animation-timeline: view(inline);
            animation-range: cover 0% cover 100%;
        }

        @keyframes catDrift {
            from { translate: -4% 0; }
            to { translate: 4% 0; }
        }
    }
}

/* FEATURED PRODUCTS */
.home-featured {
    position: relative;
    padding: 76px 0 110px;
}

.home-featured::before {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    top: 44px;
    height: 220px;
    border-radius: 28px;
    background:
        radial-gradient(400px 150px at 12% 30%, rgba(223, 92, 152, 0.08), transparent 72%),
        radial-gradient(500px 160px at 88% 25%, rgba(255, 215, 180, 0.10), transparent 74%);
    pointer-events: none;
}

.home-featured > * {
    position: relative;
    z-index: 1;
}

.home-featured__header {
    position: relative;
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: 12px;
    border-bottom: 1px solid var(--home-line);
    margin-bottom: 18px;
    padding-bottom: 12px;
}

.home-featured__header::after {
    content: "";
    position: absolute;
    right: 0;
    bottom: -1px;
    width: min(160px, 28%);
    height: 2px;
    border-radius: 999px;
    background: linear-gradient(90deg, transparent, rgba(223, 92, 152, 0.34), rgba(255, 215, 180, 0.42));
}

.home-featured__title {
    position: relative;
    display: inline-block;
    margin: 0;
    color: var(--home-ink);
    font-family: "Fraunces", "Cormorant Garamond", Georgia, "Prompt", serif;
    font-size: clamp(1.75rem, 2.5vw, 2.45rem);
    line-height: 0.95;
    letter-spacing: -0.02em;
}

.home-featured__title::before {
    content: "";
    position: absolute;
    left: -0.12em;
    right: -0.06em;
    bottom: 0.02em;
    height: 0.38em;
    z-index: -1;
    border-radius: 8px;
    background: linear-gradient(90deg, rgba(255, 215, 180, 0.16), rgba(223, 92, 152, 0.12), transparent 92%);
    transform: skewX(-10deg);
}

.home-featured__link {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 999px;
    padding: 9px 12px;
    border: 1px solid var(--home-line);
    background: rgba(255, 255, 255, 0.82);
    color: #2f3643;
    font-size: 0.78rem;
    font-weight: 800;
    letter-spacing: 0.05em;
    transition: transform 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease;
}

.home-featured__link:hover {
    transform: translateY(-1px);
    border-color: var(--home-line-strong);
    box-shadow: 0 10px 18px rgba(17, 24, 39, 0.05);
}

.home-featured__panel {
    position: relative;
    border-radius: 28px;
    border: 1px solid rgba(17, 24, 39, 0.06);
    background:
        radial-gradient(220px 120px at 14% 8%, rgba(137, 224, 216, 0.06), transparent 72%),
        radial-gradient(circle at 1px 1px, rgba(17, 24, 39, 0.018) 1px, transparent 1.2px) 0 0/18px 18px,
        linear-gradient(180deg, rgba(255, 255, 255, 0.94) 0%, rgba(255, 255, 255, 0.98) 100%);
    box-shadow: 0 26px 60px rgba(17, 24, 39, 0.08);
    padding: 20px;
}

.home-featured__panel::before {
    content: "";
    position: absolute;
    inset: 9px;
    border-radius: 18px;
    border: 1px solid rgba(17, 24, 39, 0.03);
    pointer-events: none;
}

.home-featured__panel::after {
    content: "";
    position: absolute;
    right: 14px;
    top: 14px;
    width: 118px;
    height: 118px;
    border-radius: 22px;
    background:
        radial-gradient(circle at 30% 32%, rgba(223, 92, 152, 0.16), transparent 62%),
        radial-gradient(circle at 78% 66%, rgba(255, 215, 180, 0.18), transparent 66%);
    opacity: 0.9;
    pointer-events: none;
    animation: panelBlobFloat 11s ease-in-out infinite -2.4s;
}

.home-product-grid {
    --bs-gutter-x: 1rem;
    --bs-gutter-y: 1rem;
}

.product-card-link {
    display: block;
    height: 100%;
    color: inherit;
}

.product-card {
    position: relative;
    overflow: hidden;
    isolation: isolate;
    display: flex;
    flex-direction: column;
    height: 100%;
    padding: 9px;
    border-radius: 18px;
    border: 1px solid rgba(17, 24, 39, 0.06);
    background:
        radial-gradient(140px 90px at 16% 14%, rgba(223, 92, 152, 0.06), transparent 72%),
        radial-gradient(140px 90px at 86% 8%, rgba(137, 224, 216, 0.06), transparent 74%),
        linear-gradient(180deg, #ffffff 0%, #fffdff 100%);
    box-shadow: 0 12px 26px rgba(17, 24, 39, 0.05);
    transition: transform 0.24s ease, box-shadow 0.24s ease, border-color 0.24s ease;
}

.product-card::before {
    content: "";
    position: absolute;
    inset: -26% auto auto -16%;
    width: 106px;
    height: 106px;
    border-radius: 999px;
    background: radial-gradient(circle, rgba(255, 255, 255, 0.92) 0%, rgba(255, 255, 255, 0.08) 58%, transparent 72%);
    opacity: 0.26;
    pointer-events: none;
    transition: transform 0.34s ease, opacity 0.34s ease;
}

.product-card::after {
    content: "";
    position: absolute;
    right: 10px;
    top: 10px;
    width: 8px;
    height: 8px;
    border-radius: 999px;
    background: rgba(223, 92, 152, 0.35);
    box-shadow:
        10px 4px 0 rgba(255, 215, 180, 0.35),
        2px 12px 0 rgba(137, 224, 216, 0.28);
    pointer-events: none;
}

.product-card:hover {
    transform: translateY(-4px);
    border-color: rgba(17, 24, 39, 0.10);
    box-shadow: 0 18px 34px rgba(17, 24, 39, 0.08);
}

.product-card:hover::before {
    transform: translate(10px, 8px) scale(1.14);
    opacity: 0.4;
}

.product-image-shell {
    position: relative;
    overflow: hidden;
    aspect-ratio: 1 / 1.02;
    border-radius: 14px;
    padding: 7px;
    border: 1px solid rgba(17, 24, 39, 0.06);
    background:
        radial-gradient(120px 90px at 22% 18%, rgba(223, 92, 152, 0.08), transparent 74%),
        radial-gradient(120px 90px at 80% 14%, rgba(255, 215, 180, 0.12), transparent 76%),
        linear-gradient(180deg, #fbfbfc 0%, #f4f5f7 100%);
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, 0.92),
        0 12px 24px rgba(17, 24, 39, 0.04);
    transition: border-color 0.24s ease, box-shadow 0.24s ease;
}

.product-card:hover .product-image-shell {
    border-color: rgba(17, 24, 39, 0.10);
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, 0.92),
        0 14px 28px rgba(17, 24, 39, 0.07);
}

.badge-label {
    position: absolute;
    top: 10px;
    left: 10px;
    z-index: 2;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 999px;
    padding: 5px 8px;
    border: 1px solid rgba(17, 24, 39, 0.07);
    background: rgba(255, 255, 255, 0.96);
    color: #111;
    font-size: 0.61rem;
    font-weight: 800;
    letter-spacing: 0.08em;
    line-height: 1;
    text-transform: uppercase;
    box-shadow: 0 8px 16px rgba(17, 24, 39, 0.06);
}

.badge-label-sale {
    background: linear-gradient(180deg, #f05a52 0%, #df443d 100%);
    border-color: #ef544c;
    color: #fff;
}

.product-image {
    width: 100%;
    height: 100%;
    display: block;
    object-fit: contain;
    object-position: center;
    border-radius: 10px;
    transition: transform 0.65s ease;
}

.product-card:hover .product-image {
    transform: scale(1.05);
}

.product-body {
    position: relative;
    padding: 12px 5px 3px;
    text-align: center;
}

.product-body::before {
    content: "";
    display: block;
    width: 46px;
    height: 2px;
    margin: 0 auto 10px;
    border-radius: 999px;
    background: linear-gradient(90deg, rgba(223, 92, 152, 0.12), rgba(255, 215, 180, 0.55), rgba(137, 224, 216, 0.12));
}

.product-title {
    margin: 0 0 6px;
    color: #161a23;
    font-size: 0.88rem;
    font-weight: 700;
    letter-spacing: 0.03em;
    line-height: 1.35;
    text-transform: uppercase;
    min-height: 2.6em;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    transition: color 0.2s ease;
}

.product-card-link:hover .product-title {
    color: #9f2f69;
}

.product-price {
    margin: 0;
    min-height: 1.7rem;
    color: #4f5663;
    font-size: 0.88rem;
    font-weight: 500;
}

.product-price-original {
    font-size: 0.84em;
}

.home-empty-state {
    min-height: 340px;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    text-align: center;
    border-radius: 20px;
    border: 1px dashed rgba(17, 24, 39, 0.10);
    background:
        radial-gradient(220px 120px at 50% 12%, rgba(223, 92, 152, 0.08), transparent 70%),
        linear-gradient(180deg, #fff 0%, #fffafc 100%);
    padding: 22px;
}

.home-empty-state__icon {
    font-size: 2.6rem;
    color: rgba(223, 92, 152, 0.28);
    margin-bottom: 12px;
}

.home-empty-state__title {
    margin: 0;
    color: var(--home-ink);
    font-family: "Fraunces", Georgia, "Prompt", serif;
    font-size: 1.35rem;
    line-height: 1;
    letter-spacing: -0.01em;
}

.home-empty-state__text {
    margin: 10px 0 0;
    max-width: 460px;
    color: #667085;
    line-height: 1.55;
    font-size: 0.9rem;
}

/* FOOTER SPACING */
.home-page .site-footer {
    margin-top: 0;
}

/* ---- campaign responsive --------------------------------------------
   Three hanging tags need real width. Below 768px a third of the viewport is
   narrower than the tag's own padding plus a Thai line, so the rail is dropped
   and the tags stack, each keeping a short cord so the metaphor survives. */

@media (max-width: 768px) {
    .campaign__rail {
        grid-template-columns: minmax(0, 1fr);
        gap: 38px;
    }

    .campaign__rail::before {
        display: none;
    }

    .campaign-tag {
        padding-top: 22px;
    }

    .campaign-tag__cord {
        height: 22px;
    }

    .campaign-tag__card {
        padding: 36px 22px 22px;
    }

    .campaign__head {
        margin-bottom: 34px;
    }
}

/* ---- hero responsive -------------------------------------------------
   The strand is percentage-based, so the curve simply narrows with its column
   and keeps working. Two columns are held all the way down to 860px for that
   reason: stacking earlier put a wide curve under centred copy, where it read
   as loose floating circles rather than a strand. Below 860px the curve is
   dropped for four category tiles. */

@media (max-width: 860px) {
    .hero__inner {
        grid-template-columns: 1fr;
        justify-items: center;
        text-align: center;
        gap: 8px;
    }

    .hero__copy {
        max-width: 640px;
    }

    .hero__title,
    .hero__subtitle {
        max-width: none;
        margin-inline: auto;
    }

    .hero__actions {
        justify-content: center;
    }
}

@media (max-width: 860px) {
    .hero {
        min-height: 0;
    }

    .hero__inner {
        padding-block: 92px 56px;
    }

    .hero__title {
        font-size: clamp(2.15rem, 8vw, 3.2rem);
    }

    .hero__subtitle {
        font-size: 0.97rem;
    }

    .hero__btn {
        flex: 1 1 auto;
    }

    /* Below this width the curve would scale to roughly 0.49, which leaves the
       decorative beads too small to read and packs the labels close enough to
       collide. The strand stops being a curve and becomes four plain category
       tiles at full size — same links, same photos, no tiny type. */
    .strand {
        max-width: none;
    }

    .strand__thread {
        display: none;
    }

    /* A 2x2 grid rather than flex-wrap: the nowrap labels push each flex item
       past half the container, so wrapping gave one tile per row and a hero
       twice as tall as it needed to be. */
    .strand__stage {
        width: auto;
        aspect-ratio: auto;
        display: grid;
        grid-template-columns: repeat(2, minmax(0, 1fr));
        justify-items: center;
        gap: 24px 10px;
        padding-top: 12px;
    }

    /* Hide the slot, not just the bead inside it: an empty slot is still a
       grid item and would leave three blank cells in the 2x2 layout. */
    .bead-slot--quiet {
        display: none;
    }

    .bead-slot {
        position: static;
        width: min(116px, 100%);
    }

    .bead {
        position: relative;
        translate: none;
    }

    .bead__tag {
        position: static;
        translate: none;
        opacity: 1;
        margin-top: 10px;
        font-size: 0.82rem;
    }

    .hero__scroll {
        display: none;
    }
}

/* RESPONSIVE */

@media (max-width: 992px) {
    .home-page {
        --home-shell-gutter: 12px;
    }

    .home-featured {
        padding: 66px 0 94px;
    }

    .home-featured__panel {
        border-radius: 22px;
        padding: 14px;
    }

    .home-featured__panel::before {
        inset: 7px;
        border-radius: 12px;
    }}

@media (max-width: 768px) {

    .home-featured__title::before {
        height: 0.32em;
        opacity: 0.9;
    }

    .home-featured {
        padding: 56px 0 84px;
    }

    .home-featured__header {
        margin-bottom: 12px;
        padding-bottom: 10px;
        align-items: center;
    }

    .home-featured__title {
        font-size: 1.45rem;
    }

    .home-featured__link {
        font-size: 0.69rem;
        padding: 7px 9px;
    }

    .home-featured__panel {
        border-radius: 18px;
        padding: 11px;
    }

    .home-featured__panel::after {
        width: 78px;
        height: 78px;
        right: 10px;
        top: 10px;
        border-radius: 16px;
        opacity: 0.7;
    }

    .home-product-grid {
        --bs-gutter-x: 0.8rem;
        --bs-gutter-y: 0.95rem;
    }

    .product-card {
        border-radius: 14px;
        padding: 8px;
    }

    .product-image-shell {
        border-radius: 12px;
        padding: 6px;
    }

    .badge-label {
        top: 8px;
        left: 8px;
        font-size: 0.56rem;
        padding: 4px 6px;
    }

    .product-body {
        padding: 10px 4px 2px;
    }

    .product-title {
        font-size: 0.8rem;
        margin-bottom: 5px;
    }

    .product-price {
        font-size: 0.8rem;
    }

    .home-empty-state {
        min-height: 260px;
        border-radius: 14px;
        padding: 16px;
    }

    .home-empty-state__icon {
        font-size: 2rem;
        margin-bottom: 10px;
    }

    .home-empty-state__title {
        font-size: 1.1rem;
    }

    .home-empty-state__text {
        font-size: 0.82rem;
        margin-top: 8px;
    }}

@media (max-width: 560px) {
    .home-page {
        --home-shell-gutter: 10px;
    }
}

/* REDUCED MOTION */
@media (prefers-reduced-motion: reduce) {
    /* The strand is drawn and the beads are already in place; nothing moves. */
    .strand__thread path {
        animation: none;
        stroke-dashoffset: 0;
    }

    .bead__inner,
    .hero__copy > *,
    .hero__scroll {
        animation: none;
    }

    .hero__scroll-line::after {
        animation: none;
        opacity: 0;
    }

    .campaign-tag,
    .campaign-tag__card:hover {
        animation: none;
    }

    .campaign-tag__card:hover {
        translate: none;
    }

    .home-featured__link,
    .home-featured__panel::after,
    .product-card,
    .product-card::before,
    .product-image-shell,
    .product-image {
        transition: none !important;
        animation: none !important;
    }

    .product-card:hover,
    .product-card:hover::before,
    .product-card:hover .product-image {
        transform: none !important;
    }}