/*
 * navshell.css — pill-based primary nav, lifted from
 * Mirepoix_Design_System-2026-05-27/ui_kits/mirepoix/styles.css (nav sections).
 *
 * Adaptations from the source:
 *   - All hardcoded colors replaced with token references (--ground,
 *     --red, --red-soft, --red-deep, --red-glow, --pill-bg, etc.).
 *     PR 1 added these tokens to base.css as aliases or new entries.
 *   - Drops anything that touches .appwrap or .paper (page chrome
 *     is out of scope for this port).
 *   - data-cramped is read from .navshell itself (no .appwrap host).
 *   - .navshell uses position: sticky; top: 0; z-index: 30; instead of
 *     the design system's absolute-inside-appwrap positioning.
 *
 * Collaborators:
 *   - base.css (token vocabulary)
 *   - app/views/shared/_navshell.html.erb (markup)
 *   - app/views/shared/_brand_menu.html.erb (popover)
 *   - app/javascript/controllers/nav_shell_controller.js (width measurement)
 *   - app/javascript/controllers/brand_menu_controller.js (popover)
 *
 * Reference (read-only): the design-system styles.css uses comment-divider
 * markers (── pill primitive ──, ── pill variant: brand ──, etc.) to signpost
 * each section; this file preserves them. Do not write `*` `/` next to each
 * other inside this comment — it terminates the outer block early and the
 * parser eats the next rule.
 */

/* ============================================================================
 * .pill — the one chrome primitive every nav surface inherits.
 * Opaque paper capsule with a top-edge highlight, paper-noise overlay,
 * and the system's offset-SE shadow.
 * ========================================================================= */

/* .brand-host wraps the brand pill and its popover so the brand-menu
 * Stimulus controller has both as descendants. display: contents keeps
 * the wrapper layout-transparent — the surrounding slot sees the button
 * and popover as direct children, so positioning isn't affected. */
.brand-host {
  display: contents;
}

.pill {
  position: relative;
  display: inline-flex;
  align-items: center;
  box-sizing: border-box;
  background: var(--pill-bg);
  border: 1px solid var(--pill-border);
  border-radius: var(--radius-pill);
  box-shadow: var(--pill-shadow);
  isolation: isolate;
  color: var(--text);
  font: inherit;
  -webkit-tap-highlight-color: transparent;
}
.pill::before {
  content: "";
  position: absolute;
  inset: 0;
  background-image: url("/assets/paper-noise-4ecbafa7.svg");
  background-size: var(--noise-tile) var(--noise-tile);
  border-radius: inherit;
  opacity: var(--paper-opacity);
  pointer-events: none;
}

/* =============================================================================
 * NAV BUTTON STATE SYSTEM
 *
 * Four button types share one interactive vocabulary:
 *   .pill--brand       — the Mirepoix wordmark
 *   .destination       — destinations inside the host destinations pill
 *   .pill--icon        — the search and help icon buttons
 *   .brandmenu__item   — rows inside the brand-menu popover
 *
 * Shared treatments (this block):
 *   :hover  → layer a red-soft background tint as a gradient image, so it
 *             stacks on top of the existing paper bg-color on free-standing
 *             pills, and stands on its own on transparent nested buttons.
 *             Skipped on the current destination (its inset-card chrome
 *             would fight the hover bg).
 *
 * Per-variant treatments (later blocks):
 *   inner halo glows  — each button has its own anatomy of icons/text
 *   focus indicator   — outline ring on outer pills, emboss on menu rows
 *   press deboss      — same recipe (--shadow-deboss / --shadow-deboss-deep)
 *   inner content shift on press — translate or padding shift
 *   open / current / modal-active — each is structurally different
 *
 * Tokens carry the shadow + halo recipes; dark-mode overrides flow through
 * the tokens, so the per-variant rules never need their own dark fork. */

/* :hover — shared background tint */
@media (hover: hover) {
  :is(.pill--brand,
      .destination:not([aria-current="page"]),
      .pill--icon,
      .brandmenu__item):hover {
    background-image: linear-gradient(var(--red-soft), var(--red-soft));
  }
}
.pill--brand.is-hover,
.destination.is-hover,
.pill--icon.is-hover,
.brandmenu__item.is-hover {
  background-image: linear-gradient(var(--red-soft), var(--red-soft));
}

/* ─── Pill variant: brand ───────────────────────────────────── */

/* Base */
.pill--brand {
  height: 48px;
  padding: 0 14px 0 10px;
  gap: 8px;
  cursor: pointer;
  transition:
    color var(--duration-fast),
    background var(--duration-fast),
    box-shadow var(--duration-fast);
}
.brand__mark {
  width: 20px; height: 20px;
  display: block;
  /* Inner radius rhymes with the pill's full-round outer edge — a soft
   * concentric corner (outer 24px radius − 14px mark inset ≈ 10px, eased
   * to 5px so the mark still reads as a square color-field, just softened
   * at the corners). */
  border-radius: 5px;
  overflow: hidden;
  /* Keep the mark above .pill::before's paper-noise overlay at all times. The
   * wordmark and caret carry permanent transforms, which already promote them
   * over the (absolutely positioned) noise; the mark had neither transform nor
   * filter at rest, so it alone rendered UNDER the noise — then any state that
   * adds a filter (hover, the #2629 pin flash) promoted it for the duration and
   * it visibly snapped opaque and back. Pinning it above makes its paint order
   * state-independent, and the texture stopping at the logo reads crisper. */
  position: relative;
  transition: transform var(--duration-fast), filter var(--duration-fast);
}
/* Mark internals are opaque quadrant fills baked into the SVG assets.
 * iOS Safari flattens semi-transparent overlapping SVG bars inside this
 * pill chrome, even when the SVG is loaded through <img>. */
