/* ============================================================================
   Fairly UI - shared design system
   ----------------------------------------------------------------------------
   One stylesheet behind all four surfaces (customer, portal, driver, admin) so
   they read as one product rather than four apps that happen to share a logo.

   Ledger Violet (#8b6ef5), Void Black (#0a0a0d) and the Manrope/Inter pairing
   are the brand and are unchanged. What changed is which of them owns the
   background: the customer is here to decide what to eat, and appetite needs
   warm paper. Light is now the default skin and near-black became a deliberate
   instrument - see THEMES below - rather than the ground under every screen.
   ========================================================================= */

/* ----------------------------------------------------------------------------
   THEMES

   Fairly ships one token vocabulary in two skins. Every component below
   reads the same variable names, so a surface changes its whole atmosphere by
   changing one attribute on <html> - nothing per-component.

     (default)                warm light. Customer browsing, restaurant
                              portal, admin. Food photography and appetite
                              appeal need warm paper, not a trading terminal.

     data-ff-theme="ink"      warm charcoal. Chosen from the use scene, not
                              from category habit: a driver's phone in a car
                              at night, and the deliberate premium bands
                              (hero, tracking header, sticky order bar) that
                              punctuate a light page.

   The brand is unchanged: Ledger Violet #8b6ef5 is still the one accent and
   #0a0a0d is still the ink the primary button prints on. What changed is
   which of them owns the background.
   ------------------------------------------------------------------------- */

:root {
  /* --- Warm light surfaces. Neutrals carry a violet undertone so the light
         skin reads as Fairly rather than as generic white. --- */
  --bg: #fdfaf6;
  --surface: #ffffff;
  --surface-2: #f6f1ea;
  --surface-3: #ece5db;
  --border: rgba(41, 30, 56, 0.10);
  --border-strong: rgba(41, 30, 56, 0.24);

  /* Void Black, kept as a token in both skins: it is the color the primary
     button's label prints in, and the ground of every ink band. */
  --void: #0a0a0d;
  --ink-canvas: #14101a;

  /* --- Brand accent (unchanged hue) ---
     --purple is the fill; --purple-ink is the same accent corrected for text
     and icons, which need 4.5:1 and don't get it from #8b6ef5 on paper. */
  --purple: #8b6ef5;
  --purple-ink: #5c3fd6;
  --purple-deep: #5c3fd6;
  --purple-tint: rgba(139, 110, 245, 0.13);
  --purple-tint-strong: rgba(139, 110, 245, 0.22);
  --purple-hover: #7a58f0;

  /* --- Warm accent (customer surface). Additive, not a replacement for the
     one-accent rule above - honey sits beside violet the way a receipt's
     highlight color sits beside its brand color: for one specific kind of
     good news (a real transparency stat, a "free" line), never for
     interactive/tappable elements, which stay violet. --- */
  --honey: #e0a858;
  --honey-tint: rgba(224, 168, 88, 0.16);
  --honey-ink: #8a5a12;

  /* --- Text: a warm near-black ramp, not neutral gray --- */
  --text-primary: #1a1420;
  --text-secondary: #5b5266;
  /* 3.5:1 - decoration and large type only. */
  --text-muted: #8b8296;
  /* Same visual register at 5.2:1, for small text that has to be read. */
  /* 4.36 on --surface-3 before: a hair under, and the hair matters on a
     disabled button whose label is the only thing explaining itself. */
  --text-muted-strong: #6c6375;
  --text-on-accent: #0a0a0d;
  /* Hover darkens the violet fill, which takes near-black text from 5.32 to
     4.22 - under AA on the one control a page most wants you to press. So
     the label flips with the fill. The ink skin does the opposite: its hover
     is LIGHTER, where white would be 3.0 and near-black is 6.59, so there
     the label stays dark. One token, because the right answer differs by
     skin and a hardcoded #fff would have been wrong in half the product. */
  --text-on-accent-hover: #ffffff;
  /* What prints on a solid status fill. The status hues are dark on paper and
     bright on ink, so this flips with the skin while --text-on-accent does
     not - Ledger Violet is the same value in both. */
  --text-on-status: #ffffff;

  /* --- Status. Hues preserved from the original system, darkened where the
         light ground demands it so small status text still clears AA. --- */
  --green: #15733a;
  --red: #b8202f;
  --amber: #8a5406;
  --success: var(--green);
  --success-tint: rgba(21, 128, 61, 0.10);
  --danger: var(--red);
  --danger-tint: rgba(192, 37, 53, 0.09);
  --warn: var(--amber);
  --warn-tint: rgba(161, 98, 7, 0.11);
  --info: var(--purple-ink);
  --info-tint: var(--purple-tint);

  /* --- Appetite palette. Illustration only - the fallback art behind a dish
         or restaurant that has no photograph yet. Never used for actions,
         so the one-accent rule still holds for anything tappable. --- */
  --appetite-1a: #8f4b2e; --appetite-1b: #cf8a55;
  --appetite-2a: #8a5a12; --appetite-2b: #d6a13c;
  --appetite-3a: #4c6135; --appetite-3b: #90ab63;
  --appetite-4a: #7a2f45; --appetite-4b: #bf6a7d;
  --appetite-5a: #2f5a5c; --appetite-5b: #6fa0a1;
  --appetite-6a: #7c3a1d; --appetite-6b: #c47b4a;

  /* --- Type --- */
  --font-display: 'Manrope', 'Inter', system-ui, sans-serif;
  --font-body: 'Inter', system-ui, -apple-system, sans-serif;
  /* Customer surface only (see customer-web/index.html's font <link> and the
     selectors in customer-web/styles.css that opt into it) - a serif display
     voice for the screens where deciding what to eat is the whole job.
     Portal/driver/admin keep --font-display (Manrope) untouched.

     Newsreader rather than Fraunces. The role this token fills is not only
     display: it also sets restaurant names on the browse cards at 18px
     (.ff-rcard-name), and Fraunces is a display face - at that size its
     high contrast and quirked terminals went muddy on the dark surface,
     which is the worst place to lose legibility since the name is the
     thing being chosen between. Newsreader is drawn for screen text with an
     optical-size axis from 6 to 72, so one family now covers the 18px card
     name and the 56px About headline without either end being a compromise.
     Georgia is the metric-adjacent fallback; Manrope is no longer in the
     stack because a sans fallback under serif tracking reads as a bug. */
  --font-serif: 'Newsreader', Georgia, 'Times New Roman', serif;
  --fs-hero: 52px;
  --fs-display: 32px;
  --fs-h1: 24px;
  --fs-h2: 19px;
  --fs-h3: 16px;
  --fs-body: 15px;
  --fs-sm: 14px;
  --fs-xs: 13px;
  --fs-2xs: 12px;
  --fs-micro: 11px;

  /* --- Spacing (4px base) --- */
  --sp-1: 4px;
  --sp-2: 8px;
  --sp-3: 12px;
  --sp-4: 16px;
  --sp-5: 20px;
  --sp-6: 24px;
  --sp-8: 32px;
  --sp-10: 40px;
  --sp-12: 48px;
  --sp-16: 64px;
  --sp-20: 80px;

  /* --- Radius. Food surfaces are softer than the original instrument-panel
         scale; controls stay where they were. --- */
  --r-sm: 8px;
  --r-md: 10px;
  --r-lg: 14px;
  --r-xl: 20px;
  --r-2xl: 28px;
  --r-pill: 999px;

  /* --- Elevation. Warm-tinted, not neutral black, so a lifted card on paper
         doesn't read as a hole. --- */
  --shadow-sm: 0 1px 2px rgba(41, 30, 56, 0.07);
  --shadow-md: 0 6px 18px rgba(41, 30, 56, 0.10);
  --shadow-lg: 0 18px 44px rgba(41, 30, 56, 0.16);
  --shadow-card: 0 1px 2px rgba(41, 30, 56, 0.05), 0 8px 24px -14px rgba(41, 30, 56, 0.22);

  /* --- Motion. Still short - latency-feel is a bug in a product people use
         while hungry - with one longer track for surfaces that travel. --- */
  --t-fast: 110ms;
  --t-base: 170ms;
  --t-slow: 260ms;
  --ease: cubic-bezier(0.2, 0, 0.2, 1);
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);

  /* --- Layout --- */
  --container: 1140px;
  --container-wide: 1280px;
  /* For content whose job is showing more of it, not reading it top to
     bottom - a restaurant/food discovery grid, never body text or a form.
     Wide enough that a large monitor gets real extra columns instead of
     a narrow column stranded in the middle of the screen; still capped so
     an ultrawide viewport doesn't stretch cards edge to edge. */
  --container-discovery: 1680px;
  /* The page gutter. Rails that bleed to the viewport edge read it too, so
     they stay aligned with the container at every breakpoint. */
  --gutter: 20px;
  --topbar-h: 64px;
  --tabbar-h: 60px;

  --focus-ring: 0 0 0 2px var(--bg), 0 0 0 4px var(--purple-ink);

  /* Translucent chrome (sticky bars) and the skeleton sweep both need a value
     the skin owns, not a hardcoded rgba that only works in one of them. */
  --chrome: rgba(253, 250, 246, 0.86);
  --chrome-solid: #fdfaf6;
  --skeleton-a: #efe9e1;
  --skeleton-b: #f8f4ee;

  color-scheme: light;
  accent-color: var(--purple-ink);
}

/* ----------------------------------------------------------------------------
   Ink skin. Put data-ff-theme="ink" on <html> for a whole surface (driver),
   or on any element for a band inside a light page (hero, tracking header,
   sticky order bar). Only the values change; every selector below is shared.
   ------------------------------------------------------------------------- */

:root[data-ff-theme="ink"],
[data-ff-theme="ink"] {
  --bg: #14101a;
  --surface: #1d1826;
  --surface-2: #272031;
  --surface-3: #33293f;
  --border: rgba(255, 255, 255, 0.10);
  --border-strong: rgba(255, 255, 255, 0.22);

  --purple-ink: #a289f8;
  --purple-tint: rgba(139, 110, 245, 0.16);
  --purple-tint-strong: rgba(139, 110, 245, 0.28);
  --purple-hover: #9c83f7;
  --text-on-accent-hover: #0a0a0d;

  --text-primary: #f7f4fb;
  --text-secondary: #b3aabf;
  --text-muted: #6a6276;
  /* Lifted from #8d8399: it cleared AA on the page background but fell to
     3.82 on --surface-3, so the same label was readable in one card and not
     in the next. Chosen as the smallest step that clears 4.5 against every
     ink surface, not eyeballed. */
  --text-muted-strong: #9a91a4;

  --green: #4ade80;
  --red: #f45b69;
  --amber: #fbbf24;
  --success-tint: rgba(74, 222, 128, 0.14);
  --danger-tint: rgba(244, 91, 105, 0.14);
  --warn-tint: rgba(251, 191, 36, 0.14);
  --info: var(--purple);
  --text-on-status: #0a0a0d;

  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4);
  --shadow-md: 0 6px 18px rgba(0, 0, 0, 0.45);
  --shadow-lg: 0 18px 44px rgba(0, 0, 0, 0.55);
  --shadow-card: 0 1px 2px rgba(0, 0, 0, 0.3);

  --focus-ring: 0 0 0 2px var(--bg), 0 0 0 4px var(--purple);

  /* Solid grounds for the two states a driver must never misread. Sized and
     coloured to the legal consequence, not to the layout. */
  --critical-danger-solid: #4a0f16;
  --critical-warn-solid: #3d2c05;

  --chrome: rgba(20, 16, 26, 0.88);
  --chrome-solid: #14101a;
  --skeleton-a: #221c2c;
  --skeleton-b: #2c2438;

  color-scheme: dark;
  accent-color: var(--purple);
}

