/* ==========================================================================
   KitchenAudit — motion.css (interaction layer)
   Owned by the front-end engineer. Loaded after assets/css/styles.css
   (hard-linked in every page <head>; site.js injects it as a fallback).

   This file owns animation + interaction CSS only: motion, scroll
   affordances, and JS-injected components. It never restyles copy, layout
   grids, or brand tokens — those live in styles.css.

   TOKEN STRATEGY: every value below references the theme-spec v2 tokens
   with the spec's chosen hex as fallback, e.g. var(--ink, #101014). While
   the designer's styles.css re-skin is in flight, components render in the
   correct theme colors; once the new tokens land, they take over
   automatically. The rejected skin's aliases (--terra/--ember/--stone/
   --linen/--card/--paper old set) are DEAD and referenced nowhere here.

   Motion feel (theme-spec v2 §3): luxe slow motion. The only easing with
   personality is --ease-luxe: cubic-bezier(0.22, 1, 0.36, 1). Only
   transform and opacity ever animate. All motion is gated behind
   prefers-reduced-motion (§1): with reduced motion (or no JS) everything
   renders in its final state — JS only ADDS hiding classes when it is
   about to animate.
   ========================================================================== */

/* --- §1 · Motion foundations -------------------------------------------------
   Anchor offset from the spec token; the reduced-motion kill switch renders
   every final state with zero animation. */

:root {
  --ease-luxe: cubic-bezier(0.22, 1, 0.36, 1);
}

html {
  scroll-behavior: smooth;
}

section[id],
h2[id],
h3[id] {
  scroll-margin-top: var(--anchor-offset, 88px);
}

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
  /* Belt-and-braces: reveals are added by JS only when motion is allowed,
     but final states must win regardless. */
  [data-reveal],
  .hero-fade-init,
  [data-tabs] [role="tabpanel"] {
    opacity: 1 !important;
    transform: none !important;
    visibility: visible !important;
  }
}

/* --- §2 · Hero letter-mask entrance (M1) -------------------------------------
   site.js splits each [data-letter-mask] line into .lm-mask (overflow
   hidden) > .lm-letter spans. Letters slide 110% -> 0 over 900ms with the
   luxe ease-out; --li is the global letter index (cascade across lines),
   first letter starts at 150ms, 32ms stagger (~1.5-2s total).

   No-JS: .lm-ready never lands, the headline renders as plain text.
   Reduced motion: site.js skips splitting entirely.

   Descender guard: the mask keeps a touch of bottom padding (negated by
   margin) so "y/p/g" never clip at rest — no layout shift on load (§6.9). */

[data-letter-mask].lm-ready {
  /* anchor for the run state; letters are inline-block so the final layout
     is identical to the unsplit text */
}

.lm-mask {
  display: inline-block;
  overflow: hidden;
  vertical-align: top;
  padding-bottom: 0.12em;
  margin-bottom: -0.12em;
}

/* Word wrapper: keeps each word's letters together so wrapped headline
   lines break at word boundaries, never mid-word. */
.lm-word {
  display: inline-block;
  vertical-align: top;
  white-space: nowrap;
}

.lm-letter {
  display: inline-block;
  transform: translateY(110%);
  transition: transform 900ms var(--ease-luxe);
  transition-delay: calc(150ms + var(--li, 0) * 32ms);
  will-change: transform;
}

[data-letter-mask].lm-run .lm-letter {
  transform: translateY(0);
}

/* --- §3 · Hero parallax base (M3/M4) -----------------------------------------
   site.js writes translate3d transforms on scroll (rAF-throttled):
   [data-parallax-scene] drifts at 0.25x, [data-parallax-headline] rises at
   0.45x and fades across the first 60vh. Transform-only; nothing to declare
   here beyond compositor hints. Under reduced motion site.js writes nothing. */

[data-parallax-scene],
[data-parallax-headline] {
  will-change: transform;
}

/* --- §4 · Hero subcopy + trust-pill fade-rise (M2) ---------------------------
   700ms luxe ease-out, translateY(20px) -> 0. Delays per hook: subcopy
   550ms, trust-pill 800ms. .hero-fade-init is added by JS only when motion
   is allowed — no-JS users see both immediately. */