/* Wordmark glyph — the cursive "mirepoix" mask, filled by currentColor. Sized
 * by height so it tracks the 20px coral mark; width derives from
 * --wordmark-aspect. Like the mark + caret it keeps its ink tone on hover/open
 * and gains the backlit-paper halo — the prior live-text wordmark behaved the
 * same way (a --text-soft body with a red glow, never a red fill). */
.pill--brand .wordmark__glyph {
  height: 1.7rem;
  color: var(--text-soft);
  /* Optically centre the wordmark on the coral mark. The flex row centres the
   * glyph BOX, but the eye tracks the ink centre of mass, which sits above the
   * box centre — so nudge the glyph down by the generator-derived shift. This
   * lands the mark's centreline through the wordmark's visual middle with the
   * 'p' descender hanging below. Mathematical, not measured — see
   * --wordmark-optical-shift in base.css. */
  transform: translateY(var(--wordmark-optical-shift));
  transition: transform var(--duration-fast), filter var(--duration-fast);
}
.brand__caret {
  /* Composed transform: rotate is set by [aria-expanded="true"] in the
   * open state below, translateY is set by :active in the press state.
   * Each modifies one custom property, so press + open stack instead of
   * clobbering each other.
   *
   * The glyph is a downward chevron at rest, morphing to × on open via
   * two motions running together on the inner <g>:
   *   1. The two strokes' bottom endpoints slide apart horizontally to
   *      cross at the centre (positions change, orientation doesn't).
   *   2. The group spins 90° clockwise — the rotation is purely a
   *      flourish (the strokes are already at ±45° so a 90° turn leaves
   *      them at ±45°, still forming an ×), but with --ease-bounce it
   *      overshoots ~10°, rubber-bands back, and gives the morph its
   *      physical, momentum-laden feel. Lifted from LoveFrom's info
   *      button — a one-stroke i→× spin with the same overshoot.
   * overflow: visible so the spring's overshoot doesn't clip strokes
   * against the SVG viewport edge. */
  --caret-y: 0px;
  color: var(--text-light);
  /* Optically centre the wordmark between the mark and the caret. The chevron's
   * viewBox pads its strokes with empty box on the leading edge, and the cursive
   * mask sits tight to its ink — so the +2px this carried (tuned for the old
   * live-text wordmark) over-spaced the caret. A small negative margin pulls the
   * caret's ink in to mirror the mark→wordmark gap, fixing the right-side bias
   * from #2142. */
  margin-left: -2px;
  overflow: visible;
  transform: translateY(var(--caret-y));
  transition:
    transform var(--duration-fast),
    color var(--duration-fast),
    filter var(--duration-fast);
}
.brand__caret-g {
  transform-box: fill-box;
  transform-origin: center;
  transform: rotate(0deg);
  transition: transform 0.42s var(--ease-bounce);
}
.brand__caret-l,
.brand__caret-r {
  transition: transform 0.42s var(--ease-bounce);
}

/* States — brand pill.
 *   :hover         → wordmark, mark, and caret turn red with a backlit-paper
 *                    halo. Background tint is handled by the shared :hover
 *                    rule above.
 *   :focus-visible → red outline, offset so the pill silhouette stays
 *   :active        → red-soft-2 tint over opaque pill paper + deboss-deep
 *                    shadow. Inner contents (mark, name, caret) translate
 *                    down 1 px so they settle into the recess. The caret uses
 *                    custom-prop composition so the press translate stacks
 *                    with the open rotate.
 *   [aria-expanded="true"] → same red + halo as hover, plus the caret flips.
 *                    The caret rotation is the differentiator from hover.
 */

/* :hover — halos on inner elements (background tint is shared above) */
@media (hover: hover) {
  .pill--brand:hover { color: var(--red); }
  .pill--brand:hover .brand__mark { filter: var(--halo-icon); }
  .pill--brand:hover .wordmark__glyph { filter: var(--halo-icon); }
  .pill--brand:hover .brand__caret {
    color: var(--red);
    filter: var(--halo-icon);
  }
}
.pill--brand.is-hover { color: var(--red); }
.pill--brand.is-hover .brand__mark { filter: var(--halo-icon); }
.pill--brand.is-hover .wordmark__glyph { filter: var(--halo-icon); }
.pill--brand.is-hover .brand__caret {
  color: var(--red);
  filter: var(--halo-icon);
}

/* :focus-visible */
.pill--brand:focus-visible,
.pill--brand.is-focus {
  outline: var(--focus-ring);
  outline-offset: var(--focus-ring-offset);
}

/* :active — deboss-deep + inner content settles 1 px down */
.pill--brand:active,
.pill--brand.is-pressed {
  background: var(--pill-bg);
  background-image: linear-gradient(var(--red-soft-2), var(--red-soft-2));
  box-shadow: var(--shadow-deboss-deep);
}
.pill--brand:active .brand__mark,
.pill--brand.is-pressed .brand__mark {
  transform: translateY(1px);
}
.pill--brand:active .wordmark__glyph,
.pill--brand.is-pressed .wordmark__glyph {
  /* Compose with the resting optical shift so press still settles 1px down. */
  transform: translateY(calc(var(--wordmark-optical-shift) + 1px));
}
.pill--brand:active .brand__caret,
.pill--brand.is-pressed .brand__caret { --caret-y: 1px; }

/* [aria-expanded="true"] — menu open */
.pill--brand[aria-expanded="true"],
.pill--brand.is-open { color: var(--red); }
.pill--brand[aria-expanded="true"] .brand__mark,
.pill--brand.is-open .brand__mark { filter: var(--halo-icon); }
.pill--brand[aria-expanded="true"] .wordmark__glyph,
.pill--brand.is-open .wordmark__glyph { filter: var(--halo-icon); }
.pill--brand[aria-expanded="true"] .brand__caret,
.pill--brand.is-open .brand__caret {
  color: var(--red);
  filter: var(--halo-icon);
}
.pill--brand[aria-expanded="true"] .brand__caret-g,
.pill--brand.is-open .brand__caret-g { transform: rotate(90deg); }
.pill--brand[aria-expanded="true"] .brand__caret-l,
.pill--brand.is-open .brand__caret-l { transform: translateX(1.5px); }
.pill--brand[aria-expanded="true"] .brand__caret-r,
.pill--brand.is-open .brand__caret-r { transform: translateX(-1.5px); }