/* A band of ink inside a light page paints its own ground and text, so it can
   be dropped anywhere without the caller restating both. */
[data-ff-theme="ink"]:not(:root) {
  background: var(--bg);
  color: var(--text-primary);
}
/* ============================================================================
   Base
   ========================================================================= */

*, *::before, *::after { box-sizing: border-box; }
[hidden] { display: none !important; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--text-primary);
  font-family: var(--font-body);
  font-size: var(--fs-body);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  /* Keeps fixed bottom bars clear of the iOS home indicator. */
  padding-bottom: env(safe-area-inset-bottom);
}

h1, h2, h3, h4 {
  font-family: var(--font-display);
  font-weight: 800;
  margin: 0;
  letter-spacing: -0.02em;
  line-height: 1.2;
}
h1 { font-size: var(--fs-h1); }
h2 { font-size: var(--fs-h2); }
h3 { font-size: var(--fs-h3); letter-spacing: -0.01em; }
p { margin: 0; }

button, input, select, textarea { font-family: inherit; font-size: inherit; }
img { max-width: 100%; display: block; }

/* One consistent, always-visible-on-keyboard focus ring. :focus-visible means
   mouse users never see it, so there is no reason to ever suppress it. */
:focus-visible {
  outline: none;
  box-shadow: var(--focus-ring);
  border-radius: var(--r-sm);
}

/* Browser chrome the design system still owns: selection, caret and
   scrollbars all ship with defaults that belong to no palette. */
::selection { background: var(--purple-tint-strong); color: var(--text-primary); }
input, textarea, select { caret-color: var(--purple-ink); }

* {
  scrollbar-width: thin;
  scrollbar-color: var(--border-strong) transparent;
}
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
  background: var(--border-strong);
  border-radius: var(--r-pill);
  border: 3px solid transparent;
  background-clip: content-box;
}
::-webkit-scrollbar-thumb:hover { background: var(--text-muted); background-clip: content-box; }

a { color: var(--purple-ink); text-underline-offset: 0.18em; text-decoration-thickness: 1px; }

/* Skip link - first tab stop on every page. */
.skip-link {
  position: absolute;
  left: var(--sp-4);
  top: -100px;
  z-index: 100;
  background: var(--purple);
  color: var(--text-on-accent);
  font-weight: 700;
  font-size: var(--fs-sm);
  padding: var(--sp-3) var(--sp-4);
  border-radius: var(--r-sm);
  text-decoration: none;
  transition: top var(--t-fast) var(--ease);
}
.skip-link:focus { top: var(--sp-4); }

/* Visually hidden but available to screen readers. */
.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

@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;
  }
}

/* ============================================================================
   Layout primitives
   ========================================================================= */

.ff-container {
  max-width: var(--container);
  margin: 0 auto;
  padding-left: var(--sp-5);
  padding-right: var(--sp-5);
  width: 100%;
}
@media (max-width: 640px) {
  .ff-container { padding-left: var(--sp-4); padding-right: var(--sp-4); }
}

.ff-stack { display: flex; flex-direction: column; }
.ff-stack-2 { gap: var(--sp-2); }
.ff-stack-3 { gap: var(--sp-3); }
.ff-stack-4 { gap: var(--sp-4); }
.ff-stack-6 { gap: var(--sp-6); }

.ff-row { display: flex; align-items: center; gap: var(--sp-3); }
.ff-row-between { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3); }
.ff-wrap { flex-wrap: wrap; }
.ff-grow { flex: 1; min-width: 0; }

/* Section header: title on the left, optional action on the right. Used on
   every surface so "a group of things" always looks the same. */
.ff-section-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-4);
  margin-bottom: var(--sp-4);
}
.ff-section-head h2 { font-size: var(--fs-h2); }
.ff-section-sub { color: var(--text-secondary); font-size: var(--fs-xs); margin-top: 2px; }

.ff-eyebrow {
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-size: var(--fs-micro);
  font-weight: 700;
  color: var(--text-muted-strong);
}

.ff-divider { height: 1px; background: var(--border); border: 0; margin: var(--sp-5) 0; }

/* ============================================================================
   Typography helpers
   ========================================================================= */

.muted { color: var(--text-secondary); font-size: var(--fs-sm); }
.ff-dim { color: var(--text-muted-strong); }
.ff-num {
  /* Tabular figures so price columns and countdowns don't jitter as they change. */
  font-variant-numeric: tabular-nums;
  font-feature-settings: 'tnum' 1;
}
.ff-truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ff-clamp-2 {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* ============================================================================
   Buttons
   ----------------------------------------------------------------------------
   All variants share one geometry so they can sit next to each other without
   the row looking ragged. Min-height 44px = Apple/WCAG touch target floor.
   ========================================================================= */

.ff-btn {
  --btn-h: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  min-height: var(--btn-h);
  padding: 0 var(--sp-5);
  border-radius: var(--r-md);
  border: 1px solid transparent;
  font-size: var(--fs-sm);
  font-weight: 700;
  line-height: 1;
  cursor: pointer;
  text-decoration: none;
  white-space: nowrap;
  transition: background var(--t-fast) var(--ease),
              border-color var(--t-fast) var(--ease),
              color var(--t-fast) var(--ease),
              transform var(--t-fast) var(--ease);
}
.ff-btn:active:not(:disabled) { transform: translateY(1px); }
.ff-btn:disabled, .ff-btn[aria-disabled="true"] {
  cursor: not-allowed;
  opacity: 1;
  background: var(--surface-2);
  border-color: var(--border);
  color: var(--text-muted);
}

.ff-btn-primary { background: var(--purple); color: var(--text-on-accent); }
.ff-btn-primary:hover:not(:disabled) { background: var(--purple-hover); color: var(--text-on-accent-hover); }

.ff-btn-secondary {
  background: var(--surface-2);
  border-color: var(--border);
  color: var(--text-primary);
}
.ff-btn-secondary:hover:not(:disabled) { border-color: var(--border-strong); background: var(--surface-3); }

.ff-btn-ghost { background: transparent; color: var(--text-secondary); }
.ff-btn-ghost:hover:not(:disabled) { background: var(--surface-2); color: var(--text-primary); }

.ff-btn-danger {
  background: transparent;
  border-color: rgba(244, 91, 105, 0.4);
  color: var(--danger);
}
.ff-btn-danger:hover:not(:disabled) { background: var(--danger-tint); border-color: var(--danger); }

.ff-btn-lg { --btn-h: 52px; font-size: var(--fs-body); padding: 0 var(--sp-6); border-radius: var(--r-lg); }
.ff-btn-sm { --btn-h: 36px; font-size: var(--fs-xs); padding: 0 var(--sp-3); }
.ff-btn-block { width: 100%; }

/* Inline spinner for a button that is mid-request. The label stays in place
   and the button keeps its width, so the layout never jumps. */
.ff-btn[data-loading="true"] { color: transparent !important; position: relative; pointer-events: none; }
.ff-btn[data-loading="true"]::after {
  content: '';
  position: absolute;
  width: 16px; height: 16px;
  border-radius: 50%;
  border: 2px solid currentColor;
  border-top-color: transparent;
  color: var(--text-on-accent);
  animation: ff-spin 620ms linear infinite;
}
.ff-btn-secondary[data-loading="true"]::after,
.ff-btn-ghost[data-loading="true"]::after { color: var(--text-primary); }

@keyframes ff-spin { to { transform: rotate(360deg); } }

.ff-icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px; height: 40px;
  border-radius: var(--r-md);
  background: var(--surface-2);
  border: 1px solid var(--border);
  color: var(--text-primary);
  cursor: pointer;
  font-size: 16px;
  line-height: 1;
  transition: border-color var(--t-fast) var(--ease), background var(--t-fast) var(--ease);
}
.ff-icon-btn:hover { border-color: var(--border-strong); background: var(--surface-3); }

.ff-link-btn {
  background: none;
  border: none;
  padding: 0;
  color: var(--purple);
  font-size: var(--fs-sm);
  font-weight: 600;
  cursor: pointer;
  text-decoration: none;
}
.ff-link-btn:hover { text-decoration: underline; }

/* Plain "go back" affordance, used above a detail/sub-view (a menu, an
   earnings panel reached from the overflow menu, ...) to return to what it
   was opened from. */
.back-link {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  background: none;
  border: 0;
  color: var(--text-secondary);
  font-size: var(--fs-xs);
  font-weight: 600;
  cursor: pointer;
  min-height: 44px;
  padding: var(--sp-2) var(--sp-3) var(--sp-2) var(--sp-2);
  margin-bottom: var(--sp-5);
  border-radius: var(--r-sm);
  transition: background var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
}
.back-link:hover { color: var(--text-primary); background: var(--surface-2); }

/* Overflow ("...") menu - the standard home for secondary/account-level
   actions (earnings history, profile, settings, general support) that
   shouldn't compete with the primary workspace. Used by driver-web and
   restaurant-web; never houses anything time-critical (an active delivery's
   own help/report-a-problem entry stays inline in that surface, not here). */
.ff-menu-wrap { position: relative; }
.ff-overflow-menu {
  position: absolute;
  top: calc(100% + var(--sp-2));
  right: 0;
  z-index: 60;
  min-width: 200px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-lg, var(--shadow-md));
  padding: var(--sp-2);
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.ff-overflow-menu-item {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  width: 100%;
  background: none;
  border: none;
  border-radius: var(--r-md);
  padding: var(--sp-3);
  min-height: 44px;
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--text-primary);
  cursor: pointer;
  text-align: left;
}
.ff-overflow-menu-item:hover { background: var(--surface-2); }
.ff-overflow-menu-item.is-danger { color: var(--danger); }
.ff-overflow-menu-divider { height: 1px; background: var(--border); margin: var(--sp-1) var(--sp-2); }
/* Non-interactive context line (e.g. "which restaurant/account am I in") -
   same padding rhythm as a menu item, no hover/press state. */
.ff-overflow-menu-label {
  padding: var(--sp-2) var(--sp-3) var(--sp-1);
  font-size: var(--fs-xs);
  font-weight: 600;
  color: var(--text-muted-strong);
}

/* ============================================================================
   Forms
   ========================================================================= */

.ff-field { margin-bottom: var(--sp-5); }
.ff-label {
  display: block;
  font-size: var(--fs-xs);
  font-weight: 600;
  color: var(--text-primary);
  margin-bottom: var(--sp-2);
}
.ff-hint { font-size: var(--fs-2xs); color: var(--text-muted-strong); margin-top: var(--sp-2); line-height: 1.5; }