.hero-fade-init {
  opacity: 0;
  transform: translateY(20px);
}

.hero-fade-init.is-shown {
  opacity: 1;
  transform: none;
  transition:
    opacity 700ms var(--ease-luxe),
    transform 700ms var(--ease-luxe);
}

[data-hero-subcopy].hero-fade-init.is-shown {
  transition-delay: 550ms;
}

[data-trust-pill].hero-fade-init.is-shown {
  transition-delay: 800ms;
}

/* --- §5 · Scroll reveals (M5) ------------------------------------------------
   [data-reveal]: opacity 0 / translateY(32px) -> visible, 800ms luxe
   ease-out, stagger calc(var(--i) * 100ms) with --i set per parent group by
   site.js (capped at 6). .reveal-init is added by JS only when
   IntersectionObserver exists and motion is allowed — no-JS / reduced-
   motion users see content immediately; once visible, JS unobserves. */

[data-reveal].reveal-init {
  opacity: 0;
  transform: translateY(32px);
  transition:
    opacity 800ms var(--ease-luxe),
    transform 800ms var(--ease-luxe);
  transition-delay: calc(var(--i, 0) * 100ms);
}

[data-reveal].reveal-init.is-visible,
[data-reveal].is-visible {
  opacity: 1;
  transform: none;
}

/* --- §6 · Drag carousel (M7/M8) ----------------------------------------------
   [data-drag-carousel]: free-scroll 1:1 pointer drag. NO snap
   (scroll-snap-type: none), NO momentum flick, NO arrows. Cursor
   grab -> grabbing. Touch uses native scrolling (touch-action: pan-x pan-y).

   Edge fades (M8): site.js injects .drag-fade-wrap + two .drag-fade masks
   and toggles .is-at-start / .is-at-end / .is-no-overflow on the wrapper;
   masks crossfade over 200ms linear. [data-drag-hint] fades out after the
   first drag. */

[data-drag-carousel].drag-carousel {
  overflow-x: auto;
  overflow-y: hidden;
  scroll-snap-type: none;
  scrollbar-width: none;
  -ms-overflow-style: none;
  touch-action: pan-x pan-y;
  cursor: grab;
}

[data-drag-carousel].drag-carousel::-webkit-scrollbar {
  display: none;
}

[data-drag-carousel].drag-carousel.is-dragging {
  cursor: grabbing;
}

/* While dragging, kill text selection on the whole track: otherwise the
   browser's native selection auto-scroll compounds the 1:1 tracking and
   the strip travels farther than the pointer (M7 requires exact 1:1). */
[data-drag-carousel].drag-carousel.is-dragging,
[data-drag-carousel].drag-carousel.is-dragging * {
  user-select: none;
  -webkit-user-select: none;
}