/* Pin/unpin attention cue (#2629). Pinning or unpinning a recipe changes the
 * pinned list inside the (closed) Mirepoix menu with no on-screen hint; this
 * briefly flashes the brand pill's active-state colors so the eye lands on the
 * menu. Applied in the acting tab only — brand_menu_controller#flash toggles the
 * class off a client-side recipe-pin:toggled; observing tabs converge through the
 * broadcast morph without theatre. Colour + halo only, no transforms (the caret
 * does NOT spin — that would read as the menu opening): a single gentle wash needs
 * no prefers-reduced-motion fork and sits far below flash-frequency thresholds.
 *
 * Shaped like one incandescent-bulb pulse: peak at ~28% for a quick ramp up, then
 * the longer tail decays back out. --ease-curtain (an ease-out curve) governs both
 * the rise and the fall, so each is fast-in / slow-settle. It must play once,
 * uninterrupted — the pin's originator morph is held off the pill's class by
 * brand_menu_controller#guardFlashMorph so idiomorph can't restart it into a
 * flicker. Every track (tint, halo, caret) shares this timing so the pill dims as
 * one; brand-pill-flash (on the ::after tint) is what endFlash keys on. */
/* The tint rides a ::after overlay faded by opacity — NOT an animated
 * background-image. A linear-gradient image between transparent and --red-soft
 * does not interpolate: it steps at the midpoint, so the wash used to snap off
 * partway through the decay while the halo kept fading — a phantom extra phase on
 * a separate track from the rest (#2629 review). Opacity interpolates cleanly, so
 * the wash now fades in lockstep with the glow. z-index: -1 (contained by .pill's
 * isolation) sits it above the pill paper but below the mark/wordmark, matching
 * the menu-open wash. Its brand-pill-flash animationend is what endFlash keys on. */
.pill--brand-flash::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: inherit;
  background: var(--red-soft);
  pointer-events: none;
  animation: brand-pill-flash 0.7s var(--ease-curtain);
}
@keyframes brand-pill-flash {
  0%, 100% { opacity: 0; }
  28%      { opacity: 1; }
}
/* Mark + wordmark gain the backlit-paper halo but keep their ink tone — the
 * wordmark never red-fills (see .wordmark__glyph above), matching menu-open.
 *
 * The halo's decay lands at zero EARLY (55%), not at 100% with the wash: equal
 * animation fractions are not equal visibility. The wash tops out at --red-soft's
 * 0.12 alpha and drops below perception around mid-decay, while --red-glow peaks
 * at 0.42-0.55 — on the same curve it stays visible ~200ms after the wash reads
 * as gone, leaving the logo glowing alone (the "dim-except-logo" phantom phase).
 * Pinning the glow to zero where the wash perceptually exits keeps the whole
 * pill dimming as one. Endpoints are explicit transparent drop-shadows so the
 * color channel interpolates — never an implicit filter: none discrete jump. */
.pill--brand-flash .brand__mark,
.pill--brand-flash .wordmark__glyph {
  animation: brand-glow-flash 0.7s var(--ease-curtain);
}
@keyframes brand-glow-flash {
  0%, 55%, 100% { filter: drop-shadow(0 0 5px transparent); }
  28%           { filter: var(--halo-icon); }
}
/* The caret additionally reddens, exactly as in the open state. Its ink color
 * keeps the full-length curve (solid ink at low mix exits perception with the
 * wash); only its halo gets the early zero, same as the mark's. */
.pill--brand-flash .brand__caret {
  animation: brand-caret-flash 0.7s var(--ease-curtain);
}
@keyframes brand-caret-flash {
  0%, 55%, 100% { filter: drop-shadow(0 0 5px transparent); }
  28%           { color: var(--red); filter: var(--halo-icon); }
}

/* ─── Pill variant: destinations (the floating menu plate) ─── */

/* Base */
.pill--destinations {
  height: 48px;
  /* padding(3) + border(1) = 4px ring on every side, matching the inter-item gap.
   * Inner padding-box is exactly 40px, the destination button's height. */
  padding: 3px;
  gap: 4px;
  /* override .pill's inline-flex with proper flex so children layout cleanly */
  display: flex;
}

.destination {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  background: none;
  border: none;
  border-radius: var(--radius-pill);
  padding: 2px 14px 3px;
  min-width: 60px;
  color: var(--text-soft);
  text-shadow: 0 0 0 transparent;
  cursor: pointer;
  font-family: var(--font-body);
  font-size: 0.62rem;
  letter-spacing: 0.04em;
  -webkit-tap-highlight-color: transparent;
  transition:
    background var(--duration-fast),
    color var(--duration-fast),
    box-shadow var(--duration-fast),
    text-shadow var(--duration-fast);
}
.destination__icon {
  display: flex;
  filter: drop-shadow(0 0 0 transparent);
  transition: filter var(--duration-fast), transform var(--duration-fast);
}
.destination__icon > svg { display: block; }
.destination__label {
  line-height: 1;
  transition: transform var(--duration-fast);
}