.ff-input, .ff-select, .ff-textarea {
  width: 100%;
  min-height: 44px;
  background: var(--surface);
  border: 1px solid var(--border);
  color: var(--text-primary);
  border-radius: var(--r-md);
  padding: var(--sp-3) var(--sp-4);
  font-size: var(--fs-sm);
  transition: border-color var(--t-fast) var(--ease), background var(--t-fast) var(--ease);
}
.ff-input::placeholder, .ff-textarea::placeholder { color: var(--text-muted); }
.ff-input:hover, .ff-select:hover, .ff-textarea:hover { border-color: var(--border-strong); }
.ff-input:focus, .ff-select:focus, .ff-textarea:focus {
  outline: none;
  border-color: var(--purple);
  box-shadow: 0 0 0 3px var(--purple-tint);
}
.ff-textarea { resize: vertical; min-height: 88px; line-height: 1.5; }

.ff-select {
  appearance: none;
  /* Chevron drawn in the token color rather than the OS default, which on a
     dark background renders as a nearly invisible black triangle. */
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8' fill='none'%3E%3Cpath d='M1 1.5L6 6.5L11 1.5' stroke='%23a7a7b3' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right var(--sp-4) center;
  padding-right: var(--sp-10);
}

/* Invalid state carries a border color AND an inline message + icon below, so
   the error is never signalled by color alone. */
.ff-input[aria-invalid="true"],
.ff-select[aria-invalid="true"],
.ff-textarea[aria-invalid="true"] { border-color: var(--danger); }

.ff-error {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-2);
  color: var(--danger);
  font-size: var(--fs-xs);
  margin-top: var(--sp-2);
  line-height: 1.45;
}
.ff-error::before {
  content: '!';
  flex-shrink: 0;
  width: 15px; height: 15px;
  margin-top: 1px;
  border-radius: 50%;
  background: var(--danger);
  color: var(--text-on-status);
  font-size: 10px;
  font-weight: 800;
  display: flex;
  align-items: center;
  justify-content: center;
}

.ff-success-msg {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-2);
  color: var(--success);
  font-size: var(--fs-xs);
  margin-top: var(--sp-2);
}
.ff-success-msg::before { content: '\2713'; font-weight: 800; }

/* Search input with a leading glyph. */
.ff-search { position: relative; }
.ff-search .ff-input { padding-left: var(--sp-10); }
.ff-search::before {
  content: '';
  position: absolute;
  left: var(--sp-4);
  top: 50%;
  transform: translateY(-50%);
  width: 16px; height: 16px;
  pointer-events: none;
  background: var(--text-muted-strong);
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.4' stroke-linecap='round'%3E%3Ccircle cx='11' cy='11' r='7'/%3E%3Cpath d='M20 20l-4-4'/%3E%3C/svg%3E") center/contain no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.4' stroke-linecap='round'%3E%3Ccircle cx='11' cy='11' r='7'/%3E%3Cpath d='M20 20l-4-4'/%3E%3C/svg%3E") center/contain no-repeat;
}
.ff-search-clear {
  position: absolute;
  right: var(--sp-2);
  top: 50%;
  transform: translateY(-50%);
  width: 32px; height: 32px;
  border-radius: var(--r-sm);
  background: none;
  border: none;
  color: var(--text-muted-strong);
  cursor: pointer;
  font-size: 15px;
}
.ff-search-clear:hover { color: var(--text-primary); background: var(--surface-2); }

.ff-check {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-3);
  font-size: var(--fs-sm);
  color: var(--text-secondary);
  cursor: pointer;
  line-height: 1.5;
  /* Full-width target: the whole row is clickable, not just the 16px box. */
  padding: var(--sp-1) 0;
}
.ff-check input[type="checkbox"],
.ff-check input[type="radio"] {
  accent-color: var(--purple);
  width: 18px; height: 18px;
  margin: 0;
  flex-shrink: 0;
  margin-top: 1px;
  cursor: pointer;
}

/* ============================================================================
   Cards & surfaces
   ========================================================================= */

.ff-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  padding: var(--sp-5);
  box-shadow: var(--shadow-card);
}
.ff-card-tight { padding: var(--sp-4); }
.ff-card-flush { padding: 0; overflow: hidden; }

.ff-card-interactive {
  cursor: pointer;
  text-align: left;
  color: inherit;
  width: 100%;
  font-family: inherit;
  transition: border-color var(--t-fast) var(--ease), background var(--t-fast) var(--ease);
}
.ff-card-interactive:hover { border-color: var(--border-strong); background: var(--surface-2); }

/* A card that needs attention (new order, action required). Uses a left rule
   plus the badge inside it - never color on its own. */
.ff-card-attention { border-color: rgba(139, 110, 245, 0.45); box-shadow: inset 3px 0 0 var(--purple); }
.ff-card-urgent { border-color: rgba(244, 91, 105, 0.45); box-shadow: inset 3px 0 0 var(--danger); }

/* ============================================================================
   Badges
   ----------------------------------------------------------------------------
   Every badge pairs its tint with a glyph, so status survives greyscale,
   color-blindness, and low-quality restaurant tablet screens.
   ========================================================================= */

.ff-badge {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-1);
  padding: 4px var(--sp-2);
  border-radius: var(--r-sm);
  font-size: var(--fs-micro);
  font-weight: 700;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  white-space: nowrap;
  background: var(--surface-2);
  color: var(--text-secondary);
  border: 1px solid transparent;
}
/* Drawn marks, not typography: the glyph is a mask filled with the badge's own
   color, so it stays legible in greyscale and never falls back to an OS font. */
.ff-badge::before {
  content: '';
  width: 10px;
  height: 10px;
  flex: none;
  background: currentColor;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-position: center;
  mask-position: center;
  -webkit-mask-size: contain;
  mask-size: contain;
}

.ff-badge-info    { background: var(--info-tint);    color: var(--purple-ink); }
.ff-badge-info::before    { -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='5' fill='%23000' stroke='none'/%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='5' fill='%23000' stroke='none'/%3E%3C/svg%3E"); }
.ff-badge-success { background: var(--success-tint); color: var(--success); }
.ff-badge-success::before { -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12.5l4.5 4.5L19 7.5'/%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12.5l4.5 4.5L19 7.5'/%3E%3C/svg%3E"); }
.ff-badge-warn    { background: var(--warn-tint);    color: var(--warn); }
.ff-badge-warn::before    { -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 4.5L21 19.5H3z'/%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 4.5L21 19.5H3z'/%3E%3C/svg%3E"); }
.ff-badge-danger  { background: var(--danger-tint);  color: var(--danger); }
.ff-badge-danger::before  { -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 6l12 12M18 6L6 18'/%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 6l12 12M18 6L6 18'/%3E%3C/svg%3E"); }
.ff-badge-neutral { background: var(--surface-2); color: var(--text-muted-strong); }
.ff-badge-neutral::before { -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='6'/%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='6'/%3E%3C/svg%3E"); }

/* Plan/marketing badge - outline rather than filled, so it never competes
   with an operational status badge sitting beside it. */
.ff-badge-plan {
  background: transparent;
  border-color: rgba(139, 110, 245, 0.4);
  color: var(--purple-ink);
}
.ff-badge-plan::before { content: none; }

.ff-dot {
  width: 7px; height: 7px;
  border-radius: 50%;
  flex-shrink: 0;
  background: var(--text-muted);
}
.ff-dot-live {
  background: var(--success);
  box-shadow: 0 0 0 3px var(--success-tint);
}

/* ============================================================================
   Tabs / segmented controls
   ========================================================================= */

.ff-tabs {
  display: flex;
  gap: var(--sp-1);
  border-bottom: 1px solid var(--border);
  overflow-x: auto;
  scrollbar-width: none;
}
.ff-tabs::-webkit-scrollbar { display: none; }
.ff-tab {
  position: relative;
  background: none;
  border: none;
  color: var(--text-secondary);
  font-size: var(--fs-sm);
  font-weight: 600;
  padding: var(--sp-3) var(--sp-4);
  cursor: pointer;
  white-space: nowrap;
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  transition: color var(--t-fast) var(--ease);
}
.ff-tab:hover { color: var(--text-primary); }
.ff-tab[aria-selected="true"], .ff-tab.is-active { color: var(--text-primary); }
.ff-tab[aria-selected="true"]::after, .ff-tab.is-active::after {
  content: '';
  position: absolute;
  left: var(--sp-3); right: var(--sp-3);
  bottom: -1px;
  height: 2px;
  background: var(--purple);
  border-radius: 2px 2px 0 0;
}
/* Count pill inside a tab ("Active 3"). */
.ff-tab-count {
  background: var(--surface-2);
  color: var(--text-secondary);
  border-radius: var(--r-pill);
  font-size: var(--fs-micro);
  font-weight: 700;
  padding: 2px 7px;
  min-width: 20px;
  text-align: center;
}
.ff-tab.is-active .ff-tab-count, .ff-tab[aria-selected="true"] .ff-tab-count {
  background: var(--purple-tint);
  color: var(--purple);
}

/* Top-level dashboard nav grouped into scannable clusters instead of one
   long single row - keeps each group at or under the system's own ~4-item
   chunking guidance (see admin and restaurant portal). */
.ff-nav-groups { display: flex; flex-wrap: wrap; gap: var(--sp-5) var(--sp-6); }
.ff-nav-group { display: flex; flex-direction: column; gap: var(--sp-2); }
.ff-nav-group-label {
  font-size: var(--fs-micro);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-muted-strong);
}

.ff-segmented {
  display: inline-flex;
  gap: 3px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  padding: 3px;
}
.ff-segmented-option {
  flex: 1;
  background: none;
  border: none;
  color: var(--text-secondary);
  border-radius: 7px;
  padding: var(--sp-2) var(--sp-4);
  min-height: 38px;
  font-size: var(--fs-xs);
  font-weight: 600;
  cursor: pointer;
  white-space: nowrap;
  transition: background var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
}
.ff-segmented-option:hover { color: var(--text-primary); }
.ff-segmented-option.is-active {
  background: var(--purple);
  color: var(--text-on-accent);
  font-weight: 700;
}

/* Filter chips - horizontally scrollable on mobile, no modal required. */
.ff-chips {
  display: flex;
  gap: var(--sp-2);
  overflow-x: auto;
  scrollbar-width: none;
  padding-bottom: 2px;
}
.ff-chips::-webkit-scrollbar { display: none; }
.ff-chip {
  background: var(--surface);
  border: 1px solid var(--border);
  color: var(--text-secondary);
  font-size: var(--fs-xs);
  font-weight: 600;
  padding: var(--sp-2) var(--sp-4);
  min-height: 38px;
  border-radius: var(--r-pill);
  cursor: pointer;
  white-space: nowrap;
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  transition: all var(--t-fast) var(--ease);
}
.ff-chip:hover { border-color: var(--border-strong); color: var(--text-primary); }
.ff-chip.is-active {
  background: var(--purple-tint);
  color: var(--purple);
  border-color: var(--purple);
}
/* Active chips get a dismiss affordance rather than requiring the user to
   remember which chip they turned on. */
.ff-chip.is-active::after { content: '\00D7'; font-size: 14px; line-height: 1; opacity: 0.8; }

/* ============================================================================
   Price / summary rows
   ----------------------------------------------------------------------------
   The single most trust-sensitive component in the product. Every charge is a
   labeled row, the total is visually separated and the largest thing in the
   block, and nothing is ever collapsed behind a tap.
   ========================================================================= */

.ff-summary { display: flex; flex-direction: column; }
.ff-summary-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--sp-4);
  padding: var(--sp-2) 0;
  font-size: var(--fs-sm);
  color: var(--text-secondary);
}
.ff-summary-row dt, .ff-summary-row .ff-summary-label {
  display: flex;
  align-items: center;
  gap: var(--sp-1);
  margin: 0;
}
.ff-summary-row dd { margin: 0; }
.ff-summary-value { color: var(--text-primary); font-variant-numeric: tabular-nums; }
.ff-summary-row.is-credit .ff-summary-value { color: var(--success); }
.ff-summary-total {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--sp-4);
  margin-top: var(--sp-3);
  padding-top: var(--sp-4);
  border-top: 1px solid var(--border-strong);
  font-family: var(--font-display);
  font-weight: 800;
  font-size: var(--fs-h2);
  color: var(--text-primary);
}
.ff-summary-total .ff-num { font-size: var(--fs-h1); }

/* Small "why am I paying this" affordance. Expands inline - never a modal. */
.ff-info-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 16px; height: 16px;
  border-radius: 50%;
  border: 1px solid var(--border-strong);
  background: none;
  color: var(--text-muted-strong);
  font-size: 10px;
  font-weight: 700;
  cursor: pointer;
  padding: 0;
  line-height: 1;
  flex-shrink: 0;
}
.ff-info-btn:hover { border-color: var(--purple-ink); color: var(--purple-ink); }
.ff-info-panel {
  font-size: var(--fs-2xs);
  color: var(--text-secondary);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  padding: var(--sp-3);
  margin: var(--sp-1) 0 var(--sp-2);
  line-height: 1.5;
}

