/* ============================================================
   xnav — an HTML model of the SHIPPED Flutter component.

   Source of truth: app/lib/core/widgets/nav_menu/ (8 files, 2,805 lines),
   documented at app/docs/features/hc-nav-menu.md and pinned by
   app/test/core/widgets/nav_menu/{hc_nav_menu_layout,hc_nav_menu,
   hc_nav_menu_a11y}_test.dart plus app/test/harness/nav_menu_harness_test.dart.
   Its HTML sibling is docs/builder/mockups-v5/_ds/nav.css, which is an older
   and strictly less capable port of the same Claude Design component.

   This file is NOT the HTML sibling re-skinned. Where the two disagree, the
   Flutter widget wins, because the widget is what would actually ship. The
   differences that matter:

     · TILES NEVER SHRINK. The panel fills what it has at full tile size and
       then PAGES, emitting `Back` and "N more" tiles. It does not scroll and
       it does not squeeze. hc_nav_menu_layout.dart, resolveHcNavMenuMetrics.
     · COLUMNS COME FROM AVAILABLE WIDTH, not a fixed 4-and-2 split:
       columns = floor((innerWidth + gap) / (minTileWidth + gap)), capped by
       maxColumns and by the item count. minTileWidth defaults to 128.
     · TWO METRIC SETS, chosen by min(1120, width) < 520 rather than by a
       viewport media query. That is why this file takes its numbers from JS:
       the switch reads the CONTAINER, so a 390pt phone frame inside a wide
       desktop page resolves narrow, exactly as the widget would.
     · THE COLLAPSED BAR ANSWERS "WHERE AM I": accent icon chip, company
       eyebrow, current section name, and the persistent utilities.
     · UTILITIES. Notifications / profile / search / log out, each resolving
       to off, the header pill, or the footer, under three caps.
     · PER-TILE BADGES, and an unread badge on the bell — which no dock in
       this deck renders. No preset places the bell; see preset() in
       xnav.js for why a product with no inbox has nothing to ring.
     · REDUCE MOTION collapses the transition to 1ms, matching
       MediaQuery.disableAnimations.

   Every geometric constant here is a named constant on HcNavMenuMetrics; the
   ramps are hcNavCq(shellWidth, min, percent, max). Nothing was rounded to a
   token — the widget documents that it opts out of AppSpacing/AppRadii for its
   own geometry, and matching it matters more than matching the scale.

   Color is the one thing re-pointed. HcNavMenuTheme.dark() carries literal
   colors by design (it is the only file in the widget allowed to), so this
   file derives each one from an hc.css token with color-mix. The map, and the
   delta where a derivation is not exact, is commented on each line below.
   Zero hex literals in this file.
   ============================================================ */

/* THE RAIL READS THIS BLOCK TOO. The --nav-* tokens are declared here rather
   than at :root, so a selector that is not .xnav resolves none of them — the
   desktop rail's background computed to transparent until it was added here.
   Shared rather than copied: two derivations of one palette is the drift this
   file's own header forbids. The dock's LAYOUT stays on .xnav alone, below. */