/* States — destination.
 *   :hover         → red text/icon + backlit-paper halo. Background tint is
 *                    handled by the shared :hover rule above (which skips
 *                    the current page so its inset-card chrome isn't fought
 *                    by the hover bg).
 *   :focus-visible → red outline + red text, offset 1 px because the parent
 *                    pill is tight (2 px offset would clip the next sibling)
 *   :active        → red-soft-2 tint over opaque paper + red-deep text +
 *                    deboss. Inner icon + label translate down 1 px. Wins
 *                    over [aria-current] so the current button still gives
 *                    press feedback.
 *   [aria-current="page"] → emboss — a recessed paper plate inside the host
 *                    pill.
 */

/* :hover — halos on text and icon. Applies even when the destination is
 * the current page — the halo glow is a touch of life on top of the emboss
 * (and matches every other interactive button in the system). Only the bg
 * tint above stays gated off, because it would flatten the inset card. */
@media (hover: hover) {
  .destination:hover {
    color: var(--red);
    text-shadow: var(--halo-text);
  }
  .destination:hover .destination__icon {
    filter: var(--halo-icon);
  }
}
.destination.is-hover {
  color: var(--red);
  text-shadow: var(--halo-text);
}
.destination.is-hover .destination__icon { filter: var(--halo-icon); }

/* :focus-visible */
.destination:focus-visible,
.destination.is-focus {
  outline: var(--focus-ring);
  outline-offset: 1px;
  color: var(--red);
}

/* :active — deboss + inner content settles 1 px down. The
 * [aria-current="page"]:active selector is at specificity (0,2,1) so it
 * beats the [aria-current] (0,2,0) emboss below — clicking the current page
 * (a no-op) still gives press feedback. */
.destination:active,
.destination[aria-current="page"]:active,
.destination.is-pressed {
  background: var(--content-card-bg);
  background-image: linear-gradient(var(--red-soft-2), var(--red-soft-2));
  color: var(--red-deep);
  box-shadow: var(--shadow-deboss);
}
.destination:active .destination__icon,
.destination:active .destination__label,
.destination[aria-current="page"]:active .destination__icon,
.destination[aria-current="page"]:active .destination__label,
.destination.is-pressed .destination__icon,
.destination.is-pressed .destination__label {
  transform: translateY(1px);
}

/* [aria-current="page"] — the current page */
.destination[aria-current="page"],
.destination.is-current {
  color: var(--red);
  background: var(--content-card-bg);
  box-shadow: var(--shadow-emboss);
}

/* ─── Pill variant: icon-only square (search, help) ─────────── */

/* Base */
.pill--icon {
  width: 48px;
  height: 48px;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  color: var(--text-soft);
  padding: 0;
  transition:
    color var(--duration-fast),
    background var(--duration-fast),
    box-shadow var(--duration-fast);
}
.pill--icon > svg,
.pill--icon > [data-icon] > svg { display: block; }
.pill--icon > [data-icon],
.pill--icon > svg {
  transition: filter var(--duration-fast), transform var(--duration-fast);
}

/* Label modifier — pill--icon with a visible text label alongside its icon.
 * Used for the Sign-in pill in the right cluster (icon-only is harder to
 * discover for non-recurring controls). Overrides the fixed 48 px width and
 * zero padding so the label and icon both have room. */
.pill--icon.pill--with-label {
  width: auto;
  padding: 0 14px;
  gap: 8px;
}
.pill__label {
  font-family: var(--font-body);
  font-size: var(--type-sm);
  font-weight: 500;
  letter-spacing: 0.02em;
  color: inherit;
}

/* States — icon pill (search, help).
 *   :hover         → icon turns red with a backlit-paper halo. Background
 *                    tint is shared above.
 *   :focus-visible → red outline + red icon, offset 2 px
 *   :active        → red-soft-2 tint over opaque pill paper + deboss-deep.
 *                    Icon translates down 1 px into the recess. Wins over
 *                    [aria-expanded] so press feedback fires even when the
 *                    overlay is open.
 *   [aria-expanded="true"] → "modal open" state. A persistent lighter deboss
 *                    with red-soft tint over opaque pill paper so the pill
 *                    reads as a held-down toggle while its overlay is showing.
 *                    navshell.js mirrors aria-expanded onto the trigger. (Only
 *                    the search button uses this; help has no overlay.)
 */

/* :hover — icon halo (bg tint shared above) */
@media (hover: hover) {
  .pill--icon:hover { color: var(--red); }
  .pill--icon:hover > [data-icon] > svg,
  .pill--icon:hover > svg { filter: var(--halo-icon); }
}
.pill--icon.is-hover { color: var(--red); }
.pill--icon.is-hover > [data-icon] > svg,
.pill--icon.is-hover > svg { filter: var(--halo-icon); }

/* :focus-visible */
.pill--icon:focus-visible,
.pill--icon.is-focus {
  outline: var(--focus-ring);
  outline-offset: var(--focus-ring-offset);
  color: var(--red);
}

/* :active — full pill deboss + inner icon settles down */
.pill--icon:active,
.pill--icon[aria-expanded="true"]:active,
.pill--icon.is-pressed {
  color: var(--red-deep);
  background: var(--pill-bg);
  background-image: linear-gradient(var(--red-soft-2), var(--red-soft-2));
  box-shadow: var(--shadow-deboss-deep);
}
.pill--icon:active > [data-icon],
.pill--icon:active > svg,
.pill--icon[aria-expanded="true"]:active > [data-icon],
.pill--icon[aria-expanded="true"]:active > svg,
.pill--icon.is-pressed > [data-icon],
.pill--icon.is-pressed > svg {
  transform: translateY(1px);
}

/* [aria-expanded="true"] — modal open (held-down toggle) */
.pill--icon[aria-expanded="true"],
.pill--icon.is-active {
  color: var(--red);
  background: var(--pill-bg);
  background-image: linear-gradient(var(--red-soft), var(--red-soft));
  box-shadow: var(--shadow-deboss-modal);
}