/* Trust strip - the fairness promise, stated plainly at the point of payment. */
.ff-trust {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-2);
  font-size: var(--fs-2xs);
  color: var(--text-secondary);
  background: var(--success-tint);
  border: 1px solid rgba(74, 222, 128, 0.22);
  border-radius: var(--r-sm);
  padding: var(--sp-3);
  line-height: 1.5;
}
.ff-trust::before { content: '\2713'; color: var(--success); font-weight: 800; flex-shrink: 0; }

/* ============================================================================
   Status timeline
   ========================================================================= */

.ff-timeline { display: flex; flex-direction: column; }
.ff-step {
  display: flex;
  gap: var(--sp-4);
  padding-bottom: var(--sp-5);
  position: relative;
}
.ff-step:last-child { padding-bottom: 0; }
.ff-step:not(:last-child)::before {
  content: '';
  position: absolute;
  left: 8px;
  top: 20px;
  bottom: 0;
  width: 2px;
  background: var(--border);
  border-radius: 1px;
}
.ff-step.is-done:not(:last-child)::before { background: var(--purple); }
.ff-step-marker {
  width: 18px; height: 18px;
  border-radius: 50%;
  background: var(--surface-2);
  border: 2px solid var(--border-strong);
  flex-shrink: 0;
  margin-top: 1px;
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 9px;
  font-weight: 800;
  color: var(--text-on-accent);
}
.ff-step.is-done .ff-step-marker {
  background: var(--purple);
  border-color: var(--purple);
}
.ff-step.is-done .ff-step-marker::after { content: '\2713'; }
.ff-step.is-current .ff-step-marker {
  background: var(--purple);
  border-color: var(--purple);
  box-shadow: 0 0 0 5px var(--purple-tint);
}
.ff-step-body { flex: 1; min-width: 0; padding-top: 0; }
.ff-step-label { font-size: var(--fs-sm); font-weight: 600; color: var(--text-primary); }
.ff-step:not(.is-done):not(.is-current) .ff-step-label { color: var(--text-muted-strong); font-weight: 500; }
.ff-step-meta { font-size: var(--fs-2xs); color: var(--text-muted-strong); margin-top: 2px; }
.ff-step.is-current .ff-step-label { color: var(--purple-ink); }

/* ============================================================================
   Empty / loading / error states
   ========================================================================= */

.ff-empty {
  text-align: center;
  padding: var(--sp-12) var(--sp-5);
  color: var(--text-secondary);
}
.ff-empty-icon {
  font-size: 26px;
  line-height: 1;
  margin-bottom: var(--sp-4);
  opacity: 0.5;
}
/* Drawn icons, not unicode shapes, so the glyph matches the rest of the
   set in every font and both themes. */
/* Drawn icons are display:block, which text-align cannot centre - without
   the auto margins every empty state had its icon stuck to the left edge. */
.ff-empty-icon svg { width: 28px; height: 28px; margin-inline: auto; }
.ff-empty-title {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--fs-h3);
  color: var(--text-primary);
  margin-bottom: var(--sp-2);
}
.ff-empty-body { font-size: var(--fs-sm); max-width: 380px; margin: 0 auto var(--sp-5); line-height: 1.55; }

.ff-alert {
  display: flex;
  gap: var(--sp-3);
  align-items: flex-start;
  border-radius: var(--r-md);
  padding: var(--sp-4);
  font-size: var(--fs-sm);
  line-height: 1.5;
  border: 1px solid;
}
.ff-alert::before { font-weight: 800; flex-shrink: 0; line-height: 1.4; }
.ff-alert-danger  { background: var(--danger-tint);  border-color: rgba(244,91,105,0.3); color: var(--text-primary); }
.ff-alert-danger::before  { content: '\2715'; color: var(--danger); }
.ff-alert-warn    { background: var(--warn-tint);    border-color: rgba(251,191,36,0.3); color: var(--text-primary); }
.ff-alert-warn::before    { content: '\25B2'; color: var(--warn); }
.ff-alert-success { background: var(--success-tint); border-color: rgba(74,222,128,0.3); color: var(--text-primary); }
.ff-alert-success::before { content: '\2713'; color: var(--success); }
.ff-alert-info    { background: var(--purple-tint);  border-color: rgba(139,110,245,0.3); color: var(--text-primary); }
.ff-alert-info::before    { content: 'i'; color: var(--purple); }

/* Skeletons. Shaped like the content they stand in for, so the page doesn't
   visibly re-flow when real data lands. */
.ff-skeleton {
  background: linear-gradient(90deg, var(--skeleton-a) 25%, var(--skeleton-b) 37%, var(--skeleton-a) 63%);
  background-size: 400% 100%;
  animation: ff-shimmer 1.4s ease-in-out infinite;
  border-radius: var(--r-sm);
}
@keyframes ff-shimmer {
  0% { background-position: 100% 50%; }
  100% { background-position: 0 50%; }
}
.ff-skeleton-text { height: 12px; margin-bottom: var(--sp-2); }
.ff-skeleton-title { height: 17px; width: 60%; margin-bottom: var(--sp-3); }
.ff-skeleton-thumb { width: 68px; height: 68px; border-radius: var(--r-md); flex-shrink: 0; }
@media (prefers-reduced-motion: reduce) {
  .ff-skeleton { animation: none; background: var(--surface-2); }
}

/* ============================================================================
   Toast
   ========================================================================= */

.ff-toast-region {
  position: fixed;
  left: 50%;
  bottom: calc(var(--sp-6) + env(safe-area-inset-bottom));
  transform: translateX(-50%);
  z-index: 90;
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  width: min(440px, calc(100vw - var(--sp-8)));
  pointer-events: none;
}
.ff-toast {
  pointer-events: auto;
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  background: var(--surface-3);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-md);
  padding: var(--sp-3) var(--sp-4);
  font-size: var(--fs-sm);
  box-shadow: var(--shadow-lg);
  animation: ff-toast-in var(--t-base) var(--ease);
}
@keyframes ff-toast-in {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: none; }
}
.ff-toast-success::before { content: '\2713'; color: var(--success); font-weight: 800; }
.ff-toast-danger::before  { content: '\2715'; color: var(--danger); font-weight: 800; }

/* ============================================================================
   Modal / bottom sheet
   ----------------------------------------------------------------------------
   Same component, two presentations: centered dialog on desktop, bottom sheet
   on mobile where a centered box is awkward to reach one-handed.
   ========================================================================= */

.ff-scrim {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.6);
  backdrop-filter: blur(2px);
  z-index: 60;
  animation: ff-fade var(--t-base) var(--ease);
}
@keyframes ff-fade { from { opacity: 0; } to { opacity: 1; } }

.ff-modal {
  position: fixed;
  inset: 0;
  z-index: 70;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--sp-5);
  pointer-events: none;
}
.ff-modal-card {
  pointer-events: auto;
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-xl);
  width: 100%;
  max-width: 460px;
  max-height: min(86vh, 760px);
  display: flex;
  flex-direction: column;
  box-shadow: var(--shadow-lg);
  animation: ff-modal-in var(--t-base) var(--ease);
  overflow: hidden;
}
@keyframes ff-modal-in {
  from { opacity: 0; transform: translateY(10px) scale(0.99); }
  to   { opacity: 1; transform: none; }
}
.ff-modal-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--sp-4);
  padding: var(--sp-5) var(--sp-5) var(--sp-4);
  flex-shrink: 0;
}
.ff-modal-body { overflow-y: auto; padding: 0 var(--sp-5); flex: 1; -webkit-overflow-scrolling: touch; }
.ff-modal-foot {
  padding: var(--sp-4) var(--sp-5) var(--sp-5);
  border-top: 1px solid var(--border);
  background: var(--surface);
  flex-shrink: 0;
}

@media (max-width: 640px) {
  .ff-modal { align-items: flex-end; padding: 0; }
  .ff-modal-card {
    max-width: none;
    max-height: 92vh;
    border-radius: var(--r-xl) var(--r-xl) 0 0;
    border-bottom: none;
    animation: ff-sheet-in var(--t-base) var(--ease);
    padding-bottom: env(safe-area-inset-bottom);
  }
  @keyframes ff-sheet-in {
    from { transform: translateY(100%); }
    to   { transform: none; }
  }
  /* Grab handle - signals "this is draggable/dismissable" the way native
     sheets do, without needing a visible close button in the corner. */
  .ff-modal-card::before {
    content: '';
    display: block;
    width: 36px; height: 4px;
    border-radius: 2px;
    background: var(--border-strong);
    margin: var(--sp-3) auto 0;
    flex-shrink: 0;
  }
}

/* Side drawer (cart). Full-height panel on desktop, sheet on mobile. */
.ff-drawer {
  position: fixed;
  top: 0; right: 0; bottom: 0;
  width: 400px;
  max-width: 92vw;
  background: var(--surface);
  border-left: 1px solid var(--border-strong);
  z-index: 70;
  display: flex;
  flex-direction: column;
  box-shadow: var(--shadow-lg);
  animation: ff-drawer-in var(--t-base) var(--ease);
}
@keyframes ff-drawer-in { from { transform: translateX(100%); } to { transform: none; } }
.ff-drawer-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  padding: var(--sp-5);
  border-bottom: 1px solid var(--border);
  flex-shrink: 0;
}
.ff-drawer-body { flex: 1; overflow-y: auto; padding: var(--sp-2) var(--sp-5); }
.ff-drawer-foot {
  padding: var(--sp-5);
  padding-bottom: calc(var(--sp-5) + env(safe-area-inset-bottom));
  border-top: 1px solid var(--border);
  flex-shrink: 0;
}
@media (max-width: 640px) {
  .ff-drawer {
    top: auto;
    left: 0;
    width: 100%;
    max-width: none;
    max-height: 88vh;
    border-left: none;
    border-top: 1px solid var(--border-strong);
    border-radius: var(--r-xl) var(--r-xl) 0 0;
    animation: ff-sheet-in var(--t-base) var(--ease);
  }
}