.xnav, .hc-desk-rail{
  /* ---- HcNavMenuTheme.dark(), derived from LADS tokens ----
     The widget's own note: "The menu is chrome, not content: it carries its
     own surface so it can sit as a dark bar over a light screen." So the dark
     pill over a light binder is a decision already made in shipped code, not
     an open question.

     ON THE DARK THEME THIS BAR INVERTS, AND THAT IS DELIBERATE. Every token
     below is mixed from --on-surface and --surface, so when those swap the bar
     becomes light over a dark page. It would have been one line to pin it dark
     in both themes. The reason not to: the widget's stated purpose is that the
     menu carries its OWN surface so it reads as chrome rather than content,
     and what makes that work is contrast with the page — "dark" is how that
     principle was spelled on a light screen, not the principle itself. Pinning
     the literal would have put a #1E2824 bar on a #1E2824 page and lost the
     separation the sentence was about. If this is ever wrong, the thing to
     change is this comment first.

     IT IS NO LONGER DERIVED, and the derivation is why. It used to be
     color-mix(--on-surface 82%, --hero-start), reproducing a deep forest
     #1E2824 out of the ink and the hero green. The hero is Congress Blue now,
     so that mix resolves to a blue-tinted near-black by accident rather than
     by decision — and the registered system already names the value it should
     be: --hc-shell in colors_and_type.css. So the dock is pinned, and
     role-colors.css sets it per role: Mirage for the homeowner, solid Congress
     Blue for the builder. This is the default for a page that declares no
     role. */
  --nav-shell:var(--rail-surface);
  /* strongForeground #F4F1EA. --surface (#FBFAF8) is the nearest token and is
     the same warm off-white one step brighter. */
  --nav-strong:var(--surface);
  --nav-mid:color-mix(in srgb, var(--surface) 72%, transparent);   /* @0.72 */
  --nav-dim:color-mix(in srgb, var(--surface) 66%, transparent);   /* @0.66 */
  --nav-utility-bg:color-mix(in srgb, var(--surface) 6%, transparent);  /* white @0.06 */
  --nav-avatar-bg:color-mix(in srgb, var(--surface) 10%, transparent); /* white @0.10 */
  /* fieldBackground and tileBackground are both #26302B, one step off the
     shell toward the foreground. Measured against the real pair that is 96%. */
  --nav-tile-bg:color-mix(in srgb, var(--nav-shell) 96%, var(--nav-strong));
  --nav-field-bg:var(--nav-tile-bg);
  --nav-tile-fg:color-mix(in srgb, var(--nav-strong) 92%, var(--nav-shell));
  --nav-bar:var(--nav-strong);
  /* accent is a constructor argument: a caller passes `accent: cs.primary` in
     one line, which is what this models. It is Congress Blue now, on every
     role, which is the whole of the accent change reaching the dock. */
  --nav-accent:var(--primary);
  /* THE MULTIPLY IS GONE. selectedFill was HcNavMenuTheme.darken(accent, 0.36)
     — 64% toward black — which worked while the accent was a mid green and
     fails now: Congress Blue darkened 64% is #041B31 against a #181C25 shell,
     a selected tile you cannot see. The registered system states the selection
     directly instead, as --hc-shell-selected: the shell's own foreground at
     14%, which separates on any shell without knowing what color it is. */
  --nav-selected:color-mix(in srgb, var(--nav-strong) 14%, transparent);
  --nav-selected-fg:var(--nav-strong);
  --nav-selected-ring:color-mix(in srgb, var(--on-primary) 28%, transparent);
  /* NOT TERRACOTTA, AND TWO TOKENS RATHER THAN ONE. Terracotta means "a
     genuine clash", and neither of the things this fills is one: the tile mark
     says there is something unseen behind that door, and the utility count
     says how many. That is attention, which is blue in this system.

     They split because one carries text and the other does not.
     --primary-flat's own declaration is the rule -- "bars, dots, indicators.
     NO TEXT" -- and it is the token for the 8px tile dot: pigment itself
     measures 1.79:1 on the tile ground and fails the 3:1 non-text bar, which
     is exactly why the flat step exists. The count pill is a text-bearing
     fill, so it takes --primary and --on-primary, the pair that is 8.15:1. */
  --nav-badge-bg:var(--primary);
  --nav-badge-fg:var(--on-primary);
  --nav-dot-bg:var(--primary-flat);
  /* hoverOverlay is a neutral #808080 @0.16 so it reads the same on either
     theme. There is no mid-gray token, so it is mixed from the two ends. */
  --nav-hover:color-mix(in srgb,
      color-mix(in srgb, var(--surface) 50%, var(--on-surface)) 16%, transparent);

  /* The theme owns its own shadow rather than reading AppShadows, for the same
     reason it owns its surface. --shadow-card is the nearest LADS equivalent
     to the widget's two-layer diffuse drop. */
  --nav-shadow:var(--shadow-card);

  /* Re-pointed at the system curve in stage 2. It held
     cubic-bezier(0.22,0.61,0.36,1) — the same curve, rounded — and the name is
     kept so the 24 call sites below did not all have to move. */
  --nav-ease:var(--motion-ease);

  /* Geometry. Defaults are the collapsed constants; xnav.js overwrites every
     one of these from resolveHcNavMenuMetrics once it has measured the host. */
  --nav-w:440px;            /* animated shell width */
  --nav-h:80px;             /* HcNavMenuMetrics.closedHeight */
  --nav-radius:40px;        /* closedRadiusPill */
  --nav-pad-v:14px;         /* closedPadVertical */
  --nav-pad-h:18px;         /* closedPadHorizontal */
  --nav-content-h:0px;
  --nav-grid-h:0px;
  --nav-grid-gap:14px;
  --nav-footer-gap:18px;
  --nav-render-gap:10px;
  --nav-tile-h:113px;
  --nav-cols:2;
  --nav-head-gap:10px;

  /* Ramps: hcNavCq(shellWidth, min, percent, max), resolved in JS. */
  --nav-chip:44px;
  --nav-chip-icon:24px;
  --nav-eyebrow:9px;
  --nav-section:20px;
  --nav-tile-radius:18px;
  --nav-tile-inner-gap:10px;
  --nav-tile-pad-v:10px;
  --nav-tile-icon:26px;
  --nav-tile-label:15px;

  /* Animation budget, from the same formulas as _HcNavMenuState. */
  --nav-shell-open-dur:360ms;
  --nav-shell-close-delay:0ms;
  --nav-shell-close-dur:360ms;

}
/* The dock's own layout. Not shared with the rail, which is a column. */
.xnav{
  display:flex; justify-content:center;
  align-items:flex-start;                 /* .down anchors the top */
}
.xnav[data-dir="up"]{align-items:flex-end}   /* .up anchors the bottom */