/* ============================================================================
 * .navshell — the nav layout shell.
 *
 * Two slot containers:
 *   .navshell__top   — top strip (brand; a top-level help icon only when there's
 *                      no Mirepoix menu to hold it — logged-out / no kitchen, #2361;
 *                      search + Mirabel's trigger on kitchen pages; destinations
 *                      join it when not cramped)
 *   .navshell__dock  — bottom floating dock (holds the destinations pill and
 *                      the search pill when cramped, #2353)
 *
 * JS toggles `data-cramped` on .navshell and moves pills between slots.
 * CSS nav layout keys off the .navshell attribute.
 * ========================================================================= */

.navshell {
  position: sticky;
  top: 0;
  z-index: var(--z-nav);
  pointer-events: none;
}
.navshell * { pointer-events: auto; }

/* Remove the default anchor underline on nav links (destinations,
 * brand link, sign-in pill, help, brandmenu items). The brand menu
 * popover lives inside .navshell via the .brand-host display:contents
 * wrapper, so this selector covers it too. Hover/focus states paint
 * their own affordance — halo, emboss, deboss — instead. */
.navshell a { text-decoration: none; }

/* While JS is measuring on first paint, keep things invisible so the user
 * doesn't see a flash of the wrong layout. The JS clears this on the next
 * frame after applying the right layout. */
.navshell[data-measuring="true"] { visibility: hidden; }
.navshell[data-measuring="true"] .navshell__top { width: max-content; }

/* Top strip — three columns: brand | (destinations centered) | (search + help).
 *
 * Shares the content card's EXACT box: max-width 41rem + margin-inline: auto,
 * identical to main (base.css). Because both resolve to the same centered 41rem
 * box at every width, the strip never has to "reach" a wider cap to line up — so
 * the pills sit a constant inset inside the card edges on wide screens, narrow
 * screens, and phones alike. (#2144: the drift came from the strip capping
 * WIDER than the card — 41rem + 2·gap — and only meeting the card edges once it
 * actually hit that wider max-width; in the band where it couldn't, the gap
 * ramped from flush to a full gingham inset.)
 *
 * The inset is pure padding, split by axis:
 *   - inline (--space-2, 0.5rem): the small, consistent nudge inside the card
 *     edges. This is THE alignment value — retune the inset here.
 *   - block (--gingham-gap): breathing room above the strip and between it and
 *     the card, on the same spacing rhythm as the card's outer margin. Shrinks
 *     at max-width: 720px (base.css) with the token; the inline inset stays a
 *     fixed token, deliberately constant across widths. */
.navshell__top {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: var(--gingham-gap);
  padding: var(--gingham-gap) var(--space-2);
  max-width: 41rem;
  margin-inline: auto;
  box-sizing: border-box;
  pointer-events: none;        /* the strip itself doesn't catch clicks */
}
.navshell__top > * { pointer-events: auto; }

.navshell__slot--brand   { justify-self: start;  position: relative; }
.navshell__slot--center  { justify-self: center; }
.navshell__slot--right   {
  justify-self: end;
  display: flex;
  align-items: center;
  gap: 10px;
}

/* Note: the nav icon pills (search, help, brand) are a uniform 48px. The one
 * exception is the mobile dock's destinations pill, which grows taller than
 * 48px so its destination touch targets meet the ~48px iOS/Material norm (#1569). */

/* ─── Brand menu popover ────────────────────────────────────── */

.brandmenu {
  position: absolute;
  top: calc(100% + 8px);
  left: 0;
  z-index: var(--z-nav-overflow);
  width: 268px;
  background: var(--content-card-bg);
  border: 1px solid var(--rule);
  border-radius: 8px;
  box-shadow: var(--shadow-popover);
  padding: 6px;
  animation: brandmenu-in 0.18s var(--ease-curtain);
}
@keyframes brandmenu-in {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: translateY(0);    }
}
.brandmenu__kitchen {
  font-family: var(--font-display);
  font-style: italic;
  font-size: 1.15rem;
  color: var(--text);
  line-height: 1;
}
.brandmenu__user {
  font-family: var(--font-body);
  font-size: 0.72rem;
  color: var(--text-light);
  letter-spacing: 0.04em;
  margin-top: 4px;
}
.brandmenu__list { list-style: none; padding: 0; margin: 0; }

/* Base */
.brandmenu__item {
  display: grid;
  grid-template-columns: 24px 1fr;
  column-gap: 12px;
  align-items: center;
  width: 100%;
  background: none;
  border: none;
  text-align: left;
  padding: 8px 10px;
  border-radius: 5px;
  font: inherit;
  color: var(--text);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition:
    background var(--duration-fast),
    box-shadow var(--duration-fast),
    color var(--duration-fast),
    text-shadow var(--duration-fast),
    padding var(--duration-fast);
}
.brandmenu__icon {
  display: flex; align-items: center; justify-content: center;
  color: var(--text-soft);
  width: 24px; height: 24px;
  transition: color var(--duration-fast), filter var(--duration-fast);
}
.brandmenu__icon > svg { display: block; }
.brandmenu__label {
  font-family: var(--font-body);
  font-size: 0.92rem;
  font-weight: 500;
  color: var(--text);
  grid-column: 2;
  transition: color var(--duration-fast), text-shadow var(--duration-fast);
}

/* Quiet variant base — Sign out is visually demoted from the regular items. */
.brandmenu__item--quiet .brandmenu__label { color: var(--text-soft); }

/* States — brand menu item.
 *   :hover         → label/icon turn red with backlit halo. Background tint
 *                    is handled by the shared :hover rule above.
 *   :focus-visible → embossed paper card — raised box-shadow + 1 px inset
 *                    border + red text/icon. Rhymes with the destination
 *                    [aria-current="page"] card-on-pill look. No left bar,
 *                    no halo — the raised treatment is the affordance.
 *   :active        → red-soft-2 + deboss. Inner contents settle 1 px down
 *                    via a padding shift (top +1, bottom -1) so the whole
 *                    row moves uniformly as a single grid reflow. No
 *                    per-child transforms (those drifted at different rates
 *                    because of how filters and text-shadows rasterize).
 *
 * The quiet variant (Sign out) keeps a neutral vocabulary: warm-gray hover
 * (via its own override, since the shared red-soft hover would pull brand
 * red onto a destructive action), neutral embossed focus, rule-faint press;
 * no red glow anywhere.
 */