/* ============================================================================
   App shell - topbar
   ========================================================================= */

.ff-topbar {
  position: sticky;
  top: 0;
  z-index: 50;
  background: var(--chrome);
  backdrop-filter: saturate(160%) blur(14px);
  border-bottom: 1px solid var(--border);
}
.ff-topbar-inner {
  max-width: var(--container);
  margin: 0 auto;
  min-height: var(--topbar-h);
  padding: var(--sp-2) var(--sp-5);
  display: flex;
  align-items: center;
  gap: var(--sp-4);
}
@media (max-width: 640px) {
  .ff-topbar-inner { padding: var(--sp-2) var(--sp-4); gap: var(--sp-3); }
}

.ff-brand {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  background: none;
  border: none;
  cursor: pointer;
  padding: var(--sp-1);
  margin-left: calc(var(--sp-1) * -1);
  flex-shrink: 0;
  text-decoration: none;
}
.ff-brand-mark { border-radius: 6px; width: 28px; height: 28px; }
.ff-brand-word {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 17px;
  color: var(--text-primary);
  letter-spacing: -0.02em;
}
.ff-brand-suffix { font-weight: 600; color: var(--text-muted-strong); }

.ff-topbar-actions { display: flex; align-items: center; gap: var(--sp-2); margin-left: auto; }

/* ============================================================================
   Bottom tab bar (mobile) - thumb-reachable primary navigation
   ========================================================================= */

.ff-tabbar {
  position: fixed;
  left: 0; right: 0; bottom: 0;
  z-index: 55;
  display: none;
  background: var(--chrome);
  backdrop-filter: saturate(160%) blur(14px);
  border-top: 1px solid var(--border);
  padding-bottom: env(safe-area-inset-bottom);
}
.ff-tabbar-item {
  flex: 1;
  background: none;
  border: none;
  cursor: pointer;
  color: var(--text-muted-strong);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  min-height: var(--tabbar-h);
  padding: var(--sp-2) var(--sp-1);
  font-size: var(--fs-micro);
  font-weight: 600;
  position: relative;
  transition: color var(--t-fast) var(--ease);
}
.ff-tabbar-item .ff-tabbar-glyph { font-size: 18px; line-height: 1; }
.ff-tabbar-item.is-active { color: var(--purple-ink); }
/* Current tab is marked by an underline as well as color. */
.ff-tabbar-item.is-active::before {
  content: '';
  position: absolute;
  top: 0;
  left: 28%;
  right: 28%;
  height: 2px;
  border-radius: 0 0 2px 2px;
  background: var(--purple);
}
.ff-tabbar-badge {
  position: absolute;
  top: 6px;
  left: 55%;
  background: var(--purple);
  color: var(--text-on-accent);
  font-size: 10px;
  font-weight: 800;
  min-width: 16px;
  height: 16px;
  border-radius: var(--r-pill);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 4px;
}
@media (max-width: 860px) {
  .ff-tabbar { display: flex; }
  .ff-has-tabbar { padding-bottom: calc(var(--tabbar-h) + env(safe-area-inset-bottom)); }
}

/* Sticky action bar pinned above the tab bar (e.g. "View cart · $24.10"). */
.ff-sticky-bar {
  position: fixed;
  left: 0; right: 0;
  bottom: 0;
  z-index: 56;
  padding: var(--sp-3) var(--sp-4);
  padding-bottom: calc(var(--sp-3) + env(safe-area-inset-bottom));
  background: var(--chrome);
  backdrop-filter: saturate(160%) blur(14px);
  border-top: 1px solid var(--border);
  animation: ff-bar-in var(--t-base) var(--ease);
}
@keyframes ff-bar-in { from { transform: translateY(100%); } to { transform: none; } }
.ff-sticky-bar-inner { max-width: var(--container); margin: 0 auto; }

/* ============================================================================
   Lists & tables
   ========================================================================= */

.ff-list { display: flex; flex-direction: column; gap: var(--sp-3); }
.ff-list-tight { gap: var(--sp-2); }

.ff-list-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-4);
  padding: var(--sp-3) 0;
  border-bottom: 1px solid var(--border);
  font-size: var(--fs-sm);
}
.ff-list-row:last-child { border-bottom: none; }

.ff-table { width: 100%; border-collapse: collapse; font-size: var(--fs-xs); }
.ff-table th {
  text-align: left;
  font-weight: 600;
  color: var(--text-muted-strong);
  font-size: var(--fs-micro);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  padding: var(--sp-2) var(--sp-3);
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
}
.ff-table td { padding: var(--sp-3); border-bottom: 1px solid var(--border); color: var(--text-secondary); }
.ff-table tr:last-child td { border-bottom: none; }
.ff-table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }

/* Metric tiles. Value first and large, label underneath - the number is what
   is being scanned for, so it leads. */
.ff-metrics {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: var(--sp-3);
}
.ff-metric {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  padding: var(--sp-4);
}
.ff-metric-value {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 22px;
  letter-spacing: -0.02em;
  color: var(--text-primary);
  font-variant-numeric: tabular-nums;
  line-height: 1.15;
}
.ff-metric-label {
  font-size: var(--fs-2xs);
  color: var(--text-muted-strong);
  margin-top: var(--sp-1);
}
.ff-metric-note { font-size: var(--fs-micro); color: var(--text-muted-strong); margin-top: var(--sp-1); }

/* ============================================================================
   Map container - constrained by default so it supports the status rather
   than dominating it. See order tracking.
   ========================================================================= */

.ff-map {
  height: 200px;
  border-radius: var(--r-lg);
  overflow: hidden;
  border: 1px solid var(--border);
  background: var(--surface-2);
}
.leaflet-popup-content-wrapper, .leaflet-popup-tip {
  background: var(--surface-3);
  color: var(--text-primary);
}
.leaflet-container a.leaflet-popup-close-button { color: var(--text-secondary); }
.leaflet-container { background: var(--surface-2); font-family: var(--font-body); }

/* ============================================================================
   Utilities
   ========================================================================= */

.ff-hide-mobile { }
.ff-only-mobile { display: none; }
@media (max-width: 640px) {
  .ff-hide-mobile { display: none !important; }
  .ff-only-mobile { display: revert; }
}
@media (max-width: 860px) {
  .ff-hide-tablet { display: none !important; }
}

.ff-mt-0 { margin-top: 0 !important; }
.ff-mt-2 { margin-top: var(--sp-2); }
.ff-mt-3 { margin-top: var(--sp-3); }
.ff-mt-4 { margin-top: var(--sp-4); }
.ff-mt-6 { margin-top: var(--sp-6); }
.ff-mb-0 { margin-bottom: 0 !important; }
.ff-mb-2 { margin-bottom: var(--sp-2); }
.ff-mb-3 { margin-bottom: var(--sp-3); }
.ff-mb-4 { margin-bottom: var(--sp-4); }
.ff-mb-6 { margin-bottom: var(--sp-6); }

/* ============================================================================
   Site footer - legal/accessibility links. Shared across all four web
   surfaces rather than forked per-surface, since it's identical content and
   identical purpose (discoverable Terms/Privacy/Accessibility, not buried
   only in a checkout consent line or an about page nobody visits).
   ========================================================================= */
.site-footer {
  margin-top: var(--sp-10);
  padding-top: var(--sp-6);
  border-top: 1px solid var(--border);
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-4) var(--sp-6);
  color: var(--text-secondary);
  font-size: var(--fs-xs);
}
.site-footer a {
  color: var(--text-secondary);
  /* Rendered at line-height with no padding these were 19.5px tall. */
  display: inline-flex;
  align-items: center;
  min-height: 44px;
}
.site-footer a:hover, .site-footer a:focus-visible { color: var(--text-primary); }

/* ============================================================================
   FOOD LAYER

   Everything below exists because Fairly sells dinner, not dashboards. The
   components in this section are the ones a customer actually looks at while
   deciding what to eat: media frames, restaurant cards, dish rows, category
   tiles, the search field, the quantity stepper and the order bar.
   ========================================================================= */

/* ----------------------------------------------------------------------------
   Media frames

   One aspect-ratio vocabulary for every photograph in the product, so a menu
   never turns into a ragged column of differently-shaped pictures. The frame
   owns the crop; the img only fills it.
   ------------------------------------------------------------------------- */

.ff-media {
  position: relative;
  overflow: hidden;
  background: var(--surface-2);
  border-radius: var(--r-lg);
  isolation: isolate;
  container-type: inline-size;
}
.ff-media > img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  transition: transform var(--t-slow) var(--ease-out);
}
.ff-media-wide   { aspect-ratio: 16 / 10; }
.ff-media-card   { aspect-ratio: 3 / 2; }
.ff-media-square { aspect-ratio: 1 / 1; }
.ff-media-hero   { aspect-ratio: 21 / 9; border-radius: var(--r-2xl); }

@media (max-width: 640px) {
  .ff-media-hero { aspect-ratio: 16 / 10; border-radius: var(--r-xl); }
}

/* Appetite art: what a dish or restaurant gets when it has no photograph yet.
   Fairly is pre-launch, so this is the common case, not the edge case - it
   has to be something the customer is happy to look at rather than a grey
   hole. Six warm, low-saturation grounds, picked by hashing the item's own
   name so the same dish always looks the same. Illustration only: none of
   these colors is ever used on a control, so Ledger Violet remains the only
   thing on screen that means "act here". */
.ff-media-fallback {
  --a: var(--appetite-1a);
  --b: var(--appetite-1b);
  display: grid;
  place-items: center;
  background:
    /* Scrim under the centred mark: white-on-gradient is the one contrast case
       no static analyser can compute, so it is guaranteed here rather than
       assumed. */
    radial-gradient(58% 58% at 50% 50%, rgba(0, 0, 0, 0.22), transparent 72%),
    radial-gradient(120% 90% at 78% 12%, rgba(255, 255, 255, 0.26), transparent 58%),
    radial-gradient(80% 70% at 12% 96%, rgba(0, 0, 0, 0.22), transparent 60%),
    linear-gradient(148deg, var(--a) 0%, var(--b) 100%);
  /* Matches the photo's own transition (.ff-media > img) so a generated
     ground - the common pre-launch case - lifts on card hover exactly like a
     real photo would, instead of the photo-less majority feeling flatter. */
  transition: transform var(--t-slow) var(--ease-out);
}
/* A faint scatter over the gradient so the frame reads as a surface with a
   texture rather than a flat swatch of color. */