/* ==========================================================================
   DARK — and this block exists because the dock silently inverted.

   THE BUG. --nav-shell above is `color-mix(--on-surface 82%, --hero-start)`,
   and it reads correctly exactly once: on a light screen, where --on-surface is
   #363D49 and the mix lands on the deep forest the widget wants. On the dark
   theme --on-surface flips to #F4F1EA, so the same declaration resolved to
   `color-mix(#F4F1EA 82%, #24313D)` = a PALE GRAY PILL — measured, srgb(0.810,
   0.810, 0.796) — carrying --nav-strong (#1E2824, the dark surface) as its ink.
   The dock did not merely lose contrast; it turned into the lightest object on
   a dark screen and stopped reading as chrome at all.

   It shipped that way in both roles. Caught by probing a page in a browser, not
   by reading the sheet: in light every one of these tokens passes, and the file
   is written entirely in terms of tokens that look theme-safe.

   THE RULE IT BREAKS is already in hc.css, at --on-hero: "If a surface does not
   flip with the theme, its ink does not either, and it needs a name of its own."
   The dock is that kind of surface. So its ink is --on-hero here — the one
   token in the system deliberately NOT re-pointed in dark — and its ground is
   derived from --hero-start rather than from the page's ink.

   WHICH DIRECTION THE PILL MOVES, AND WHY IT IS UP. The file's own comment
   argues that "dark" was how the principle was spelled on a light screen and
   not the principle itself, which is contrast with the page. On a dark screen
   the page is #1E2824 and the glow runs it to #232E29, so a darker pill has
   four points of luminance to work with and no shadow to help — hc.css is
   explicit that shadows do not read on the shell. Lifting is the only move that
   leaves the separation legible, so the dark pill is the hero green raised
   toward the ink, and it keeps its role tint because --hero-start carries one.

   THE TWO QUIET TONES GET LOUDER. --nav-mid and --nav-dim were 72% and 66% of
   the strong ink over a very dark pill; over the lifted one the 9px eyebrow
   measured 3.70:1. They move to 85% and 80%, which is the same relationship
   the light theme already had rather than a new judgment.

   NEITHER BADGE TOKEN IS OVERRIDDEN, and the reason survived the fill moving
   off terracotta. --nav-badge-fg is --on-primary, which goes near-black here
   exactly as the fill goes bright, so the pair measures on its own in both
   appearances: white on #283EC4 is 8.15:1 and #0B0E1F on #8B98EC is 6.67:1.
   The dot is --primary-flat, which is the step declared for a dot with nothing
   written on it, and it measures 4.65:1 on the dark tile ground against
   pigment's own 6.40:1 -- the flat step is the darker of the two here and is
   still the right token, because the rule is what may be written on it, not
   which one wins a ratio.
   ========================================================================== */