/* :hover — halos on label and icon (bg tint shared above) */
@media (hover: hover) {
  .brandmenu__item:hover .brandmenu__label,
  .brandmenu__item:hover .brandmenu__arrow {
    color: var(--red);
    text-shadow: var(--halo-text);
  }
  .brandmenu__item:hover .brandmenu__icon {
    color: var(--red);
    filter: var(--halo-icon);
  }
  /* Quiet variant overrides the shared red-soft tint with a neutral
   * warm-gray and skips the halo. */
  .brandmenu__item--quiet:hover {
    background-image: linear-gradient(var(--surface-alt), var(--surface-alt));
  }
  .brandmenu__item--quiet:hover .brandmenu__label,
  .brandmenu__item--quiet:hover .brandmenu__icon {
    color: var(--text);
    text-shadow: none;
    filter: none;
  }
}
.brandmenu__item.is-hover .brandmenu__label,
.brandmenu__item.is-hover .brandmenu__arrow {
  color: var(--red);
  text-shadow: var(--halo-text);
}
.brandmenu__item.is-hover .brandmenu__icon {
  color: var(--red);
  filter: var(--halo-icon);
}
.brandmenu__item--quiet.is-hover {
  background-image: linear-gradient(var(--surface-alt), var(--surface-alt));
}
.brandmenu__item--quiet.is-hover .brandmenu__label,
.brandmenu__item--quiet.is-hover .brandmenu__icon {
  color: var(--text);
  text-shadow: none;
  filter: none;
}

/* :focus-visible — embossed paper card. Uses --surface-alt (slightly
 * different from the popover's --content-card-bg in both light and dark
 * modes) so the raised card actually reads against the popover. The
 * bordered emboss adds a subtle 1 px inset edge so the silhouette is
 * legible even when the bg contrast is low. */
.brandmenu__item:focus-visible,
.brandmenu__item.is-focus,
.brandmenu__item--quiet:focus-visible,
.brandmenu__item--quiet.is-focus {
  outline: none;
  background: var(--surface-alt);
  box-shadow: var(--shadow-emboss-bordered);
}
.brandmenu__item:focus-visible,
.brandmenu__item.is-focus { color: var(--red); }
.brandmenu__item:focus-visible .brandmenu__label,
.brandmenu__item:focus-visible .brandmenu__icon,
.brandmenu__item:focus-visible .brandmenu__arrow,
.brandmenu__item.is-focus .brandmenu__label,
.brandmenu__item.is-focus .brandmenu__icon,
.brandmenu__item.is-focus .brandmenu__arrow { color: var(--red); }
.brandmenu__item--quiet:focus-visible .brandmenu__label,
.brandmenu__item--quiet:focus-visible .brandmenu__icon,
.brandmenu__item--quiet.is-focus .brandmenu__label,
.brandmenu__item--quiet.is-focus .brandmenu__icon {
  color: var(--text);
  text-shadow: none;
  filter: none;
}

/* :active — deboss + padding shift (top +1, bottom -1) so the whole grid
 * content reflows down 1 px as one unit. */
.brandmenu__item:active,
.brandmenu__item.is-pressed {
  padding: 9px 10px 7px;
  background: var(--red-soft-2);
  box-shadow: var(--shadow-deboss);
}
.brandmenu__item:active .brandmenu__label,
.brandmenu__item:active .brandmenu__icon,
.brandmenu__item:active .brandmenu__arrow,
.brandmenu__item.is-pressed .brandmenu__label,
.brandmenu__item.is-pressed .brandmenu__icon,
.brandmenu__item.is-pressed .brandmenu__arrow { color: var(--red-deep); }

.brandmenu__item--quiet:active,
.brandmenu__item--quiet.is-pressed {
  padding: 9px 10px 7px;
  background: var(--rule-faint);
  box-shadow: var(--shadow-deboss);
}
.brandmenu__item--quiet:active .brandmenu__label,
.brandmenu__item--quiet:active .brandmenu__icon,
.brandmenu__item--quiet.is-pressed .brandmenu__label,
.brandmenu__item--quiet.is-pressed .brandmenu__icon { color: var(--text); }

/* Home link — a .brandmenu__item variant whose content is the serif kitchen
 * name + user line instead of a plain label. Base + shared hover/focus/press
 * come from .brandmenu__item; these rules lay out the two-line text and
 * re-apply the SAME tokens the shared rules apply to .brandmenu__label,
 * targeting .brandmenu__kitchen. */
.brandmenu__item--home { align-items: center; }
.brandmenu__home-text { grid-column: 2; display: flex; flex-direction: column; }

@media (hover: hover) {
  .brandmenu__item--home:hover .brandmenu__kitchen {
    color: var(--red);
    text-shadow: var(--halo-text);
  }
}
.brandmenu__item--home.is-hover .brandmenu__kitchen {
  color: var(--red);
  text-shadow: var(--halo-text);
}
.brandmenu__item--home:focus-visible .brandmenu__kitchen,
.brandmenu__item--home.is-focus .brandmenu__kitchen { color: var(--red); }
.brandmenu__item--home:active .brandmenu__kitchen,
.brandmenu__item--home.is-pressed .brandmenu__kitchen { color: var(--red-deep); }

.brandmenu__divider {
  height: 1px;
  background: var(--rule-faint);
  margin: 6px 0;
}