.ff-media-fallback::before {
  content: '';
  position: absolute;
  inset: 0;
  background-image:
    radial-gradient(circle at 20% 30%, rgba(255, 255, 255, 0.16) 0 1.4px, transparent 1.6px),
    radial-gradient(circle at 68% 18%, rgba(255, 255, 255, 0.12) 0 1.1px, transparent 1.3px),
    radial-gradient(circle at 44% 74%, rgba(255, 255, 255, 0.10) 0 1.6px, transparent 1.8px),
    radial-gradient(circle at 86% 62%, rgba(0, 0, 0, 0.10) 0 1.3px, transparent 1.5px);
  background-size: 92px 92px, 74px 74px, 118px 118px, 64px 64px;
  opacity: 0.9;
  pointer-events: none;
}
.ff-media-fallback[data-appetite="1"] { --a: var(--appetite-1a); --b: var(--appetite-1b); }
.ff-media-fallback[data-appetite="2"] { --a: var(--appetite-2a); --b: var(--appetite-2b); }
.ff-media-fallback[data-appetite="3"] { --a: var(--appetite-3a); --b: var(--appetite-3b); }
.ff-media-fallback[data-appetite="4"] { --a: var(--appetite-4a); --b: var(--appetite-4b); }
.ff-media-fallback[data-appetite="5"] { --a: var(--appetite-5a); --b: var(--appetite-5b); }
.ff-media-fallback[data-appetite="6"] { --a: var(--appetite-6a); --b: var(--appetite-6b); }

/* The mark inside the fallback: the restaurant's or dish's own initial, large
   and confident, so a photo-less grid still reads as a set of distinct places
   rather than repeated wallpaper. */
.ff-media-initial {
  position: relative;
  z-index: 1;
  font-family: var(--font-display);
  font-weight: 800;
  font-size: clamp(26px, 24cqi, 64px);
  line-height: 1;
  letter-spacing: -0.04em;
  color: #fff;
  text-shadow: 0 2px 14px rgba(0, 0, 0, 0.30);
  user-select: none;
}

/* A logo is a mark, not a photograph - letterboxing it on its own ground
   keeps it from being cropped into an unrecognizable slice. */
.ff-media-logo > img { object-fit: contain; padding: 12%; background: #fff; }

/* ----------------------------------------------------------------------------
   Icons

   Drawn, from one authored sprite (shared/fairly-icons.js) at a single
   stroke weight - never a unicode glyph standing in for an icon.
   ------------------------------------------------------------------------- */

.ff-i {
  width: 20px;
  height: 20px;
  flex: none;
  display: block;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.ff-i-sm { width: 16px; height: 16px; stroke-width: 1.75; }
.ff-i-lg { width: 24px; height: 24px; stroke-width: 1.5; }
.ff-i-xl { width: 32px; height: 32px; stroke-width: 1.4; }
.ff-i-fill { fill: currentColor; stroke: none; }

/* ----------------------------------------------------------------------------
   Restaurant card

   A photograph the size of a decision, then the four facts that decide it:
   who, what kind of food, how they are rated, and whether you can order now.
   ------------------------------------------------------------------------- */

.ff-rgrid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(272px, 1fr));
  gap: var(--sp-8) var(--sp-5);
}
/* One column on a phone, not two. A restaurant card is chosen from its
   photograph, and a two-up grid at 375px leaves that photograph 165px wide -
   too small to make anyone hungry. Full width gives it 343px. */
@media (max-width: 560px) {
  .ff-rgrid { grid-template-columns: 1fr; gap: var(--sp-6); }
}

/* Reference card pattern: photo on top, a plain white body below it - not
   text overlaid on the photo. Card itself is the clipping/shadow/radius
   surface (border + shadow-sm + 28.8px radius, matched exactly to the
   reference's computed styles); the photo inside it carries no radius of
   its own since the card already clips it. */
.ff-rcard {
  display: flex;
  flex-direction: column;
  padding: 0;
  border: 1px solid var(--border);
  background: var(--surface);
  color: inherit;
  text-align: left;
  font: inherit;
  cursor: pointer;
  width: 100%;
  border-radius: 28.8px;
  overflow: hidden;
  box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px -1px rgba(0, 0, 0, 0.1);
  transition: transform var(--t-fast) var(--ease), box-shadow var(--t-fast) var(--ease);
}
.ff-rcard .ff-media { border-radius: 0; box-shadow: none; }
.ff-rcard .ff-media { position: relative; aspect-ratio: 16 / 10; }
.ff-rcard:hover { transform: translateY(-2px); box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1); }
.ff-rcard:hover .ff-media > img,
.ff-rcard:hover .ff-media-fallback { transform: scale(1.05); }
.ff-rcard:active { transform: translateY(-1px); }

.ff-rcard-body {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 16px;
}
.ff-rcard-name {
  font-family: var(--font-serif, var(--font-display));
  font-weight: 600;
  font-size: 18px;
  letter-spacing: -0.005em;
  line-height: 1.2;
  color: var(--text-primary);
}
.ff-rcard-meta {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  flex-wrap: wrap;
  font-size: var(--fs-sm);
  color: var(--text-secondary);
  margin-top: 4px;
}
.ff-rcard-meta .ff-sep { color: var(--text-muted-strong); }
.ff-rcard-meta .ff-dim { color: var(--text-muted-strong); }
.ff-rcard-top { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--sp-3); }
.ff-rcard-body .ff-rating { color: var(--text-primary); }
.ff-rcard-body .ff-rating .ff-i { color: var(--honey, #e0a858); }
.ff-rcard-body .ff-rating-count, .ff-rcard-body .ff-rating-new { color: var(--text-muted-strong); }

/* Overlays live on the photo, where the eye already is. */
.ff-media-tags {
  position: absolute;
  z-index: 2;
  left: var(--sp-3);
  top: var(--sp-3);
  display: flex;
  gap: var(--sp-2);
  flex-wrap: wrap;
}
.ff-media-tags-bottom { top: auto; bottom: var(--sp-3); }
.ff-media-tag {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  background: var(--chrome);
  backdrop-filter: saturate(150%) blur(10px);
  color: var(--text-primary);
  font-size: var(--fs-micro);
  font-weight: 700;
  letter-spacing: 0.02em;
  padding: 5px 9px;
  border-radius: var(--r-pill);
  box-shadow: var(--shadow-sm);
}
.ff-media-tag-accent { color: var(--purple-ink); }

/* A closed restaurant is still worth looking at - it stays legible and gains
   an explicit label, rather than being dimmed into an unreadable grey. */
.ff-rcard-closed .ff-media::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 1;
  background: rgba(20, 16, 26, 0.42);
}
.ff-rcard-closed .ff-media > img { filter: saturate(0.55); }

/* The rating: a drawn star, a number, and a count. "New" when there is nothing
   to average yet - never an invented score. */
.ff-rating {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: var(--fs-xs);
  font-weight: 700;
  color: var(--text-primary);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.ff-rating .ff-i { color: var(--purple-ink); }
.ff-rating-count { color: var(--text-muted-strong); font-weight: 500; }
.ff-rating-new { font-weight: 700; color: var(--text-muted-strong); }

/* ----------------------------------------------------------------------------
   Dish card

   The menu is the product catalogue. Same grid language as the restaurant
   card (.ff-rgrid/.ff-rcard below) - a photo the size of a decision, then
   the facts that make it, so browsing a menu and browsing restaurants feel
   like one system. Three columns is what the math below actually produces
   at this container's real widths (see customer-web/styles.css .menu-layout),
   not a number picked by eye: minmax(250px,1fr) is the widest a tile can be
   while still leaving room for a third at the ~860px desktop menu column,
   with slack under 272px (the restaurant grid's own minimum) so a scrollbar
   never knocks it down to two. Section headings are rendered as plain
   children of this same grid (see .ff-menu-section-title, .menu-group-title,
   .menu-featured-note in this file and customer-web/styles.css) - each spans
   every column so it still reads as a full-width break between groups of
   cards, the same job the old list's section headers did between its rows.
   ------------------------------------------------------------------------- */

.ff-menu {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
  gap: var(--sp-8) var(--sp-5);
  align-items: start;
}
.ff-menu-section-title,
.menu-group-title,
.menu-featured-note {
  grid-column: 1 / -1;
}
@media (max-width: 640px) {
  /* One column on a phone, not a cramped two-up - same reasoning as
     .ff-rgrid: a photo that has to make someone hungry needs more than
     half of a 375px screen. Zero grid-gap here, not var(--sp-6): below
     this width the dish card itself reverts to a bordered row (see
     .ff-dish further down, after its base rule - this override has to
     come AFTER the unconditional .ff-dish/.ff-dish-media rules in the
     cascade, not before, since equal-specificity same-file rules resolve
     by source order regardless of which one sits inside an active media
     query), which supplies its own rhythm the same way the old list did -
     a grid gap on top of that would double the spacing. */
  .ff-menu { grid-template-columns: 1fr; gap: 0; }
}

.ff-dish {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  width: 100%;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
  border-radius: var(--r-lg);
  transition: transform var(--t-fast) var(--ease);
}
.ff-dish:hover { transform: translateY(-3px); }
.ff-dish:hover .ff-dish-name { color: var(--purple-ink); }
.ff-dish:hover .ff-media > img,
.ff-dish:hover .ff-media-fallback { transform: scale(1.05); }
.ff-dish:active { transform: translateY(0); }
.ff-dish-text { min-width: 0; display: flex; flex-direction: column; gap: 5px; padding: 0 2px; }
.ff-dish-name {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: var(--fs-h3);
  letter-spacing: -0.012em;
  line-height: 1.3;
  color: var(--text-primary);
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  flex-wrap: wrap;
  transition: color var(--t-fast) var(--ease);
}
.ff-dish-desc {
  color: var(--text-secondary);
  font-size: var(--fs-sm);
  line-height: 1.5;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.ff-dish-price {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: var(--fs-body);
  color: var(--text-primary);
  font-variant-numeric: tabular-nums;
  margin-top: 2px;
}
/* Visually first despite following the text in markup (see renderMenuItemCard) -
   a decorative image (alt="") has nothing to say before the dish's own name
   does, so the reading order stays name-first while the eye still meets the
   photo first, same as every other card in this system. */
.ff-dish-media { order: -1; width: 100%; box-shadow: var(--shadow-card); }
.ff-dish-media .ff-media-initial { font-size: 34px; }

.ff-dish-soldout { opacity: 0.6; cursor: default; }
.ff-dish-soldout:hover { transform: none; }
.ff-dish-soldout:hover .ff-dish-name { color: var(--text-primary); }
.ff-dish-soldout .ff-media > img { filter: saturate(0.3); }

@media (max-width: 640px) {
  /* A phone menu gets scanned fast while doing something else, not
     browsed slowly the way the desktop grid invites - one giant
     full-bleed square photo per row (right at home with room to spare
     above 640px) turns a five-item menu into a lot of scrolling. Revert
     to the compact thumbnail-row shape this list used before the desktop
     grid existed: small square thumbnail beside the text, flush rows
     separated by a hairline instead of card gaps. */
  .ff-dish {
    display: grid;
    grid-template-columns: 1fr 80px;
    align-items: center;
    gap: var(--sp-4);
    padding: var(--sp-4) 0;
    border-bottom: 1px solid var(--border);
  }
  .ff-dish:last-child { border-bottom: 0; }
  .ff-dish:hover { transform: none; }
  .ff-dish-text { padding: 0; }
  .ff-dish-desc { font-size: var(--fs-xs); }
  .ff-dish-media { order: 0; width: 80px; box-shadow: none; border-radius: var(--r-md); }
}

/* The tap target that skips the modal for an item with no required choices. */
.ff-dish-add {
  position: absolute;
  right: 8px;
  bottom: 8px;
  z-index: 3;
  width: 34px;
  height: 34px;
  display: grid;
  place-items: center;
  border-radius: var(--r-pill);
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text-primary);
  box-shadow: var(--shadow-md);
  cursor: pointer;
  transition: background var(--t-fast) var(--ease), color var(--t-fast) var(--ease), transform var(--t-fast) var(--ease);
}
.ff-dish-add:hover { background: var(--purple); color: var(--text-on-accent); border-color: transparent; }
.ff-dish-add:active { transform: scale(0.92); }

.ff-menu-section-title {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: var(--fs-display);
  letter-spacing: -0.03em;
  color: var(--text-primary);
  margin: var(--sp-12) 0 var(--sp-2);
  scroll-margin-top: 140px;
}
.ff-menu-section-title:first-child { margin-top: 0; }
.ff-menu-section-note { color: var(--text-secondary); font-size: var(--fs-sm); margin-bottom: var(--sp-2); }
@media (max-width: 640px) {
  .ff-menu-section-title { font-size: var(--fs-h1); margin-top: var(--sp-8); }
}

/* ----------------------------------------------------------------------------
   Category tiles + horizontal rails
   ------------------------------------------------------------------------- */

.ff-rail {
  display: flex;
  gap: var(--sp-3);
  overflow-x: auto;
  scrollbar-width: none;
  scroll-snap-type: x proximity;
  padding-bottom: var(--sp-1);
}
.ff-rail::-webkit-scrollbar { display: none; }
.ff-rail > * { scroll-snap-align: start; flex: none; }
/* Lets a rail bleed to the viewport edge inside a padded container, so the
   last tile is visibly cut off and the row reads as scrollable. */
.ff-rail-bleed { margin-inline: calc(var(--gutter) * -1); padding-inline: var(--gutter); }

.ff-cat {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-2);
  width: 88px;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  cursor: pointer;
  border-radius: var(--r-md);
}
.ff-cat-art {
  width: 72px;
  height: 72px;
  border-radius: var(--r-pill);
  display: grid;
  place-items: center;
  color: #fff;
  transition: transform var(--t-base) var(--ease-out), box-shadow var(--t-base) var(--ease);
  box-shadow: var(--shadow-card);
}
.ff-cat:hover .ff-cat-art { transform: translateY(-3px); box-shadow: var(--shadow-md); }
.ff-cat:active .ff-cat-art { transform: translateY(0); }
.ff-cat-label {
  font-size: var(--fs-2xs);
  font-weight: 600;
  color: var(--text-secondary);
  text-align: center;
  line-height: 1.3;
}
.ff-cat[aria-pressed="true"] .ff-cat-art { box-shadow: 0 0 0 2px var(--bg), 0 0 0 4px var(--purple-ink); }
.ff-cat[aria-pressed="true"] .ff-cat-label { color: var(--purple-ink); font-weight: 700; }

/* ----------------------------------------------------------------------------
   Search - the front door of a food app, sized like it
   ------------------------------------------------------------------------- */

.ff-searchbig {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-pill);
  padding: 0 var(--sp-5);
  height: 56px;
  box-shadow: var(--shadow-card);
  transition: border-color var(--t-fast) var(--ease), box-shadow var(--t-fast) var(--ease);
}
.ff-searchbig:focus-within {
  border-color: var(--purple-ink);
  box-shadow: 0 0 0 3px var(--purple-tint), var(--shadow-md);
}
.ff-searchbig > .ff-i { color: var(--text-muted-strong); }
.ff-searchbig:focus-within > .ff-i { color: var(--purple-ink); }
.ff-searchbig input {
  flex: 1;
  min-width: 0;
  border: 0;
  background: none;
  color: var(--text-primary);
  font-size: var(--fs-body);
  height: 100%;
  outline: none;
  appearance: none;
}
/* The pill itself already draws the focus ring on :focus-within above - the
   global :focus-visible ring (small radius, meant for the sharp-cornered
   controls elsewhere in the system) would otherwise stack a second,
   squarer ring directly on the input, floating inside the pill. */