:root[data-theme="dark"] .xnav,
:root[data-theme="dark"] .hc-desk-rail{
  /* Mirage in dark too. In light the dock is dark against warm paper and in
     dark it is the canvas itself, separated by the hairline and the shadow
     rather than by value — which is the registered system's own dark rule,
     "luminance edges instead of invisible shadows". */
  --nav-shell:var(--rail-surface);
  --nav-strong:var(--on-hero);
  --nav-mid:color-mix(in srgb, var(--on-hero) 85%, transparent);
  --nav-dim:color-mix(in srgb, var(--on-hero) 80%, transparent);
  --nav-utility-bg:color-mix(in srgb, var(--on-hero) 8%, transparent);
  --nav-avatar-bg:color-mix(in srgb, var(--on-hero) 13%, transparent);
  /* The dark selection is stated, not mixed: --hc-shell-selected is Azure in
     dark, which lifts off Mirage where a 14% white veil would not. Romance on
     Azure is 5.78:1.

     THE BUG THIS BLOCK WAS WRITTEN FOR IS STILL FIXED, by a different route.
     It was --on-primary going dark in this theme and landing dark ink on a
     dark fill — 1.29:1 on the homeowner's dock, the worst number anywhere in
     this deck. --on-primary is Romance in both themes now, so the token that
     caused it no longer flips; the explicit --nav-selected-fg stays anyway,
     because the dock's ink is the shell's, not the accent's. */
  --nav-selected:#33689C;
  --nav-selected-fg:var(--on-hero);
  --nav-selected-ring:color-mix(in srgb, var(--on-hero) 28%, transparent);
}

/* ---- shell ----
   The content column is always laid out at its full open height and the shell
   clips it, anchored to the header edge. That is why the header stays put
   while the grid and footer slide out of view, in either direction. */
.xnav-shell{
  position:relative; box-sizing:border-box; overflow:hidden;
  width: var(--nav-w); height: var(--nav-h);
  border-radius:var(--nav-radius);
  background:var(--nav-shell);
  box-shadow:var(--nav-shadow);
  cursor:pointer;
  transition:width var(--nav-shell-close-dur) var(--nav-ease) var(--nav-shell-close-delay),
             height var(--nav-shell-close-dur) var(--nav-ease) var(--nav-shell-close-delay),
             border-radius var(--nav-shell-close-dur) var(--nav-ease) var(--nav-shell-close-delay);
}
.xnav[data-open="true"] .xnav-shell{
  cursor:default;
  transition:width var(--nav-shell-open-dur) var(--nav-ease),
             height var(--nav-shell-open-dur) var(--nav-ease),
             border-radius var(--nav-shell-open-dur) var(--nav-ease);
}
/* A collapsed bar is one big affordance: tapping anywhere opens it. */
.xnav:not([data-open="true"]) .xnav-shell:hover{background:
  color-mix(in srgb, var(--nav-hover), var(--nav-shell))}

.xnav-content{
  position:absolute; left: var(--nav-pad-h); right: var(--nav-pad-h);
  height: var(--nav-content-h);
  display:flex; flex-direction:column;
  transition:left var(--nav-shell-close-dur) var(--nav-ease) var(--nav-shell-close-delay),
             right var(--nav-shell-close-dur) var(--nav-ease) var(--nav-shell-close-delay),
             top var(--nav-shell-close-dur) var(--nav-ease) var(--nav-shell-close-delay),
             bottom var(--nav-shell-close-dur) var(--nav-ease) var(--nav-shell-close-delay);
}
.xnav[data-open="true"] .xnav-content{
  transition:left var(--nav-shell-open-dur) var(--nav-ease),
             right var(--nav-shell-open-dur) var(--nav-ease),
             top var(--nav-shell-open-dur) var(--nav-ease),
             bottom var(--nav-shell-open-dur) var(--nav-ease);
}
.xnav[data-dir="down"] .xnav-content{top:var(--nav-pad-v)}
/* up: the column reverses so the header pins to the bottom edge. The grid's
   own row order is never reversed. */
.xnav[data-dir="up"] .xnav-content{bottom: var(--nav-pad-v); flex-direction:column-reverse}

/* ---- header: HcNavMenuHeader ---- */
.xnav-head{
  height: 56px; flex-shrink:0;             /* HcNavMenuMetrics.headerHeight */
  display:flex; align-items:center; gap: var(--nav-head-gap);
}
/* The icon chip carries the current section's glyph on the selected fill,
   with a 220ms cross-fade when the section changes. */