/* Install-nudge gesture hint (#2584). Plain dialog, brand-menu adjacent. */
.installnudge-dialog {
  max-width: 22rem;
  padding: 1.25rem 1.5rem;
  background: var(--content-card-bg);
  border: 1px solid var(--rule);
  border-radius: 12px;
}
.installnudge-dialog::backdrop {
  background: rgba(0, 0, 0, 0.4);
}
.installnudge-dialog__heading {
  margin-top: 0;
  font-size: 1.15rem;
}
.installnudge-dialog__benefit {
  font-size: 0.9rem;
  opacity: 0.8;
}

/* ─── Search overlay ──────────────────────────────────────────
 *
 * The markup uses native <dialog class="search-overlay"> opened via
 * showModal() (search_overlay_controller.js). The browser places the
 * dialog in the top layer and emits a ::backdrop pseudo-element for
 * the dim layer. We unset the dialog's native chrome and style the
 * inner .search-panel as the floating paper card.
 *
 * Class names match the existing ERB partial and the dynamic class
 * names emitted by the controller (.search-result, .search-result-title,
 * .search-result-category, .search-no-results, .selected).
 * ─────────────────────────────────────────────────────────── */

dialog.search-overlay {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  max-width: none;
  max-height: none;
  margin: 0;
  border: none;
  background: transparent;
  padding: 14% 16px 24px;
  box-sizing: border-box;
  z-index: var(--z-overlay);
  overflow: visible;
  display: flex;
  align-items: flex-start;
  justify-content: center;
}
dialog.search-overlay:not([open]) { display: none; }
dialog.search-overlay::backdrop {
  background: rgba(40, 24, 22, 0.42);
  backdrop-filter: blur(3px);
  -webkit-backdrop-filter: blur(3px);
  animation: fade-in var(--duration-normal) var(--ease-curtain);
}
@keyframes fade-in { from { opacity: 0; } to { opacity: 1; } }

.search-panel {
  position: relative;
  width: 100%;
  max-width: 32rem;
  background: var(--content-card-bg);
  border: 1px solid var(--rule);
  border-radius: 12px;
  box-shadow: var(--shadow-popover-lg);
  overflow: hidden;
  animation: paper-in var(--duration-curtain) var(--ease-curtain);
}
@keyframes paper-in {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: translateY(0);    }
}
.search-panel::before {
  content: "";
  position: absolute;
  inset: 0;
  background-image: url("/assets/paper-noise-4ecbafa7.svg");
  background-size: var(--noise-tile) var(--noise-tile);
  opacity: var(--paper-opacity);
  pointer-events: none;
}
.search-panel > * { position: relative; }

.search-input-wrapper {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 16px 18px;
  border-bottom: 1px solid var(--rule-faint);
}
.search-icon {
  color: var(--text-light);
  display: flex;
  align-items: center;
  font-size: 1.2rem;
  line-height: 1;
}
.search-icon > svg { display: block; }

/* Container for grocery quick-add chips and similar context pills.
 * Empty by default; populated by search_overlay_controller. */
.search-pill-area {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  align-items: center;
}
.search-pill-area:empty { display: none; }

.search-input {
  all: unset;
  flex: 1;
  font-family: var(--font-display);
  font-style: italic;
  font-size: 1.35rem;
  color: var(--text);
  min-width: 0;
  line-height: 1.2;
}
.search-input::placeholder { color: var(--text-light); }

.search-results {
  list-style: none;
  margin: 0;
  padding: 6px 6px 10px;
  max-height: 60vh;
  overflow-y: auto;
}
.search-results:empty { display: none; }

.search-result {
  display: flex;
  flex-direction: column;
  width: 100%;
  text-align: left;
  background: none;
  border: none;
  padding: 10px 14px;
  border-radius: 6px;
  cursor: pointer;
  font: inherit;
  -webkit-tap-highlight-color: transparent;
  transition: background var(--duration-fast);
}
.search-result:hover,
.search-result:focus-visible,
.search-result.selected {
  background: var(--red-soft);
  outline: none;
}
.search-result-title {
  font-family: var(--font-display);
  font-size: 1.05rem;
  color: var(--red);
  line-height: 1.2;
}
.search-result-category {
  font-family: var(--font-body);
  font-size: 0.74rem;
  color: var(--text-light);
  letter-spacing: 0.04em;
  margin-top: 2px;
}
.search-no-results {
  padding: 16px;
  font-family: var(--font-display);
  font-style: italic;
  font-size: 0.95rem;
  color: var(--text-light);
  text-align: center;
}

/* Grocery quick-add section — emitted by the controller above or below
 * the recipe results depending on tier of match. */
.grocery-section--above,
.grocery-section--below {
  list-style: none;
  margin: 0;
  padding: 0;
}
.grocery-section--above {
  border-bottom: 1px solid var(--rule-faint);
  padding-bottom: 6px;
  margin-bottom: 6px;
}
.grocery-section--below {
  border-top: 1px solid var(--rule-faint);
  padding-top: 6px;
  margin-top: 6px;
}

/* Cramped layout — when nav_shell_controller flips data-cramped="true",
 * the dock becomes visible and the body needs bottom padding to clear it.
 * (In the design system, .appwrap[data-cramped="true"] handled this; we
 * apply it to body via :has() since the navshell sits at body level.)
 */
body:has(.navshell[data-cramped="true"]) {
  /* rem so the reserved space grows with text-zoom when the dock wraps to two
     rows; 5rem == today's 80px at the 16px mobile root. */
  padding-bottom: calc(env(safe-area-inset-bottom, 0px) + 5rem);
}

/* Dock spans the viewport (left:0/right:0) and centers its contents via
 * justify-content. The destinations pill shrink-wraps (max-width + flex-wrap),
 * so with the full viewport available it wraps only when its content exceeds
 * the viewport — not prematurely at ~50%, as left:50% shrink-to-fit forced
 * before. Search docks here beside the destinations pill when cramped (#2353,
 * reversing #1569); Mirabel's trigger took the freed top-right spot. */