.ff-searchbig input:focus-visible { box-shadow: none; border-radius: 0; }
.ff-searchbig input::placeholder { color: var(--text-muted-strong); }
.ff-searchbig input::-webkit-search-cancel-button { appearance: none; }
.ff-searchbig-clear {
  border: 0;
  background: none;
  color: var(--text-muted-strong);
  cursor: pointer;
  display: grid;
  place-items: center;
  width: 32px;
  height: 32px;
  border-radius: var(--r-pill);
  flex: none;
}
.ff-searchbig-clear:hover { background: var(--surface-2); color: var(--text-primary); }
@media (max-width: 640px) {
  .ff-searchbig { height: 50px; padding: 0 var(--sp-4); }
}

/* ----------------------------------------------------------------------------
   Quantity stepper
   ------------------------------------------------------------------------- */

.ff-stepper {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--r-pill);
  padding: 3px;
}
.ff-stepper button {
  width: 38px;
  height: 38px;
  display: grid;
  place-items: center;
  border: 0;
  border-radius: var(--r-pill);
  background: none;
  color: var(--text-primary);
  cursor: pointer;
  transition: background var(--t-fast) var(--ease), color var(--t-fast) var(--ease), transform var(--t-fast) var(--ease);
}
.ff-stepper button:hover:not(:disabled) { background: var(--surface); color: var(--purple-ink); }
.ff-stepper button:active:not(:disabled) { transform: scale(0.92); }
.ff-stepper button:disabled { color: var(--text-muted); cursor: not-allowed; }
.ff-stepper-value {
  min-width: 30px;
  text-align: center;
  font-weight: 800;
  font-family: var(--font-display);
  font-variant-numeric: tabular-nums;
  color: var(--text-primary);
}

/* ----------------------------------------------------------------------------
   Order bar - the persistent "you have food waiting" surface
   ------------------------------------------------------------------------- */

