/* TWOFROGS - Featured Species carousel
 * Extracted from index.html so field.html can reuse the same CSS.
 */
    #featured {
      background: var(--g900);
      padding: 4.5rem 3rem 4rem;
      position: relative;
      overflow: hidden;
      /* Easing tokens (CLAUDE.md Animation & Motion). Locally scoped so they
         only apply within this section without polluting :root. */
      --ease-out:        cubic-bezier(0.23, 1, 0.32, 1);
      --ease-out-expo:   cubic-bezier(0.16, 1, 0.3, 1);
      --ease-out-quint:  cubic-bezier(0.22, 1, 0.36, 1);
    }
    .ft-inner { max-width: 1360px; margin: 0 auto; }

    /* Scroll-triggered reveals (CLAUDE.md Animation & Motion).
       Header chunks fade-up with a stagger; carousel stage uses the heavy
       cinematic reveal (translateY + blur + fade). All wrapped in
       prefers-reduced-motion: no-preference per the hard rules. */
    @media (prefers-reduced-motion: no-preference) {
      #featured [data-reveal] {
        opacity: 0;
        transform: translateY(20px);
        transition: opacity 700ms var(--ease-out-expo),
                    transform 700ms var(--ease-out-expo);
      }
      #featured [data-reveal].is-visible { opacity: 1; transform: translateY(0); }
      #featured [data-reveal-delay="1"] { transition-delay: 80ms; }
      #featured [data-reveal-delay="2"] { transition-delay: 160ms; }
      #featured [data-reveal-delay="3"] { transition-delay: 240ms; }
      #featured [data-reveal-delay="4"] { transition-delay: 320ms; }

      #featured [data-heavy-reveal] {
        opacity: 0;
        transform: translateY(48px);
        filter: blur(8px);
        transition: opacity 800ms var(--ease-out-expo),
                    transform 800ms var(--ease-out-expo),
                    filter 800ms var(--ease-out-expo);
      }
      #featured [data-heavy-reveal].is-visible {
        opacity: 1;
        transform: translateY(0);
        filter: blur(0);
      }
    }

    /* Subtle SVG noise grain overlay — adds editorial texture without weight.
       Per the design system: fixed pseudo-element only, never on a scrolling container. */
    #featured::before {
      content: ''; position: absolute; inset: 0; pointer-events: none; z-index: 0;
      background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.04'/%3E%3C/svg%3E");
      mix-blend-mode: overlay; opacity: 0.35;
    }
    .ft-inner > * { position: relative; z-index: 1; }

    /* Header — title block on the left, counter pinned right */
    .ft-head {
      display: grid; grid-template-columns: 1fr auto;
      align-items: end; gap: 2.5rem;
      margin-bottom: 0.95rem;
    }
    .ft-head-main { min-width: 0; }
    /* Title + View-piece CTA share a row, anchored to the bottom edge of
       the title's text. flex-end (rather than baseline) keeps the pill's
       lower edge flush with the title's lower edge — a cleaner anchor than
       baseline alignment when the type sizes are this far apart. */
    .ft-title-row {
      display: flex; align-items: flex-end; flex-wrap: wrap;
      gap: 1rem 1.75rem;
    }
    @media (max-width: 767px) {
      .ft-title-row { gap: .75rem 1rem; }
    }

    /* Active product block: CTA stacked above the rating, both centred under
       the carousel. The CTA is the primary action (so visually heavier);
       the rating sits beneath as supporting proof. */
    .ft-active-block {
      display: flex; flex-direction: column;
      align-items: center; gap: 0.75rem;
      margin: 1.25rem auto 1.2rem;
      position: relative; z-index: 1;
    }
    @media (max-width: 767px) {
      .ft-active-block { gap: 0.55rem; margin: 0.85rem auto 0.95rem; }
    }
    .ft-tag {
      font-family: 'Syne', sans-serif; font-size: 0.62rem; letter-spacing: 0.22em;
      text-transform: uppercase; color: rgba(236,242,199,0.55);
      padding: 0.5rem 0.95rem;
      border: 1px solid rgba(236,242,199,0.18);
      border-radius: 999px; background: rgba(236,242,199,0.04);
      display: inline-flex; align-items: center; gap: 0.55rem;
      margin-bottom: 1.25rem;
    }
    .ft-tag::before {
      content: ''; width: 5px; height: 5px;
      background: var(--sage); border-radius: 50%;
      box-shadow: 0 0 8px var(--sage);
    }
    @media (prefers-reduced-motion: no-preference) {
      .ft-tag::before { animation: ft-dot-pulse 2.4s ease-in-out infinite; }
    }
    @keyframes ft-dot-pulse {
      0%, 100% { opacity: 0.7;  transform: scale(1); }
      50%      { opacity: 1;    transform: scale(1.18); }
    }
    .ft-title {
      font-family: 'Roca', serif; font-weight: 800;
      font-size: clamp(2.2rem, 5vw, 4rem);
      letter-spacing: -0.02em; line-height: 0.98;
      color: var(--cream);
    }
    .ft-title em {
      font-family: 'Roca', serif; font-style: normal; font-weight: 600;
      color: var(--sage);
    }
    .ft-sub {
      font-family: 'Varela Round', sans-serif;
      font-size: 0.92rem; line-height: 1.55;
      color: rgba(196,184,158,0.55);
      max-width: 420px;
      margin-top: 0.85rem;
    }
    /* Editorial position counter — "01 / 08" tabular-nums, italic Fraunces.
       Sits in the second column of the head row, top-right, low-contrast. */
    .ft-counter {
      align-self: end; justify-self: end;
      font-family: 'Fraunces', serif;
      font-style: italic; font-weight: 400;
      font-size: clamp(1.5rem, 2.6vw, 2.1rem);
      line-height: 1;
      letter-spacing: 0.02em;
      color: rgba(236,242,199,0.42);
      font-variant-numeric: tabular-nums;
      white-space: nowrap;
    }
    .ft-counter-active {
      color: var(--sage); font-weight: 500;
      display: inline-block;
    }
    .ft-counter-sep { margin: 0 0.35rem; opacity: 0.55; }
    @media (max-width: 767px) {
      .ft-counter { font-size: 1.15rem; }
    }
    /* Counter change animation — number flips up + fades on every advance.
       Triggered by re-adding the class via JS (force reflow first). */
    @media (prefers-reduced-motion: no-preference) {
      .ft-counter-active.is-flipping {
        animation: ft-counter-flip 480ms cubic-bezier(0.22,1,0.36,1);
      }
    }
    @keyframes ft-counter-flip {
      0%   { opacity: 0; transform: translateY(10px); }
      40%  { opacity: 0; transform: translateY(10px); }
      100% { opacity: 1; transform: translateY(0); }
    }

    /* Sage hairline rule between the head row and the carousel — editorial divider */
    .ft-rule {
      height: 1px;
      background: linear-gradient(to right, transparent, rgba(236,242,199,0.22) 22%, rgba(236,242,199,0.22) 78%, transparent);
      margin-bottom: 1.25rem;
      position: relative;
      z-index: 1;
    }

    /* Stage: positioning context for the halo + arrows + viewport */
    .ft-stage { position: relative; height: 380px; margin-bottom: 1.4rem; }

    /* Halo: stays put as a fixed pseudo-element in the viewport. Cards
       animate THROUGH it — the glow itself never moves or fades. */
    .ft-halo {
      position: absolute; left: 50%; top: 50%;
      width: 540px; height: 380px;
      transform: translate(-50%, -50%);
      background: radial-gradient(ellipse at center, rgba(58,90,63,0.30) 0%, rgba(58,90,63,0.10) 50%, transparent 78%);
      filter: blur(8px);
      pointer-events: none;
      z-index: 1;
    }

    /* Viewport: clips the moving track. Fixed-width window. */
    .ft-viewport {
      position: absolute;
      inset: 0;
      overflow: hidden;
      z-index: 2;
    }

    /* Track: holds all cards as rigid flex siblings. The whole track
       translates by N × cardWidth on advance — cards don't move relative
       to each other, only the track moves under the viewport.
       Transition applied unconditionally: arrow click is user-initiated
       motion (WCAG SC 2.3.3 — acceptable under reduced-motion), and the
       carousel is meaningless without the slide. */
    .ft-track {
      display: flex;
      align-items: center;
      height: 100%;
      width: max-content;
      transition: transform 600ms var(--ease-out-quint);
      will-change: transform;
    }

    /* Cards: rigid 280px slots in the flex track. Per-card scale + opacity
       transitions in lockstep with the track translate, same easing/duration,
       so the slide and the focus-shift read as one cinematic motion. */
    .ft-card {
      flex: 0 0 280px;
      height: 320px;
      display: flex; align-items: center; justify-content: center;
      transform: scale(0.4);
      opacity: 0;
      pointer-events: none;
      filter: drop-shadow(0 0 0 rgba(58,90,63, 0)) blur(6px);
      transition: transform 600ms var(--ease-out-quint),
                  opacity   600ms var(--ease-out-quint),
                  filter    600ms var(--ease-out-quint);
      will-change: transform, opacity, filter;
      cursor: pointer;
      border-radius: 18px;
      text-decoration: none;
    }
    .ft-card[data-distance="0"] {
      transform: scale(1.4);
      opacity: 1;
      pointer-events: auto;
      z-index: 5;
      /* Tighter sage glow — concentrated near the silhouette so the alpha
         at the viewport's clip edge is essentially zero. No more visible
         cut-off line below the active card. */
      filter: drop-shadow(0 10px 22px rgba(58,90,63, 0.55)) blur(0);
    }
    .ft-card[data-distance="1"] {
      transform: scale(0.75);
      opacity: 0.6;
      filter: drop-shadow(0 0 0 rgba(58,90,63, 0)) blur(1.2px);
      z-index: 3;
    }
    .ft-card[data-distance="far"] {
      transform: scale(0.55);
      opacity: 0.35;
      filter: drop-shadow(0 0 0 rgba(58,90,63, 0)) blur(2.5px);
      z-index: 2;
    }
    .ft-card:focus-visible { outline: 2px solid var(--sage); outline-offset: 6px; border-radius: 18px; }
    .ft-img {
      display: block;
      max-width: 100%; max-height: 100%;
      width: 100%; height: 100%;
      object-fit: contain;
      pointer-events: none;
      user-select: none;
      -webkit-user-drag: none;
    }
    /* Active product rating — a single centred line under the carousel that
       always shows the currently-selected card's stars and review count.
       Replaces the per-card pill that was getting clipped by the viewport
       silhouette on every product except the Poison Dart Frog. Fades in
       lockstep with the title crossfade so the rating swap reads as one
       motion with the card advance. */
    .ft-active-rating {
      display: flex; align-items: center; justify-content: center;
      gap: 0.65rem;
      min-height: 1.4rem;
      opacity: 0;
      transform: translateY(4px);
      transition: opacity 320ms var(--ease-out-expo),
                  transform 320ms var(--ease-out-expo);
      pointer-events: none;
      text-align: center;
    }
    .ft-active-rating.has-rating { opacity: 1; transform: translateY(0); }
    .ft-active-rating.is-swapping {
      transition: opacity 240ms var(--ease-out-expo),
                  transform 240ms var(--ease-out-expo);
      opacity: 0;
      transform: translateY(6px);
    }
    .ft-active-rating-stars {
      position: relative;
      display: inline-block;
      font-family: 'Courier New', monospace;
      font-size: 0.85rem;
      letter-spacing: 0.08em;
      line-height: 1;
    }
    .ft-active-rating-bg { color: rgba(236,242,199,0.18); }
    .ft-active-rating-fill {
      position: absolute; inset: 0;
      overflow: hidden;
      color: var(--sage);
      white-space: nowrap;
    }
    .ft-active-rating-text {
      display: inline-flex; align-items: baseline; gap: 0.45rem;
      font-family: 'Syne', sans-serif;
      font-size: 0.6rem;
      letter-spacing: 0.18em;
      text-transform: uppercase;
      color: rgba(236,242,199,0.62);
      font-variant-numeric: tabular-nums;
    }
    .ft-active-rating-text strong {
      font-family: 'Fraunces', serif;
      font-style: italic;
      font-weight: 500;
      font-size: 0.95rem;
      letter-spacing: 0;
      text-transform: none;
      color: var(--sage);
    }
    .ft-active-rating-sep {
      opacity: 0.45;
      font-family: 'Syne', sans-serif;
    }
    @media (max-width: 767px) {
      .ft-active-rating { gap: 0.55rem; }
      .ft-active-rating-stars { font-size: 0.78rem; }
      .ft-active-rating-text  { font-size: 0.56rem; }
      .ft-active-rating-text strong { font-size: 0.88rem; }
    }
    /* Poison Dart Frog PNG was exported with ~10% bottom padding vs ~20% on
       every other beanie — silhouette sits ~23px lower, pushing the active
       drop-shadow into the viewport's clip boundary. Two-part fix:
       (a) shift the image up via object-position to align silhouettes; and
       (b) shrink the active scale a touch so the whole card + shadow fits
       within the viewport bounds even with the lower silhouette. */
    .ft-card[data-handle="poison-dart-frog-beanie"] .ft-img {
      object-position: 50% 25%;
    }
    .ft-card[data-handle="poison-dart-frog-beanie"][data-distance="0"] {
      transform: scale(1.3);
    }
    /* Mobile: card transforms are force-disabled, so the desktop scale(1.3)
       counter-rule doesn't apply. The Poison Dart PNG has ~10% less internal
       padding than the other beanies, so its silhouette reads ~12% larger
       at the same container size. Scale the IMG down to bring the silhouette
       back in line with the rest of the species. */
    @media (max-width: 767px) {
      .ft-card[data-handle="poison-dart-frog-beanie"] .ft-img {
        transform: scale(0.88);
        transform-origin: center;
      }
    }

    /* Arrows */
    .ft-arrow {
      position: absolute;
      top: 50%; transform: translateY(-50%);
      width: 48px; height: 48px;
      border-radius: 50%;
      border: 1px solid rgba(236,242,199,0.22);
      background: rgba(8,15,10,0.55);
      color: var(--sage);
      padding: 0;
      display: inline-flex; align-items: center; justify-content: center;
      cursor: pointer;
      z-index: 6;
      transition: background 220ms var(--ease-out),
                  border-color 220ms var(--ease-out),
                  transform 220ms var(--ease-out),
                  opacity 200ms var(--ease-out);
      backdrop-filter: blur(4px); -webkit-backdrop-filter: blur(4px);
    }
    /* Inline SVG chevrons sit perfectly centred — no font-metric drift like the
       text glyphs ‹ › had. The svg block is dimensioned and centred via flex. */
    .ft-arrow svg { width: 16px; height: 16px; display: block; }
    .ft-arrow svg path { stroke: currentColor; stroke-width: 1.75; stroke-linecap: round; stroke-linejoin: round; fill: none; }
    .ft-arrow:hover { background: rgba(58,90,63,0.45); border-color: var(--sage); transform: translateY(-50%) scale(1.05); }
    .ft-arrow:active { transform: translateY(-50%) scale(0.97); }
    .ft-arrow:focus-visible { outline: 2px solid var(--sage); outline-offset: 3px; }
    .ft-arrow-prev { left: 1rem; }
    .ft-arrow-next { right: 1rem; }

    /* (Detail block previously sat below the carousel; the View-piece CTA
       now lives baseline-aligned next to the title — see .ft-title-row.) */

    /* Title crossfade — desktop only. The product name in the section
       title fades out + nudges down briefly while the carousel cards
       slide, then fades back in. Per CLAUDE.md exit-faster rule:
       240ms out (75% of 320ms in). Mobile gets instant updates. */
    .ft-title,
    .ft-detail-cta.ft-swappable {
      transition: opacity 320ms var(--ease-out-expo),
                  transform 320ms var(--ease-out-expo);
    }
    .ft-title.is-swapping,
    .ft-detail-cta.ft-swappable.is-swapping {
      transition: opacity 240ms var(--ease-out-expo),
                  transform 240ms var(--ease-out-expo);
      opacity: 0;
      transform: translateY(8px);
    }
    /* CTA — diagonal cream shine sweeps across on hover via a ::before
       pseudo-element. One-shot animation re-fires on each hover entry.
       Subtle bg tint + brighter outline give the button something for
       the shine to glide across. */
    .ft-detail-cta {
      position: relative;
      overflow: hidden;
      isolation: isolate;
      display: inline-flex; align-items: center; gap: 0.5rem;
      font-family: 'Syne', sans-serif; font-size: 0.7rem;
      letter-spacing: 0.18em; text-transform: uppercase;
      color: var(--sage);
      padding: 0.85rem 1.6rem;
      border: 1px solid var(--sage);
      border-radius: 999px;
      background: rgba(236,242,199, 0.04);
      text-decoration: none;
      transition: background 280ms var(--ease-out),
                  border-color 280ms var(--ease-out),
                  transform 220ms var(--ease-out);
    }
    .ft-detail-cta::before {
      content: '';
      position: absolute; inset: 0;
      background: linear-gradient(
        115deg,
        transparent 0%,
        transparent 38%,
        rgba(243,238,228, 0.32) 48%,
        rgba(243,238,228, 0.55) 50%,
        rgba(243,238,228, 0.32) 52%,
        transparent 62%,
        transparent 100%
      );
      transform: translateX(-110%);
      pointer-events: none;
      z-index: 0;
    }
    .ft-detail-cta > span {
      display: inline-block;
      position: relative;
      z-index: 1;
      transition: transform 350ms var(--ease-out-quint);
    }
    .ft-detail-cta:hover {
      background: rgba(236,242,199, 0.10);
      border-color: rgba(236,242,199, 0.75);
      transform: translateY(-2px);
    }
    .ft-detail-cta:hover::before {
      animation: ft-cta-shine 850ms var(--ease-out-quint);
    }
    .ft-detail-cta:hover > span { transform: translateX(4px); }
    .ft-detail-cta:active { transform: translateY(0) scale(0.98); }
    .ft-detail-cta:focus-visible { outline: 2px solid var(--sage); outline-offset: 3px; }
    @keyframes ft-cta-shine {
      0%   { transform: translateX(-110%); }
      100% { transform: translateX(110%); }
    }

    /* Tablet (≥768 and <1024) — narrower card slots, gentler scale on active */
    @media (min-width: 768px) and (max-width: 1023px) {
      .ft-stage { height: 360px; }
      .ft-card  { flex-basis: 240px; height: 280px; }
      .ft-card[data-distance="0"] { transform: scale(1.25); }
    }

    /* Trust bridge between carousel and next section. Centered editorial
       block: sage hairline above, three icon trust pillars stacked in the
       middle, CTA centered beneath. The icon pillars (Free returns /
       Authentic reviews / Free shipping) carry the conversion-confidence
       weight; the CTA pulls users into the catalogue. */
    .ft-trust {
      max-width: 1360px; margin: 1.75rem auto 0;
      padding-top: 1.6rem;
      border-top: 1px solid rgba(236,242,199,0.07);
      display: flex; flex-direction: column; align-items: center;
      gap: 1.4rem; text-align: center;
    }
    /* Icon pillars nested inside the trust bridge — full width, no own border
       (the parent .ft-trust already drew the hairline above). */
    .ft-trust > .voices-trust {
      width: 100%;
      border-top: none !important;
      margin: 0 !important;
      padding-top: 0 !important;
    }
    .ft-trust-list {
      list-style: none; padding: 0; margin: 0;
      display: flex; flex-wrap: wrap; justify-content: center; gap: 1rem 2rem;
    }
    .ft-trust-list li {
      display: inline-flex; align-items: center; gap: .55rem;
      font-family: 'Syne', sans-serif; font-size: 0.6rem;
      letter-spacing: 0.18em; text-transform: uppercase; font-weight: 600;
      color: rgba(196,184,158, 0.62);
    }
    .ft-trust-dot {
      width: 5px; height: 5px;
      background: var(--sage);
      border-radius: 50%;
      box-shadow: 0 0 8px rgba(236,242,199, 0.4);
      flex-shrink: 0;
    }
    @media (prefers-reduced-motion: no-preference) {
      .ft-trust-list li:nth-child(1) .ft-trust-dot { animation: ft-trust-pulse 2.4s ease-in-out infinite; }
      .ft-trust-list li:nth-child(2) .ft-trust-dot { animation: ft-trust-pulse 2.4s ease-in-out 0.4s infinite; }
      .ft-trust-list li:nth-child(3) .ft-trust-dot { animation: ft-trust-pulse 2.4s ease-in-out 0.8s infinite; }
    }
    @keyframes ft-trust-pulse {
      0%, 100% { opacity: 0.7;  transform: scale(1); }
      50%      { opacity: 1;    transform: scale(1.3); }
    }
    .ft-trust-cta {
      flex-shrink: 0;
      display: inline-flex; align-items: center; gap: .65rem;
      font-family: 'Syne', sans-serif; font-size: 0.6rem;
      letter-spacing: 0.22em; text-transform: uppercase; font-weight: 700;
      color: var(--sage); text-decoration: none;
      padding: 0.45rem 0;
      border-bottom: 1px solid rgba(236,242,199, 0.18);
      transition: color 0.3s cubic-bezier(0.22, 1, 0.36, 1),
                  border-color 0.3s ease,
                  letter-spacing 0.35s cubic-bezier(0.22, 1, 0.36, 1);
    }
    .ft-trust-cta:hover {
      color: var(--cream);
      border-color: var(--sage);
      letter-spacing: 0.26em;
    }
    .ft-trust-cta:focus-visible { outline: 1px solid var(--sage); outline-offset: 4px; }
    .ft-trust-cta svg { transition: transform 0.4s cubic-bezier(0.22, 1, 0.36, 1); }
    .ft-trust-cta:hover svg { transform: translateX(3px); }
    .ft-trust-cta .ft-trust-cta-line {
      transform-origin: left center;
      transform: scaleX(0.7);
      transition: transform 0.4s cubic-bezier(0.22, 1, 0.36, 1);
    }
    .ft-trust-cta:hover .ft-trust-cta-line { transform: scaleX(1); }

    @media (max-width: 767px) {
      .ft-trust {
        gap: 0.95rem; margin-top: 1.4rem; padding-top: 1.2rem;
      }
      .ft-trust-list { gap: 0.7rem 1.2rem; width: 100%; }
      .ft-trust-list li { font-size: 0.56rem; letter-spacing: 0.16em; }
      .ft-trust-cta { font-size: 0.58rem; letter-spacing: 0.2em; }
    }

    /* Default (desktop): swipe hints + progress bar hidden — desktop has
       its own ft-arrow buttons. Declared OUTSIDE the mobile MQ so the in-MQ
       display rules below win on mobile (later same-specificity rule would
       otherwise override). */
    .ft-swipe-hint,
    .ft-progress { display: none; }

    /* Mobile (<768): native scroll-snap on the viewport. JS sets card width
       to viewport.clientWidth in pixels — bypasses the circular `flex: 0 0 100%`
       against `width: max-content` issue that breaks iOS Safari sizing. */
    @media (max-width: 767px) {
      #featured { padding: 3.5rem 1.5rem; }
      .ft-head { grid-template-columns: 1fr; gap: 0.5rem; }
      .ft-sub { max-width: none; }
      /* Uniform mobile title size — every species name uses the same scale,
         so swiping through the carousel doesn't reflow the layout when a
         longer name comes up. Sized to fit "Poison Dart Frog Beanie" (23
         chars, the longest in the catalogue) on a single line. */
      .ft-title { font-size: clamp(1.7rem, 5vw, 2.6rem); white-space: nowrap; }
      /* Extra breathing room below "View piece" so the button doesn't feel
         crammed against the counter / rule line below. */
      .ft-title-row .ft-detail-cta { margin-bottom: 0.6rem; }
      .ft-stage { height: auto; margin-bottom: 1.75rem; }
      .ft-halo { width: 88%; height: 240px; }
      .ft-viewport {
        position: relative;
        inset: auto;
        overflow-x: auto;
        overflow-y: hidden;
        scroll-snap-type: x mandatory;
        scroll-behavior: smooth;
        -webkit-overflow-scrolling: touch;
        scrollbar-width: none;
      }
      .ft-viewport::-webkit-scrollbar { display: none; }
      .ft-track {
        transform: none !important;
        transition: none !important;
        will-change: auto;
      }
      .ft-card {
        /* flex-basis set inline by JS to viewport.clientWidth so each card
           is exactly one viewport wide, snap-aligns cleanly */
        height: 320px;
        transform: none !important;
        opacity: 1 !important;
        scroll-snap-align: center;
        pointer-events: auto !important;
        filter: none !important;
        z-index: auto !important;
        transition: none !important;
      }
      .ft-card[data-distance="0"] { filter: drop-shadow(0 8px 18px rgba(58,90,63, 0.45)) !important; }
      .ft-arrow { display: none; }

      /* Swipe affordance — tiny chevron buttons at the carousel edges. They
         signal "this is swipeable" AND are tappable as a backup to the swipe
         gesture. The right chevron carries a soft horizontal nudge on a loop
         to telegraph the swipe direction. After the first user interaction
         (touch or tap), the .ft-swiped state stops the nudge animation so
         the chevrons settle into a quiet steady state — they remain visible
         and clickable, just no longer pulsing for attention. */
      .ft-swipe-hint {
        display: flex; align-items: center; justify-content: center;
        position: absolute; top: 50%;
        width: 22px; height: 32px;
        color: rgba(236,242,199,0.62);
        background: transparent;
        border: none;
        padding: 0;
        cursor: pointer;
        z-index: 5;
        transition: color .25s ease, opacity .35s cubic-bezier(0.22, 1, 0.36, 1);
        -webkit-tap-highlight-color: transparent;
      }
      /* Expand the touch target without changing visual size — pseudo extends
         the hit area to ~44x52px while the chevron stays 22x32. */
      .ft-swipe-hint::before {
        content: '';
        position: absolute;
        inset: -10px -12px;
      }
      .ft-swipe-hint svg { width: 13px; height: 20px; display: block; pointer-events: none; }
      .ft-swipe-hint-l { left: 0.25rem; transform: translate(0, -50%); }
      .ft-swipe-hint-r { right: 0.25rem; transform: translate(0, -50%); }
      .ft-swipe-hint:active { color: var(--sage); }
      .ft-swipe-hint:focus-visible { outline: 1px solid var(--sage); outline-offset: 4px; }

      @media (prefers-reduced-motion: no-preference) {
        .ft-stage:not(.ft-swiped) .ft-swipe-hint-l { animation: ft-swipe-nudge-l 2.4s cubic-bezier(0.22, 1, 0.36, 1) infinite; }
        .ft-stage:not(.ft-swiped) .ft-swipe-hint-r { animation: ft-swipe-nudge-r 2.4s cubic-bezier(0.22, 1, 0.36, 1) infinite; }
      }
      @keyframes ft-swipe-nudge-r {
        0%, 70%, 100% { transform: translate(0,    -50%); opacity: .58; }
        35%           { transform: translate(6px,  -50%); opacity: 1;   }
      }
      @keyframes ft-swipe-nudge-l {
        0%, 70%, 100% { transform: translate(0,    -50%); opacity: .42; }
        35%           { transform: translate(-6px, -50%); opacity: .75; }
      }

      /* Progress bar — thin sage track sitting beneath the carousel, fills
         left → right as the user swipes through the eight species. Width
         driven by --ft-progress (0..1) set by JS in the update() loop. */
      .ft-progress {
        display: block;
        position: relative;
        margin: 1.1rem auto 0;
        width: 84%;
        max-width: 280px;
        height: 2px;
        background: rgba(236,242,199, 0.10);
        border-radius: 999px;
        overflow: hidden;
      }
      .ft-progress-fill {
        position: absolute;
        inset: 0 auto 0 0;
        width: calc(var(--ft-progress, 0) * 100%);
        background: var(--sage);
        border-radius: 999px;
        box-shadow: 0 0 8px rgba(236,242,199, 0.28);
        transition: width .42s cubic-bezier(0.22, 1, 0.36, 1);
      }
    }

    /* Reduced motion: track + card transitions are gated on
       prefers-reduced-motion: no-preference above, so they're already off
       in this branch. Strip the remaining UI transitions for completeness. */
    @media (prefers-reduced-motion: reduce) {
      .ft-detail-cta { transition: none !important; }
      .ft-arrow { transition: none !important; }
    }