.navshell[data-cramped="true"] .navshell__dock {
  position: fixed;
  bottom: calc(14px + env(safe-area-inset-bottom, 0px));
  left: 0;
  right: 0;
  display: flex;
  /* Wrap so search drops to its own row at full 48px when the destinations pill
     and search can't share one line under text zoom — instead of flex-shrinking
     search below the 48dp touch-target norm (#2353). */
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  gap: 10px; /* destinations pill + search pill (nav_shell_controller moves both here) */
  z-index: var(--z-nav);
}

/* Dock-mode destinations. Sized in rem calibrated to the 16px mobile root
 * (base.css drops the root to 16px at <=720px + coarse pointer), so the base
 * render is unchanged while the label now scales with text-zoom (WCAG 1.4.4).
 * ~48px tall meets the iOS 44pt / Material 48dp touch-target norm. The pill
 * wraps to two rows rather than clipping destinations off-screen under large
 * text (WCAG 1.4.10). (#1569) */
.navshell[data-cramped="true"] .destination {
  flex: 1 1 0;
  padding: 0.5rem 0.625rem;
  min-width: 0;
}
.navshell[data-cramped="true"] .destination__label {
  font-size: 0.625rem;
}
.navshell[data-cramped="true"] .pill--destinations {
  flex-wrap: wrap;
  justify-content: center;
  width: min(21rem, calc(100vw - 86px));
  max-width: calc(100vw - 28px);
  height: auto;
}

.navshell:not([data-cramped="true"]) .navshell__dock {
  display: none;
}

/* Whisk brand-menu motion. These source-order overrides keep the existing
 * Stimulus/ARIA contract: aria-expanded spins the caret, and removing [hidden]
 * restarts the popover entrance animation. */
.brandmenu__list li:nth-child(1) .brandmenu__item { --row: 0; }
.brandmenu__list li:nth-child(2) .brandmenu__item { --row: 1; }
.brandmenu__list li:nth-child(3) .brandmenu__item { --row: 2; }
.brandmenu__list li:nth-child(4) .brandmenu__item { --row: 3; }
.brandmenu__item--quiet { --row: 4; }

/* Help group — its own list so the destinations stagger above (and its tested
 * nth-child rules) stay untouched. Continue the row sequence for a coherent
 * rise; the contextual row gets a trailing column for its → affordance. */
.brandmenu__pins { list-style: none; padding: 0; margin: 0; }
.brandmenu__section-label {
  margin: 6px 10px 2px;
  font-family: var(--font-body);
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-soft);
}
.brandmenu__help { list-style: none; padding: 0; margin: 0; }
.brandmenu__help li:nth-child(1) .brandmenu__item { --row: 4; }
.brandmenu__help li:nth-child(2) .brandmenu__item { --row: 5; }
.brandmenu__help .brandmenu__item { grid-template-columns: 24px 1fr auto; }
/* The arrow sits dim at rest and joins the label's red/halo on hover/focus/press
 * (state rules above add .brandmenu__arrow alongside .brandmenu__label); the
 * transition matches the label so the glow eases in, not snaps. */
.brandmenu__arrow {
  color: var(--text-soft);
  font-size: 0.85em;
  transition: color var(--duration-fast), text-shadow var(--duration-fast);
}

@keyframes ma-row-rise {
  from { opacity: 0; transform: translateY(7px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* stylelint-disable-next-line no-duplicate-selectors -- whisk-override-layer: re-states the structural caret defaults (line ~191) to supersede the bounce transition via source order; merging up would scatter the Whisk motion identity */
.brand__caret-g {
  transition: transform 0.7s var(--ease-whisk);
  will-change: transform;
}
/* stylelint-disable-next-line no-duplicate-selectors -- whisk-override-layer: open-state caret rotation override (90deg→450deg) layered after the structural block via source order */
.pill--brand[aria-expanded="true"] .brand__caret-g,
.pill--brand.is-open .brand__caret-g {
  transform: rotate(450deg);
}
/* stylelint-disable-next-line no-duplicate-selectors -- whisk-override-layer: source-order override of the bounce transition (line ~197) with the Whisk easing */
.brand__caret-l,
.brand__caret-r {
  transition: transform 0.7s var(--ease-whisk);
}

/* stylelint-disable-next-line no-duplicate-selectors -- whisk-override-layer: adds the menu entrance animation to .brandmenu defined as a structural container earlier (line ~567) */
.brandmenu {
  animation: wh-menu 0.55s var(--ease-whisk);
  transform-origin: top left;
}
@keyframes wh-menu {
  0%   { opacity: 0; transform: rotate(-16deg) translateY(-6px) scale(0.88); }
  100% { opacity: 1; transform: rotate(0deg) translateY(0) scale(1); }
}
/* stylelint-disable-next-line no-duplicate-selectors -- whisk-override-layer: adds the per-row rise animation to .brandmenu__item defined for layout earlier (line ~606) */
.brandmenu__item {
  animation: ma-row-rise 0.34s var(--ease-bounce) both;
  animation-delay: calc(var(--row) * 0.05s + 0.06s);
}

@media (prefers-reduced-motion: reduce) {
  /* Flatten the spring and whirl; keep color, focus, and press feedback. */
  .brand__caret-g,
  .brand__caret-l,
  .brand__caret-r {
    transition-duration: var(--duration-fast) !important;
    transition-timing-function: ease-out;
  }

  .pill--brand[aria-expanded="true"] .brand__caret-g,
  .pill--brand.is-open .brand__caret-g {
    transform: rotate(90deg);
  }

  .brandmenu {
    animation: none;
    transform-origin: center;
  }

  .brandmenu__item {
    animation: none;
  }
}