.xnav-chip{
  width: var(--nav-chip); height: var(--nav-chip); flex-shrink:0;
  border-radius:14px; background:var(--nav-selected); color:var(--nav-selected-fg);
  display:flex; align-items:center; justify-content:center;
  transition:width 360ms var(--nav-ease), height 360ms var(--nav-ease);
}
.xnav-chip svg{width: var(--nav-chip-icon); height: var(--nav-chip-icon); display:block;
  transition:width 220ms var(--nav-ease), height 220ms var(--nav-ease)}
.xnav-id{flex:1; min-width: 0; display:flex; flex-direction:column; justify-content:center}
.xnav-eyebrow{
  font-size:var(--nav-eyebrow); font-weight:700; letter-spacing:1.4px;
  line-height:1.2; text-transform:uppercase; color:var(--nav-dim);
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
  margin-bottom: 2px;                       /* AppSpacing.xxs */
}
.xnav-section{
  font-size:var(--nav-section); font-weight:700; letter-spacing:-.4px;
  line-height:1.2; color:var(--nav-strong);
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
}
.xnav-utils{display:flex; align-items:center; gap: 8px; flex-shrink:0}

/* Utility buttons. Square 44 in the header, 48 in the footer. */
.xnav-util{
  position:relative; width: 48px; height: 48px; flex-shrink:0;
  border:none; padding: 0; cursor:pointer; border-radius:14px;
  background:var(--nav-utility-bg); color:var(--nav-strong);
  display:flex; align-items:center; justify-content:center;
  transition:background 150ms var(--nav-ease);
}
.xnav-util:hover{background:color-mix(in srgb, var(--nav-hover), var(--nav-utility-bg))}
.xnav-util svg{width: 24px; height: 24px; display:block}
.xnav-avatar{
  width: 48px; height: 48px; flex-shrink:0; border:none; padding: 0; cursor:pointer;
  border-radius:var(--r-full); background:var(--nav-avatar-bg); color:var(--nav-strong);
  display:flex; align-items:center; justify-content:center;
  font-size:14px; font-weight:700; letter-spacing:.3px; line-height:1;
  transition:background 150ms var(--nav-ease);
}
.xnav-avatar:hover{background:color-mix(in srgb, var(--nav-hover), var(--nav-avatar-bg))}
/* Search and log out unfurl with the panel; the persistent ones stay. In this
   deck that is the avatar alone — the bell is off in every preset. */
.xnav-util[data-unfurl]{width: 0; opacity:0; overflow:hidden;
  transition:width 360ms var(--nav-ease), opacity 360ms var(--nav-ease)}
.xnav[data-open="true"] .xnav-util[data-unfurl]{width: 48px; opacity:1}

.xnav-badge{
  position:absolute; top: 4px; right: 4px;
  min-width: 16px; height: 16px; padding: 0 4px; border-radius:9px;
  background:var(--nav-badge-bg); color:var(--nav-badge-fg);
  font-size:10px; font-weight:700; line-height:1;
  display:flex; align-items:center; justify-content:center;
}

/* ---- hamburger: three bars folding into an X ----
   barWidth 26, barHeight 2.5, barSpacing 7.75, quarter turn 45deg,
   middle bar scaleX 1 - 0.7 * progress and fading out. */
.xnav-toggle{
  width: 48px; height: 48px; flex-shrink:0; border:none; padding: 0;
  background:transparent; border-radius:12px; cursor:pointer;
  display:flex; align-items:center; justify-content:center;
  transition:background 150ms var(--nav-ease);
}
.xnav-toggle:hover{background:var(--nav-hover)}
.xnav-lines{position:relative; width: 24px; height: 16px; display:block}
.xnav-lines i{
  position:absolute; left: 0; width: 24px; height: 2.5px; border-radius:2px;
  background:var(--nav-bar); display:block;
}
.xnav-lines i:nth-child(1){top: 0;      transition:transform 300ms var(--nav-ease)}
.xnav-lines i:nth-child(2){top: 7.75px; transition:opacity 180ms var(--nav-ease), transform 240ms var(--nav-ease)}
.xnav-lines i:nth-child(3){top: 15.5px; transition:transform 300ms var(--nav-ease)}
.xnav[data-open="true"] .xnav-lines i:nth-child(1){transform:translateY(7.75px) rotate(45deg)}
.xnav[data-open="true"] .xnav-lines i:nth-child(2){opacity:0; transform:scaleX(.3)}
.xnav[data-open="true"] .xnav-lines i:nth-child(3){transform:translateY(-7.75px) rotate(-45deg)}