[data-drag-carousel].drag-carousel:focus-visible {
  outline: 2px solid var(--focus, #101014);
  outline-offset: 3px;
}

/* Edge-fade wrapper: position-only, no layout change to the track. */
.drag-fade-wrap {
  position: relative;
}

.drag-fade {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 88px;
  z-index: var(--z-carousel-hint, 10);
  pointer-events: none;
  opacity: 0;
  transition: opacity 200ms linear;
}

.drag-fade--left {
  left: 0;
  background: linear-gradient(to right, var(--paper, #fbfaf7), transparent);
}

.drag-fade--right {
  right: 0;
  background: linear-gradient(to left, var(--paper, #fbfaf7), transparent);
}

.drag-fade-wrap:not(.is-at-start):not(.is-no-overflow) .drag-fade--left,
.drag-fade-wrap:not(.is-at-end):not(.is-no-overflow) .drag-fade--right {
  opacity: 1;
}

[data-drag-hint] {
  transition: opacity 300ms ease;
}

[data-drag-hint].is-dismissed {
  opacity: 0;
  pointer-events: none;
}

@media (prefers-reduced-motion: reduce) {
  [data-drag-carousel].drag-carousel {
    scroll-snap-type: none;
  }
}

/* --- §7 · Tab explorer (M9/M10/M11) ------------------------------------------
   [data-tabs]: JS adds .tab-stage to the panels' common parent so panels
   grid-stack (same cell) and crossfade 350ms ease-in-out — outgoing fades
   while incoming fades over it. JS sets [hidden] on inactive panels (never
   in no-JS, so all panels stay readable). [data-tab-word] runs the giant
   word swap (300ms luxe, 50ms delay): blur 80px -> 56px. Active pill fills
   white over 300ms ease-out. */

.tab-stage {
  display: grid;
}

.tab-stage > [role="tabpanel"] {
  grid-area: 1 / 1;
  min-width: 0;
}

[data-tabs] [role="tabpanel"][hidden] {
  display: none;
}

@media (prefers-reduced-motion: no-preference) {
  [data-tabs] [role="tabpanel"] {
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    transition:
      opacity 350ms ease-in-out,
      visibility 0s linear 350ms;
  }

  [data-tabs] [role="tabpanel"].is-active {
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
    transition: opacity 350ms ease-in-out;
  }
}

/* Giant blurred word — contract styling + the M10 swap animation. */
.giant-word {
  font-size: var(--fs-giant-word, clamp(6rem, 22vw, 20rem));
  font-weight: 800;
  letter-spacing: var(--tracking-tight, -0.02em);
  line-height: 1;
  filter: blur(56px);
  opacity: 0.5;
  pointer-events: none;
  user-select: none;
  -webkit-user-select: none;
}

@media (prefers-reduced-motion: no-preference) {
  [data-tab-word].word-enter {
    animation: tab-word-in 300ms var(--ease-luxe) 50ms both;
  }
}

@keyframes tab-word-in {
  from {
    opacity: 0;
    filter: blur(80px);
    transform: translateY(24px);
  }
  to {
    opacity: 0.5;
    filter: blur(56px);
    transform: translateY(0);
  }
}

/* Active pill (M11): transparent -> white, ink text, 300ms ease-out.
   Base pill styling (dark-glass inactive) lives in styles.css. */
[data-tabs] [data-tablist] [role="tab"],
[data-tabs] [role="tablist"] [role="tab"] {
  transition:
    background-color 300ms ease-out,
    color 300ms ease-out;
}

[data-tabs] [data-tablist] [role="tab"][aria-selected="true"],
[data-tabs] [role="tablist"] [role="tab"][aria-selected="true"] {
  background: #ffffff;
  color: var(--ink, #101014);
}

/* --- §8 · Frosted nav (M12), reading progress (M13), back-to-top (M14) ------- */

.site-header {
  background-color: var(--nav-glass, rgba(251, 250, 247, 0.72));
  -webkit-backdrop-filter: blur(18px) saturate(1.1);
  backdrop-filter: blur(18px) saturate(1.1);
  transition:
    box-shadow 300ms ease,
    background-color 300ms ease;
}

.site-header.is-scrolled {
  -webkit-backdrop-filter: blur(24px) saturate(1.15);
  backdrop-filter: blur(24px) saturate(1.15);
  box-shadow: var(
    --shadow-nav,
    0 1px 0 rgba(16, 16, 20, 0.06),
    0 12px 32px -16px rgba(16, 16, 20, 0.18)
  );
}

.reading-progress {
  position: fixed;
  top: 0;
  left: 0;
  z-index: var(--z-progress, 120);
  width: 100%;
  height: 3px;
  pointer-events: none;
  background: transparent;
}

.reading-progress span {
  display: block;
  height: 100%;
  width: 100%;
  background: var(--ink, #101014);
  transform-origin: 0 50%;
  transform: scaleX(0);
}

.back-to-top {
  position: fixed;
  right: 1.25rem;
  bottom: 1.25rem;
  z-index: var(--z-back-to-top, 90);
  width: 46px;
  height: 46px;
  border: 1px solid var(--line, #eaeaea);
  border-radius: 50%;
  background: var(--primary, #0a0a0b);
  color: var(--on-primary, #ffffff);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
  transform: translateY(10px);
  pointer-events: none;
  transition:
    opacity 240ms ease-out,
    transform 240ms ease-out,
    background-color 200ms ease;
}

.back-to-top.is-visible {
  opacity: 1;
  transform: none;
  pointer-events: auto;
}

.back-to-top:hover {
  background: var(--primary-hover, #26262c);
}

.back-to-top:focus-visible {
  outline: 2px solid var(--focus, #101014);
  outline-offset: 3px;
}

.back-to-top::before {
  content: "";
  width: 10px;
  height: 10px;
  border-left: 2px solid currentColor;
  border-top: 2px solid currentColor;
  transform: rotate(45deg) translate(2px, 2px);
}

/* --- §9 · FAQ accordions (M15) ------------------------------------------------
   site.js toggles .open on .faq-item, wraps panel children in .faq-a-inner
   (reusing the designer's static wrapper when present), and upgrades the
   "+/–" glyph to a CSS chevron (.is-chevron). Panels animate 300ms ease-out
   via grid-rows; the chevron rotates 180° over 240ms.

   Pre-JS bridge: while the designer's markup may ship .faq-a-inner
   statically without .open, panels stay collapsed via [hidden] (designer's
   HTML) — the selectors below only take over once JS classes land. */

.faq-a {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 300ms ease-out;
}

/* Keep [hidden] working now that .faq-a sets display:grid. */
.faq-a[hidden] {
  display: none;
}

.faq-a-inner {
  overflow: hidden;
  min-height: 0;
}

.faq-item.open .faq-a {
  grid-template-rows: 1fr;
}

/* CSS chevron replaces the bare "+/–" glyph. Inherits the icon color. */
.faq-q .icon.is-chevron {
  font-size: 0;
  line-height: 0;
  width: 22px;
  height: 22px;
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.faq-q .icon.is-chevron::before {
  content: "";
  width: 9px;
  height: 9px;
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: rotate(45deg) translate(-1px, -1px);
  transition: transform 240ms ease-out;
}

.faq-item.open .faq-q .icon.is-chevron::before {
  transform: rotate(225deg) translate(-1px, -1px);
}

/* --- §10 · Newsletter (M17), jump-nav scrollspy (M16), table hint (M18) ------ */

.newsletter-status {
  opacity: 0;
  transform: translateY(12px);
  transition:
    opacity 300ms ease-out,
    transform 300ms ease-out;
}

.newsletter-status.is-shown {
  opacity: 1;
  transform: none;
}

nav.jumpnav a {
  transition:
    background-color 200ms linear,
    color 200ms linear,
    border-color 200ms linear;
}

nav.jumpnav a.is-active {
  background: var(--ink, #101014);
  border-color: var(--ink, #101014);
  color: var(--on-primary, #ffffff);
}

.table-scroll {
  position: relative;
}

.table-scroll:focus {
  outline: none;
}

.table-scroll:focus-visible {
  outline: 2px solid var(--focus, #101014);
  outline-offset: 3px;
}

.table-scroll-hint {
  position: absolute;
  right: 0.75rem;
  bottom: 0.75rem;
  z-index: 3;
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.4rem 0.75rem;
  border-radius: 999px;
  background: var(--primary, #0a0a0b);
  color: var(--on-primary, #ffffff);
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  pointer-events: none;
  opacity: 0;
  transform: translateY(6px);
  transition:
    opacity 200ms ease-out,
    transform 200ms ease-out;
}

.table-scroll-hint.is-shown {
  opacity: 0.94;
  transform: none;
}

/* --- §11 · Featured card hover zoom (M6) --------------------------------------
   Proposed contract hook [data-featured-card] (homepage "Featured roundup"
   tall cards — no hook in §4a covers them; this rule is CSS-only and inert
   until the designer adds the hook). 400ms ease-out, scale 1 -> 1.04,
   shadow -> var(--shadow-card-hover). Never on touch. */

@media (hover: hover) and (prefers-reduced-motion: no-preference) {
  [data-featured-card] {
    transition:
      transform 400ms cubic-bezier(0.33, 1, 0.68, 1),
      box-shadow 400ms ease-out;
  }

  [data-featured-card]:hover,
  [data-featured-card]:focus-visible {
    transform: scale(1.04);
    box-shadow: var(--shadow-card-hover, 0 32px 80px -24px rgba(16, 16, 20, 0.26));
  }
}

/* --- §12 · Mobile nav: keep the current page link in view --------------------
   The horizontal nav row can clip the active link; JS recenters it. */

@media (max-width: 640px) {
  .site-nav {
    -webkit-overflow-scrolling: touch;
    scroll-behavior: smooth;
  }
}