.ff-orderbar {
  position: fixed;
  left: 50%;
  bottom: calc(var(--sp-4) + env(safe-area-inset-bottom));
  z-index: 56;
  width: min(560px, calc(100% - var(--sp-8)));
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-3) var(--sp-3) var(--sp-5);
  border: 0;
  border-radius: var(--r-pill);
  background: var(--ink-canvas);
  color: #f7f4fb;
  font: inherit;
  cursor: pointer;
  box-shadow: 0 18px 44px rgba(20, 16, 26, 0.34);
  transform: translateX(-50%);
  animation: ff-orderbar-in var(--t-slow) var(--ease-out);
}
.ff-orderbar:hover { background: #1d1826; }
.ff-orderbar-count {
  display: grid;
  place-items: center;
  min-width: 26px;
  height: 26px;
  padding: 0 7px;
  border-radius: var(--r-pill);
  background: var(--purple);
  color: var(--void);
  font-size: var(--fs-2xs);
  font-weight: 800;
  font-variant-numeric: tabular-nums;
}
.ff-orderbar-label { flex: 1; min-width: 0; font-size: var(--fs-sm); font-weight: 600; text-align: left; }
.ff-orderbar-total {
  font-family: var(--font-display);
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  background: var(--purple);
  color: var(--void);
  padding: 8px var(--sp-4);
  border-radius: var(--r-pill);
}
@keyframes ff-orderbar-in {
  from { opacity: 0; transform: translate(-50%, 14px); }
  to   { opacity: 1; transform: translate(-50%, 0); }
}
/* Sits above the mobile tab bar rather than on top of it. */
body.has-tabbar .ff-orderbar { bottom: calc(var(--tabbar-h) + var(--sp-3) + env(safe-area-inset-bottom)); }

/* ----------------------------------------------------------------------------
   Section rhythm

   Alternating grounds are what stop a long page from reading as one endless
   column of identical cards.
   ------------------------------------------------------------------------- */

.ff-band { padding: var(--sp-20) 0; }
.ff-band-tight { padding: var(--sp-12) 0; }
.ff-band-paper { background: var(--surface); }
.ff-band-warm { background: var(--surface-2); }
@media (max-width: 640px) {
  .ff-band { padding: var(--sp-12) 0; }
}

.ff-lede {
  font-size: var(--fs-body);
  line-height: 1.6;
  color: var(--text-secondary);
  max-width: 62ch;
}
.ff-section-title {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: clamp(26px, 3.4vw, 38px);
  letter-spacing: -0.03em;
  line-height: 1.12;
  text-wrap: balance;
  color: var(--text-primary);
}

/* ----------------------------------------------------------------------------
   Reveal-on-scroll. One authored entrance, used on marketing bands only -
   never on anything a customer is waiting to interact with.
   ------------------------------------------------------------------------- */

/* Scoped to .ff-js: the hidden starting state only exists once the script
   that can reveal it has actually run. Without this, a failed or blocked
   script leaves the marketing bands permanently invisible. */
.ff-js .ff-reveal {
  opacity: 0;
  transform: translateY(18px);
  transition: opacity 620ms var(--ease-out), transform 620ms var(--ease-out);
}
.ff-js .ff-reveal.is-in { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) {
  .ff-js .ff-reveal { opacity: 1; transform: none; transition: none; }
}

/* ============================================================================
   Critical band

   The alcohol-handoff and return-required states. Not a tinted alert among
   other alerts: a solid ground, a heavy border, a drawn mark, the rule named
   in words, and the instruction at a size a driver reads without stopping.
   ========================================================================= */

.ff-critical {
  border-radius: var(--r-xl);
  border: 2px solid;
  padding: var(--sp-5);
  margin-bottom: var(--sp-4);
}
.ff-critical-danger { background: var(--critical-danger-solid, var(--danger-tint)); border-color: var(--danger); }
.ff-critical-warn { background: var(--critical-warn-solid, var(--warn-tint)); border-color: var(--warn); }

.ff-critical-head {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  margin-bottom: var(--sp-3);
}
.ff-critical-rule {
  font-size: var(--fs-xs);
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.ff-critical-danger .ff-critical-head { color: var(--danger); }
.ff-critical-warn .ff-critical-head { color: var(--warn); }
.ff-critical-headline {
  font-family: var(--font-display);
  font-size: 20px;
  font-weight: 800;
  letter-spacing: -0.02em;
  line-height: 1.25;
  color: var(--text-primary);
  margin-bottom: var(--sp-2);
}
.ff-critical-body { font-size: var(--fs-sm); line-height: 1.55; color: var(--text-secondary); }
.ff-critical-body + .ff-critical-body { margin-top: var(--sp-2); }
@media (max-width: 640px) {
  .ff-critical-headline { font-size: 18px; }
}

/* ============================================================================
   Map (Mapbox GL) - shared/fairly-map.js
   ----------------------------------------------------------------------------
   Fairly's own marker faces, standing in for Mapbox's default red pin
   everywhere a map ships. One driver-dot design, one pin design, used by
   every surface (driver-web, customer-web, restaurant-web, admin-web) so a
   map reads as the same product wherever it appears.
   ========================================================================= */

.ff-map { position: relative; width: 100%; border-radius: var(--r-lg); overflow: hidden; background: var(--surface-2); }
.ff-map .mapboxgl-map { border-radius: inherit; }
/* Mapbox's own control chrome, recolored to the token set instead of its
   default light-grey-on-white regardless of which style is loaded. */
.ff-map .mapboxgl-ctrl-group { background: var(--surface); box-shadow: var(--shadow-card); border-radius: var(--r-sm); }
.ff-map .mapboxgl-ctrl-attrib { background: transparent; font-size: 10px; }

.ff-map-unavailable {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  min-height: 220px;
  padding: var(--sp-6);
  text-align: center;
  color: var(--text-secondary);
  background: var(--surface-2);
  border-radius: var(--r-lg);
  font-size: var(--fs-sm);
}

/* Recenter control - the map's own affordance for "the camera stopped
   following me on purpose, tap to resume" (driver nav) or "show me my
   driver again" (customer tracking). Floats over the map, never competes
   with Mapbox's own top-right zoom control. */
.ff-map-recenter-btn {
  position: absolute;
  right: var(--sp-3);
  bottom: var(--sp-3);
  z-index: 5;
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  height: 40px;
  padding: 0 var(--sp-4);
  background: var(--surface);
  color: var(--text-primary);
  border: 1px solid var(--border);
  border-radius: var(--r-pill);
  font-size: var(--fs-xs);
  font-weight: 700;
  cursor: pointer;
  box-shadow: var(--shadow-md);
  transition: opacity var(--t-fast) var(--ease), transform var(--t-fast) var(--ease);
}
.ff-map-recenter-btn:hover { transform: translateY(-1px); }
.ff-map-recenter-btn[hidden] { display: none; }

/* Driver marker: a live position, not a pin - a dot reads as "a moving
   thing," a pin reads as "a fixed place," and only one of those is true
   here. The heading wedge only shows when heading is actually known
   (fairly-map.js hides it otherwise) - never implying a direction of
   travel Fairly doesn't actually have data for. */
.ff-map-marker-driver { width: 34px; height: 34px; position: relative; }
.ff-map-marker-pulse {
  position: absolute;
  inset: 0;
  border-radius: 999px;
  background: var(--purple);
  opacity: 0.35;
  animation: ffMapPulse 2.2s ease-out infinite;
}
.ff-map-marker-driver.is-stale .ff-map-marker-pulse { animation: none; opacity: 0; }
@keyframes ffMapPulse {
  0% { transform: scale(0.55); opacity: 0.45; }
  100% { transform: scale(1.5); opacity: 0; }
}
.ff-map-marker-dot {
  position: absolute;
  inset: 7px;
  border-radius: 999px;
  background: var(--purple);
  border: 3px solid var(--void-black, #0a0a0d);
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.35);
}
.ff-map-marker-driver.is-stale .ff-map-marker-dot { background: var(--text-muted-strong); }
.ff-map-marker-heading {
  position: absolute;
  top: -11px;
  left: 50%;
  width: 0;
  height: 0;
  margin-left: -6px;
  border-left: 6px solid transparent;
  border-right: 6px solid transparent;
  border-bottom: 9px solid var(--purple);
  transform-origin: 6px 20px;
  transition: transform 300ms var(--ease);
}

/* Restaurant/customer marker: a pin, for a fixed place. Restaurant and
   customer share one shape and differ only by color, so the two never get
   visually confused with the driver's moving dot. */
.ff-map-marker-restaurant, .ff-map-marker-customer { width: 26px; height: 34px; }
.ff-map-marker-pin {
  display: block;
  width: 26px;
  height: 26px;
  border-radius: 50% 50% 50% 0;
  transform: rotate(-45deg);
  margin-top: 4px;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
  border: 2px solid var(--surface);
}
.ff-map-marker-restaurant .ff-map-marker-pin { background: var(--appetite-terracotta, #8f4b2e); }
.ff-map-marker-customer .ff-map-marker-pin { background: var(--purple-ink); }

/* ----------------------------------------------------------------------------
   PREFERENCES (language, appearance)

   A labelled select pair, used in the customer footer, the mobile more-menu,
   and the driver and portal headers. Built from the same tokens as
   everything else, so it inherits both skins without a line of its own.

   Deliberately a native <select>: it is the one control that already works
   with every screen reader, every keyboard, and every mobile picker, and a
   language switcher is the last place to spend novelty on.
   ------------------------------------------------------------------------- */
.ff-pref {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  font-size: var(--fs-2xs);
  color: var(--text-secondary);
}

.ff-pref-label {
  white-space: nowrap;
}

.ff-pref select {
  font: inherit;
  font-size: var(--fs-2xs);
  color: var(--text-primary);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  padding: var(--sp-1) var(--sp-2);
  cursor: pointer;
  /* Without this the native control keeps the OS light chrome in the ink
     skin - white text on a white popup, which is how a theme "works
     everywhere" and is still unreadable in the one place it matters. */
  color-scheme: inherit;
}

.ff-pref select:focus-visible {
  outline: none;
  box-shadow: var(--focus-ring);
}

.ff-footer-prefs {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-5);
  padding: var(--sp-4) 0 0;
  border-top: 1px solid var(--border);
  margin-top: var(--sp-4);
}

.topbar-more-prefs {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  padding: var(--sp-3);
  border-top: 1px solid var(--border);
  margin-top: var(--sp-1);
}

.topbar-more-prefs .ff-pref {
  justify-content: space-between;
  width: 100%;
}

/* "Forgot password?" under every sign-in form - one style for all three
   sites, since only the driver site had a link-button style of its own and
   the other two showed a bare grey browser button. */
.forgot-password-btn {
  display: inline-block;
  margin-top: var(--sp-3);
  padding: var(--sp-1) 0;
  min-height: 32px;
  border: 0;
  background: none;
  /* --purple is the brand fill; --purple-ink is the one that passes as text
     (3.72 vs 6.69 on white). */
  color: var(--purple-ink);
  font: inherit;
  font-size: var(--fs-sm);
  font-weight: 600;
  cursor: pointer;
  text-align: left;
}
.forgot-password-btn:hover { text-decoration: underline; }
.forgot-password-btn:focus-visible { outline: 2px solid var(--purple); outline-offset: 2px; border-radius: 4px; }
#forgot-password-status { margin-top: var(--sp-2); font-size: var(--fs-sm); }

/* "Known for" on a browse card, and the popularity badges on a dish.
   ---------------------------------------------------------------------------
   Both say the same kind of thing - other people order this - and both are
   counts rather than claims, so they are allowed to look confident. Amber
   rather than the brand violet: violet is Fairly talking about itself, and a
   kitchen's reputation is its own. --amber and --warn-tint already flip per
   theme, so neither needs an ink override. */
.ff-rcard-known {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-top: 6px;
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--amber);
}
.ff-rcard-known [data-icon] { flex-shrink: 0; }

.ff-dish-badge {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 3px 9px;
  border-radius: var(--r-pill);
  font-size: var(--fs-xs);
  font-weight: 700;
  line-height: 1.3;
  background: var(--warn-tint);
  color: var(--amber);
}
.ff-dish-badge [data-icon] { flex-shrink: 0; }
/* A runner-up should not shout as loudly as the dish that actually leads. */
.ff-dish-badge.is-quiet {
  background: var(--surface-2);
  color: var(--text-muted-strong);
  font-weight: 600;
}

/* The popularity badge sits between the dish name and its description: after
   the thing being named, before the sentence that sells it, and never in the
   price's line where it would compete with the number. */
.ff-dish-badges { display: flex; gap: 6px; flex-wrap: wrap; margin: 2px 0 1px; }

/* ============================================================================
   Motion
   ----------------------------------------------------------------------------
   Character, kept to the places where movement means something.

   The rule followed here: motion acknowledges an action, or it does not ship.
   A control that moves when you press it is telling you it heard you; a logo
   that moves when you reach for it is telling you it is a button. Anything
   that moves on its own, forever, with nothing to say, is noise - so there
   is none of that below.

   Deliberately absent: the kitchen display and the driver's active delivery.
   Those are read in a hurry by somebody holding a pan or a steering wheel,
   and the last thing either needs is a screen that fidgets. They keep their
   existing, quieter set.

   The global prefers-reduced-motion rule above flattens every duration here
   to 0.01ms, so all of this degrades to the plain state change underneath.
   ========================================================================= */

/* --- The press ----------------------------------------------------------
   Compress fast, release slow. That asymmetry is what reads as a physical
   button rather than a rectangle changing size: the finger's push is
   instant, the material's recovery is not. */
.ff-btn,
.primary-btn,
.secondary-btn,
.toggle-btn,
.order-now-btn,
.about-cta,
.about-closing-cta {
  transition: background var(--t-fast) var(--ease),
              border-color var(--t-fast) var(--ease),
              color var(--t-fast) var(--ease),
              box-shadow var(--t-base) var(--ease),
              transform var(--t-base) var(--ease-out);
}
.ff-btn:hover:not(:disabled),
.primary-btn:hover:not(:disabled),
.secondary-btn:hover:not(:disabled),
.order-now-btn:hover:not(:disabled),
.about-cta:hover,
.about-closing-cta:hover {
  transform: translateY(-1px);
}
.ff-btn:active:not(:disabled),
.primary-btn:active:not(:disabled),
.secondary-btn:active:not(:disabled),
.toggle-btn:active:not(:disabled),
.order-now-btn:active:not(:disabled),
.about-cta:active,
.about-closing-cta:active {
  transform: scale(0.97);
  transition-duration: 80ms;
}

/* An icon riding inside a button leans into the press with it, rather than
   sitting still while the surface under it moves. */
.ff-btn .ff-i { transition: transform var(--t-base) var(--ease-out); }
.ff-btn:hover:not(:disabled) .ff-i { transform: scale(1.08); }

/* --- Icon-only controls -------------------------------------------------- */
.ff-icon-btn,
.icon-btn {
  transition: background var(--t-fast) var(--ease),
              color var(--t-fast) var(--ease),
              transform var(--t-base) var(--ease-out);
}
.ff-icon-btn:hover:not(:disabled),
.icon-btn:hover:not(:disabled) { transform: scale(1.08); }
.ff-icon-btn:active:not(:disabled),
.icon-btn:active:not(:disabled) { transform: scale(0.94); transition-duration: 80ms; }

/* --- Disclosure ---------------------------------------------------------
   A chevron that turns is the cheapest honest way to say which direction
   this thing is about to move. */
[data-icon="chevron-down"], [data-icon-done="chevron-down"],
.ff-chev,
.chev {
  transition: transform var(--t-base) var(--ease-out);
}
[aria-expanded="true"] .ff-chev,
[aria-expanded="true"] .chev,
[aria-expanded="true"] [data-icon="chevron-down"],
[aria-expanded="true"] [data-icon-done="chevron-down"],
details[open] > summary .chev { transform: rotate(180deg); }