/* ---- spacers between header, grid and footer ---- */
.xnav-gap-grid{height: var(--nav-grid-gap); flex-shrink:0}
.xnav-gap-footer{height: var(--nav-footer-gap); flex-shrink:0}

/* ---- tile grid ----
   Fixed height from the busiest page, so a short page leaves its last row
   empty rather than the panel resizing between pages. Rows are laid from the
   top; the grid scrolls only in the degenerate case the widget's own
   SingleChildScrollView covers, which the row math never actually reaches. */
.xnav-grid{
  height: var(--nav-grid-h); flex-shrink:0;
  display:grid; grid-template-columns:repeat(var(--nav-cols), minmax(0,1fr));
  grid-auto-rows:var(--nav-tile-h); align-content:start;
  gap: var(--nav-render-gap); overflow:auto;
  touch-action:pan-y;                      /* horizontal drag pages */
}

.xnav-tile{
  position:relative;
  /* Section tiles are anchors and the Back / "N more" tiles are buttons, so the
     button defaults have to go: a UA `border:2px outset` drew a rim on the
     paging tiles that the section tiles did not have, and no tile in the widget
     carries a border at all. */
  border:0; appearance:none; font:inherit;
  background:var(--nav-tile-bg); border-radius:var(--nav-tile-radius);
  color:var(--nav-tile-fg); text-decoration:none; cursor:pointer;
  display:flex; flex-direction:column; align-items:center; justify-content:center;
  padding: var(--nav-tile-pad-v) 8px;       /* AppSpacing.sm horizontal */
}
.xnav-tile:hover{background-color:color-mix(in srgb, var(--nav-hover), var(--nav-tile-bg))}
.xnav-tile .glyph{position:relative; line-height:0}
.xnav-tile svg{width: var(--nav-tile-icon); height: var(--nav-tile-icon); display:block}
.xnav-tile .lbl{
  margin-top: var(--nav-tile-inner-gap);
  font-size:var(--nav-tile-label); font-weight:400; line-height:1.2;
  text-align:center; overflow:hidden;
}
/* The selected tile's ring is an inset shadow, not a border, so it adds no
   layout and a selected tile can still take a focus ring. */
.xnav-tile[data-selected="true"]{
  background:var(--nav-selected); color:var(--nav-selected-fg);
  box-shadow:inset 0 0 0 1.5px var(--nav-selected-ring);
}
.xnav-tile:focus-visible{outline:3px solid currentColor; outline-offset:-3px}
/* Back and "N more" carry the dim foreground, per HcNavMenuTheme. */
.xnav-tile[data-paging="true"]{color:var(--nav-dim)}

/* Per-tile badge: pinned above and right of the glyph, 20px tall, clamped
   to "9+" by the caller. A count, never a ratio. */
/* A DOT, NOT A COUNT — see NAV_AND_COMMIT_RULINGS.md ruling 4. This was a
   24px pill carrying "3" or "9+". It carries nothing now, because the only
   thing a badge can say honestly is that there is something unseen behind the
   door, and that is one bit.
   The ring is the reason it survives on any tile ground: the dot sits over the
   glyph on both the resting tile and the selected fill, and a 2px ring in the
   tile's own background is what keeps its edge legible on either without a
   second color. */
.xnav-tile .tile-badge{
  position:absolute; top: -2px; left: calc(var(--nav-tile-icon) - 2px);
  width: 8px; height: 8px; border-radius: var(--r-full, 999px);
  background:var(--nav-dot-bg);
  box-shadow: 0 0 0 2px var(--nav-tile-bg);
}
.xnav-tile[data-selected="true"] .tile-badge{
  background:var(--nav-selected-fg);
  box-shadow: 0 0 0 2px var(--nav-selected);
}

/* ---- footer: HcNavMenuFooter ----
   Search takes the flexible slot; the rest are fixed-width buttons. Narrow,
   search claims its own line and the icons wrap beneath it, which is the
   two-line case HcNavMenuMetrics.footerLines reserves height for. */
.xnav-foot{flex-shrink:0; display:flex; align-items:center; gap:10px}
.xnav-foot[data-lines="2"]{flex-direction:column; align-items:stretch}
.xnav-foot[data-lines="2"] .foot-icons{display:flex; gap:10px}
.xnav-field{
  flex:1; height: 48px; padding: 0 16px; border-radius:var(--r-base);
  background:var(--nav-field-bg); color:var(--nav-strong);
  display:flex; align-items:center; gap: 12px;
}
.xnav-field svg{width: 24px; height: 24px; flex-shrink:0; color:var(--nav-dim)}
/* THE INPUT FILLS THE FIELD, and that is a touch-target fix rather than a
   cosmetic one. .xnav-field is 48px, but the input inside it drew a 21px line
   box, so the 27px of field above and below it looked tappable and focused
   nothing. scripts/check-deck-posture.sh measured the input at 250x21 against
   the 44px floor; stretching it makes the whole field the control it already
   looks like. */
.xnav-field input{
  flex:1; min-width: 0; align-self: stretch; height: 100%;
  border:none; outline:none; background:transparent;
  font:inherit; font-size:16px; color:var(--nav-strong);
}
.xnav-field input::placeholder{color:var(--nav-dim); opacity:.55}
.xnav-foot .xnav-util{width: 48px; height: 48px; border-radius:var(--r-base);
  background:var(--nav-field-bg)}
.xnav-foot .xnav-util svg{width: 24px; height:22px}
.xnav-foot .xnav-badge{top: 8px; right:6px}
.xnav-foot .xnav-avatar{width: 48px; height: 48px; font-size:15px;
  background:var(--nav-field-bg)}
.xnav-logout{
  height: 48px; padding: 0 16px; border:none; cursor:pointer;
  border-radius:var(--r-base); background:var(--nav-field-bg); color:var(--nav-mid);
  display:flex; align-items:center; gap: 8px;
  font:inherit; font-size:15px; font-weight:500;
}

/* ---- the stagger ----
   One clock. Every element's window is expressed in the controller's
   normalized 0-1 space, so open and close use the same mapping and an
   interrupted transition is continuous rather than snapping. xnav.js writes
   the four numbers per element:
     open  window = [120 + i*stagger, +300] over openTotal
     close window = the same, reversed, over the shorter closeTotal
   Opening therefore runs left to right; closing runs right to left.

   The tile's own selection cross-fade is a separate 220ms AnimatedContainer in
   the widget and is not driven by the open/close controller, so background and
   color ride along at a fixed 220ms with no delay. */
.xnav-stagger{
  opacity:0; transform:translateY(18px) scale(.96); visibility:hidden;
  transition-property:opacity, transform, visibility, background-color, color;
  transition-duration:var(--c-dur), var(--c-dur), 0s, 220ms, 220ms;
  transition-timing-function:var(--nav-ease), var(--nav-ease), linear, var(--nav-ease), var(--nav-ease);
  transition-delay:var(--c-delay), var(--c-delay), calc(var(--c-delay) + var(--c-dur)), 0s, 0s;
}
.xnav[data-dir="up"] .xnav-stagger{transform:translateY(-18px) scale(.96)}
.xnav[data-open="true"] .xnav-stagger{
  opacity:1; transform:translateY(0) scale(1); visibility:visible;
  transition-duration:var(--o-dur), var(--o-dur), 0s, 220ms, 220ms;
  transition-delay:var(--o-delay), var(--o-delay), var(--o-delay), 0s, 0s;
}
/* A hidden element must also be untouchable: the grid keeps its tiles in the
   tree at opacity 0, and an opening-upward panel puts that overflow directly
   over the pill, where a stray click would navigate instead of opening. The
   widget wraps each staggered element in IgnorePointer(ignoring: !_open);
   this is that. xnav.js carries the same guard in the click handler. */
.xnav:not([data-open="true"]) .xnav-stagger{pointer-events:none}

/* The first paint must not animate. The widget seeds its controller with
   `value: _open ? 1 : 0`, so a menu declared openByDefault is simply open on
   mount rather than unfolding at the reader. xnav.js flips data-ready one
   frame after the first render, which is also what keeps a menu laid out
   before the page has a size from getting stuck mid-transition. */
.xnav:not([data-ready]), .xnav:not([data-ready]) *{
  transition-duration:0s !important; transition-delay:0s !important;
}

/* MediaQuery.disableAnimations collapses the open/close transition to 1ms. */
@media (prefers-reduced-motion: reduce){
  .xnav *, .xnav{transition-duration:1ms !important; transition-delay:0ms !important}
}
