/* Evolve Shell — tokens + components. Sections appended per task. */

/* ============ Evolve Shell tokens ============ */
.evsh-root {
  --evsh-white: #ffffff;
  --evsh-neutral-1: #00202f;      /* primary text */
  --evsh-neutral-2: #666666;      /* secondary text */
  --evsh-neutral-3: #b2b2b2;      /* Figma `neutral/neutral 3` — control borders, outline stroke */
  --evsh-neutral-4: #d9d9d9;      /* Figma `neutral/neutral 4` — input field border */
  --evsh-neutral-5: #ebebeb;      /* Figma `neutral/neutral 5` — disabled input fill (#500) */
  --evsh-neutral-blue-1: #7a8d97; /* Figma `netural blue/neutral blue 1` — disabled nav label (D32) */
  --evsh-neutral-blue-2: #a2b0b7; /* Figma `netural blue/neutral blue 2` */
  --evsh-neutral-blue-3: #dbddde; /* Figma `netural blue/neutral blue 3` — menu divider */
  --evsh-neutral-blue-4: #f1f5f8; /* hover fill */
  /* pass-3 #59 — the member chip's fill, Figma `4:3885`. Purpose-named, not a palette entry:
     Figma builds it from rgba(255,255,255,0.75) over rgb(41,161,240) and gives it no token. */
  --evsh-tokenfield-token-bg: #cae8fb;
  --evsh-neutral-blue-5: #f6f9fa; /* sidebar bg */
  --evsh-bg-light-blue: #e8eef3;  /* app canvas bg */
  --evsh-bg-light-purple: #efe6f2; /* Figma `background/light-purple` — personal workspace mark (D31) */
  --evsh-blue-0: #196ba0;         /* active text / links */
  --evsh-blue-1: #1e7fbd;         /* Figma `blue/blue 1` — Accent button fill, focused input border */
  --evsh-blue-2: #2b89c5;         /* Figma `blue/blue 2` — Accent-button hover, progress rail, checked checkbox.
                                     ⛔ NOT the credits bar, despite the comment it carried until #498 said so:
                                     the bar is `blue/blue 4` below. Two blues, and the wrong one was named. */
  --evsh-blue-4: #7cc4f2;         /* credits usage-bar fill (#498). VALUE measured on component 1:1020, in file
                                     3gwifp9Rkgj4R7FiQ8ac2l — ⛔ those ids are DEAD in the screens file
                                     fYvRupTfIiOEBXYGOcvZZZ, which is how the ticket first talked itself into
                                     "the design is silent" about two of the three bands. The `-4` follows the
                                     Suite app's pre-existing `--color-blue-4`, byte-identical and already
                                     painting the Members usage bar and the sign-up progress bar; the ramp
                                     position is inherited from that, not re-read here. */
  --evsh-green-1: #54a966;        /* Figma `green/green 1` — SupportDialog's success check.
                                     NOT a substitute for --evsh-green-3 (#5fc575): different colour. */
  --evsh-green-3: #5fc575;        /* Figma `green/green 3` */
  /* Figma `gold/gold 2` — EDGE's brand fill for its rail Icon Container. Measured over REST
     2026-09-21 on the placed instance `I695:45206;1:198;1:189` ("App: Edge") in the Nav Bar
     `695:45206`, file `fYvRupTfIiOEBXYGOcvZZZ`.
     ⛔ NOT `--evsh-gold` below. That one is #edb835, the credits usage-bar band, and its own
     comment already warns it is neither `gold/gold 1` nor `gold/gold 2`. Reusing it would paint
     Edge a colour Figma never gives it — the same class of mistake as painting it Coach blue. */
  --evsh-gold-2: #d4a83a;
  --evsh-gold: #edb835;           /* credits usage-bar gold band, measured on component 1:1020 (#498).
                                     VERIFIED — the old "verify vs DS gold1" TODO is answered: it held
                                     #c19528, which is `gold/gold 1` (the Suite's `--color-gold-1`), and the
                                     bar draws neither that nor `gold/gold 2`. */
  --evsh-red: #c5273a;            /* credits usage-bar red band. VERIFIED TWICE and left alone: component
                                     1:1020 draws the red band #C5273A (#498), and frame 4:9416 fills the
                                     destructive confirm button the same. The "verify vs DS red2" TODO is
                                     answered — this was the one band of three that already matched. */
  --evsh-red-1: #b12031;          /* Figma `red/red 1` — inline error text */
  --evsh-red-3: #d45463;          /* Figma `red/red 3` — input field error border */
  --evsh-red-4: #f0aeb6;          /* Figma `red/red 4` — risky menu-item tint (spec MSxs1dktxb) */
  --evsh-purple-2: #b878e2;
  --evsh-logo-ink: #090b0f;       /* the mobile Tool Bar logo's outline and product name (#627, Coach `I2827:20202;152:1754`) — no named Figma variable */
  --evsh-switch-off: var(--evsh-neutral-blue-2); /* Figma `netural blue/neutral blue 2` */
  --evsh-switch-on: var(--evsh-green-3);         /* Figma `green/green 3` */
  --evsh-benefit-check: #89cf98;  /* Credits Detailed benefit-line check circle (wiki 3XDQoF0G2E, Figma 1:720/1:769) */
  --evsh-toast-border: #a2b0b7;       /* Toast pill border, success/undoable (FND-008 #575) — value-duplicate of --evsh-neutral-blue-2, minted as its own semantic token so the toast surface isn't coupled to an unrelated nav-palette token */
  --evsh-toast-border-error: #d68b94; /* Toast pill border, error variant (FND-008 #575) */
  --evsh-toast-border-warning: #c9b585; /* Toast pill border, warning variant (#1024, Figma 343:4010) — #977114 at 48% white, the error token's method */
  --evsh-toast-green-4: #89cf98;      /* Toast success pill background base (FND-008 #575) — value-duplicate of --evsh-benefit-check, minted as its own semantic token for the same reason as --evsh-toast-border */
  --evsh-shadow-med: 0 4px 12px rgba(0, 32, 47, 0.13);
  --evsh-shadow-nav-active: 0 4px 6px rgba(0, 32, 47, 0.13); /* D30 — Figma drop-shadow on the active nav item: blur 6, NOT --evsh-shadow-med's 12 */
  --evsh-radius-item: 14px;       /* sidebar item pill — DESIGN.md "off-scale" 14px */
  --evsh-radius-card: 12px;
  /* Skeleton shimmer sweep (evolve-suite, owner ask 2026-09-11). UNMEASURED — Figma has no
     shimmer/loading motion anywhere in fYvRupTfIiOEBXYGOcvZZZ (see the shimmer section below), so
     there is no prototype to read a duration or sweep width from. Picked to read as a calm,
     continuous sweep rather than a flashing one — slow enough not to be distracting on a load that
     can take seconds, fast enough that the row still visibly reads as "busy". One constant drives
     both `background-size` (how wide the gradient canvas is) and the `background-position` travel
     distance in the keyframes below, which are the same number in this technique. */
  --evsh-skeleton-shimmer-duration: 1.6s;
  --evsh-skeleton-shimmer-band: 200%;
  --evsh-font: 'Lato', system-ui, -apple-system, sans-serif;
  font-family: var(--evsh-font);
  color: var(--evsh-neutral-1);
}
/* type scale (Figma: subtitle-1 16/24 600 · subtitle-2 15/20 700 · body-2 15/20 500 · body-3 14/18 500 · subtitle-4 12/18 900) */
/* subtitle-1 carries a 640px arm in Coach's own utility (14/20). It is deliberately NOT ported:
   this file has exactly one @media rule in 1600+ lines and no responsive typography anywhere,
   so porting it would make subtitle-1 the only responsive entry in the scale. Revisit when the
   Suite defines mobile type generally. */
/* Figma `heading/heading-3` — Lato Black. ADDED for the Detailed credits card's title (#account
   billing), which named `.evsh-subtitle-2` and so shipped 15/20/700 where Figma declares 18/24/900. */
/* `margin: 0` on every type-scale class (evolve-suite#1019). Each heading and paragraph the shell
   renders carries one of these classes (or a component class that zeroes its own margin — the
   accordion heading, the create-workspace intro). The Suite and Coach load a Tailwind preflight
   that zeroes block margins, so the shell was designed against margin 0 without owning it; Edge
   has no reset, and the UA `h2` default (0.83em = 13.28px) made the SupportDialog 27px taller
   there. Owned HERE, on the class the element carries, not as a `.evsh-root h2` descendant rule:
   `evsh-root` also wraps host content in Popover/Tooltip/Menu/Accordion slots, and an unlayered
   descendant reset would silently beat the host's margin utilities on that content. */
.evsh-heading-3 { font-size: 18px; line-height: 24px; font-weight: 900; margin: 0; }
.evsh-subtitle-1 { font-size: 16px; line-height: 24px; font-weight: 600; margin: 0; }
.evsh-subtitle-2 { font-size: 15px; line-height: 20px; font-weight: 700; margin: 0; }
/* Figma `subtitle/subtitle-3` — Lato SemiBold. ADDED for the credits card's "Add credits to:"
   label, which borrowed `.evsh-body-2` (15/20/500) against a declared 14/18/600. Same reason
   `.evsh-body-1` above exists: a scale step the system was missing, not a one-screen value. */
.evsh-subtitle-3 { font-size: 14px; line-height: 18px; font-weight: 600; margin: 0; }
/* Figma `body/body-1` — Lato Regular. ADDED for the create-workspace step label (pass-1 #21),
   which had no scale step to name and borrowed `.evsh-body-2`/`.evsh-body-3` instead: a different
   size, leading AND weight. It is a scale step the design system was missing, not a value local
   to one screen, so it belongs here rather than inline in the app. */
.evsh-body-1 { font-size: 15px; line-height: 26px; font-weight: 400; margin: 0; }
.evsh-body-2 { font-size: 15px; line-height: 20px; font-weight: 500; margin: 0; }
.evsh-body-3 { font-size: 14px; line-height: 18px; font-weight: 500; margin: 0; }
.evsh-body-4 { font-size: 12px; line-height: 18px; font-weight: 500; margin: 0; }
.evsh-subtitle-4 { font-size: 12px; line-height: 18px; font-weight: 900; margin: 0; }

/* ============ User Avatar (wiki: User Avatar / User Color) ============ */
.evsh-avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%; /* circle at all sizes, per User Avatar spec */
  color: var(--evsh-white);
  font-family: var(--evsh-font);
  /* font-size and font-weight are NOT declared here — both vary per size (Figma 113:61: 20/28/30
     are Bold 700, 32 is Semi Bold 600) and are set inline per instance by UserAvatar. A single
     hardcoded font-weight: 700 here previously forced every size to Bold, which was wrong for 32. */
}

/* ==== CreditsSection (wiki: Credits Billing Section — Compact variant) ==== */
.evsh-credits {
  width: 232px; /* sits in the 264px sidebar with 16px side padding on each side */
  background: var(--evsh-white);
  /* D33 radius 16 (not --evsh-radius-card's 12). D34: NO shadow — on the #f6f9fa sidebar the
     design is a deliberately flat white card. D35: no padding-bottom, because the Add credits
     button below supplies its own (D46) and is separated by a hairline (D45); the three are one
     change. D36: 12px between sections. */
  border-radius: 16px;
  padding: 10px 16px 0;
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.evsh-credits-summary {
  display: flex;
  flex-direction: column;
  gap: 8px;
  width: 100%;
  background: none;
  border: none;
  padding: 0 0 16px; /* D36: Usage Info carries pb-16 */
  margin: 0;
  font: inherit;
  color: inherit;
  text-align: left;
}
button.evsh-credits-summary {
  cursor: pointer;
}
/* ">" affordance row — only the clickable (canManage) variant has this; the
   read-only summary is a single line, no row wrapper needed. */
.evsh-credits-summary-row {
  display: flex;
  align-items: center;
  /* D40: no space-between — the text takes the slack (flex-1 below) and the icon simply
     trails it, at a 4px gap. */
  gap: 4px;
  width: 100%;
}
/* D38: Figma styles the line as runs, not one uniform block — the leading count in Bold 15/20
   #00202f, the remainder in Medium 14/18 #666. The wrapper carries the #666 so only the count
   needs to restate a colour. Type comes from the existing scale classes. */
.evsh-credits-line {
  flex: 1 1 0;
  min-width: 0;
  color: var(--evsh-neutral-2);
}
.evsh-credits-count {
  color: var(--evsh-neutral-1);
}
.evsh-credits-chevron {
  flex-shrink: 0;
  display: flex;
  color: var(--evsh-neutral-2);
}
/* Two segments (filled + remainder) in a flex row with a 4px gap between them — Figma component
   1:1020 models both the Compact and Detailed bars this way (fill ends, THEN a 4px gap, THEN the
   remainder starts), not a single fill overlaying a track background. The filled/remainder
   segments are each rendered only when non-zero (see CreditsBar in credits-section.tsx), so the
   gap collapses for free at 0%/100% — `gap` only inserts space BETWEEN children that exist. */
.evsh-credits-bar {
  display: flex;
  width: 100%;
  height: 6px; /* D41. Detailed restates 8px via --lg, so this value is Compact's alone. */
  gap: 4px;
}
/* D42/D43 — r16 and the #E8EEF3 remainder live on the SHARED rules, reaching both variants.
   They used to hang off `.evsh-credits`, scoped to the Compact card on purpose because only
   Compact had been measured (R-N12). That scope was silently dead for the billing page:
   `credits-section.tsx` renders Detailed as `evsh-credits-detailed`, a DIFFERENT CLASS and not a
   modifier on `evsh-credits`, so the Detailed bar fell through to 2px corners and a #f1f5f8
   remainder. #498 measured the Detailed instance on component 1:1020 and it wants the same two
   values, so the scope comes off rather than being duplicated — one rule, one value, no drift. */
.evsh-credits-bar-fill {
  display: block;
  height: 100%;
  border-radius: 16px;
}
.evsh-credits-bar-remainder {
  display: block;
  height: 100%;
  border-radius: 16px;
  background: var(--evsh-bg-light-blue); /* unfilled track colour — #E8EEF3 in all three bands */
}
.evsh-credits-bar-fill--blue { background: var(--evsh-blue-4); }
.evsh-credits-bar-fill--gold { background: var(--evsh-gold); }
.evsh-credits-bar-fill--red { background: var(--evsh-red); }
.evsh-credits-add {
  display: block;
  width: 100%;
  background: none;
  border: none;
  /* D45/D46: the button carries the card's missing bottom padding and is separated from the
     usage block by a hairline — the other half of D35. D44: Figma's label is Bold; .evsh-body-2
     supplies the 15/20 but its 500 weight is wrong here. */
  border-top: 0.5px solid var(--evsh-neutral-blue-3);
  padding: 8px 12px 12px;
  font-weight: 700;
  color: var(--evsh-blue-0);
  cursor: pointer;
  text-align: center;
}

/* ==== CreditsSection Detailed variant (wiki: Credits Billing Section — Detailed, FND-009 #577) ====
   764px fixed width — all four Detailed frames in Figma (1:720, 1:867, 1:769, 1:818) are
   consistently 764 wide, the same pattern as Compact's fixed 232px sidebar card above.
   max-width: 100% clamps it inside a narrower container instead of overflowing (same
   fixed-width-plus-clamp pattern as .evsh-legal-dialog-card). */
.evsh-credits-detailed {
  width: 764px;
  max-width: 100%;
  /* Figma keeps the 18/20 padding INSIDE the 764 — instance `22:42104` is 764 wide and its
     Header frame is 728 = 764 - 2*18 (re-measured 2026-09-21; the component id `588:41875`
     this once cited is dead in all four estate files, #961) — so the box must include the padding.

     ⛔ CORRECTION. This comment used to end "or the card renders 36px wide", stating a failure
     mode that CANNOT REPRODUCE IN EITHER APP. Both `apps/suite` and Coach's frontend
     `@import 'tailwindcss'`, and Tailwind v4's preflight (`preflight.css:7-16`, measured on the
     pinned 4.3.3) already declares `box-sizing: border-box` on `*, ::after, ::before, ::backdrop,
     ::file-selector-button`. So the content-box overflow never happens there, and anyone who
     "verifies" the 36px will find nothing and may then delete this line as dead.

     ⭐ IT IS STILL RIGHT TO DECLARE, for the same reason `.evsh-root` restates its own tokens:
     this package ships its stylesheet to consumers that may carry no global reset, and that is a
     real audience rather than a hypothetical one — `.evsh-shell-main` (:362) and `.evsh-sidebar`
     (:516) already declare it for themselves on exactly that reasoning. What changed is the
     PROVENANCE, not the value: kept as defence-in-depth for a consumer without preflight, not
     because a measured overflow was observed in this repo.
     (Caught by `billing-page-dev`, who grepped the built bundle rather than taking the comment
     at its word.) */
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  /* THE CARD'S OWN itemSpacing, 20. The balance/bar/limits trio sits 12px apart inside
     `.evsh-credits-detailed-usage-info`, which is a SEPARATE frame in Figma (1:870 / 1:723). One
     flat gap cannot express both, and the 12 that used to be here was Usage Info's number applied
     to the card — so the Header sat 8px too close to the balance in every state. */
  gap: 20px;
  /* The card treatment itself, which this rule simply did not have: Figma draws a filled, rounded,
     padded panel and the page drew a bare flex column on the page background. Measured identically
     on `22:42104` (unpaid): padding 20/18, itemSpacing 20, cornerRadius 26 — so these are component
     values. ⚠️ Its two companions in this claim, `22:42444` (paid) and the component `588:41875`,
     are BOTH dead in all four estate files (measured 2026-09-21, #961). The surviving instance
     still carries every number this rule states.
     The Workspace Billing frame 4:10143 mounts the SAME instance (4:10174, same child ids), so
     correcting it here corrects both pages rather than making them diverge. */
  padding: 20px 18px;
  background: var(--evsh-neutral-blue-5);
  border-radius: 26px;
}
/* Figma's `Usage Info` frame — the 12px stack the card's gap used to stand in for. Full width so
   the progress bar still spans the card's 728px content box. */
.evsh-credits-detailed-usage-info {
  display: flex;
  flex-direction: column;
  gap: 12px;
  width: 100%;
}
/* Actions live in the header, top-right (Figma 1:856/1:809 — "Plan Actions" sits at the header's
   vertical position, not in a trailing row below the card; wave-4 review, finding 1a). */
.evsh-credits-detailed-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
}
/* height only — the container itself paints nothing (see .evsh-credits-bar above), so a radius
   here has no visible effect; each segment carries its own 16px radius regardless of size. */
.evsh-credits-bar--lg {
  height: 8px;
}
/* The balance line: leading count, trailing label, then the info tooltip — all one row, gap 4px,
   items aligned to the label's baseline-ish bottom edge (Figma "Analysis used" 1:1023: flex,
   items-end, gap 4). The tooltip sits HERE, not in the header — Figma's Header frame (1:819/1:770)
   has the title as its only child (wave-4 review, finding 1b). */
.evsh-credits-detailed-balance {
  display: flex;
  align-items: flex-end;
  gap: 4px;
}
/* Distinct, larger display type for the leading number (Figma: NOT one of this package's Lato
   type-scale utilities) — 28px/28px, a display face with a Lato fallback since this package loads
   no font of its own (wave-4 review, finding 1d). */
.evsh-credits-detailed-balance-count {
  font-family: 'Gluten', var(--evsh-font);
  font-size: 28px;
  line-height: 28px;
  font-weight: 500;
  color: var(--evsh-neutral-1);
}
.evsh-credits-detailed-balance-label {
  font-family: var(--evsh-font);
  font-size: 15px;
  line-height: 26px;
  font-weight: 400;
  color: var(--evsh-neutral-2);
}
.evsh-credits-detailed-balance-tooltip {
  padding-bottom: 3px; /* Figma "Tooltip Container" 1:1027 — nudges the icon toward the text baseline */
}
/* DEV-2 — the CREDITS CARD's ⓘ is blue-2; every other caller's stays neutral-blue-2.
   ⛔ SCOPED ON PURPOSE. THE OBVIOUS FIX — swapping `.evsh-tooltip-trigger`'s colour — WOULD
   REVERT #483, A DEFECT THE OWNER HIMSELF REPORTED ("a saturated blue dot beside grey type").
   Both readings are right; they are different instances:
     22:42104  I22:42104;1:723;1:1028;1:1260   #2B89C5   <- the credits card, this rule
                (re-cited 2026-09-21, #961: the previous pair `22:42413` / `I22:42444;…` is dead)
     4:3920    I4:3951;4:804;1:1260 (+3 more)  #A2B0B7   <- pack cards, #483's frames
     4:4110    I4:4115;1:1260                  #A2B0B7   <- balance row, #483's frame
   Measured over REST on 2026-09-11, counting non-null and checking the returned file name.
   ⚠️ The component MASTER in `3gwifp9Rkgj4R7FiQ8ac2l` says #2B89C5 and the screen INSTANCES
   override it — so reading the master alone says "blue everywhere" and reading #483's two frames
   alone says "grey everywhere". Neither is the whole answer.
   ⛔ AND #483'S PREMISE IS STALE: it states "those two are the only production callers". There are
   SIX — step-balance-view, pack-card, charge-day-field, input-label, workspace-menu, and
   credits-section, the last being exactly the one that disagrees. The other three are NOT measured
   here and keep the shared default; that is a deliberate non-claim, not a verdict. */
.evsh-credits-detailed-balance-tooltip .evsh-tooltip-trigger {
  color: var(--evsh-blue-2);
}
/* Status line + usage summary are ONE justified row (Figma "Limits" 1:1036: a single h=18 frame,
   status left, usage+arrow right-aligned), not two stacked paragraphs (wave-4 review, finding 1c).
   These two classes previously had no CSS rule at all. */
.evsh-credits-detailed-limits {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 16px;
}
/* Figma paints both lines `neutral/neutral 2` #666 (1:1037, 1:1039, 1:1041). They declared no
   colour at all, so they inherited `.evsh-root`'s neutral-1 #00202f and rendered near-black. */
.evsh-credits-detailed-status,
.evsh-credits-detailed-usage {
  margin: 0;
  color: var(--evsh-neutral-2);
}
.evsh-credits-detailed-usage-link {
  background: none;
  border: none;
  padding: 0;
  margin: 0;
  font: inherit;
  /* Figma gives the whole "0 credits used · Last 30 days ↗" line ONE colour (#666) and lets the
     arrow carry the affordance; `--evsh-blue-0` turned the trailing run blue mid-sentence. */
  color: inherit;
  cursor: pointer;
}
/* Measured on the live instance `22:42104`: the benefits block `I22:42104;1:724` spaces
   label-to-list by 12, the list `I22:42104;1:726` ("Benefits (Hidden)") spaces rows by 16, and one
   row `I22:42104;1:727` spaces its 18px icon from its text by 12. All three shipped as 8.
   Re-cited 2026-09-21 (#961): the component ids this named — 588:41875/41894/41896/41897 — are all
   dead in the four estate files, and every spacing above was RE-MEASURED on the live nodes rather
   than carried over. */
.evsh-credits-detailed-benefits {
  display: flex;
  flex-direction: column;
  gap: 12px;
}
/* `I22:42104;1:725` ("Label", TEXT 14px/600; re-cited 2026-09-21 — `588:41895` is dead) is
   `subtitle/subtitle-3` in neutral-2; the weight/size come from the class the
   component names, the colour has to be stated because `.evsh-root` inherits neutral-1. */
.evsh-credits-detailed-benefits-label {
  color: var(--evsh-neutral-2);
}
.evsh-credits-detailed-benefit-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 16px;
}
.evsh-credits-detailed-benefit {
  display: flex;
  align-items: center;
  gap: 12px;
}
.evsh-credits-detailed-actions {
  display: flex;
  gap: 8px;
}

/* ==== CompactRail (wiki: Suite Navigation — Compact sidebar) ==== */
.evsh-rail {
  width: 64px;
  /* Kept even though `.evsh-shell` now paints the same white behind it: `CompactRail` is a public
     export and must look right rendered on its own (preview app, host-side experiments). Retint
     both together if Figma ever changes the Nav Bar surface. */
  background: var(--evsh-white);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: space-between;
  /* `pt-16 px-8`, and NO padding-bottom — the 16px below the Account button now comes from
     `.evsh-shell`'s `padding: 16px 0`, which is where Figma puts it (the "Stack" wraps the rail
     AND the sidebar). Total: TOP 32 = shell 16 + this 16, matching `I4:3751;1:198`'s own `pt-16`
     inside that Stack. BOTTOM 16 = shell 16 + this 0.
     ⛔ A previous pass parked the bottom 16 HERE, because the rail was in scope and the sidebar
     was not, and called the top "an unresolved 16-vs-32 conflict between two components". Neither
     was right. `891:20840` is the rail EXTRACTED STANDALONE, with the parent's inset re-absorbed
     as `py-16` and its own `pt-16` dropped — so it agrees with the screens once you read the
     parent. That is the same "one level too low" mistake that produced the missing bottom padding
     in the first place, made twice on two edges.
     ⚠️ RESTORING A `padding-bottom` HERE WOULD DOUBLE THE INSET TO 32. The D5 GUARD in
     `navbar-figma.test.tsx` asserts it is supplied exactly once, by one side or the other. */
  padding: 16px 8px 0;
  height: 100%;
  box-sizing: border-box;
}
.evsh-rail-top,
.evsh-rail-bottom {
  display: flex;
  flex-direction: column;
  /* Figma reads flex-start on both stacks (D6) — `891:20842` / `891:20849` are both
     `items-start`, so this is agreement, not a layout change.
     ⛔ A NOTE HERE USED TO CLAIM THIS RULE HAD NO VISUAL CONSEQUENCE, on the grounds that every
     child of these stacks is 48px wide. THAT WAS FALSE, and it is why the divider stayed
     off-centre through a 46-item audit: the
     divider is 32px, and flex-start pinned it to this column's LEFT edge. The stack's alignment
     is right; the narrow child now opts out with its own `align-self`. See .evsh-rail-divider. */
  align-items: flex-start;
  gap: 4px;
}
/* Anchors MoreAppsMenu (`position: absolute`) under the More button — the
   consumer wraps just the button, not the whole `.evsh-rail-top` stack, so
   the menu's `top: 100%` measures from the button's own box. */
.evsh-rail-more-wrap {
  position: relative;
}
/* Figma `891:20844` draws this as a full-width row (`flex justify-center px-[8px] py-[4px]
   w-full`) whose border child is `flex-[1_0_0] h-px` — i.e. a 32px line CENTRED in the 48px
   column. Drawn directly as a 32px block it inherits the stack's `align-items: flex-start` and
   hugs the left edge instead, 8px off, under tiles that are all centred. `align-self` re-centres
   this one child without touching the stack's own (correct) flex-start. */
.evsh-rail-divider {
  display: block;
  align-self: center;
  width: 32px;
  height: 1px;
  margin: 4px 0;
  background: var(--evsh-neutral-blue-3, #dbddde);
}
.evsh-rail-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  width: 48px;
  padding: 8px 8px 6px;
  border-radius: var(--evsh-radius-card);
  background: none;
  border: none;
  font: inherit;
  color: inherit;
  text-decoration: none;
  cursor: pointer;
}
.evsh-rail-item--selected {
  background: var(--evsh-neutral-blue-5);
}
/* Progressive tool-bar states (Coach #632, spec eqb7VfpiM2), the rail counterpart
   of the .evsh-nav-item--skeleton/--disabled rules below. Both render as <span>s,
   so this only removes the affordance the base .evsh-rail-item rule sets. */
.evsh-rail-item--skeleton,
.evsh-rail-item--disabled {
  cursor: default;
}
/* D10/D11 — Figma's rail skeleton: the 32px icon box filled neutral-blue-2 @ 50%, and the
   label slot kept at 18px holding a 4px full-width bar (radius 16) in the same fill. The bar is
   a pseudo-element so the slot keeps its height and the tile does not shrink. */
.evsh-rail-item--skeleton .evsh-rail-icon {
  background: var(--evsh-neutral-blue-2);
  opacity: 0.5;
}
.evsh-rail-item--skeleton .evsh-rail-label {
  width: 100%;
  height: 18px;
  display: flex;
  align-items: center;
}
.evsh-rail-item--skeleton .evsh-rail-label::before {
  content: '';
  display: block;
  width: 100%;
  height: 4px;
  border-radius: 16px;
  background: var(--evsh-neutral-blue-2);
  opacity: 0.5;
}
.evsh-rail-item--disabled .evsh-rail-icon,
.evsh-rail-item--disabled .evsh-rail-label {
  /* Confirmed against Figma (pass 1, R-N6): the "App: Edge" tile I4:3736;1:198;1:189 dims BOTH
     the icon container and the label to 0.5 and keeps the brand fill. Code already matched. */
  opacity: 0.5;
}
.evsh-rail-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  /* ⚠️ REQUIRED BY THE HOVER RULE BELOW, NOT COSMETIC. Figma's hovered More tile puts a 1px border
     on this box; without `border-box` that border would grow it 32 → 34 and shift the glyph off
     centre — a hover that visibly nudges the icon. No-op today in every other state, since nothing
     else here has a border or padding. */
  box-sizing: border-box;
  border-radius: 9.6px; /* Figma rounded-[9.6px] icon container — off-scale, verified via design context */
  color: var(--evsh-neutral-2);
}
/* Figma `332:85233` State=Hovered, Type=More — the rail had NO `:hover` treatment at all, while the
   sibling `.evsh-nav-item` in the same stylesheet has a four-rule ladder. A missing element, not a
   wrong value, which is why no per-property audit found it.
   THE TILE GAINS NOTHING: `332:85233`'s own background is none — only the ICON CONTAINER changes,
   from no border to a 1px `background/light-blue` #e8eef3. The glyph and label stay full strength,
   because a More tile never dims in any state.
   ⛔ SCOPED TO THE GLYPH CLASS ON PURPOSE. A generic `.evsh-rail-item:hover { background: … }`
   would also paint the ACCOUNT tile — which inherits `.evsh-rail-item`'s 12px radius that Figma's
   Account frame `891:20858` does not have, so a background there would suddenly reveal a corner
   the design never specified. Account carries no `.evsh-rail-icon` at all, so this cannot reach it.
   ⚠️ The APP tiles' hover is deliberately NOT here: its defining feature is the icon container
   going to FULL strength, which only means something once the resting-opacity question is ruled.
   Building it first risks encoding the wrong half. Parked with that ruling, not forgotten. */
/* #625 — SUPPORT TAKES THE SAME HOVER, because it is the same component. #626 was titled "the
   More tile" and cited the VARIANT; it never enumerated the variant's INSTANCES, and
   `compact-rail.tsx` gives Support a different class, so the rule could not reach it.
   ⭐ A variant-list is not an instance-list: a state is specified once and consumed many times.
   Measured over REST — all 181 nodes under Nav Bar `695:45206`, matching on `componentId`:
     I695:45206;1:198;1:190   More Apps   componentId=332:85231
     I695:45206;1:198;37:1568 Support     componentId=332:85231
   Exactly two instances; the enumeration is complete. Border read on the hovered variant itself,
   `1:273` in `3gwifp9Rkgj4R7FiQ8ac2l` (name verified "Evolve Components"): the TILE `1:273` has
   no stroke, its child `1:274` Icon Container has `#E8EEF3` at 1px — which is why this is scoped
   to the glyph class and not the tile. */
.evsh-rail-item:hover .evsh-rail-icon--more,
.evsh-rail-item:hover .evsh-rail-icon--support {
  border: 1px solid var(--evsh-bg-light-blue);
}
/* Figma `332:85270` paints the More tile's "+" **#00202F** (`--evsh-neutral-1`), NOT the
   `--evsh-neutral-2` #666666 that `.evsh-rail-icon` hands every glyph. The tile's LABEL is the
   #666 one — icon and label are different colours here, which is exactly what made this invisible.
   Same defect `apps/suite/.../apps/more-icon.tsx` already fixed on the Apps page.
   Declared BEFORE `--selected` so an open More menu still wins the cascade and paints white. */
.evsh-rail-icon--more {
  color: var(--evsh-neutral-1);
}
/* Figma `891:20852` paints the Support glyph **#00202F** (`--evsh-neutral-1`), exactly like the
   More tile's "+" — both are the "plain" rail tiles, whose Icon Container has no fill, so the glyph
   carries the weight instead of a white-on-brand mark.
   ⚠️ Kept as its OWN rule rather than folded into `--more`'s selector list: `css-rule.ts`'s
   `declaration()` refuses to read a selector that appears only as one member of a group, so
   grouping them would break the per-rule pins both glyphs have. Two rules, one value, stated. */
.evsh-rail-icon--support {
  color: var(--evsh-neutral-1);
}
/* ⛔ THIS RULE USED TO BE `.evsh-rail-icon--selected { background: var(--evsh-blue-2); color:
   var(--evsh-white) }` — ONE colour for every product, keyed by STATE. That is Eli item 2: with
   Coach selected the tile went #2b89c5 (its own colour, so it looked fine) and with EDGE selected
   it went #2b89c5 too, where Figma gives it #d4a83a.
   ⭐ THE TRAP THAT PRODUCED IT, worth carrying: in the component set `1:225`, ALL THREE App
   variants — Default `1:226`, Selected `1:237`, Hovered `1:248` — carry Icon Container fill
   **#2B89C5**. It is Coach's colour used as the component's PLACEHOLDER, and every placed instance
   overrides it. Reading a brand colour off a STATE therefore returns Coach blue for everything.
   ⇒ Product identity is the Icon Container FILL and is keyed by PRODUCT, never by state. Measured
   on the placed instances in Nav Bar `695:45206` (file `fYvRupTfIiOEBXYGOcvZZZ`):
     Suite `I695:45206;1:198;1:185` #00202F · Coach `…;1:188` #2B89C5 · Edge `…;1:189` #D4A83A,
   each holding the SAME white 24x24 mark in every state.
   ⚠️ DELIBERATELY PARTIAL, and that is the honest fallback, not a gap: a key with no measured
   colour gets NO fill rather than a borrowed one. `ShellApp.key`'s own comment says never to
   hardcode the set, and this respects it — an unknown product still renders, just untinted. A
   wrong brand colour looks deliberate; a missing one looks like what it is. */
.evsh-rail-icon--suite {
  background: var(--evsh-neutral-1);
  color: var(--evsh-white);
}
.evsh-rail-icon--coach {
  background: var(--evsh-blue-2);
  color: var(--evsh-white);
}
.evsh-rail-icon--edge {
  background: var(--evsh-gold-2);
  color: var(--evsh-white);
}
/* The More tile is the ONE tile whose icon container is filled by its STATE rather than by a brand,
   so what the deleted shared rule used to do for it is restated here with its own node.
   Figma `1:266` State=Selected, Type=More: Icon Container **#F1F5F8**, and the "+" stays **#00202F**
   (`.evsh-rail-icon--more` above already paints it). The old rule gave it #2B89C5 with a WHITE
   glyph — wrong on both axes, invisible because nobody separated "selected" from "a product". */
.evsh-rail-icon--more.evsh-rail-icon--selected {
  background: var(--evsh-neutral-blue-4);
}
/* ─── Eli item 15a — the PRODUCT tiles' resting, hover and selected strengths ───────────────────
   `rail-hover-figma.test.tsx` and `rail-support-hover-figma.test.ts` both recorded this as
   deliberately parked: *"its defining feature is the icon container going to FULL strength, which
   only means something once the resting-opacity question is ruled."* This is that ruling, measured
   over REST 2026-09-21 on component set `1:225` in `3gwifp9Rkgj4R7FiQ8ac2l`:
     `1:226` Default  — tile bg none   · Icon Container AND Label at opacity **0.5**
     `1:237` Selected — tile bg #F6F9FA · both at opacity **1**
     `1:248` Hovered  — tile bg #F1F5F8 · both at opacity **1**
   The label is #666666 in EVERY variant; only its opacity moves, which is why no label colour
   changes here and no `--muted` class comes back (navbar D8 deleted it for exactly that reason).
   ⚠️ SCOPED TO `--app`, NOT TO `.evsh-rail-item`. More, Support and Account never dim — Figma gives
   their labels opacity 1 in every variant (`1:259`, `1:266`, `1:273`) — and the Account tile must
   stay out of reach of any tile background: its frame `I695:45206;1:198;1:192` is 48x48 with NO
   corner radius (measured; it is the live replacement for the dead `891:20858` this file used to
   cite), so painting it would reveal a 12px corner the design never specifies.
   ⚠️ HOVER IS EXCLUDED ON A SELECTED TILE. Figma has no Selected+Hovered variant, so rather than
   invent a blend the selected tile simply holds its #F6F9FA. Stated, not assumed — revisit if a
   Selected+Hovered frame ever appears. */
.evsh-rail-item--app .evsh-rail-icon,
.evsh-rail-item--app .evsh-rail-label {
  opacity: 0.5;
}
.evsh-rail-item--app:not(.evsh-rail-item--selected):hover {
  background: var(--evsh-neutral-blue-4);
}
.evsh-rail-item--app:not(.evsh-rail-item--selected):hover .evsh-rail-icon,
.evsh-rail-item--app:not(.evsh-rail-item--selected):hover .evsh-rail-label,
.evsh-rail-item--app.evsh-rail-item--selected .evsh-rail-icon,
.evsh-rail-item--app.evsh-rail-item--selected .evsh-rail-label {
  opacity: 1;
}
.evsh-rail-label {
  /* Settled in the Task 7 pixel pass: Figma dev-mode (get_design_context on
     node 2766:7164, the "Suite" rail label) reports `text-[12px]`, not the
     11px carried over from Task 4's brief — using the confirmed value.

     D1: the weight is Lato SemiBold (600), measured twice — I4:3780;1:198;1:185;1:247 and
     I4:3736;1:198;1:189;1:236. A note here previously claimed the hosted Lato ships only
     400/700/900 and kept 700 on that basis. It was true when written and is not now: `ea94c7e`
     self-hosts five weights including Lato-SemiBold.woff2, and 600 already renders elsewhere in
     this file. Checked across consumers before changing it — Suite and Coach both host the 600
     face and both render this label; Edge hosts 400/700/900 and does not render the rail, where
     a 600 would fall back to the nearest face rather than break. */
  font-size: 12px;
  line-height: 16px;
  font-weight: 600;
  color: var(--evsh-neutral-2);
  text-align: center;
  white-space: nowrap;
}
/* Figma `891:20850` Support is 48x**46**, not 48x48 — the label slot is hidden, so the tile keeps
   the SAME padding as every other rail item and is simply shorter.
   ⛔ THIS RULE SAID `padding: 8px`, WHICH MADE IT 48 (8 + 32 + 8). It looks like a copy of
   `.evsh-rail-avatar-btn` below, where `8px` IS right — the Account frame `891:20858` really is
   48x48. One tile's correct value applied to its neighbour, which is 2px shorter.
   The value below is identical to `.evsh-rail-item`'s own; it is kept as an explicit override
   rather than deleted so the next reader sees 46 was measured, not inherited by accident. */
.evsh-rail-support {
  padding: 8px 8px 6px;
}
/* #384 part 3 — the rail's Account tile. Figma draws it as a `<frame>` (I4:3780;1:198;1:193):
   a 32px circle in #b878e2 with 16px SemiBold white initials. It was rendering UserAvatar, and
   the shared table's regression made it ACCIDENTALLY CORRECT at 16 — so reverting that table
   without this rule would have dropped this tile to 14px and shipped a new defect as the repair.
   Its size now comes from here, not from that table.
   #103: NO background here. Figma's #b878e2 is that instance's sample fill; the tile is the USER's
   mark, so compact-rail.tsx paints their STORED colour inline (neutral when none is stored). */
.evsh-rail-avatar-mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  color: var(--evsh-white);
  font-family: var(--evsh-font);
  font-size: 16px;
  font-weight: 600;
}
.evsh-rail-avatar-btn {
  padding: 8px;
}

/* ==== SidebarNav (wiki: Suite Navigation — Full sidebar) ==== */
.evsh-sidebar {
  width: 264px;
  height: 100%;
  box-sizing: border-box;
  background: var(--evsh-neutral-blue-5);
  border-radius: 20px; /* Figma rounded-[20px] outer sidebar — off-scale, verified via design context */
  display: flex;
  flex-direction: column;
  /* Figma `1:200` "Primary Menu Items" — the sidebar's FIRST block — is `paddingTop: 12`, and it
     wraps the Workspace Dropdown AND the Nav Bar Items. That block has no element of its own here
     (the DOM hoists its children straight into this column), so the 12px lands on the sidebar.
     Geometrically identical: Figma's `1:199` "Sidebar" declares NO paddingTop — measured, the key
     is absent and `1:200` sits at the sidebar's own y=852 — so a top padding on the sidebar and a
     top padding on its first child put the same 12px in the same place. The footer does not move:
     `.evsh-nav-list` is `flex: 1 1 0` and absorbs the whole reduction.

     ⛔ IT USED TO SIT ON `.evsh-nav-list`, i.e. BELOW the workspace selector instead of above it.
     Not a missing value — a MISPLACED one, and that distinction is the whole repair:
       · The nav rows land identically either way, because `12 + 70 + 0 === 0 + 70 + 12 === 98`.
         So every row below the selector was already pixel-exact against Figma `1:205` (rel 98), and
         only the selector and its 36px mark were displaced — 12px up, to 33 where Figma draws 45
         (`I1:201;1:320` y=881 against Stack `1:197` y=836). That is why a 46-item audit walked past
         it: the error was invisible everywhere except the one row at the top of the column.
       · ⚠️ SO THE FIX IS A MOVE, NEVER AN ADD. Adding a second 12px here while leaving the nav
         list's in place corrects the mark and pushes all four nav rows from 98 to 110 — one number
         fixed, four broken, and the two are indistinguishable at the only element anyone is
         watching. `sidebar-header-offset-figma.test.tsx` pins the first nav ROW at 98 for exactly
         that reason; pinning `.evsh-nav-list` itself would NOT catch it, since that box resolves to
         98 under both.

     ⚠️ The reported symptom was "the rail tile and the sidebar mark should read as one row". THEY
     SHOULD NOT. Figma puts the mark's top 5px BELOW the rail icon's (45 against 40) and its centre
     7px below. The browser had it 7px ABOVE with centres 5px apart — the same two magnitudes with
     the sign flipped. Equalising them would be a second defect, not a fix. */
  padding-top: 12px;
  padding-bottom: 8px;
}
.evsh-sidebar-header {
  padding: 8px 16px;
  /* Anchors WorkspaceMenu (`position: absolute`) under the workspace
     selector — the consumer wraps this header row, not the whole
     `.evsh-sidebar` column, so the menu's `top: 100%` measures from the
     header's own box instead of the full-height sidebar. */
  position: relative;
}
.evsh-sidebar-workspace-btn {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 8px 12px 8px 8px;
  border-radius: 18px;
  background: none;
  border: none;
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
}
.evsh-sidebar-workspace-btn:hover {
  background: var(--evsh-neutral-blue-4);
}
/* D29: while the workspace menu is open the selector takes the background/light-blue fill.
   Driven by a prop, the same way CompactRail drives .evsh-rail-item--selected from `moreOpen`. */
.evsh-sidebar-workspace-btn--open {
  background: var(--evsh-bg-light-blue);
}
.evsh-sidebar-workspace-mark {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  border-radius: 9.6px;
  color: var(--evsh-white);
  font-weight: 700;
  font-family: var(--evsh-font);
}
/* D12: the TEAM selector mark — a white tile with a hairline border carrying the building glyph
   from 13:1352. Geometry comes from the rule above. `color` is restated because that rule paints
   text white for the old initial, and the glyph inherits it as `currentColor`. */
.evsh-sidebar-workspace-mark--team {
  box-sizing: border-box;
  background: var(--evsh-white);
  border: 1px solid rgba(0, 0, 0, 0.04);
  /* R-N16: the asset's own stroke is #7A8D97 — exactly --evsh-neutral-blue-1, the token D32
     added. MEASURED, replacing the #00202f that was inferred while R-N14 stood. */
  color: var(--evsh-neutral-blue-1);
}
/* D28: Figma draws the PERSONAL selector mark as a 36px rounded square in background/light-purple
   with dark initials — not the round UserAvatar. Geometry is inherited from the rule above; only
   the fill, border and type differ. box-sizing keeps the 1px border inside the measured 36px. */
.evsh-sidebar-workspace-mark--personal {
  box-sizing: border-box;
  background: var(--evsh-bg-light-purple);
  border: 1px solid rgba(0, 0, 0, 0.04);
  color: var(--evsh-neutral-1);
  /* M4: 16px / 21.333 SemiBold — the same type the 32px menu mark carries. Initials do not
     scale with tile size. */
  font-size: 16px;
  line-height: 21.333px;
  font-weight: 600;
}
/* evolve-suite#928 — the TEAM selector mark when the workspace has a photo (Figma `1:317`,
   Type=Team Completed=True `1:330`): the photo fills the 36px / 9.6px tile inherited from the
   base rule, with a 1px black-at-4% INSIDE stroke painted OVER it.
   Outline pulled 1px inward rather than a border: a border would inset the image by 1px and
   leave a pale ring between photo and edge, while an outline paints above the content, as the
   frame does. The white fill is the team tile's own, so nothing shows through at the rounded
   corners. A photo that FAILS to load is not left to this tile — a browser draws its broken-image
   glyph inside it — the component swaps in the default icon instead (`usePhotoFallback`). */
.evsh-sidebar-workspace-mark--photo {
  background: var(--evsh-white);
  outline: 1px solid rgba(0, 0, 0, 0.04);
  outline-offset: -1px;
  overflow: hidden;
}
.evsh-sidebar-workspace-photo {
  display: block;
  width: 100%;
  height: 100%;
  /* `cover`, as the switcher's photo: the tile is a filled square in Figma, and letterboxing a
     non-square logo would leave bars inside it. */
  object-fit: cover;
}
.evsh-sidebar-workspace-details {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
}
.evsh-sidebar-workspace-name,
.evsh-sidebar-workspace-subtitle {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* D4: Figma declares #00202f on the name itself. Its own rule, not the grouped one above —
   that group also covers the subtitle, which must stay #666. */
.evsh-sidebar-workspace-name {
  color: var(--evsh-neutral-1);
}
.evsh-sidebar-workspace-subtitle {
  color: var(--evsh-neutral-2);
}
/* evolve-suite#1030 (Figma verifier D2): `#A2B0B7` = neutral-blue-2 on `I966:1244;1:325` and on the
   Nav Bar's `I45:64366;1:201;1:325`. It was neutral-2 (#666), the subtitle's colour, not the icon's. */
.evsh-sidebar-chevron {
  flex-shrink: 0;
  display: flex;
  color: var(--evsh-neutral-blue-2);
}
/* ⛔ NO `padding-top` HERE. It carried Figma `1:200`'s 12px, which belongs ABOVE the workspace
   selector, not below it — see the long note on `.evsh-sidebar`, which now owns it. Figma stacks
   the Workspace Dropdown `1:201` and the first Nav Bar Item `1:205` with NO gap between them
   (`1:201` y=864 h=70 ends exactly where `1:205` y=934 begins), so this element's own top inset is
   zero. Restoring a value here would ADD to the sidebar's rather than replace it, pushing every nav
   row from Figma's 98 to 110 while the selector still looked correct. */
.evsh-nav-list {
  flex: 1 1 0;
  min-height: 0;
  display: flex;
  flex-direction: column;
  width: 100%;
  box-sizing: border-box;
  overflow-y: auto;
}
.evsh-nav-item-wrap {
  padding: 4px 16px;
  width: 100%;
  box-sizing: border-box;
  display: flex;
  align-items: center;
  gap: 4px;
}
/* Sits after the (flex: 1) nav list, so it settles at the bottom of the
   column. 16px each side gives CreditsSection's hardcoded 232px width
   exactly (264 - 32), matching its own CSS comment. */
.evsh-sidebar-footer {
  padding: 4px 16px 12px; /* D37: pt-4 pb-12 px-16 */
  width: 100%;
  box-sizing: border-box;
}
.evsh-nav-item {
  display: flex;
  align-items: center;
  gap: 16px;
  width: 100%;
  flex: 1;
  min-width: 0;
  box-sizing: border-box;
  padding: 10px 16px;
  border-radius: var(--evsh-radius-item);
  background: none;
  border: none;
  font: inherit;
  color: var(--evsh-neutral-1);
  text-decoration: none;
  cursor: pointer;
  text-align: left;
}
/* N1: measured on the component, nodes 1:293/1:294 — background/light-blue, not the
   neutral-blue-4 the code carried. */
.evsh-nav-item:hover {
  background: var(--evsh-bg-light-blue);
}
.evsh-nav-item--active {
  background: var(--evsh-white);
  box-shadow: var(--evsh-shadow-nav-active);
  color: var(--evsh-blue-0);
  font-weight: 700;
}
/* ⛔ THE WEIGHT MUST BE RE-DECLARED ON THE LABEL, NOT LEFT TO INHERIT FROM THE RULE ABOVE.
   `sidebar-nav.tsx` renders the text in a nested `<span class="evsh-nav-item-label evsh-body-2">`,
   and `.evsh-body-2` declares `font-weight: 500` ON THAT SPAN. A declared value beats an inherited
   one unconditionally — inheritance only supplies a value to an element that has none of its own —
   so the 700 above reached the anchor and never the text. The parent still looked mostly right
   (its white pill and `blue-0` survive, because `color` DOES inherit and the label re-declares
   nothing), which is what kept the missing bold invisible. Guarded by
   `nav-active-weight-cascade.test.tsx`. */
.evsh-nav-item--active .evsh-nav-item-label {
  font-weight: 700;
}
.evsh-nav-item--active:hover {
  /* Higher specificity than the plain :hover rule above, so hovering the
     active item keeps its white pill instead of reverting to the hover fill. */
  background: var(--evsh-white);
}
/* Progressive-sidebar states (Coach #632, spec eqb7VfpiM2). Both are
   non-interactive by construction — they render as <div>s, so the cursor and
   hover rules below only remove the affordance the base .evsh-nav-item rule
   sets, in the same spirit as the Button disabled rule at L826-837. */
.evsh-nav-item--skeleton,
.evsh-nav-item--disabled {
  cursor: default;
}
.evsh-nav-item--skeleton:hover,
.evsh-nav-item--disabled:hover {
  background: none;
}
/* D13 — Figma's nav skeleton: a 14px placeholder (radius 4) centred in the untouched 20px
   icon slot, and a 4px x 79px bar (radius 16) for the label. Both neutral-blue-2 @ 50%.
   The placeholder is a pseudo-element so the 20px slot keeps the geometry measured as a MATCH;
   drawing it by resizing the slot itself would have moved the row. */
.evsh-nav-item--skeleton .evsh-nav-item-icon::before {
  content: '';
  display: block;
  width: 14px;
  height: 14px;
  border-radius: 4px;
  background: var(--evsh-neutral-blue-2);
  opacity: 0.5;
}
.evsh-nav-item--skeleton .evsh-nav-item-label {
  flex: 0 0 auto;
  width: 79px;
  height: 4px;
  border-radius: 16px;
  background: var(--evsh-neutral-blue-2);
  opacity: 0.5;
}
.evsh-nav-item--disabled {
  /* D32: #7a8d97, CONFIRMED ON THE COMPONENT (node 167:3212), not only on the instance it was
     first measured from ("Apps", I4:4225;1:209;1:286, step-2 frame 4:4211). Figma's own token
     name there is `netural blue/neutral blue 1`, matching --evsh-neutral-blue-1 by name as well
     as value. The label's type — Lato Medium 15/20 — already comes from .evsh-body-2 on the base
     rule, so only the colour was ever wrong. */
  color: var(--evsh-neutral-blue-1);
}
/* Optional trailing action on a nav item (Edge's Create-Brief "+"). No Figma variant defines
   this — "Nav Bar | Item" (1:280) has no trailing-action state — so it deliberately borrows the
   nav item's own hover treatment rather than inventing a second visual language. */
.evsh-nav-item-action {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 32px;
  height: 32px;
  padding: 0;
  border: none;
  background: none;
  border-radius: var(--evsh-radius-item);
  color: var(--evsh-neutral-1);
  cursor: pointer;
  line-height: 0;
}
.evsh-nav-item-action:hover {
  background: var(--evsh-neutral-blue-4);
}
.evsh-nav-item-icon {
  flex-shrink: 0;
  width: 20px;
  height: 20px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.evsh-nav-item-label {
  flex: 1 1 0;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Nav item sub-tree — read directly off Figma fYvRupTfIiOEBXYGOcvZZZ 4:10143 (Sub Items
   I4:10168;1:218, rows 1:219/1:220/1:221; readings in the chapter's t7-figma-readings.md).
   Rows keep the PARENT geometry (16px margin, 14px radius, text at 52px so sub-labels align
   with parent labels) with a 6px vertical padding (32px container in a 40px row). The left
   rule is the frame's own construction: a 1px neutral-blue-3 line centred in the 20px icon
   column (asset 76f9968c…, #DBDDDE, spanning the full row so it runs continuously). */
.evsh-nav-subnav {
  display: flex;
  flex-direction: column;
  position: relative;
  margin: 0 16px 4px 16px;
}
.evsh-nav-subnav::before {
  /* The continuous rule, at the icon column's centre: 16px item padding + 10px to the
     centre of the 20px icon slot (line x=10.5 on node 1:219;1:289). */
  content: '';
  position: absolute;
  left: 26px;
  top: 0;
  bottom: 0;
  width: 1px;
  background: var(--evsh-neutral-blue-3);
}
.evsh-nav-subitem {
  display: flex;
  align-items: center;
  width: 100%;
  box-sizing: border-box;
  /* 52px left = 16px padding + 20px icon column + 16px gap — text aligns with parent
     labels exactly (node 1:219;1:291 x=52). */
  padding: 6px 16px 6px 52px;
  border-radius: var(--evsh-radius-item);
  background: none;
  border: none;
  font: inherit;
  color: var(--evsh-neutral-1);
  text-decoration: none;
  text-align: left;
  cursor: pointer;
}
/* N2: the sub-item hovers by a DIFFERENT MECHANISM from the row above it. Figma 1:299/1:300 has
   NO background on hover at all; the label recolours #00202f -> #666 (node 1:303). Assuming the
   main row's answer covered this one would have painted a fill the design does not have. */
.evsh-nav-subitem:hover {
  color: var(--evsh-neutral-2);
}
.evsh-nav-subitem--active {
  /* Node 1:220;1:315 (the selected "Billing" sub-item): Lato Bold, neutral-1 — the white
     card + shadow + blue treatment belongs to the PARENT item (1:217;1:306) only.
     Re-measured on `695:45206` for this fix: the active sub-item is `Lato:Bold` and its two
     siblings are `Lato:Medium`, and ALL THREE are `#00202f`. ⚠️ There is no colour change —
     darkening the active label would contradict the node. Weight is the whole treatment. */
  font-weight: 700;
}
/* ⛔ Same override as the parent item above, and here it is the WHOLE selected state rather than
   half of it: bold is the sub-item's only treatment, so `.evsh-body-2`'s 500 on the label span
   left the active row byte-identical to its siblings — a sub-menu with no visible selected state
   at all, while the model, the CSS rule and 21 passing nav tests were each individually correct. */
.evsh-nav-subitem--active .evsh-nav-subitem-label {
  font-weight: 700;
}
/* Both non-default states render as <div>s, so these rules only remove the affordance the
   base .evsh-nav-subitem rule sets — same spirit as the nav-item states above. */
.evsh-nav-subitem--skeleton,
.evsh-nav-subitem--disabled {
  cursor: default;
}
.evsh-nav-subitem--skeleton:hover,
.evsh-nav-subitem--disabled:hover {
  background: none;
}
.evsh-nav-subitem--skeleton .evsh-nav-subitem-label {
  /* No skeleton sub-item variant exists in 4:10143's component set (verified in the T7 pass)
     — mirroring .evsh-nav-item--skeleton is the deliberate treatment, not a placeholder.
     D13 moved that treatment to a 4px bar in neutral-blue-2 @ 50%. The nav item's measured
     79px width is deliberately NOT mirrored: it belongs to that node, and a sub-item bar width
     was never measured. Width stays flexible, as it always was here. */
  background: var(--evsh-neutral-blue-2);
  opacity: 0.5;
  border-radius: 16px;
  height: 4px;
}
.evsh-nav-subitem--disabled {
  /* Same: no disabled sub-item variant in the frame; mirrors .evsh-nav-item--disabled, which
     D32 moved to neutral-blue-1. DERIVED from that rule, not separately measured. */
  color: var(--evsh-neutral-blue-1);
}
.evsh-nav-subitem-label {
  flex: 1 1 0;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ============ Skeleton shimmer (evolve-suite, owner ask 2026-09-11) ============
 * Owner, on the Create-workspace loading state (static grey rail tiles + sidebar rows):
 * "this should be a real shimmer effect, check if its viable to implement our own (fast and easy)
 * or if need to use a specific library for that". It is — this package already had 7 `@keyframes`
 * blocks and a working reduced-motion precedent (the shake above, #471) before this section
 * existed, so this is one more instance of an established technique, not a new one.
 *
 * ⚠️ FIGMA IS SILENT ON THE MOTION — checked over REST (`X-Figma-Token` header, file key as a
 * parameter), never the desktop MCP, which only reads the active tab and cannot tell "no node
 * found" from "wrong tab". File `fYvRupTfIiOEBXYGOcvZZZ` ("Evolve Suite"): every "Skeleton" frame
 * in it (Create workspace > Mobile, Navigation / Switch apps > Mobile — 10 instances, all
 * non-null) is a flat solid "bone" rectangle — zero `effects`, zero `interactions`/reactions, no
 * gradient fill. The one node literally named "Creating workspace" (`11:23730`, Index page) is a
 * table-of-contents link to the wizard frame, not a design frame. So this sweep is the owner's
 * direct ask, not a Figma-specified motion — noted here so neither the wiki nor a future ticket
 * implies Figma drew it. The highlight band below reuses --evsh-neutral-blue-3, already in the
 * palette (menu divider) — no colour is minted for this.
 *
 * ⭐ ADDITIVE ON PURPOSE, NOT A REPLACEMENT OF THE RULES ABOVE. Each selector below already has its
 * own rule declaring `background: var(--evsh-neutral-blue-2)` (D10/D11/D13 — Figma-measured, and
 * pinned byte-for-byte by navbar-figma.test.tsx). This rule adds ONLY `background-image` +
 * `animation` on top of that flat fill, so those Figma-parity pins stay untouched and the block
 * still reads as the correct flat grey if this rule is ever removed.
 *
 * Duration and sweep width: see --evsh-skeleton-shimmer-duration / --evsh-skeleton-shimmer-band on
 * .evsh-root — UNMEASURED, no Figma prototype exists to read them from.
 */
.evsh-rail-item--skeleton .evsh-rail-icon,
.evsh-rail-item--skeleton .evsh-rail-label::before,
.evsh-nav-item--skeleton .evsh-nav-item-icon::before,
.evsh-nav-item--skeleton .evsh-nav-item-label,
.evsh-nav-subitem--skeleton .evsh-nav-subitem-label {
  background-image: linear-gradient(90deg, transparent 0%, var(--evsh-neutral-blue-3) 50%, transparent 100%);
  background-size: var(--evsh-skeleton-shimmer-band) 100%;
  background-repeat: no-repeat;
  animation: evsh-skeleton-shimmer var(--evsh-skeleton-shimmer-duration) linear infinite;
}
@keyframes evsh-skeleton-shimmer {
  0%   { background-position: var(--evsh-skeleton-shimmer-band) 0; }
  100% { background-position: calc(-1 * var(--evsh-skeleton-shimmer-band)) 0; }
}

/* ==== WorkspaceMenu (wiki: Suite Navigation — Workspace menu; Create Workspace —
   Workspace menu open/hovered) ==== */
/* Measured from the OPEN instance 4:3749 (inside frame 4:3735) — D14-D27. The note that stood
   here, claiming no open-state instance existed in Figma and that these values were a brief's
   fallback, was refuted by that read and is deleted rather than amended. No portal —
   position:absolute relative to the nearest positioned ancestor the consumer wraps around the
   workspace selector button. */
.evsh-workspace-menu {
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  width: 278px;
  z-index: 10;
  box-sizing: border-box;
  background: var(--evsh-white);
  border-radius: 24px;
  box-shadow: var(--evsh-shadow-med);
  padding: 12px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}
/* D17 is two gaps, not one: 12px between the menu's sections (overline / list / divider /
   create) on the rule above, and 8px between the workspace rows here (I4:3749;1:497). */
.evsh-workspace-menu-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
  /* The LIST scrolls, not the panel — so "In {email}" above and the "Create workspace" row below
     stay reachable at any workspace count. Scrolling the whole panel would push Create off-screen,
     which is the bug: with 14 workspaces the panel ran past the viewport and Create was
     unreachable with no scrollbar anywhere.

     THE CAP IS DERIVED, NOT PICKED — AND IT CUTS THE FIRST HIDDEN ROW IN HALF, because that
     half-visible row is the affordance that tells you there is more. With the scrollbar hidden
     (evolve-suite#1027, below) it is the ONLY one, so it must hold at every window height.
     A row is 8+8 padding around two lines (20+18) = 54px, plus the 8px gap = a 62px pitch, so
     half a row is 27px. (Pinned against those rules by workspace-menu-scroll.test.ts: change a
     padding, line-height or the gap and re-derive these numbers.)
       - Tall windows: 5 rows + half the sixth = 5*62 + 27 = 337px.
       - Short windows: the room below the anchor, snapped DOWN to whole rows, plus half a row.
         239px is what sits above and around the list, measured in Chromium 2026-09-22: the panel's
         top edge at 102px (.evsh-shell is position: fixed, and no host restyles it — Suite, Coach
         and Edge main checked 2026-09-22 — so this holds in all three) plus the panel's
         own chrome — overline, divider, Create row, three 12px gaps, 12px padding — at 137px.
         Both parts include the divider's 12px gaps and the menu's 4px offset; if either changes,
         re-measure 239. A smaller real value only makes this cap cautious, a larger one would let
         the panel run off-screen again.
     So the cut always lands 27px into the first hidden row. Measured on the BUILT row in Chromium
     149, Firefox 151 and WebKit 26.5, at every window height 400-1100px: 16px of its 32px avatar
     and 19px of its 20px name line show, and the subtitle is hidden.
     Figma is silent here: the Workspace Menu (Evolve Components `3gwifp9Rkgj4R7FiQ8ac2l`,
     `150:1648`) only ever draws 2 workspaces, with no scrollbar, fade or cut row (read over REST
     by figma-1027, 2026-09-22). The half row is this stylesheet's own cue, not a Figma one.
     ⛔ The previous cap, `min(320px, calc(80vh - 120px))`, claimed the same half-row cue and never
     delivered it: 320px cut the sixth row 10px in, inside its transparent top padding, so five
     whole rows sat above blank space (Marcos's #1027 screenshot); the `80vh` term cut at arbitrary
     points (a cue at 8 of 71 window heights, 400-1100px); and its ~120px chrome estimate was 17px
     short, so below ~560px tall the panel ran past the viewport and clipped the Create row.
     It stays as the FIRST declaration only as the fallback for engines without CSS `round()`
     (Chromium <125, Firefox <118, Safari <15.4 per MDN browser-compat-data, read 2026-09-22),
     which drop the second one as invalid.

     `min-height: 0` is load-bearing: this is a flex child, and without it `overflow-y` never
     engages because the default `min-height: auto` refuses to shrink below content height.
     `overscroll-behavior: contain` stops a scroll that reaches the end from chaining to the page
     behind the menu. */
  max-height: min(320px, calc(80vh - 120px));
  max-height: min(337px, calc(round(down, 100vh - 239px - 27px, 62px) + 27px));
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  /* evolve-suite#1027 — scroll WITHOUT a visible bar. `overflow-y: auto` draws the platform
     scrollbar on any system set to always show one (or with a mouse attached). Measured
     2026-09-22 in Chromium 149, Firefox 151 and WebKit 26.5: this property alone hid the bar in
     all three, while the `::-webkit-scrollbar` rule below alone left it in Firefox. That rule is
     kept for engines older than this property (Chromium <121, Safari <18.2 per MDN
     browser-compat-data — not measured here). The list still scrolls — only the bar is gone. */
  scrollbar-width: none;
}
.evsh-workspace-menu-list::-webkit-scrollbar {
  display: none;
}
/* D24: "In {email}", Lato Black 12/18 via .evsh-subtitle-4. Inset 8px to sit over the rows'
   own left padding. */
.evsh-workspace-menu-overline {
  padding: 0 8px;
  color: var(--evsh-neutral-2);
  /* evolve-suite#1049 — ONE line, ellipsis, never a wrap.
     ⛔ FIGMA-vs-OWNER: Figma is SILENT on truncation for this node and in fact draws it WRAPPING.
     Measured on `4:3749` "Workspace Menu" (278x245, p-12) in the file the shell's other node
     citations resolve against: the overline text `I4:3749;1:496` (238x18, inside container
     `I4:3749;1:495` 254x18 px-8) carries `[word-break:break-word] flex-[1_0_0] min-w-px` and NO
     overflow/ellipsis/nowrap class. That is a CONTROLLED negative, not an unread one — the
     sibling subtitle nodes in the SAME component ("Private", "Team workspace", "Manage people and
     usage") each DO carry `overflow-hidden text-ellipsis w-full whitespace-nowrap`, so the
     generator emits those classes when the design asks for them and simply was not asked here.
     The owner overrode the design directly: "this should not break line, show an ellipsis and a
     tooltip when hovering". Recorded so the conflict is visible rather than looking like drift.
     The three properties below are this stylesheet's own established truncation idiom — the same
     trio used by .evsh-sidebar-workspace-name, .evsh-nav-item-label, .evsh-workspace-menu-item-name
     and four others. */
  display: block;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* The Tooltip wrapper is the overline's width bound: it must fill the menu's 254px content box
   and be allowed to shrink, or the button inside it sizes to its text and never clips. */
.evsh-workspace-menu-overline-tip {
  display: flex;
  min-width: 0;
}
/* D25: a 1px rule inset by that same 8px, between the workspace list and Create workspace.
   The line is a pseudo-element so the wrapper keeps Figma's px-8 py-2 box. */
.evsh-workspace-menu-divider {
  padding: 2px 8px;
}
.evsh-workspace-menu-divider::before {
  content: '';
  display: block;
  height: 1px;
  background: var(--evsh-neutral-blue-3);
}
.evsh-workspace-menu-item {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  box-sizing: border-box;
  /* D18: no fixed height — it is content-driven. The old 36px could not hold a row's two
     lines of text plus its 8px padding. */
  padding: 8px 12px 8px 8px;
  border-radius: 18px;
  background: none;
  border: none;
  font: inherit;
  color: var(--evsh-neutral-1);
  cursor: pointer;
  text-align: left;
}
/* R-N15 — WHY THESE TWO MUST NOT SHARE A VALUE.
   D27 put the arrow on EVERY row and D21 measured SELECTED as neutral-blue-4 (#f1f5f8). Each was
   correct alone; together they cancelled, because #f1f5f8 was already the hover fill — leaving
   the menu with no cue at all for which workspace is active.
   Selected keeps Figma's measured #f1f5f8. Hover takes #f6f9fa, the value THIS APP used for
   selected before D21 — an existing in-repo value preserved for a state Figma cannot currently
   tell us, not a colour invented to fill the gap.
   BOTH VALUES ARE NOW MEASURED on the component: 1:412's variant logic gives Hovered +
   Private/Team `neutral-blue-5` #f6f9fa, and Selected #f1f5f8.
   ⚠️ #f6f9fa WAS INVENTED BEFORE IT WAS MEASURED, AND IT HAPPENED TO BE RIGHT. Nothing in the
   reasoning that produced it had access to 1:412 — it was this app's own pre-D21 selected value,
   reused because the menu needed SOME distinction. One shade off and it would have shipped
   looking exactly as justified. The value is kept because it is measured, not because it was
   guessed correctly. */
.evsh-workspace-menu-item:hover {
  background: var(--evsh-neutral-blue-5);
}
.evsh-workspace-menu-item--selected {
  background: var(--evsh-neutral-blue-4);
}
.evsh-workspace-menu-item-details {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
}
.evsh-workspace-menu-item-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.evsh-workspace-menu-item-subtitle {
  font-size: 12px;
  line-height: 18px;
  color: var(--evsh-neutral-2);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* D27: a right arrow replaces the checkmark, and it sits on EVERY workspace row — selection is
   carried by the fill alone. No colour is declared: the arrow is no longer an active-state
   marker, so the old --evsh-blue-0 would be wrong, and the read did not give one. It inherits
   the row's own #00202f. */
.evsh-workspace-menu-item-arrow {
  flex-shrink: 0;
  display: flex;
}
/* pitchiq#361 — the Create row's ONLY consumer of this class, so both changes below are scoped to
   it by construction (verified: no other component renders `.evsh-workspace-menu-item-icon`).
   Resized 20px -> 32px, radius 9.6, matching `.evsh-workspace-menu-mark`'s own footprint (Figma
   I4:3749;1:502: a 32x32 avatar-tile slot, no fill/stroke by default, holding a 20x20 "Add Icon"
   frame). The 20px glyph centres inside it via the flex-centring already below — unchanged.
   Without the tile the icon's own gap:12 put the row's label 12px LEFT of every other row's name
   (Eli's screenshot, next to "TapTrap LLC"). */
.evsh-workspace-menu-item-icon {
  flex-shrink: 0;
  width: 32px;
  height: 32px;
  border-radius: 9.6px;
  display: flex;
  align-items: center;
  justify-content: center;
  /* Figma I4:3749;1:502, bound variable 42:29 — the SAME variable every other row's name uses,
     #00202f / --evsh-neutral-1. Was --evsh-neutral-2 (#666), a deviation. */
  color: var(--evsh-neutral-1);
}
/* Figma I4:3749;1:502, bound variable 42:29 — the SAME variable every other row's name uses,
   #00202f / --evsh-neutral-1. Was --evsh-blue-0, a deviation (the row rendered blue). */
.evsh-workspace-menu-item--create {
  color: var(--evsh-neutral-1);
}
/* N3: in Figma's 1:412 the `More` type takes NO row fill in either hover or selected — the fill
   moves to its 32px avatar tile (1:431/1:485). This row was inheriting the generic row hover.
   Only the suppression is applied here; see the report for why the tile half is not. */
.evsh-workspace-menu-item--create:hover {
  background: none;
}
/* pitchiq#361 — completing N3 above: the TILE fill it deferred. At 7a0c4415 (N3's own commit) the
   Create row's icon was still a bare 20px box, not a tile — there was nothing to fill, which is
   why only the suppression landed then. Figma Hovered 1:430 (canonical library
   3gwifp9Rkgj4R7FiQ8ac2l, component set 1:412) fills the 32px tile neutral-blue-5 (#f6f9fa), the
   same value the ordinary row hover already uses (:1298 above). The row itself still fills
   nothing — the rule above is unchanged. */
.evsh-workspace-menu-item--create:hover .evsh-workspace-menu-item-icon {
  background: var(--evsh-neutral-blue-5);
}
/* M2: 32px at radius 9.6, measured on rows 891:19190 / 891:19198 / 891:19208.
   ⛔ M4 CORRECTS THE FONT. This rule carried 14px, DERIVED from UserAvatar's 32px row on the
   theory that initials track the tile. They do not: 891:19191 measures Lato SemiBold 16 / 21.333,
   the same type as the 36px selector mark. The derivation was the error, not the arithmetic.
   No COLOUR here since #928: the white initial it carried was the colour-hashed team mark's, and
   that mark is gone — every kind now sets its own colour in the modifiers below. */
.evsh-workspace-menu-mark {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border-radius: 9.6px;
  font-weight: 600;
  font-family: var(--evsh-font);
  font-size: 16px;
  line-height: 21.333px;
}
/* ⛔ MODIFIERS GO AFTER THE BASE (evolve-suite#386). The base and every modifier are a SINGLE
   class, so a property both set resolves by ORDER — the later rule wins. #386 shipped this block
   BEFORE the base, and the base's white `color` beat its dark one. Since #928 the base sets no
   `color` and shares no property with a modifier at a different value, so the order changes
   nothing TODAY. It is kept anyway, and pinned by `test/cascade-computed.test.tsx`, so the first
   property added to both cannot silently lose. The sidebar pair above
   (.evsh-sidebar-workspace-mark and its --team/--personal) has always been ordered this way.
   A declared value is not a computed value: `declaration()` read this rule correctly and truthfully
   the whole time. The cascade test asks the DOM instead. */
/* #384 part 2 — the PERSONAL menu row's mark. Figma draws it as a `<slot>` (I4:3749;…;1:468):
   a 32px rounded square in background/light-purple with dark initials. It was rendering the
   circular UserAvatar, which is a different component that merely looks similar. Geometry comes
   from the base rule below; fill, border and type are this slot's own. box-sizing keeps the 1px
   border inside the measured 32px. --evsh-bg-light-purple already exists (D31) — not minted here. */
.evsh-workspace-menu-mark--personal {
  box-sizing: border-box;
  background: var(--evsh-bg-light-purple);
  border: 1px solid rgba(0, 0, 0, 0.04);
  color: var(--evsh-neutral-1);
  font-size: 16px;
  font-weight: 600;
}
/* #928 — a TEAM row with no photo draws the DEFAULT WORKSPACE ICON (ruled from the Suite file's
   instance `I459:25843;152:1795;1:449`): a white 32px tile, the 1px black-at-4% INSIDE stroke
   Figma `1:412` gives every mark, and the 18px building glyph, whose `currentColor` stroke is
   #7A8D97 — the same tile the sidebar's `--team` modifier draws at 36px. After the base, per the
   order note above. */
.evsh-workspace-menu-mark--team {
  box-sizing: border-box;
  background: var(--evsh-white);
  border: 1px solid rgba(0, 0, 0, 0.04);
  color: var(--evsh-neutral-blue-1);
}
/* #1009 — the workspace PHOTO variant of the mark.
   ⛔ PLACED AFTER THE BASE ON PURPOSE, per the order note above: this modifier and the base are
   both single classes, so specificity ties and the later rule wins. evolve-suite#386 shipped a
   modifier BEFORE its base and made the whole block dead code that still read correctly in the
   source. Geometry (32px, radius, flex centring) is inherited from the base; this only changes
   the fill, clips the image to the tile, and draws Figma's hairline.
   #928: the 1px black-at-4% INSIDE stroke of `1:412` paints OVER the photo, so it is an outline
   pulled 1px inward — a border would inset the image and leave a pale ring. The same treatment as
   the sidebar's `--photo` modifier. */
.evsh-workspace-menu-mark--photo {
  /* The default tile's own white behind the image, so nothing shows through at
     the rounded corners. A photo that FAILS to load is swapped for the default
     icon by the component (`usePhotoFallback`), because a browser draws its
     broken-image glyph inside this tile rather than leaving it empty. */
  background: var(--evsh-white);
  outline: 1px solid rgba(0, 0, 0, 0.04);
  outline-offset: -1px;
  overflow: hidden;
  padding: 0;
}
.evsh-workspace-menu-photo {
  display: block;
  width: 100%;
  height: 100%;
  /* `cover`, not `contain`: the tile is a filled brand mark in Figma, and
     letterboxing a non-square logo would leave bars inside a 32px square. The
     cost is that a very wide logo loses its edges — worth confirming against a
     real logo during manual QA rather than asserting it here. */
  object-fit: cover;
}

/* ==== MoreAppsMenu (wiki: Suite Navigation — More-app menu; Figma `3gwifp9Rkgj4R7FiQ8ac2l`
   `1:381` "Tool Bar | More Menu Item") ====
   evolve-suite#1178 — built on the shared Popover (`position="left"`), which is where the card's
   position:absolute, radius, padding, shadow, background and caret now come from — see
   `.evsh-popover`/`.evsh-popover--left`. This class overrides only the WIDTH: figma-1582 measured
   294px on this menu, wider than the base Popover card's 251 (measured from a different node,
   `1:1225` — the two cards agree on radius 32 and padding 16, which is why neither is redeclared
   here). The menu no longer supplies its own position/top/left/z-index/background/shadow — a
   SIDE popover with a caret (Figma's `Position: Left` variant) replaces the below-anchored
   `left: 0` dropdown this used to be.
   ⛔ TWO CLASSES, ON PURPOSE (#627 review). As a single class this rule TIED with
   `.evsh-popover { width: 251px }` on specificity, and the Popover section comes later in the file,
   so the later rule won: the desktop More menu rendered 251 wide, not Figma's 294. The menu is
   always a Popover card, so `.evsh-popover.evsh-more-apps-menu` out-ranks the base wherever it is
   declared — and the mobile app switcher, which carries this same class, inherits the fix. Pinned
   on the resolved cascade by `more-apps-menu-figma.test.tsx`. */
.evsh-popover.evsh-more-apps-menu {
  width: 294px;
}
.evsh-more-apps-heading {
  padding: 8px 8px 4px;
  color: var(--evsh-neutral-2);
}
.evsh-more-apps-item {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  height: 50px;
  box-sizing: border-box;
  padding: 8px;
  border-radius: 12px;
  /* Every not-enabled row is itself the clickable target (a <button>), not a
     plain <div> with an inner action — reset UA button chrome so it reads as
     the same row it always was, just with the whole thing selectable. */
  background: none;
  border: none;
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
}
/* figma-1582: hover fills `neutral-blue-5` #F6F9FA — NOT `neutral-blue-4` #F1F5F8, which is
   Figma's SELECTED fill (this row has no selected state; spec L155-156 puts that on the rail
   tile, already correct in `compact-rail.tsx`). */
.evsh-more-apps-item:hover {
  background: var(--evsh-neutral-blue-5);
}
.evsh-more-apps-item-details {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
}
.evsh-more-apps-item-name {
  /* figma-1582: Lato Bold 700 15/20 #00202F — the size/weight/line-height come from the composed
     `.evsh-subtitle-2` class (`more-apps-menu.tsx`); the colour is declared here explicitly rather
     than left to inherit, same as every other measured Figma colour in this file. */
  color: var(--evsh-neutral-1);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.evsh-more-apps-item-description {
  font-size: 12px;
  line-height: 18px;
  font-weight: 500;
  color: var(--evsh-neutral-2);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* evolve-suite#1178 — the trailing chevron, VERBATIM export at
   `figma-assets/more-menu-chevron.svg`. One colour, so it inherits via `currentColor`
   (`more-apps-menu.tsx`'s `ChevronIcon`) rather than a hardcoded fill — `--evsh-neutral-blue-2`
   IS the export's literal `#A2B0B7` (SH-11: every chevron in this file strokes that token). */
.evsh-more-apps-item-chevron {
  flex-shrink: 0;
  color: var(--evsh-neutral-blue-2);
}

/* ==== LegalDialog (wiki: Suite Navigation — Legal Acceptance) ====
   Fixed overlay + centered card, per the brief. */
.evsh-legal-dialog-overlay {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: center;
  /* evolve-suite#419 — `#000000` at 0.38, measured. NOT `#00202f` at 0.40: wrong hue AND wrong
     opacity. Confirmed on three independent dialogs — the support dialog's seven state frames,
     `36:62281` (Switch app - Request access), and `4:5912` (Disabling an app) — so this is the
     design's settled scrim, not one frame's accident.
     ⛔ WHY IT SURVIVED A CAREFUL CHECK: the support dialog was verified against its COMPONENT
     MASTER, and every other attribute is exact. A component has nothing behind it, so it has no
     scrim — this attribute exists ONLY on the frames. The frame carries what the component
     cannot express: scrim, drop shadow, per-state visibility. Those are the unchecked ones. */
  background: rgba(0, 0, 0, 0.38);
}
.evsh-legal-dialog-card {
  /* Figma `4:3697` ("Dialog", accept-terms): 416 wide, radius 20 — the same card four other
     Suite dialogs draw.

     ⛔ AN EARLIER VERSION OF THIS COMMENT SAID "no frame exists in any spec node list searched"
     and pinned 420 as deliberately-unmeasured. **The frame existed; it was not in the two node
     lists I had searched.** Those are different things, and the difference is the whole point:
     "there is nothing to check" and "I did not find it" produce the same empty result and
     licence opposite decisions. The pin was honest and it was wrong.
     ⭐ Not `--evsh-radius-card` (12px) for the same reason as the sibling below: four other rules
     depend on that token. */
  width: 416px;
  max-width: calc(100vw - 32px);
  box-sizing: border-box;
  background: var(--evsh-white);
  border-radius: 20px;
  box-shadow: var(--evsh-shadow-med);
  padding: 24px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.evsh-legal-dialog-description {
  color: var(--evsh-neutral-2);
}
/* evolve-suite#45 — the two legal documents, measured off Figma `4:3697` ("Dialog").
   Replaces `.evsh-legal-dialog-links` / `-link`, which drew them as blue text links side by side.

   ⛔ THE FRAME EXISTS. The sibling `dialog-scrim-figma.test.ts` records "the legal dialog has no
   frame of its own in either spec's node list" — an honest "I did not find it", written down so
   it could be corrected. `4:3697` is that frame, and every value below comes off it. */
.evsh-legal-dialog-docs {
  /* `4:3702` — border 1px `background/light-blue`, radius 16, padding 4px 16px. */
  display: flex;
  flex-direction: column;
  border: 1px solid var(--evsh-bg-light-blue);
  border-radius: 16px;
  padding: 4px 16px;
}
.evsh-legal-dialog-doc-row {
  /* `4:3703` / `4:3713` — flex, gap 12, py 4. */
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 4px 0;
  cursor: pointer;
  /* The row was an anchor before this ticket and still is (see `DocumentRow`), so it inherits the
     UA underline the old blue-link treatment wanted and this one does not. */
  text-decoration: none;
}
.evsh-legal-dialog-doc-row--divided {
  /* `4:3703` alone carries `border-b`, and in `neutral/neutral-5` — a DIFFERENT grey from the
     container's `background/light-blue` outline above. Two tokens, not one. */
  border-bottom: 1px solid var(--evsh-neutral-5);
}
.evsh-legal-dialog-doc-content {
  /* `4:3704` — gap `spacing/spacing-2` 8px, py 11, `flex-[1_0_0]`, clipped.
     Kept as its own box rather than flattened into the row (the shape `.evsh-support-dialog-row`
     uses) because the two gaps differ: 8px icon-to-label, 12px content-to-chevron. Flattening
     would have to pick one. */
  display: flex;
  align-items: center;
  flex: 1 0 0;
  min-width: 0;
  gap: 8px;
  padding: 11px 0;
  overflow: hidden;
}
.evsh-legal-dialog-doc-icon {
  /* `4:3705` — a fixed 24px square that centres the smaller glyph inside it. */
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 24px;
  height: 24px;
  /* Drives the glyph's `currentColor` strokes; the token IS the asset's `#7A8D97`. */
  color: var(--evsh-neutral-blue-1);
}
.evsh-legal-dialog-doc-label {
  /* `4:3710` — `subtitle/subtitle-2`, `neutral/neutral-1`. The type step is applied as a class in
     the markup; only the colour is local. */
  flex: 1 0 0;
  min-width: 0;
  color: var(--evsh-neutral-1);
}
.evsh-legal-dialog-doc-chevron {
  /* `4:3711` — 16px, stroke `neutral/neutral-1`. Full-strength, NOT the muted
     `--evsh-neutral-blue-2` the support dialog's otherwise-identical row chevron uses. */
  flex-shrink: 0;
  color: var(--evsh-neutral-1);
}
.evsh-legal-dialog-team-notice {
  color: var(--evsh-neutral-2);
}
.evsh-legal-dialog-actions {
  display: flex;
  justify-content: flex-end;
  gap: 12px;
  margin-top: 8px;
}
.evsh-legal-dialog-cancel {
  background: none;
  border: none;
  padding: 8px 12px;
  color: var(--evsh-neutral-2);
  cursor: pointer;
}
.evsh-legal-dialog-accept {
  /* evolve-suite#45 — Figma `4:3726` fills this button `neutral/neutral-1` (#00202f), the dark
     navy, NOT the `blue/blue 2` accent it shipped as. The token resolves to exactly the node's
     value, so this reproduces the frame rather than approximating it.
     ⚠️ Reached as a MEASUREMENT, not a preference: it arrived as an eyeballed screenshot
     comparison and was checked against the node before anything moved. `4:3697`'s other
     button deltas (radius, padding, the Cancel label colour) were measured at the same time and
     deliberately left alone — see `legal-dialog-accept-button-figma.test.ts` for the table. */
  background: var(--evsh-neutral-1);
  border: none;
  border-radius: 8px;
  padding: 8px 16px;
  color: var(--evsh-white);
  cursor: pointer;
}

/* ==== RequestAccessDialog (wiki: Suite Navigation — Legal Acceptance)
   Same fixed-overlay + centered-card + actions-row structure as LegalDialog,
   kept as its own class namespace (this package's convention — see
   MoreAppsMenu/WorkspaceMenu, which are visually similar dropdowns that each
   own distinct classes rather than sharing one) rather than reusing
   evsh-legal-dialog-* directly. */
.evsh-request-access-dialog-overlay {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: center;
  /* evolve-suite#419 — `#000000` at 0.38, measured. NOT `#00202f` at 0.40: wrong hue AND wrong
     opacity. Confirmed on three independent dialogs — the support dialog's seven state frames,
     `36:62281` (Switch app - Request access), and `4:5912` (Disabling an app) — so this is the
     design's settled scrim, not one frame's accident.
     ⛔ WHY IT SURVIVED A CAREFUL CHECK: the support dialog was verified against its COMPONENT
     MASTER, and every other attribute is exact. A component has nothing behind it, so it has no
     scrim — this attribute exists ONLY on the frames. The frame carries what the component
     cannot express: scrim, drop shadow, per-state visibility. Those are the unchecked ones. */
  background: rgba(0, 0, 0, 0.38);
}
.evsh-request-access-dialog-card {
  /* Figma `36:62282` ("Switch app - Request access"): 416 wide, radius 20 — the same card the
     Suite draws for every confirmation dialog, confirmed on four frames.
     ⛔ NOT `--evsh-radius-card` (12px). That token is shared with `.evsh-rail-item`,
     `.evsh-textfield-input` and `.evsh-tokenfield-field`; moving it to 20 would restyle the nav
     rail and every text field to fix one dialog. An explicit value is the smaller change. */
  width: 416px;
  max-width: calc(100vw - 32px);
  box-sizing: border-box;
  background: var(--evsh-white);
  border-radius: 20px;
  box-shadow: var(--evsh-shadow-med);
  padding: 24px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.evsh-request-access-dialog-description {
  color: var(--evsh-neutral-2);
}
.evsh-request-access-dialog-actions {
  display: flex;
  justify-content: flex-end;
  gap: 12px;
  margin-top: 8px;
}
.evsh-request-access-dialog-cancel {
  background: none;
  border: none;
  padding: 8px 12px;
  color: var(--evsh-neutral-2);
  cursor: pointer;
}
.evsh-request-access-dialog-request {
  background: var(--evsh-blue-2);
  border: none;
  border-radius: 8px;
  padding: 8px 16px;
  color: var(--evsh-white);
  cursor: pointer;
}

/* ==== EvolveShell (composition root: CompactRail + SidebarNav, fixed to the
   viewport's left edge; the host offsets its own <main> by SHELL_TOTAL_WIDTH)
   ====
   Deliberately a DISTINCT class from `.evsh-root` rather than adding these
   layout rules to `.evsh-root` itself (as the task brief's shorthand
   literally reads). `.evsh-root` is applied to every other component's own
   root element too (CompactRail's <nav>, SidebarNav's <nav>, WorkspaceMenu's
   and MoreAppsMenu's popovers, LegalDialog's overlay) purely for the shared
   token/font declarations — none of those carry position/layout rules today.
   This composition nests several of them (CompactRail, SidebarNav) as flex
   children inside this wrapper. If `position: fixed; inset-block: 0; left: 0`
   were added to `.evsh-root` instead, EVERY nested child sharing that class
   would also become `position: fixed`, pulling them out of this flex row
   entirely — both CompactRail's <nav> and SidebarNav's <nav> would
   independently pin to the viewport's left edge and overlap each other
   instead of sitting side by side. Scoping the fixed layout to this shell-
   only class avoids that collision without touching the shared token class
   every other component depends on. */
.evsh-shell {
  position: fixed;
  inset-block: 0;
  left: 0;
  display: none;
  flex-direction: row;
  /* Figma `I4:3751;1:197` "Stack" — the wrapper around the rail AND the sidebar — is `py-[16px]`.
     Both children sit at y=16 h=868 in the 900-tall Nav Bar (`I695:45206;1:198` and `;1:199`).
     ⛔ IT WAS MISSING ENTIRELY, and the tell was in this stylesheet all along: `.evsh-sidebar`
     declares `border-radius: 20px` with `height: 100%` inside this fixed, inset-block:0 box — so
     its rounded corners were being cut flat against the viewport. Nobody specifies a radius on a
     corner that cannot be seen.
     ⚠️ `.evsh-rail`'s own `padding-bottom` MUST stay 0 while this exists, or the rail's bottom
     inset is applied twice. `navbar-figma.test.tsx`'s D5 GUARD enforces exactly-once. */
  padding: 16px 0;
  /* Figma `695:45206` "Nav Bar" — the instance that wraps the Stack — is `bg-white` (SOLID
     #FFFFFF, measured 2026-09-21). ⚠️ This cited `4:4652` when it landed earlier today; that id
     resolves in NONE of the four estate files (#961). So the 16px
     inset above and below the rail and the sidebar is WHITE, continuous with the rail, not a
     window onto the host page. Without this the host body colour (Coach `#e8eef3`, Edge
     `#f3f3f3`) showed through the inset, which read as a stray gap above the nav and left the
     sidebar's 20px corner sitting against page colour instead of white. Geometry is untouched:
     rail 64 @ x0/y16, sidebar 264 @ x64/y16, total width still 328. */
  background: var(--evsh-white);
}
/* evolve-suite#1078 — WHILE THE WORKSPACE MENU IS OPEN, the shell's layer rises above the page.

   `.evsh-shell` above is `position: fixed` with no z-index. A fixed box makes its own stacking
   context, so the menu's `z-index: 10` only ranks it among the shell's children, and the shell as a
   whole sits at level 0 of the page. The host's positioned boxes that come LATER in the DOM paint
   over all of it. Measured in Chromium, both builds:
   - on the App Selector, the menu floats over the page (x 16–294), and every row hit-tested to a
     positioned host box;
   - on the Nav Bar, the menu is 278 wide from x=64 (Figma), so it ends at x=342, 14px into the page.
     At 1024–1168px wide the Overview column starts at x=328, and its Period chip and chart owned
     that strip, the rows' last 2px included. A click there fired the chip and closed the menu.

   ⭐ ONLY WHILE THE MENU IS MOUNTED. At every other moment the shell keeps today's stacking, so no
   host page or dialog is re-layered. A global z-index was measured too and rejected: it re-stacks
   the shell in Suite, Coach and Edge, and it paints over Coach's full-screen Settings (z-110).

   WHY 15: above the Suite's in-page layers (z-0 / z-[1] / z-10), below its popovers (20), its
   dialogs (50), the shell's dialog overlays (100) and the toasts (1010). A host dialog that opens
   while the menu is open still covers it (measured at 50 / 100 / 110 / 200).

   ⚠️ THE ONE CASE IT CANNOT COVER: a host dialog mounted INSIDE a stacking context below 15 (say a
   `relative z-0` wrapper) that opens by itself while the menu is open. It would paint under the
   shell. A pointer-opened dialog cannot hit this: the click that opens it closes the menu first. */
.evsh-shell:has(.evsh-workspace-menu) {
  z-index: 15;
}
@media (min-width: 1024px) {
  .evsh-shell {
    display: flex;
  }
  /* SH-03: the toast stack is portalled into <body>, so it cannot see the host's <main> offset. While the shell
     is shown, centre it over the content column [SHELL_TOTAL_WIDTH, 100%] instead of the whole viewport: Figma
     centres every toast on 'Main Content' (1112 wide, right of the 328px shell), 164.5px off the screen centre.
     164px = SHELL_TOTAL_WIDTH / 2. Pages without the shell keep the viewport centre. The base rule sits LATER in
     this file, so this one wins on specificity (0,2,1 over 0,1,0), not on order. */
  body:has(.evsh-shell) .evsh-toast-container {
    left: calc(50% + 164px);
  }
  /* evolve-suite#1030 — the App Selector has no Nav Bar and centres its content in the FULL viewport,
     so its toasts go back to the viewport centre. Higher specificity than the SH-03 rule above
     ((0,3,1) over (0,2,1)), so it wins regardless of order. Inside THIS block because
     `toast-position-figma.test.ts` requires exactly one 1024px media block. */
  body:has(.evsh-shell.evsh-shell--app-selector) .evsh-toast-container {
    left: 50%;
  }
  /* The Switching Loader covers the whole screen, shell included, and the toast layer (1010) paints over it (1005).
     A toast raised while it is up — "[app] was added to your account." on the way into a product — is centred on
     the screen, like the loader's own tile and label, not on the content column the loader hides (Eli, 2026-10-05;
     Toast spec er8L2y3nj1 rev 16: "top center of the screen"). Measured in Chromium at 1440 wide before this rule:
     the toast sat 164px right of the screen centre. Same weight as the SH-03 rule ((0,2,1)), so it wins on ORDER:
     keep it after that rule (`toast-position-figma.test.ts` pins both). */
  body:has(.evsh-switching-loader) .evsh-toast-container {
    left: 50%;
  }
}

/* ==== App Selector chrome (evolve-suite#1030; spec ah3Bdrmp7i rev 17 "Page layout"; Figma 966-1201) ====
   `EvolveShell variant="app-selector"`: the 64px tool bar with only Support + Account, no Nav Bar, and
   the Workspace Dropdown FLOATING top-left. Figma puts its container at x=16 y=20, 245x54 — over the
   empty top of the tool-bar column, which the spec words as "offset from the toolbar"; Figma wins on
   position (verifier C4). Absolute inside `.evsh-shell` (position: fixed is its containing block), and
   positioned so WorkspaceMenu's own `position: absolute; top: calc(100% + 4px)` anchors under it
   exactly as it does under the Nav Bar's header row. */
.evsh-app-selector-workspace {
  position: absolute;
  top: 20px;
  left: 16px;
  width: 245px;
}

/* ==== Mobile chrome (evolve-suite#627) — the shared Tool Bar + Nav Bar below 1024px ====
   Figma Coach `d15UNnN4OKK64hgdshVD5D` Navigation › Mobile `2827:20678` (frames `2827:20187` personal,
   `2827:20359` team admin, `2827:20497` team member), and the Suite file `fYvRupTfIiOEBXYGOcvZZZ`'s
   app switcher (`430:10797`, `446:17157`) and workspace menu (`452:23888`) flows. Every value below
   was read with `get_design_context` on 2026-09-29 and is pinned by `mobile-bars-figma.test.tsx`.

   ⭐ SHADOWS ARE `--evsh-shadow-med`. The codegen writes the Nav Bar, FAB and app popover as
   `drop-shadow(0 4px 6px …)`, but the style it cites is `shadow/medium` — "DROP_SHADOW, offset
   (0, 4), radius 12" — which is this token's `box-shadow: 0 4px 12px`. A `filter: drop-shadow()`
   blur is a standard deviation (half the box-shadow blur), so `6px` there IS `12px` here: one
   effect, two notations. The workspace panels are emitted as `shadow-[0px_4px_12px]` directly.

   ⭐ OFFSETS FOR THE HOST. Both bars are `position: fixed` and paint OVER the page, like the desktop
   chrome. A host reserves their space with the two properties published on `:root` below —
   `padding-top: var(--evsh-mobile-top-offset, 0px); padding-bottom: var(--evsh-mobile-bottom-offset, 0px)`
   on its <main> — which are 0px unless that bar is actually on screen (they key off the bar's own
   presence, so `hideTopBar` and the App Selector's missing Nav Bar are already accounted for). */
:root {
  --evsh-mobile-top-offset: 0px;
  --evsh-mobile-bottom-offset: 0px;
}
/* The wrapper `EvolveShell` renders around the two bars exists only as a query root for its focus
   restore; it must not create a box. */
.evsh-mobile-chrome {
  display: contents;
}

/* ---- Tool Bar — `2827:20202`: white, px-12 py-4, gap 12, 430x48, 1px neutral-blue-4 bottom rule. */
.evsh-mobile-top-bar {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  /* 15: the same tier `.evsh-shell` takes while its workspace menu is open — above the Suite's
     in-page layers (0 / 1 / 10), below its popovers (20), dialogs (50) and the shell's own
     overlays (100). A bar that scrolls content under it has to sit above that content always.
     While one of its menus is open it rises further (the `:has()` rule below). */
  z-index: 15;
  display: none;
  align-items: center;
  gap: 12px;
  box-sizing: border-box;
  /* The top safe-area inset is ADDED above Figma's 48 (the frame draws the status bar above the
     Tool Bar, at y 1-63); it is 0 unless the host opts into `viewport-fit=cover`. */
  height: calc(48px + env(safe-area-inset-top));
  padding: calc(4px + env(safe-area-inset-top)) 12px 4px;
  background: var(--evsh-white);
  /* Figma's `border-b` is an INSIDE stroke (the bar stays 48 with a 40px Logo Container at y 4-44),
     so it is painted as an inset shadow: a real border would eat 1px of the 40. */
  box-shadow: inset 0 -1px 0 var(--evsh-neutral-blue-4);
}
/* #627 review — WHILE ONE OF ITS MENUS IS OPEN, the Tool Bar's layer rises above the host's.
   The bar is `position: fixed` with `z-index: 15`, so it is a stacking context and the switcher and
   workspace panel inside it can rank no higher than 15 against the page, whatever their own
   z-index. A host layer above 15 paints over them and takes their clicks. Measured in Chromium
   2026-09-29 with Coach's own mobile call-detail toolbar (`max-lg:sticky top-0 z-20`,
   `mobile-detail-toolbar.tsx`): the sticky box intercepted the press on level 1's Workspace
   Dropdown.
   ⭐ THE DESKTOP'S APPROACH, NOT A PORTAL: `.evsh-shell:has(.evsh-workspace-menu)` above lifts the
   shell only while its menu is mounted, for the same reason. A portal would detach the cards from
   the bar they are positioned against (`Popover`'s own docblock declines it for that reason), and
   lifting only while open leaves every other moment's stacking as it was.
   WHY 60 (the desktop's 15 is already this bar's resting tier): above every in-page and floating
   layer the hosts use on mobile — Suite ≤ 10 in-page and 20 for its popovers; Coach ≤ 40 in-page
   (the z-20 toolbar, z-30/40 chart and player layers) and 50 for its menus and the floating video
   player, which sits top-right over where the workspace panel opens — and below the support
   launcher/panel (90), the shell's dialog overlays (100), Coach's full-screen Settings (110) and
   dialogs (200+), and the toasts (1010). A host menu cannot be open at the same time: the press that
   opens this one is an outside press to it. Not the Nav Bar: it opens nothing. */
.evsh-mobile-top-bar:has(.evsh-mobile-apps-menu, .evsh-mobile-workspace-panel) {
  z-index: 60;
}
/* Suite `987:9725` — the App Selector's Tool Bar draws no bottom rule. */
.evsh-mobile-top-bar--app-selector {
  box-shadow: none;
}
/* "Tool Bar | Item" with its label hidden — `I2827:20202;152:1906`: 36x36, p-2, radius 12, holding a
   32px Icon Container (radius 9.6) with a 20px glyph. The exports stroke #00202F. */
.evsh-mobile-tool-btn {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  box-sizing: border-box;
  padding: 2px;
  border: none;
  border-radius: 12px;
  background: none;
  color: var(--evsh-neutral-1);
  font: inherit;
  cursor: pointer;
}
.evsh-mobile-tool-btn-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  box-sizing: border-box;
  border-radius: 9.6px;
}
/* The same component's Hovered and Selected states the rail's More/Support tiles already carry
   (`332:85233` hover: a 1px background/light-blue border on the Icon Container; `1:266` selected:
   Icon Container #F1F5F8). Selected = its menu is open. */
.evsh-mobile-tool-btn:hover .evsh-mobile-tool-btn-icon {
  border: 1px solid var(--evsh-bg-light-blue);
}
.evsh-mobile-tool-btn--open .evsh-mobile-tool-btn-icon {
  background: var(--evsh-neutral-blue-4);
}
/* Logo Container — `I2827:20202;152:1742`: 40 tall, gap 12, a 37.333px mark then the product name. */
.evsh-mobile-logo {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  gap: 12px;
  height: 40px;
  /* The mark's FILL (`currentColor`), keyed by product like the rail's tiles. The base is the
     Suite's #00202F (`I459:25843;152:1743`), which is also what an unknown product gets. */
  color: var(--evsh-neutral-1);
}
.evsh-mobile-logo--coach {
  color: var(--evsh-blue-2);
}
.evsh-mobile-logo--edge {
  color: var(--evsh-gold-2);
}
.evsh-mobile-logo-mark {
  flex-shrink: 0;
  display: block;
}
/* `I2827:20202;152:1754`: Lato Bold 16.579/17.684, tracking 1.0611, UPPERCASE, #090B0F. The odd
   decimals are Figma's own (the logo lockup is scaled as a unit); not rounded. */
.evsh-mobile-logo-name {
  font-family: var(--evsh-font);
  font-size: 16.579px;
  line-height: 17.684px;
  font-weight: 700;
  letter-spacing: 1.0611px;
  text-transform: uppercase;
  color: var(--evsh-logo-ink);
  white-space: nowrap;
}
/* Actions — `I2827:20202;152:1755`: fills the rest, right-aligned, gap 8. */
.evsh-mobile-tool-actions {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 8px;
}
/* Workspace — `I2827:20202;152:1795`: 48 wide, px-8, radius 18, holding the 32px workspace mark that
   `.evsh-workspace-menu-mark` already draws (the same slot, measured at 32 / radius 9.6). */
.evsh-mobile-workspace-btn {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 48px;
  height: 32px;
  box-sizing: border-box;
  padding: 0 8px;
  border: none;
  border-radius: 18px;
  background: none;
  font: inherit;
  cursor: pointer;
}

/* ---- App switcher — Suite Popover `430:10897` / `430:10863`: x=12, y=113 in the frame, i.e. 2px
   under the 48px Tool Bar (at y 63), left-aligned with the More Apps button. Absolute against the
   fixed Tool Bar, so it scrolls with nothing. Radius 32 and padding 16 are `.evsh-popover`'s own,
   measured identical here, and the 294 width is `.evsh-popover.evsh-more-apps-menu`'s (the card
   carries both classes). One class is enough for top/left: `.evsh-popover` sets neither, and
   `position="none"` adds no side class that would.
   ⭐ CAPPED TO THE ROOM BELOW THE TOOL BAR, AND SCROLLS INSIDE (#627 review). The card hangs off a
   FIXED bar, so a list taller than the viewport cannot be reached by scrolling the page: on a
   landscape phone (844x390) the Suite, three apps and a More section already overflow. The cap is
   the viewport minus the card's top (the bar's 48 + the 2px gap + the top inset) minus a 16px bottom
   margin — the panel's own edge inset — so 66px plus the inset. `vh` here, `dvh` in the
   `@supports` block after the workspace panel (why a block and not a second line is explained
   there). Figma draws no scroll state; the card keeps its radius, padding and shadow, and the bar
   stays hidden as on the desktop workspace list (#1027) — the cut row is the cue. Measured in
   Chromium 2026-09-29 at 844x390: the card stops at y 374 (390 - 16) and its last row scrolls into
   reach; at 430x932 it is its natural 416 tall, uncapped. */
.evsh-mobile-apps-menu {
  top: calc(env(safe-area-inset-top) + 50px);
  left: 12px;
  max-height: calc(100vh - env(safe-area-inset-top) - 66px);
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: none;
}
/* The rows are the desktop More menu's (`.evsh-more-apps-item`: 50 tall, p-8, gap 12, radius 12,
   hover #F6F9FA). Two need an anchor reset, since primary rows are links like the rail's tiles. */
.evsh-mobile-apps-item {
  text-decoration: none;
  color: inherit;
}
/* "Tool Bar | More Menu Item" State=Selected — `I430:10863;146:89;430:10858`: #F1F5F8. Held on
   hover too, as the rail's selected tile holds its fill. */
.evsh-mobile-apps-item--selected,
.evsh-mobile-apps-item--selected:hover {
  background: var(--evsh-neutral-blue-4);
}
/* The rail's disabled tile (`I4:3736;1:198;1:189`) dims icon and label to 0.5; the row does the same. */
.evsh-mobile-apps-item--disabled {
  cursor: default;
  opacity: 0.5;
}
.evsh-mobile-apps-item--disabled:hover {
  background: none;
}
/* "Icon R" — the export strokes #A2B0B7, which is `--evsh-neutral-blue-2`. */
.evsh-mobile-apps-item-chevron {
  flex-shrink: 0;
  color: var(--evsh-neutral-blue-2);
}
/* The selected row's check — the export strokes #00202F. */
.evsh-mobile-apps-item-check {
  flex-shrink: 0;
  color: var(--evsh-neutral-1);
}
/* "Overline Container" — `I430:10863;146:89;430:10860`: pt-16 px-8, Lato Black 12/18 #666 (the type
   is `.evsh-subtitle-4`). NOT `.evsh-more-apps-heading`, whose `8px 8px 4px` belongs to the desktop
   menu's first line; this one separates two groups. */
.evsh-mobile-apps-overline {
  padding: 16px 8px 0;
  color: var(--evsh-neutral-2);
}

/* ---- Workspace panel — Suite `452:24791` / `452:24738` (level 1, 278 wide) and `452:24773`
   (level 2, 254 wide): both right-aligned 16px from the screen edge (x 136 + 278 = 414 = 430 - 16;
   x 160 + 254 = 414) and 2px under the Tool Bar (y 113). */
.evsh-mobile-workspace-panel {
  position: absolute;
  top: calc(env(safe-area-inset-top) + 50px);
  right: 16px;
  z-index: 10;
}
/* Level 1 — p-12, radius 24, gap 8, `shadow-[0px_4px_12px]`. Capped and scrolling exactly as the
   app switcher is (see `.evsh-mobile-apps-menu`): both levels hang off the fixed Tool Bar. The cap
   sits on each level's CARD, not on the panel around them, so the cards' shadows are not clipped. */
.evsh-mobile-workspace-summary {
  box-sizing: border-box;
  width: 278px;
  max-height: calc(100vh - env(safe-area-inset-top) - 66px);
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: none;
  padding: 12px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  background: var(--evsh-white);
  border-radius: 24px;
  box-shadow: var(--evsh-shadow-med);
}
/* The Workspace Dropdown's MOBILE breakpoint (`706:68424`) is the desktop selector raised onto a
   white card with the medium shadow — padding 8/12/8/8, radius 18, gap 12 and the 36px mark are
   `.evsh-sidebar-workspace-btn`'s own. */
.evsh-mobile-workspace-summary .evsh-sidebar-workspace-btn {
  background: var(--evsh-white);
  box-shadow: var(--evsh-shadow-med);
}
.evsh-mobile-workspace-summary .evsh-sidebar-workspace-btn:hover {
  background: var(--evsh-neutral-blue-4);
}
/* "<Credits> | Billing Section" — `I452:24791;156:2751`: pt-10 px-8 on no card of its own, Usage
   Info pb-12, 12 between the line and the bar, and NO Add credits button in any mobile frame (the
   line's own ">" is the billing entry, as it already is on desktop). */
.evsh-mobile-workspace-credits .evsh-credits {
  width: auto;
  padding: 10px 8px 0;
  background: none;
  border-radius: 0;
}
.evsh-mobile-workspace-credits .evsh-credits-summary {
  padding-bottom: 12px;
  gap: 12px;
}
.evsh-mobile-workspace-credits .evsh-credits-add {
  display: none;
}
/* "Manage my account" — `I452:24791;156:2754`: pl-8 pr-12, gap 8, radius 18, a 32px icon box, Lato
   Medium 14/18 #666 (`.evsh-body-3`) and a 12px arrow. Hover is the menu rows' #F6F9FA (`1:412`). */
.evsh-mobile-account-row {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  box-sizing: border-box;
  padding: 0 12px 0 8px;
  border: none;
  border-radius: 18px;
  background: none;
  font: inherit;
  color: var(--evsh-neutral-2);
  text-align: left;
  cursor: pointer;
}
.evsh-mobile-account-row:hover {
  background: var(--evsh-neutral-blue-5);
}
.evsh-mobile-account-row-icon {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
}
.evsh-mobile-account-row-label {
  flex: 1 1 0;
  min-width: 0;
}
.evsh-mobile-account-row-arrow {
  flex-shrink: 0;
  display: flex;
}
/* Level 2 is the desktop `WorkspaceMenu`, in flow inside the panel and at the mobile component's
   254 (`891:19248`) instead of desktop's 278. */
.evsh-mobile-workspace-panel .evsh-workspace-menu {
  position: static;
  width: 254px;
}
/* Level 2's cap goes on its workspace LIST, not on the card: the card's "In {email}" Tooltip floats
   ABOVE the overline, and an overflowing card would clip it. The list already scrolls, and keeps
   Create on screen, under the desktop cap (`.evsh-workspace-menu-list`) — but that cap assumes the
   desktop panel's top (102px) and `100vh`, which on a phone is the LARGE viewport, taller than the
   visible one while the browser's toolbars show. So the same derivation, re-based:
     the room = 100dvh - the top inset - 205px, where 205 = 50 (the card's top) + 139 (this level's
     own chrome — overline row with Back, divider, Create row, the gaps and padding — measured in
     Chromium 2026-09-29 as the menu's height minus its list's) + the 16px bottom margin;
   snapped DOWN to whole 62px rows plus the 27px half row, and never above the desktop's 337. That
   formula is in the `@supports` block below; this base line is for engines without `round()`
   (Chromium <125), which get the room with no half-row snap. */
.evsh-mobile-workspace-panel .evsh-workspace-menu-list {
  max-height: calc(100vh - env(safe-area-inset-top) - 205px);
}
/* `scrollbar-width` for engines older than it (Chromium <121, Safari <18.2) — `.evsh-workspace-menu-list`'s pair. */
.evsh-mobile-apps-menu::-webkit-scrollbar,
.evsh-mobile-workspace-summary::-webkit-scrollbar {
  display: none;
}
/* The `dvh` caps. `dvh` follows the VISIBLE viewport as a phone browser's toolbars show and hide;
   `vh` is the large viewport, taller than what is visible while they show.
   ⛔ AN @SUPPORTS BLOCK, NOT A SECOND `max-height` LINE. Every value here contains `env()`, and a
   declaration with `env()` or `var()` is accepted at PARSE time whatever else it holds — so an
   engine without `dvh` (or `round()`) does not skip the line and keep the `vh` one before it; it
   keeps this line and resolves it to `none`, the property's initial value, i.e. NO cap at all.
   Measured in Chromium 2026-09-29 with an unknown unit: `max-height: calc(100vh - 10px);
   max-height: calc(100zzz - 10px)` resolves to 390px, the same pair with `- env(safe-area-inset-top)`
   added resolves to `none`. */
@supports (height: 100dvh) {
  .evsh-mobile-apps-menu {
    max-height: calc(100dvh - env(safe-area-inset-top) - 66px);
  }
  .evsh-mobile-workspace-summary {
    max-height: calc(100dvh - env(safe-area-inset-top) - 66px);
  }
}
@supports (height: round(down, 100dvh, 62px)) {
  .evsh-mobile-workspace-panel .evsh-workspace-menu-list {
    max-height: min(337px, calc(round(down, 100dvh - env(safe-area-inset-top) - 205px - 27px, 62px) + 27px));
  }
}
/* Its overline row with Back — `891:19257`: gap 8, Back 20 · centred text · a 20px "Empty". */
.evsh-workspace-menu-overline-row {
  display: flex;
  align-items: center;
  gap: 8px;
}
.evsh-workspace-menu-overline-row .evsh-workspace-menu-overline-tip {
  flex: 1 1 0;
  justify-content: center;
}
.evsh-workspace-menu-overline-row .evsh-workspace-menu-overline {
  padding: 0;
  text-align: center;
}
/* A 24px hit target around Figma's 20px Back, with the extra 2px taken back by the margin so the row
   keeps Figma's geometry (WCAG 2.2 2.5.8 asks for 24). */
.evsh-workspace-menu-back {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  margin: -2px;
  padding: 0;
  border: none;
  border-radius: 6px;
  background: none;
  color: var(--evsh-neutral-1);
  cursor: pointer;
}
.evsh-workspace-menu-overline-spacer {
  flex-shrink: 0;
  width: 20px;
  height: 20px;
}

/* ---- Nav Bar — `2827:20195`: white, `shadow/medium`, px-42 py-8, a row of tabs sharing the width
   equally, then Figma's 16px "iOS Bottom Padding" (`152:2429`) and the 8px bottom padding.
   ⭐ THE iOS PADDING BECOMES THE SAFE-AREA INSET. Figma's frame is an iPhone with a 34px Home
   Indicator (`2827:20246`, y 898-932) and the tabs end at y 900 — the bar's 32px below them (gap 8 +
   16 + padding 8) is the inset, drawn. So the bottom padding is `max(8px, inset)`: on a notched
   iPhone the tabs clear the indicator exactly (34 vs Figma's 32), and a device with no inset keeps
   the frame's own 8. Adding the inset ON TOP of 32 would float the tabs 42px above the edge. */
.evsh-mobile-bottom-bar {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 15;
  display: none;
  box-sizing: border-box;
  padding: 8px 42px max(8px, env(safe-area-inset-bottom));
  background: var(--evsh-white);
  box-shadow: var(--evsh-shadow-med);
}
.evsh-mobile-nav-list {
  display: flex;
  align-items: center;
}
/* "Nav Bar | Item (Mobile Only)" — `I2827:20195;152:2356`: flex 1, 42 tall, px-16, gap 2, radius 14,
   a 20px icon over a Lato Medium 12/18 label (`.evsh-body-4`). Inactive #00202F. */
.evsh-mobile-nav-item {
  flex: 1 1 0;
  min-width: 0;
  height: 42px;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  padding: 0 16px;
  border: none;
  border-radius: var(--evsh-radius-item);
  background: none;
  font: inherit;
  color: var(--evsh-neutral-1);
  text-decoration: none;
  cursor: pointer;
}
/* Active — the Home tab `I2827:20195;152:2356`: label AND icon #196BA0 (the export strokes the
   house #196BA0), no fill. */
.evsh-mobile-nav-item--active {
  color: var(--evsh-blue-0);
}
.evsh-mobile-nav-item-icon {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
}
.evsh-mobile-nav-item-label {
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.evsh-mobile-nav-item--skeleton,
.evsh-mobile-nav-item--disabled {
  cursor: default;
}
/* The wizard's skeleton tab — Suite `I459:28802;152:2356`: a 14px radius-4 square in the 20px slot
   and a 29x4 radius-16 bar in an 18px line (`py-7`), both neutral-blue-2 at 50%. The same
   placeholder language as `.evsh-nav-item--skeleton`, at the tab's own sizes. */
.evsh-mobile-nav-item--skeleton .evsh-mobile-nav-item-icon::before {
  content: '';
  display: block;
  width: 14px;
  height: 14px;
  border-radius: 4px;
  background: var(--evsh-neutral-blue-2);
  opacity: 0.5;
}
.evsh-mobile-nav-item--skeleton .evsh-mobile-nav-item-label {
  display: flex;
  align-items: center;
  height: 18px;
}
.evsh-mobile-nav-item--skeleton .evsh-mobile-nav-item-label::before {
  content: '';
  display: block;
  width: 29px;
  height: 4px;
  border-radius: 16px;
  background: var(--evsh-neutral-blue-2);
  opacity: 0.5;
}
/* No mobile disabled frame exists; the sidebar's measured disabled colour (D32) is reused. */
.evsh-mobile-nav-item--disabled {
  color: var(--evsh-neutral-blue-1);
}
/* FAB — `I2827:20195;199:3429`: neutral-1 pill, radius 42, padding 10/18, gap 8, `shadow/medium`,
   16px above the Nav Bar's top edge (y -56, 40 tall) and 24px from the right (x 301 + 105 = 406). */
.evsh-mobile-fab {
  position: absolute;
  right: 24px;
  bottom: calc(100% + 16px);
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 18px;
  border: none;
  border-radius: 42px;
  background: var(--evsh-neutral-1);
  color: var(--evsh-white);
  box-shadow: var(--evsh-shadow-med);
  font: inherit;
  cursor: pointer;
}
/* "Left Icon Wrapper" `93:2` is 16x20 with its 20px slot pinned right, so the icon overhangs 4px
   toward the pill's edge — see `MobileFab`. */
.evsh-mobile-fab-icon-wrap {
  position: relative;
  flex-shrink: 0;
  width: 16px;
  height: 20px;
}
.evsh-mobile-fab-icon {
  position: absolute;
  top: 0;
  right: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
}
/* `91:5`: Lato SemiBold 15/20, white. */
.evsh-mobile-fab-label {
  font-size: 15px;
  line-height: 20px;
  font-weight: 600;
  white-space: nowrap;
}

/* THE MOBILE LINE: exactly the complement of `.evsh-shell`'s `min-width: 1024px`, so at every width
   exactly one chrome is on screen. The offsets turn on with the bar they describe. */
@media (max-width: 1023px) {
  .evsh-mobile-top-bar {
    display: flex;
  }
  .evsh-mobile-bottom-bar {
    display: block;
  }
  /* Supported-browser floor for these offsets (and the support launcher's `body:has()` lift): `:has()` — Chrome/Edge 105, Safari 15.4, Firefox 121; older engines get 0px offsets, by decision no fallback. */
  :root:has(.evsh-mobile-top-bar) {
    --evsh-mobile-top-offset: calc(48px + env(safe-area-inset-top));
  }
  :root:has(.evsh-mobile-bottom-bar) {
    --evsh-mobile-bottom-offset: calc(50px + max(8px, env(safe-area-inset-bottom)));
  }
}

/* ============ Button (wiki: Buttons W0lmLlXTqx · Figma 87:6) ============ */
.evsh-btn {
  display: inline-flex;
  box-sizing: border-box;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 7px 12px;          /* 7px block + 20px line = the 34px height in Figma */
  border: none;
  border-radius: 10px;
  background: none;
  font-family: var(--evsh-font);
  font-size: 15px;
  line-height: 20px;
  font-weight: 600;           /* Lato SemiBold */
  cursor: pointer;
}
/* Icon slots reproduce the wiki's mask-frame geometry: a 16px-wide frame holding the consumer's
   icon, so the icon lands 8px from the button edge rather than the 12px the padding alone would
   give.

   ⛔ CORRECTED (`evolve-suite#667`). This comment used to say the frame "does NOT clip, holding a
   20px icon that overflows it". That overflow HAS NEVER OCCURRED: the shipped svg computes to 16
   because it is sized by this frame, and no overflow is produced. It was a hypothesis written in
   the grammar of a measurement, which is the worst kind — it reads as something someone checked.

   ⭐ AND THE 16 IS THE RIGHT NUMBER, RULED `evolve-suite#667`. The Members mock draws its buttons
   at a 20px icon frame, which looks like Figma contradicting the Buttons spec's 16. It does not:
   every node in both chains (`4:7455`-`4:7458`, `4:6881`-`4:6883`) is a plain FRAME/GROUP with
   `componentId` null — measured over the REST API — so those buttons were drawn LOCALLY in the
   mock and were never placed as instances of this component. A mock that does not use the
   component is weak evidence about the class backing every button in the Suite, whereas the
   Buttons spec is *about* that component. ⚠️ The rendered inset is 8px either way, so nothing a
   user sees was ever wrong; widening the frame to 20 would restyle every button in the Suite for
   no visible gain. The Buttons spec expresses this as slot offsets inside
   the mask frame (left x = -4, right x = 0) measured from opposite edges. Implemented here as a
   symmetric -4px outer margin on each side: with 12px padding and a 16px frame, 12 - 4 = 8px from
   the button edge on both sides, which is the inset the spec actually specifies. Do NOT change
   the right margin to 0 — that would put the right icon at 12px and miss the spec. Consumers
   supply a 20px icon; a different size still sits in the 16px column but will not hit the 8px
   inset. */
.evsh-btn-icon {
  display: inline-flex;
  align-items: center;
  flex-shrink: 0;
  width: 16px;
  overflow: visible;
}
.evsh-btn-icon--left { justify-content: flex-start; margin-left: -4px; }
.evsh-btn-icon--right { justify-content: flex-end; margin-right: -4px; }
.evsh-btn-label {
  white-space: nowrap;
}

/* A disabled button must not advertise interactivity. Browsers do NOT dim a button that has an
   author-set background-color, and :hover is hit-test driven so it still matches while disabled —
   without these two rules the disabled sign-in button shows a hand cursor and lightens under the
   pointer. This removes false affordance rather than introducing a disabled visual, so it does not
   add the disabled variant #570 forbids. The genuine disabled appearance is a queued spec gap. */
.evsh-btn:disabled {
  cursor: default;
}
.evsh-btn--primary:disabled:hover { background: var(--evsh-neutral-1); }
.evsh-btn--secondary:disabled:hover { background: var(--evsh-bg-light-blue); }
.evsh-btn--accent:disabled:hover { background: var(--evsh-blue-1); }
.evsh-btn--outline:disabled:hover { background: none; }

/* primary — hover dims the BACKGROUND only; the label stays solid white */
.evsh-btn--primary { background: var(--evsh-neutral-1); color: var(--evsh-white); }
.evsh-btn--primary:hover { background: rgba(0, 32, 47, 0.8); }
/* :active deliberately absent — spec: Pressed matches Default for this variant */

.evsh-btn--secondary { background: var(--evsh-bg-light-blue); color: var(--evsh-neutral-1); }
.evsh-btn--secondary:hover { background: rgba(232, 238, 243, 0.85); }
/* :active deliberately absent — spec: Pressed matches Default */

.evsh-btn--ghost {
  background: none;
  color: var(--evsh-neutral-1);
  padding-left: 4px;
  padding-right: 4px;
  border-radius: 0;
  /* evolve-suite#823 — Figma `1245:12053` (REST) declares itemSpacing 4, not the 8 every other
     variant uses. Ghost-scoped deliberately: `gap: 8px` on `.evsh-btn` is correct for the rest. */
  gap: 4px;
}
/* #823, the other half. The base rule's `-4px` exists to pull the icon to the spec's 8px inset
   against 12px padding — correct for every filled variant, and the PRIMARY control in the probe
   measures exactly 8. Ghost overrides padding to 4px, where the same -4px drives the inset to
   **0** and the icon sits flush to the button edge. Figma gives it paddingRight 4, so ghost
   zeroes the pull instead of inheriting it.

   ⭐ BOTH HALVES OR NEITHER, AND THE TOTAL IS WHY. Measured in Chromium over file://, before:
   gap 8, inset 0, total 205.61. Changing only the gap would have shortened the button by 4px and
   broken a total that was already within 0.8 of Figma's 194 (that 0.8 is label text rendering,
   which no CSS change addresses). Moving the 4px rather than removing it keeps the total at
   205.61 and puts the gap where Figma draws it.

   ⚠️ `--left` is NOT given the same treatment: no frame draws a ghost button with a left icon and
   no call site renders one, so there is no measurement to implement. Measure it before adding it. */
.evsh-btn--ghost .evsh-btn-icon--right { margin-right: 0; }
/* ghost has NO hover and NO pressed treatment — the spec states "no visible change in fill"
   for both. Reported as a spec gap; not invented here. */

.evsh-btn--accent { background: var(--evsh-blue-1); color: var(--evsh-white); }
.evsh-btn--accent:hover { background: rgba(30, 127, 189, 0.8); }
.evsh-btn--accent:active { background: var(--evsh-blue-0); }  /* #196ba0 — the ONLY filled
                                                                  variant whose Pressed differs */

.evsh-btn--outline {
  background: none;
  color: var(--evsh-neutral-1);
  box-shadow: inset 0 0 0 1px var(--evsh-neutral-3, #b2b2b2);   /* 1px INSIDE stroke, so it doesn't affect layout */
}
.evsh-btn--outline:hover { background: rgba(232, 238, 243, 0.3); }
.evsh-btn--outline:active { background: rgba(232, 238, 243, 0.5); }

/* ============ Popover (wiki: Popover 7VOVWJ93ml · Figma 1:1225) ============ */
.evsh-popover {
  position: absolute;
  z-index: 20;                        /* ABOVE the workspace/more-apps menus (both 10) and below
                                         the dialogs (100). A popover may legitimately be opened
                                         from inside a menu, so tying with them at 10 would leave
                                         paint order decided by DOM order. */
  box-sizing: border-box;             /* 251px is Figma's OUTER width — without this the
                                         16px padding would make it render 283px */
  width: 251px;
  padding: 16px;
  border-radius: 32px;                /* Figma 1:1225 popover card — off-scale, verified via design context */
  background: var(--evsh-white);
  box-shadow: var(--evsh-shadow-med);
  display: flex;
  flex-direction: column;
}
.evsh-popover-details {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 8px;
  width: 100%;
}
.evsh-popover-title-row {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  width: 100%;
}
.evsh-popover-icon {
  display: inline-flex;
  flex-shrink: 0;
}
.evsh-popover-title {
  flex: 1 0 0;
  font-size: 16px;        /* Figma style `subtitle/subtitle-1` */
  line-height: 24px;
  font-weight: 600;       /* Lato SemiBold */
  color: var(--evsh-neutral-1);
  word-break: break-word;
}
.evsh-popover-content {
  width: 100%;
  font-size: 14px;        /* Figma style `body/body-3` */
  line-height: 18px;
  font-weight: 500;
  color: var(--evsh-neutral-2);
  word-break: break-word;
}
/* Caret: 14x8, colour inherited from the card so it always matches the white surface. */
.evsh-popover-caret {
  position: absolute;
  width: 14px;
  height: 8px;
  color: var(--evsh-white);
  line-height: 0;
}
/* `position` names the edge the CARET sits on, matching Figma's variant names. A caret on the
   bottom edge therefore means the surface floats ABOVE its trigger. The 12px gap is Figma's:
   the tooltip popover sits at bottom:30px against an 18px trigger. */
.evsh-popover--bottom {
  bottom: calc(100% + 12px);
  left: 50%;
  transform: translateX(-50%);
}
.evsh-popover--bottom .evsh-popover-caret { bottom: -8px; left: calc(50% - 7px); }

.evsh-popover--top {
  top: calc(100% + 12px);
  left: 50%;
  transform: translateX(-50%);
}
.evsh-popover--top .evsh-popover-caret { top: -8px; left: calc(50% - 7px); transform: rotate(180deg); }

.evsh-popover--right {
  right: calc(100% + 12px);
  top: 50%;
  transform: translateY(-50%);
}
.evsh-popover--right .evsh-popover-caret { right: -11px; top: calc(50% - 4px); transform: rotate(270deg); }

.evsh-popover--left {
  left: calc(100% + 12px);
  top: 50%;
  transform: translateY(-50%);
}
.evsh-popover--left .evsh-popover-caret { left: -11px; top: calc(50% - 4px); transform: rotate(90deg); }

/* `none` carries no caret and no offset — the consumer positions it. It is still
   `position: absolute`, so without a caller-supplied offset it sits at its static position.
   Consumers using `none` are expected to position it themselves. */

/* Anchor for a standalone Popover. The spec requires the surface be "anchored to the trigger"
   and #572's acceptance says "Popovers remain anchored to their triggers" — but the component
   positions against its nearest POSITIONED ancestor, which is only the trigger if something
   makes it one. Tooltip gets this from `.evsh-tooltip`; every other consumer must wrap the
   trigger and the popover in this. Without it the popover anchors to whatever arbitrary
   positioned ancestor happens to exist, or to the initial containing block. */
/* ============ Tooltip (wiki: Tooltip 6uEhX8qhTn · Figma 1:1254) ============ */
.evsh-popover-anchor,
.evsh-tooltip {
  position: relative;
  display: inline-flex;
}
.evsh-tooltip-trigger {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  padding: 0;
  border: none;
  background: none;
  /* evolve-suite#483 — `netural blue/neutral blue 2` #a2b0b7, MEASURED on the two frames that
     actually draw this glyph in `fYvRupTfIiOEBXYGOcvZZZ`: the pack card's tooltip (`4:3920`,
     vector `I4:3951;4:804;1:1260`) and the post-purchase balance row's (`4:4110`, vector
     `I4:4115;1:1260`). Both carry ONE opaque SOLID paint bound to the same Figma variable
     `…/5349:9` — a Figma VARIABLE id, not a node id, so a `/nodes` lookup returns null for it by
     construction and a citation sweep must not read that as a dead reference (#961) — nothing
     translucent stacked over it, and the 18x18 "Bounding box" rect sharing
     that group is `isMask: true`, not a second fill. Those two are the only production callers:
     `<Tooltip` appears in exactly `pack-card.tsx` and `step-balance-view.tsx` outside tests.
     ⛔ This was `--evsh-blue-2` (#2b89c5), commented "the info glyph fill in Figma". That
     citation named no node, and neither frame carries that colour anywhere — the icon shipped as
     a saturated blue dot beside grey type. Reported by the owner on #483. */
  color: var(--evsh-neutral-blue-2);
  cursor: pointer;
  line-height: 0;
}
/* evolve-suite#1049 — the variant where the trigger IS the content (Tooltip's `trigger` prop),
   rather than this component's own 18x18 ⓘ glyph. Every dimension and colour above is specific to
   that glyph, so each is handed back to the supplied content: the button must be able to shrink
   below its text for `text-overflow: ellipsis` to have anything to do. */
.evsh-tooltip-trigger--content {
  display: block;
  width: auto;
  height: auto;
  min-width: 0;
  max-width: 100%;
  text-align: left;
  font: inherit;
  color: inherit;
  line-height: inherit;
}

/* ============ Switch (wiki: Switch iKqkaJT0mp · Figma 1:932) ============ */
/* The wiki spec carries no numeric values whatsoever — every dimension and colour below was read
   from the Figma nodes (1:933 off, 1:936 on), which export as a flattened SVG. The two track
   colours ARE bound to Figma variables — `netural blue/neutral blue 2` and `green/green 3` (see
   the token block above) — so `--evsh-switch-off`/`--evsh-switch-on` now resolve to the DS-named
   `--evsh-neutral-blue-2`/`--evsh-green-3` tokens. The `--evsh-switch-*` names are kept
   deliberately rather than renamed to match: they're already declared on `.evsh-root`, and
   renaming them would break any consumer who overrode one. */
.evsh-switch {
  position: relative;
  display: inline-block;
  box-sizing: border-box;
  width: 48px;
  height: 26px;
  padding: 0;
  border: none;
  border-radius: 13px;          /* height / 2 — a pill */
  background: var(--evsh-switch-off); /* off track, Figma 1:933 */
  cursor: pointer;
}
.evsh-switch--on {
  background: var(--evsh-switch-on);  /* on track, Figma 1:936 */
}
.evsh-switch-knob {
  position: absolute;
  top: 2px;
  left: 2px;                    /* 26 - 22 = 4, so a 2px inset on every side */
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: var(--evsh-white);
  box-shadow: var(--evsh-shadow-med);
}
.evsh-switch--on .evsh-switch-knob {
  left: 24px;                   /* 48 - 22 - 2 — flush against the right inset */
}

/* ============ Toggle — two-option view switcher (wiki: Toggle cXP03wg52B · Figma 1:503) ======== */
.evsh-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  padding: 4px;
  border-radius: 24px;
  background: var(--evsh-neutral-blue-4);
}
.evsh-toggle-option {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  padding: 3px 12px;             /* 3 + 18 line + 3 = 24px tall; track adds 4+4 = 32px */
  border: none;
  border-radius: 24px;
  background: none;              /* unselected, not hovered: no fill, no shadow */
  font-family: var(--evsh-font);
  font-size: 14px;               /* 14px HERE — Dropdown/MenuItem/Checkbox/Radio are 15px */
  line-height: 18px;
  font-weight: 500;
  color: var(--evsh-neutral-1);  /* unchanged in every state */
  white-space: nowrap;
  cursor: pointer;
}
.evsh-toggle-option:hover {
  background: var(--evsh-bg-light-blue);
}
.evsh-toggle-option--selected {
  background: var(--evsh-white);
  box-shadow: var(--evsh-shadow-med);
}
/* Hovering the SELECTED option shifts its fill but keeps the shadow. Declared after the plain
   hover so it wins on source order at equal specificity. */
.evsh-toggle-option--selected:hover {
  background: var(--evsh-neutral-blue-5);
  box-shadow: var(--evsh-shadow-med);
}

/* ====== Checkbox and Radio (wiki: h4WMkX5pki / n3ZHKggSHg · Figma 1:1011 / 1:1003) ======
   Shared row, shared label/description block — the two differ only in the control's shape and
   its checked mark. Every value here is from Figma; the wiki gives only the two font sizes. */
.evsh-choice {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 11px 0;
  cursor: pointer;
  /* No width: the 284px in Figma is the instance's width, not a component constraint. */
}
/* Visually hidden but still in the accessibility tree and the tab order. NOT `display: none`,
   which would remove the input from the tab order and strip the label from assistive tech.
   Shared by the real <input> and by a label hidden via showLabel={false}. */
.evsh-visually-hidden,
.evsh-choice-input {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}
.evsh-choice-box {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  flex-shrink: 0;
  width: 24px;
  height: 24px;
  border: 1px solid var(--evsh-neutral-3);
  background: var(--evsh-white);
  color: var(--evsh-white);   /* the checkmark stroke inherits this */
}
.evsh-choice-box--checkbox { border-radius: 8px; }
.evsh-choice-box--radio { border-radius: 50%; }
/* Unchecked: the mark is present in the DOM but not painted, so the box never reflows. */
.evsh-choice-box > svg,
.evsh-choice-dot { visibility: hidden; }
.evsh-choice-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--evsh-white);
}
.evsh-choice-input:checked + .evsh-choice-box {
  background: var(--evsh-blue-2);
  border-color: var(--evsh-blue-2);
}
.evsh-choice-input:checked + .evsh-choice-box > svg,
.evsh-choice-input:checked + .evsh-choice-box .evsh-choice-dot { visibility: visible; }
/* `controlPosition="trailing"` — the box moves to the far right of the row and the text takes the
   space between (Figma `500:52489`, Create Workspace Step 2: the control sits at x=444 of 468).

   `order` rather than `flex-direction: row-reverse` on the container: reversing would flip EVERY
   child, and this only needs the box moved. The hidden input is `position: absolute`, so it is not
   a flex item and is unaffected either way — but `order` keeps the change to the one element that
   actually moves, which is easier to reason about when a third child is added later. */
.evsh-choice--trailing .evsh-choice-box {
  order: 1;
  margin-left: auto;
}

/* evolve-suite#331 — Figma `1:1011` Disabled=True (8 variants now: Checked × Disabled × Badge, where
   there were 2 when the checkbox docblock's earlier note was written): the whole row — control, label
   and description — dims to opacity 0.5. Keyed off the native `:disabled` pseudo-class via a general
   sibling combinator, so the component's markup adds no class of its own for this state. */
.evsh-choice-input:disabled ~ .evsh-choice-box,
.evsh-choice-input:disabled ~ .evsh-choice-text {
  opacity: 0.5;
}

.evsh-choice-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}
.evsh-choice-label {
  font-family: var(--evsh-font);
  color: var(--evsh-neutral-1);
  /* Figma pass on #1200 — `1:1011`/`1:1003`, measured on all 7 screens: 24px, not the 20px
     `.evsh-body-2` (also applied on this span for its 15/500) carries for every other label that
     shares it. Scoped here rather than changed on `.evsh-body-2` itself. */
  line-height: 24px;
}
/* evolve-suite#331 (S3) — Checkbox's `badge` prop: the label and its Badge share one row, Figma's
   "Title+Badge row", gap 8. Only rendered when `badge` is given, so an existing caller with no
   badge keeps the plain `.evsh-choice-label` markup untouched. */
.evsh-choice-label-row {
  display: flex;
  align-items: center;
  gap: 8px;
}
.evsh-choice-description {
  font-family: var(--evsh-font);
  color: var(--evsh-neutral-2);
}

/* ====== Badge (evolve-suite#331 S3 · Figma Evolve-Components `1:1011` Badge=True variants) ======
   "Disabled beside the app name" is the caller's copy (Checkbox's `badge` prop); this rule is
   purely the shape. Lato 700 10/16, radius 6, 1px inside stroke, padding 4 horizontal / 0 vertical
   (height 16, REST-verified on Checkbox `1:1011` → Badge `347:135` → component `1:1387`) — every
   value literal because no shared 10/16/700 scale exists yet (see `.evsh-input-label-button`'s
   neighbouring note on not inventing one). */
.evsh-badge {
  display: inline-flex;
  align-items: center;
  box-sizing: border-box;
  padding: 0 4px;
  border-radius: 6px;
  box-shadow: inset 0 0 0 1px var(--evsh-neutral-blue-3);
  font-family: var(--evsh-font);
  font-weight: 700;
  font-size: 10px;
  line-height: 16px;
  color: var(--evsh-neutral-2);
  white-space: nowrap;
}

/* ====== Dropdown (wiki: Dropdown & Menu MSxs1dktxb · Figma 109:191) ======
   FIGMA OVERRIDES THE WIKI TWICE. The wiki says the unapplied default is FILLED light-blue and that
   its hover has no colour change; Figma shows a border-only default that GAINS the fill on hover.
   Figma is the visual source of truth — both discrepancies are reported as spec gaps. */
.evsh-dropdown {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  box-sizing: border-box;
  padding: 7px 12px;
  /* ⭐ AN INSIDE STROKE, PAINTED INSIDE — `evolve-suite#669`. Figma's `strokeAlign` on this frame
     is INSIDE, so the 1.5px ring belongs WITHIN the 34px box, not added around it. A real `border`
     is painted outside the padding box and made the control 34 + 1.5 + 1.5 = 37. An inset
     `box-shadow` paints the identical ring and occupies no layout, which is the same technique and
     the same reason as `.evsh-btn--outline` above. ⛔ Do NOT restore `border` here: it silently
     grows every dropdown in the Suite by 3px. */
  box-shadow: inset 0 0 0 1.5px var(--evsh-bg-light-blue);
  border-radius: 24px;                 /* pill */
  background: none;                    /* unapplied default: border only, NO fill */
  font-family: var(--evsh-font);
  font-size: 15px;
  line-height: 20px;
  font-weight: 500;
  color: var(--evsh-neutral-1);
  cursor: pointer;
}
.evsh-dropdown:hover {
  background: var(--evsh-bg-light-blue);
}
.evsh-dropdown-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 16px;
  height: 16px;
}
.evsh-dropdown-label { white-space: nowrap; }
.evsh-dropdown--applied {
  /* The applied pill is a solid fill with no ring — clearing the inset shadow is the exact
     counterpart of the `border-color: transparent` this replaced. */
  box-shadow: none;
  background: var(--evsh-blue-1);
  color: var(--evsh-white);
}
.evsh-dropdown--applied:hover {
  background: var(--evsh-blue-0);
}

/* ====== Menu, MenuItem, MenuDivider (Figma 107:1817 / 111:56 / 107:1527) ====== */
.evsh-menu {
  position: absolute;
  /* The wiki and #569's acceptance both require the menu to open BELOW the trigger. `position:
     absolute` with no offset would resolve to the ancestor's origin, i.e. ON TOP of the trigger —
     so anchor it to the bottom edge explicitly. No gap is invented: no source specifies one, and
     `top: 100%` is directly beneath by definition. The consumer must make the wrapper a positioned
     ancestor; `.evsh-popover-anchor` (shipped in 0.4.0) already does exactly that and is reused
     here rather than adding a second anchor class. */
  top: 100%;
  left: 0;
  z-index: 20;                         /* same tier as Popover: above the rail menus (10) */
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  padding: 4px;                        /* items stack flush — no gap */
  border-radius: 12px;
  background: var(--evsh-white);
  box-shadow: var(--evsh-shadow-med);
  /* No width by default: the spec says the menu follows its widest child unless the consumer
     supplies a fixed value.
     ⛔ CORRECTED (evolve-suite#369 FM1). This comment used to add "The 193px in Figma is that
     instance's width, not a constraint." That was MEASURED FALSE: eleven independent desktop frames
     across the Period, Members and Groups menus all give 193, and a content-hug would have to vary
     between "All time" and "No groups yet" — it does not vary at all. It IS a constraint for those
     menus. The shell keeps the content-hug default for its other consumers; the Overview menus now
     pass 193 explicitly (`filter-menu.tsx` MENU_WIDTH). Do not re-derive a width from this default
     and cite the old sentence as licence. */
}
.evsh-menu-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  box-sizing: border-box;
  width: 100%;
  padding: 8px 12px;
  border: none;
  border-radius: 10px;
  background: none;                    /* default: NO background — see the note below */
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.evsh-menu-item:hover { background: var(--evsh-neutral-blue-4); }
/* FIGMA OVERRIDES THE WIKI ON ONE VALUE ONLY. The wiki fills Default with #f6f9fa, the same as
   Selected, and says a right-slot checkmark carries the distinction. Figma 111:56 leaves Default
   UNFILLED. Hover (#f1f5f8) and Selected (#f6f9fa) match the wiki exactly — do not "correct" them.
   Because Default is unfilled here, Selected reads as distinct on its own; a checkmark is optional
   extra signal rather than the only one. */
.evsh-menu-item--selected { background: var(--evsh-neutral-blue-5); }

/* #801 — the Risky variant (spec MSxs1dktxb): a destructive or irreversible action reads red in
   every state. Backgrounds are `red/red 4` under a white overlay at the spec's opacities, composited
   with the same gradient trick as `.evsh-toast--error` below rather than a hand-computed flat hex —
   that keeps the base token legible and lets a token change flow through.

   NO RESTING BACKGROUND, deliberately. The spec's table fills Risky/Default with `neutral blue 5`,
   but that is the SAME wiki value the note above already records Figma as overruling for the
   standard variant's Default — and the spec's own usage line says "the layout and structure remain
   identical to the standard variant". Painting it would give a risky item a grey ground on the white
   menu surface that none of its neighbours have. Default therefore differs from standard in COLOUR
   only, and the base rule's `background: none` stands.

   `color` (not a descendant selector) so a caller-supplied `leftIcon` drawn with `currentColor`
   inherits it — the spec colours the icon `red 1` too.

   Order matters twice: these follow the base rules so they win on equal specificity, and `:hover` is
   placed LAST so hovering an already-selected risky item shows the hover treatment — the same
   precedence the base variant gets from `.evsh-menu-item:hover` out-specifying
   `.evsh-menu-item--selected`. */
.evsh-menu-item--risky {
  color: var(--evsh-red-1);
}
/* `.evsh-menu-item-label` DECLARES its own colour, and a declared value always beats an inherited
   one — so the `color` above never reaches the label text on its own. It still has to be set there:
   it is what a `currentColor` leftIcon inherits. Out-specifies `.evsh-menu-item-label` (0,2,0). */
.evsh-menu-item--risky .evsh-menu-item-label {
  color: var(--evsh-red-1);
}
.evsh-menu-item--risky.evsh-menu-item--selected {
  background-color: var(--evsh-red-4);
  background-image: linear-gradient(rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0.8));
}
.evsh-menu-item--risky:hover {
  background-color: var(--evsh-red-4);
  background-image: linear-gradient(rgba(255, 255, 255, 0.7), rgba(255, 255, 255, 0.7));
}
/* The EXTERNAL-LINK variant (`eqb7VfpiM2` rev42 L298, `RpT5ddf3HE` rev14 L196: "styled as an
   external link"). Figma `1146:53415` draws the label `#196BA0` against the pack rows' `#00202F`;
   `--evsh-blue-0` IS that value and is already declared as "active text / links".
   ⚠️ Set on BOTH the row and the label, for the reason `--risky` above spells out: the label
   declares its own colour, and a declared value beats an inherited one — so the row-level rule
   never reaches the text on its own. It is still needed there: it is what a `currentColor`
   rightIcon inherits, which is how the external arrow gets its colour. */
.evsh-menu-item--link {
  color: var(--evsh-blue-0);
}
.evsh-menu-item--link .evsh-menu-item-label {
  color: var(--evsh-blue-0);
}

.evsh-menu-item-stack {
  display: flex;
  align-items: center;
  /* 6px, not 8 (evolve-suite#369 FM4). Figma's menu rows put the label 6px after the icon.
     SAFE to change here rather than per-consumer: this stack holds ONLY the optional leftIcon and
     the label — `rightIcon` renders OUTSIDE it — so the gap is inert on any item with no left icon,
     and a sweep of this repo finds exactly two production MenuItem leftIcons, both the Overview
     Filters root rows this row is about. Every other `leftIcon=` in the tree belongs to Button or
     Dropdown, which use their own classes. */
  gap: 6px;
  min-width: 0;
  flex: 1 0 0;
}
.evsh-menu-item-left {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}
.evsh-menu-item-left--16 { width: 16px; height: 16px; }
.evsh-menu-item-left--20 { width: 20px; height: 20px; }
/* The right slot is always 16px regardless of the left size — the wiki is explicit about this. */
.evsh-menu-item-right {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 16px;
  height: 16px;
}
.evsh-menu-item-label {
  font-family: var(--evsh-font);
  font-size: 15px;
  line-height: 20px;
  font-weight: 500;
  color: var(--evsh-neutral-1);        /* unchanged across default/hover/selected; the
                                          Risky variant overrides it — see #801 above */
  min-width: 0;
}
.evsh-menu-divider {
  display: flex;
  align-items: center;
  width: 100%;
  padding: 4px 0;
}
.evsh-menu-divider-rule {
  flex: 1 0 0;
  height: 1px;
  background: var(--evsh-neutral-blue-3);
}

/* ============ Input Label (wiki: Inputs nE0g1ecWmE · Figma Evolve-Components 98:33) ========= */
.evsh-input-label {
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 0 8px;
  color: var(--evsh-neutral-1);
}
.evsh-input-label-text {
  color: inherit;
}
.evsh-input-label-optional {
  color: var(--evsh-neutral-3);
}
.evsh-input-label-button {
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
  color: var(--evsh-blue-0);
  font-family: var(--evsh-font);
  font-size: 12px;
  /* Figma text node 98:32 renders `leading-[normal]` with no bound type variable, unlike the
     label text (bound to body/body-3, which resolves to an 18px line-height). Neither a token
     name nor a line-height is sourced for this size, so this stays `normal` rather than a
     guessed pixel value — do not invent an `.evsh-body-4` utility, which would imply a published
     scale step that doesn't exist. */
  line-height: normal;
  font-weight: 500;
  margin-left: auto;
}

/* ============ TextField (FND-002, #568) ============ */
.evsh-textfield {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.evsh-textfield-field {
  position: relative;
  width: 100%;
}
/* ⛔ SH-02 / evolve-suite#653: THE STROKE IS AN INSET SHADOW, NOT A BORDER. The design-system component
   set `98:70` (file `3gwifp9Rkgj4R7FiQ8ac2l`) draws every TextField variant 38 tall — pad 8/12/10/12,
   a 1.5px stroke aligned INSIDE — and a sweep of every drawn text input on Sign In, Account Settings,
   Create Workspace and Workspace Settings found 44 of 44 the same, 0 overrides. A CSS border ADDS to
   an auto height (41) and pushes the text 1.5px past the design's 12; an inset shadow paints the same
   ring and takes no layout. Every state below recolours the shadow, never a border. */
.evsh-textfield-input {
  background: var(--evsh-white);
  border: 0;
  box-shadow: inset 0 0 0 1.5px var(--evsh-neutral-4);
  border-radius: var(--evsh-radius-card); /* 12px, Figma `Border/Radius/border-radius-default-2` = 12 */
  padding: 8px 12px 10px;
  color: var(--evsh-neutral-1);
  font-family: var(--evsh-font);
  width: 100%;
  box-sizing: border-box;
}
.evsh-textfield-input::placeholder {
  color: var(--evsh-neutral-3);
}
.evsh-textfield-input:focus {
  box-shadow: inset 0 0 0 1.5px var(--evsh-blue-1);
}
/* ONE FOCUS INDICATOR (manual QA, Billing → Adjust limit): Chromium's own `outline: auto 1px` was drawn around the
   inset blue-1 ring on keyboard focus and after Escape. Figma's Focused variant (`98:52`) is the 1.5 blue-1 stroke with no
   effect, so the outline goes wherever that ring shows. NOT on an errored field: error beats focus, its ring stays red
   on focus, and the browser outline is then the only thing telling a keyboard user where focus is (WCAG 2.4.7). */
.evsh-textfield-input:focus:not([aria-invalid]) {
  outline: none;
}
/* (0,3,0) — beats the plain `:focus` rule above (0,2,0) regardless of which is declared first.
   This is what makes an errored, focused field stay red instead of reverting to the focus blue.
   See the "ERROR BEATS FOCUS" docblock note in textfield.tsx. */
.evsh-textfield-input[aria-invalid],
.evsh-textfield-input[aria-invalid]:focus {
  box-shadow: inset 0 0 0 1.5px var(--evsh-red-3);
}
/* ─── TextField disabled (evolve-suite#500) ───────────────────────────────────────────────────
 *
 * Spec: "Inputs" `nE0g1ecWmE` — `Disabled` is a Boolean, default `false`, "Available in
 * `Placeholder` and `Filled` states only". Figma component set `98:70` agrees STRUCTURALLY: it
 * holds exactly six variants, and `Disabled=True` exists only for Placeholder (`260:574`) and
 * Filled (`260:584`) — there is no disabled Focused and no disabled Error to build.
 *
 * MEASURED on the nodes by file key `3gwifp9Rkgj4R7FiQ8ac2l` (`requested 6, non-null 6`):
 *
 *   variant                             field fill   stroke    visible   value text
 *   State=Placeholder, Disabled=False   #FFFFFF      #D9D9D9   true      #B2B2B2
 *   State=Filled,      Disabled=False   #FFFFFF      #D9D9D9   true      #00202F
 *   State=Focused,     Disabled=False   #FFFFFF      #1E7FBD   true      #00202F
 *   State=Error,       Disabled=False   #FFFFFF      #D45463   true      #B2B2B2
 *   State=Placeholder, Disabled=True    #EBEBEB      #D9D9D9   FALSE     #B2B2B2
 *   State=Filled,      Disabled=True    #EBEBEB      #D9D9D9   FALSE     #666666
 *
 * ⚠️ SPEC-VS-FIGMA CONFLICT, RESOLVED TOWARD FIGMA PER THE HOUSE RULE — and flagged, not silently
 * absorbed. The Inputs doc says the disabled TextField has a "grey border (`#D9D9D9`)". On the
 * node that stroke is PRESENT BUT `visible: false`, in BOTH disabled variants. The control is what
 * makes that a finding rather than a misread: all 4 enabled variants carry a VISIBLE stroke and
 * both disabled variants carry a hidden one, so the flag is doing real work and flips exactly on
 * `Disabled`.
 *
 * ⭐ AND THE SPEC ITSELF CORROBORATES FIGMA ONE PARAGRAPH LATER. The same doc's ComboBox entry —
 * the sibling component, same disabled state — says "border matching the background (`#EBEBEB`)".
 * So two of the three signals describe a field with no visible edge, and the TextField sentence's
 * `#D9D9D9` is the outlier. Implemented as the ComboBox sentence words it: the border is painted
 * the SAME colour as the fill. Reported to the designer; if `#D9D9D9` was intended, one line here
 * changes.
 *
 * The STROKE is recoloured to the fill. ⚠️ Since SH-02 (#653) the stroke is an inset shadow (see the
 * base rule), so this no longer protects geometry — a shadow takes none — but painting it the fill
 * colour is still the literal form of "border matching the background", and it keeps the edge
 * invisible on any ground behind the control.
 *
 * `:disabled` (0,2,0) ties with `[aria-invalid]` (0,2,0) on specificity, but they can never both
 * apply: `textfield.tsx` refuses to emit `aria-invalid` or render an error while disabled, because
 * the spec gives no disabled Error variant. That exclusion is enforced in the component and tested
 * there — not left to source order here.
 */
.evsh-textfield-input:disabled {
  background: var(--evsh-neutral-5);
  box-shadow: inset 0 0 0 1.5px var(--evsh-neutral-5);
  color: var(--evsh-neutral-2);
}
/* Disabled + Placeholder keeps #B2B2B2, which `--evsh-neutral-3` already is on the enabled
   placeholder rule above — so there is deliberately NO placeholder override here. Measured: both
   `260:574` and the enabled `98:34` paint their placeholder text #B2B2B2. */

.evsh-textfield-input--with-icon {
  padding-right: 44px; /* 12 field padding + 20 icon + 12 gap to the icon */
}
.evsh-textfield-icon {
  position: absolute;
  right: 12px;
  top: 50%;
  transform: translateY(-50%);
  display: flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  padding: 2px;
  margin: -2px; /* 24x24 hit area, 20x20 visual — WCAG 2.5.8 AA */
}

/* ─── TextField text suffix (evolve-suite#502) ────────────────────────────────────────────────
 *
 * A UNIT LABEL INSIDE THE FIELD — "credits" on "Credits per month". Figma draws it as a TEXT node
 * named "Label", a sibling of the value inside the bordered "Input field" frame. It is NOT the
 * 20x20 `Right Icon Slot` that `rightIcon` implements, so it gets its own slot here.
 *
 * ⛔ WHY `rightIcon` COULD NOT CARRY IT — this is a slot mismatch, not a styling slip.
 * `.evsh-textfield-icon` above is a fixed `width: 20px` box, `justify-content: center`, anchored
 * `right: 12px`. Lay a ~45px word into it and the overflow splits evenly across both edges, so
 * roughly half the word renders OUTSIDE the field's border. No value of `right` or `padding`
 * repairs a box that is 25px too narrow; the content has to stop being 20px wide.
 *
 * MEASURED on the Evolve-Suite file `fYvRupTfIiOEBXYGOcvZZZ`:
 *   Create Workspace step 4 `I4:4069;1444:104502`  field 500 wide
 *     suffix `I4:4069;1444:104502;1444:104501`  w=45, its right edge 12 short of the field's own
 *     value  ends exactly where that suffix begins (gap 0), so the value box measures 431
 *
 *   ⚠️ RE-CITED 2026-09-21 (#961). The six ids this block used to name (`I4:4069;4:10737-10739`
 *   and `I4:4069;4:10712-10714`) are dead in all four estate files, while the PARENT `4:4069`
 *   ("Form | Reload and Usage") is ALIVE — so its children were renumbered, not removed. The
 *   500-wide field above was re-measured on the live nodes today.
 *   ⛔ The sibling "Credit amount" field (357 wide) was NOT re-found in that subtree today, so the
 *   second half of the two-width control below is UNVERIFIED as of 2026-09-21 — stated rather than
 *   quietly dropped. The 57px reserve is unchanged: the live 500-wide field still measures
 *   500 / 45 / 12.
 *
 * ⭐ TWO FIELD WIDTHS, ONE RESERVE — which is the control that makes 57 safe to state once here
 * rather than per call site. 500-443 = 57 and 357-300 = 57. In both frames the suffix is 45 wide,
 * sits 12 from the right edge, and the value's box ends EXACTLY where the suffix begins (gap 0).
 * So the reserve is a property of the suffix, not of the field it is dropped into.
 *
 * `--evsh-textfield-suffix-reserve` is the escape hatch for a future suffix that is not the word
 * "credits" — the default is exact for the only unit the design currently uses.
 */
.evsh-textfield-input--with-suffix {
  padding-right: var(--evsh-textfield-suffix-reserve, 57px); /* 12 right inset + 45 suffix width */
}
.evsh-textfield-suffix {
  position: absolute;
  right: 12px;
  /* Same centring mechanism as `.evsh-textfield-icon` above. Figma puts the suffix and the value
     on the same y (both 8..28 in a 38-tall field); centring lands the suffix within 1px of the
     value's own box given the input's 8/10 padding (its stroke is an inset shadow and takes no
     room — SH-02), and unlike a hardcoded top it survives a change of line-height. */
  top: 50%;
  transform: translateY(-50%);
  /* THE FIX: content-width, never the icon slot's fixed 20px. */
  white-space: nowrap;
  /* The unit is not interactive, and it sits over the input's own padding — letting the click
     through means the word focuses the field instead of being a dead zone inside it. */
  pointer-events: none;
}
/* SH-07: Figma `384:802` (Sign In "Email input field") child `I384:802;98:67` "Error Text" is Lato 500 14
   with line-height 16.8 (INTRINSIC 120%) in a 17-tall "Inline Error" frame — not the fixed 18 its label
   uses. Every caller also carries `evsh-body-3` (line-height 18, same specificity), so this rule must stay
   declared AFTER that one to win the cascade; `cascade-computed.test.tsx` resolves it. */
.evsh-textfield-error {
  color: var(--evsh-red-1);
  padding: 0 8px;
  line-height: 16.8px;
}
/* Helper text: every "Helper Text" node in the design-system TextField set `98:70` (`98:42`, `98:51`, `98:60`,
   `98:69`, `260:583`, `260:593`) is Lato 500 14 on a 16.8 line (INTRINSIC 120%), #666666 — the same metrics as
   the inline error above (SH-07), and again it must be declared after `.evsh-body-3` (18) to win. */
.evsh-textfield-helper {
  color: var(--evsh-neutral-2);
  padding: 0 8px;
  line-height: 16.8px;
}

/* ─── TokenField (#276) ──────────────────────────────────────────────────────
   A chip input whose caret flows inline among the chips. Mirrors `.evsh-textfield`'s
   column/gap so the two read identically when stacked in a form.

   Figma, Create Workspace step 3: field `4:3883` is 160px in BOTH the empty and filled
   states, so the height is FIXED and the area scrolls — a content-driven box would push
   the helper and everything below it down as chips are added, which is the #254 reflow
   in a new costume. */
.evsh-tokenfield {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.evsh-tokenfield-field {
  background: var(--evsh-white);
  border: 1.5px solid var(--evsh-neutral-4);
  border-radius: var(--evsh-radius-card);
  padding: 8px 12px 10px;
  /* Figma 4:3883. Fixed, not min-height — see the block comment above. */
  height: 160px;
  box-sizing: border-box;
  overflow-y: auto;
  /* The whole area is a typing target, so it must read as one. */
  cursor: text;
}
.evsh-tokenfield-field:focus-within {
  border-color: var(--evsh-blue-1);
}
/* pass-3 #59 — Figma `4:3885` paints the chip as `rgba(255,255,255,0.75)` OVER `rgb(41,161,240)`,
   which composites to ≈#cae8fb. Stored FLATTENED, as one opaque colour, because that is what the
   two layers actually produce and it stays correct over any parent — a translucent stack would
   shift with whatever sits behind the field. It was `--evsh-neutral-blue-4` #f1f5f8, a grey: the
   chip read as disabled rather than as a selected person. Purpose-named because Figma gives this
   no token of its own; it is a computed layer, not a palette entry. */
.evsh-tokenfield-tokens {
  display: flex;
  flex-wrap: wrap;
  /* 8px on BOTH axes — Figma measures 192−184=8 across and 38−30=8 down. */
  gap: 8px;
  align-content: flex-start;
  margin: 0;
  /* pass-3 #60 — Figma's Token list `4:3884` is `py-4`; this was 0. */
  padding: 4px 0;
  list-style: none;
}
.evsh-tokenfield-token {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 6px;
  border-radius: 22px;
  background: var(--evsh-tokenfield-token-bg);
  color: var(--evsh-neutral-1);
}
/* ─── Chip contents are centred INSIDE their own 16px box, not merely beside each other ───
   Figma `4:3885` measures a 26px chip with `py-4` / `items-center`, and BOTH 16×16 controls sit at
   y=5 — 5 above, 16 tall, 5 below. The email text `4:3891` is 18 tall at y=4. Every child is
   centred on the chip's axis.

   ⛔ `align-items: center` ON THE CHIP WAS NOT ENOUGH, AND THAT IS THE WHOLE BUG. It centres each
   child's BOX and says nothing about where the content sits inside that box. The remove control is
   a `<button>` whose child is the text glyph `×`, so the glyph landed on the button's own text
   BASELINE — low against the email, which is what the owner's screenshot shows. The icon span had
   the same latent gap: `inline-flex` with no alignment leaves its SVG at the flex start. Both boxes
   were already in the right place; only their contents were not. */
.evsh-tokenfield-token-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
}
.evsh-tokenfield-token-remove {
  border: 0;
  background: none;
  padding: 0;
  width: 16px;
  height: 16px;
  /* The centring trio. `line-height: 1` earns its place as much as the flex pair does: left at the
     UA's button default the line box is taller than the 16px control and shoves the glyph off
     centre even once it is a flex item. */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  line-height: 1;
  cursor: pointer;
  color: var(--evsh-neutral-2);
  font-family: var(--evsh-font);
}
/* The caret's own list item. `flex: 1` lets it take the rest of the current row, so clicking
   anywhere to the right of the last chip lands in the input rather than on dead space. */
.evsh-tokenfield-caret {
  flex: 1 1 120px;
  display: flex;
  min-width: 120px;
}
.evsh-tokenfield-input {
  border: 0;
  outline: none;
  background: transparent;
  color: var(--evsh-neutral-1);
  font-family: var(--evsh-font);
  width: 100%;
  padding: 0;
}
.evsh-tokenfield-error {
  color: var(--evsh-red-1);
  padding: 0 8px;
}
/* pass-3 #61 — Figma's helper `626:91771` is line-height `normal`; `.evsh-body-3` pins 18px.
   Overridden HERE, on the tokenfield's own class, never on `.evsh-body-3` — that scale step is
   worn by the whole suite, and restyling it to fix one helper is the mistake R-W11 names. */
.evsh-tokenfield-helper {
  line-height: normal;
  color: var(--evsh-neutral-2);
  padding: 0 8px;
}

/* ─── Invalid entry (#471, corrected) ────────────────────────────────────────
   ⛔ THIS USED TO BE `.evsh-tokenfield-field--invalid`, PAINTING THE WHOLE 160px BOX RED. The
   owner asked for *"a red border around the email, just like we showed this blue one"* — and
   "this blue one" is the CHIP (Figma `4:3885`), not the field that holds them. Reddening the
   container indicts a whole list because one entry was wrong, so the ring moved to the one
   address that was actually refused. The owner reported it twice, in the same words.

   `--evsh-red-3` (#d45463) is unchanged and is the design system's OWN "input field error
   border", the same token `.evsh-textfield-input[aria-invalid]` uses — so no token is minted and
   the only thing this correction changes is WHICH element wears it.

   ⚠️ FIGMA HAS NO ERROR STATE FOR THIS CHIP. Six `Member Chip` nodes exist on the Create
   workspace page (`4:3885`/`4:3894`/`4:3903` and their three mobile twins) and none is an invalid
   variant; the spec's own pointer to a Step 3 error frame, `507:53425`, is dead in the file. So
   the ring is the existing error token on the existing chip, and nothing else about the chip
   changes — deliberately the smallest choice available where the design is silent.

   ⭐ `box-shadow: inset` RATHER THAN `border`, AND NOT FOR NEATNESS. `.evsh-tokenfield-token` has
   no border, and its `padding: 4px 6px` is measured against Figma's 26px chip (#470). Adding a
   1.5px border would grow every invalid chip by 3px in each axis and break that measurement; an
   inset shadow paints the identical ring inside the existing box and moves nothing. Same 1.5px as
   the field's own border, so the two read as one system. */
.evsh-tokenfield-token--invalid {
  box-shadow: inset 0 0 0 1.5px var(--evsh-red-3);
  /* Owner, 2026-09-10: *"neither the background, its just the red border"*. The valid chip's
     `#cae8fb` fill reads as "accepted"; wearing it under a red border said both at once. With the
     leading icon dropped in `token-field.tsx` too, the border is now the ENTIRE invalid treatment
     — which is exactly why the reduced-motion block below must never absorb it. */
  background: transparent;
}

/* Two mirrored keyframes rather than one, alternated per rejection. An animation whose computed
   value does not change is never replayed, so a single class would shake once and then go quiet on
   every repeat press — see SHAKE_CLASSES in token-field.tsx. They ride on the CHIP, not the field:
   shaking the container moves every valid chip the user already added. */
@keyframes evsh-tokenfield-shake-a {
  0%, 100% { transform: translateX(0); }
  20%      { transform: translateX(4px); }
  40%      { transform: translateX(-4px); }
  60%      { transform: translateX(3px); }
  80%      { transform: translateX(-2px); }
}
@keyframes evsh-tokenfield-shake-b {
  0%, 100% { transform: translateX(0); }
  20%      { transform: translateX(-4px); }
  40%      { transform: translateX(4px); }
  60%      { transform: translateX(-3px); }
  80%      { transform: translateX(2px); }
}
.evsh-tokenfield-token--shake-a { animation: evsh-tokenfield-shake-a 300ms ease-in-out; }
.evsh-tokenfield-token--shake-b { animation: evsh-tokenfield-shake-b 300ms ease-in-out; }
/* evolve-suite IN-13: a refused TextField shakes its field box on the SAME two keyframes, alternated for the same reason
   — see SHAKE_CLASSES in textfield.tsx. Reused, not copied: one motion for every refusal in the product. */
.evsh-textfield-field--shake-a { animation: evsh-tokenfield-shake-a 300ms ease-in-out; }
.evsh-textfield-field--shake-b { animation: evsh-tokenfield-shake-b 300ms ease-in-out; }

/* ⛔ NOT A NICETY. A lateral shake is the motion that provokes symptoms in vestibular disorders,
   so it is removed outright here — while the red border and the `role="alert"` message, declared
   OUTSIDE this block, stay exactly as they are. Nothing the user needs is carried by the motion.

   Belt and braces with `usePrefersReducedMotion` in token-field.tsx, which already withholds these
   classes: this rule is what still holds if a consumer renders the class some other way.

   ⭐ THE RING IS DECLARED OUTSIDE THIS BLOCK AND MUST STAY THERE. Moving it inside would delete
   the information for exactly the users who cannot have the motion. A test asserts this block
   mentions no `box-shadow`. */
@media (prefers-reduced-motion: reduce) {
  .evsh-tokenfield-token--shake-a,
  .evsh-tokenfield-token--shake-b {
    animation: none;
  }
  /* IN-13: the TextField's shake, off for the same users — its Error state and message stay. */
  .evsh-textfield-field--shake-a,
  .evsh-textfield-field--shake-b {
    animation: none;
  }
  /* Skeleton shimmer — SAME BLOCK, deliberately not a second reduced-motion query: exactly one
     such at-rule exists in this stylesheet (token-field.test.tsx asserts that count via a plain
     substring search, so this comment avoids spelling the marker out literally, or it would count
     as a second occurrence itself), so this rule joins it rather than opening a new one.

     Both `animation` AND `background-image` are dropped. `animation: none` alone would leave the
     gradient frozen mid-sweep — a static highlight band Figma's flat "bone" placeholder does not
     have, which is a different, undesigned rest state, not "no motion". Dropping the image too
     restores the exact flat --evsh-neutral-blue-2 fill the D10/D11/D13 rules above already declare
     on their own, unconditionally. */
  .evsh-rail-item--skeleton .evsh-rail-icon,
  .evsh-rail-item--skeleton .evsh-rail-label::before,
  .evsh-nav-item--skeleton .evsh-nav-item-icon::before,
  .evsh-nav-item--skeleton .evsh-nav-item-label,
  .evsh-nav-subitem--skeleton .evsh-nav-subitem-label {
    animation: none;
    background-image: none;
  }
  /* Support widget (evolve-suite#1086) — SAME BLOCK, for the same reason as the shimmer above:
     the panel's and the cards' pop-in is decorative, and the package's rule for animate-on-mount
     surfaces (use-prefers-reduced-motion.ts) is a stylesheet guard. Nothing the user needs is
     carried by the motion: the panel, the escalation and degrade cards and the thanks card all
     render in place, still. */
  .evsh-support-fade,
  .evsh-support-panel {
    animation: none;
  }
  /* Switching Loader (evolve-suite#1302) — SAME BLOCK: the loader animates ON MOUNT, and its JS preference starts
     `false` (use-prefers-reduced-motion.ts), so without this the first frame would be an animated one. The
     resting tile is pinned here, before any script runs: no travel, one tile, the static stage (the same
     values as `.evsh-switching-loader--reduced`, which then agrees with it; the rules are scoped under the root so
     each selector is declared once in the file). `!important` because the loop
     writes its frames as inline styles. */
  .evsh-switching-loader .evsh-switching-loader-stage {
    width: var(--evsh-switching-loader-size);
    height: var(--evsh-switching-loader-size);
    margin-top: 0;
    margin-bottom: 0;
  }
  .evsh-switching-loader .evsh-switching-loader-tile {
    transform: none !important;
    opacity: 1 !important;
    border-radius: 14px !important;
  }
  .evsh-switching-loader .evsh-switching-loader-tile + .evsh-switching-loader-tile {
    display: none;
  }
  .evsh-switching-loader .evsh-switching-loader-content {
    opacity: 1 !important;
  }
}

/* ============ TableHeader (FND-005, #574) ============ */
/*
 * #269. The `<thead>` carried `evsh-table-header` and NOTHING defined it — the only class in this
 * package applied via `className` with no matching rule (measured: 1 of 183 applied, against 215
 * defined). An unknown class is not a type error, not a lint error and not a test failure; it is
 * ignored silently, so the header row rendered with none of its design.
 *
 * The three values come from Figma node `115:35896` ("Table Header", file `fYvRupTfIiOEBXYGOcvZZZ`),
 * which is the node issue #574 links as this component's design: `bg-white`, `border-b` in
 * `background/light-blue` #e8eef3, and `h-[32px]`. The wiki spec (`table-Sb9Tu9DtfD`) is silent on
 * the row's appearance — it defines only the slot behaviour — so Figma is the sole source here.
 *
 * ⛔ RETIRED (SH-15) — "The border renders because both consumers set `border-collapse` on their `<table>`". It rendered, and
 * that was the defect: a collapsed border is SHARED by the rows it separates, half inside each. Measured in Chromium with
 * the built Suite CSS: the Members table's first body row lost 0.5 (its hover strip 55.5, not 56) and the rule's pixel sat
 * at y=32, below the header; on Billing → Invoices the first row was 40.5 and every later row sat half a pixel low.
 * `115:35896`'s stroke is 1px, bottom, `strokeAlign: INSIDE` — inside the 32, taking nothing from the row below.
 */
.evsh-table-header {
  background: var(--evsh-white);
  /* Figma's frame is 32 tall around an 18px label. On a `<thead>` this behaves as a minimum, which
     is the intent: it must not clip a taller cell, only stop a short one collapsing to the label. */
  height: 32px;
}
/* SH-15: the rule, drawn INSIDE each header cell as an inset shadow — the way the Members table draws its row rules
   (`ROW_RULE`, `members-view.tsx`). A shadow collapses with nothing, so the header stays 32 with the rule on its last
   pixel and the first body row keeps its full height. `th`, not `.evsh-table-header-cell`: a consumer's own plain `<th>`
   (the Members action column) must carry the rule too, or the line breaks under it. */
.evsh-table-header th {
  box-shadow: inset 0 -1px 0 var(--evsh-bg-light-blue);
}
/* The label sits at the TOP of the 32px header: `115:35896` is VERTICAL, no padding, MIN-aligned, and the placed
   instances `77:32994` / `77:33270` put every label at y=0. A `<th>` centres by default, and its content sits on a
   line box whose strut (the page's 16/24) adds a baseline offset on top — QA measured 8.25 on dev. So the cell is
   top-aligned AND its contents are block-level (below), leaving no line box to sit on. */
.evsh-table-header-cell {
  text-align: left; /* browsers default <th> to center; the design is left-aligned */
  vertical-align: top;
}
.evsh-table-header-label {
  display: block;
  color: var(--evsh-neutral-2);
}
.evsh-table-header-sort {
  display: flex;
  align-items: center;
  gap: 4px; /* label 29 wide, icon at x=33 — 4px after the label, per Figma geometry */
  background: none;
  border: none;
  padding: 0;
  margin: 0;
  font: inherit;
  color: inherit;
  cursor: pointer;
}
.evsh-table-sort-icon {
  flex-shrink: 0;
}
/* Default (unsorted): both chevrons neutral — the affordance that the column is sortable at all. */
.evsh-table-sort-arrow {
  color: var(--evsh-neutral-blue-2);
}
.evsh-table-sort-arrow--active {
  color: var(--evsh-blue-1);
}

/* ============ Toast (wiki: Toast er8L2y3nj1 · FND-008 matchiq-for-sellers#575 ·
   Figma Coach-file 594-32091 / 609-12329 / 609-20589 — visuals ported verbatim
   from Coach's QA'd implementation at ca72ad9b) ============ */
/* z-index: 1010 — NEW top tier above the dialog overlays (100). Deliberately
   matches Coach's historical value so a toast fired while any modal is open
   stays visible above it. */
/* SH-03: top 24px — Figma `4:6985`, `4:10209`, `4:9330` and `4:9634` all sit 24px down their 900-tall screen.
   Centred on the viewport here; beside the shown shell, the rule in the 1024px block below moves it over the
   content column. */
.evsh-toast-container {
  position: fixed;
  left: 50%;
  top: 24px;
  transform: translateX(-50%);
  z-index: 1010;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  pointer-events: none;
}

.evsh-toast {
  display: flex;
  align-items: center;
  gap: 4px;
  max-width: min(92vw, 420px);
  border-radius: 42px;
  border: 0.5px solid var(--evsh-toast-border);
  padding: 8px 8px 8px 10px;
  box-shadow: var(--evsh-shadow-med); /* Coach's shadow-card — same value, reused rather than duplicated */
  pointer-events: auto;
}
/* Variant backgrounds are the base color with a 90% white overlay composited on top — the
   gradient trick ported verbatim from Coach's toast-bg-success/toast-bg-error utilities. */
.evsh-toast--success {
  background-color: var(--evsh-toast-green-4);
  background-image: linear-gradient(rgba(255, 255, 255, 0.9), rgba(255, 255, 255, 0.9));
}
.evsh-toast--error {
  background-color: var(--evsh-red-3);
  background-image: linear-gradient(rgba(255, 255, 255, 0.9), rgba(255, 255, 255, 0.9));
  border-color: var(--evsh-toast-border-error);
}
/* #1024 (ELI-028): Figma `343:4010` "Type=Warning" is the error pill with three colours changed —
   gold under the same 90% white overlay (renders #FDF8EB), its own border, and the gold icon. */
.evsh-toast--warning {
  background-color: var(--evsh-gold);
  background-image: linear-gradient(rgba(255, 255, 255, 0.9), rgba(255, 255, 255, 0.9));
  border-color: var(--evsh-toast-border-warning);
}
.evsh-toast--undoable {
  background: var(--evsh-white);
  padding-left: 16px;
}

.evsh-toast-body {
  display: flex;
  min-width: 0;
  align-items: center;
  gap: 8px;
}
/* Coach also steps text-body-2/text-subtitle-2 down to 14px/18px line-height below a 640px
   breakpoint (on both .evsh-toast-message below and .evsh-toast-undo/.evsh-toast-link further
   down); the shell is desktop-only, so that responsive step is intentionally omitted here. */
.evsh-toast-message {
  overflow-wrap: break-word;
  color: var(--evsh-neutral-1);
}
.evsh-toast-undo,
.evsh-toast-link {
  color: var(--evsh-blue-1);
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
}
.evsh-toast-undo:hover,
.evsh-toast-link:hover {
  text-decoration: underline;
}
.evsh-toast-icon {
  flex-shrink: 0;
}
.evsh-toast-dismiss {
  display: flex;
  width: 20px;
  height: 20px;
  flex-shrink: 0;
  align-items: center;
  justify-content: center;
  color: var(--evsh-neutral-2);
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
}

/* Package's first @keyframes rules. */
@keyframes evsh-toast-enter {
  from { opacity: 0; transform: translateY(-8px); }
  to   { opacity: 1; transform: translateY(0); }
}
@keyframes evsh-toast-enter-reduced {
  from { opacity: 0; }
  to   { opacity: 1; }
}
@keyframes evsh-toast-exit {
  from { opacity: 1; transform: translateY(0); }
  to   { opacity: 0; transform: translateY(48px); }
}
@keyframes evsh-toast-exit-reduced {
  from { opacity: 1; }
  to   { opacity: 0; }
}
.evsh-toast--enter         { animation: evsh-toast-enter 160ms ease-out; }
.evsh-toast--enter-reduced { animation: evsh-toast-enter-reduced 160ms ease-out; }
/* 300ms exit duration must stay in lockstep with EXIT_DURATION in toast-context.ts — that
   constant is the fallback removal timeout for when animationend never fires (e.g. in tests). */
.evsh-toast--exit         { animation: evsh-toast-exit 300ms ease-in forwards; }
.evsh-toast--exit-reduced { animation: evsh-toast-exit-reduced 220ms ease-in forwards; }

/* ============ Support dialog (issue #712 SUITE-019; Figma Evolve-Components 1:1261) ============
   Own namespace, per this file's convention that each component owns distinct classes rather than
   reusing a sibling's.

   ⛔ THIS BLOCK USED TO SAY the scrim matched its siblings "rather than Coach's rgba(0,0,0,0.38)".
   `rgba(0,0,0,0.38)` IS NOT COACH'S — it is the Suite's own Figma value, measured on ELEVEN
   frames: seven Support states, the disable dialog, the members remove dialog, request-access,
   and accept-terms. Every one `#000000` at 0.38.

   ⭐ The instinct was right and the premise was false: three Suite dialogs DO share one scrim
   treatment — they were simply harmonised onto a value none of them should have, because the
   correct value had been attributed to the wrong product. The reason is deleted with the value
   it justified, because a wrong reason left in the tree wins the next argument. */
.evsh-support-dialog-overlay {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: center;
  /* evolve-suite#419 — `#000000` at 0.38, measured. NOT `#00202f` at 0.40: wrong hue AND wrong
     opacity. Confirmed on three independent dialogs — the support dialog's seven state frames,
     `36:62281` (Switch app - Request access), and `4:5912` (Disabling an app) — so this is the
     design's settled scrim, not one frame's accident.
     ⛔ WHY IT SURVIVED A CAREFUL CHECK: the support dialog was verified against its COMPONENT
     MASTER, and every other attribute is exact. A component has nothing behind it, so it has no
     scrim — this attribute exists ONLY on the frames. The frame carries what the component
     cannot express: scrim, drop shadow, per-state visibility. Those are the unchecked ones. */
  background: rgba(0, 0, 0, 0.38);
}
.evsh-support-dialog-card {
  width: 376px;
  max-width: calc(100vw - 32px);
  box-sizing: border-box;
  background: var(--evsh-white);
  border: 0.5px solid var(--evsh-bg-light-blue);
  border-radius: 24px;
  box-shadow: var(--evsh-shadow-med);
  padding: 18px;
}
.evsh-support-dialog-content {
  display: flex;
  flex-direction: column;
  gap: 24px;
}
/* Coach nests header + description in their own 16px column inside the outer 24px one
   (`flex flex-col gap-4` within `gap-6`). Without this the description sits 24px below the
   title instead of 16px — measured against the extraction source. */
.evsh-support-dialog-intro {
  display: flex;
  flex-direction: column;
  gap: 16px;
}
.evsh-support-dialog-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}
/* Menu-reached flows centre the title between Back and Close; direct entries leave it left,
   which is the only alignment statement either child spec makes (Contact Us, Variant 1). */
.evsh-support-dialog-title--centered {
  text-align: center;
  flex: 1;
}
.evsh-support-dialog-icon-button {
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
  color: var(--evsh-neutral-1);
  flex-shrink: 0;
  display: inline-flex;
}
.evsh-support-dialog-icon-button:hover {
  color: var(--evsh-blue-0);
}
.evsh-support-dialog-description {
  color: var(--evsh-neutral-2);
  margin: 0;
}
.evsh-support-dialog-rows {
  display: flex;
  flex-direction: column;
}
.evsh-support-dialog-row {
  display: flex;
  align-items: center;
  gap: 16px;
  /* Coach nests two paddings on this row: outer py-1 (4px) + inner py-2.5 (10px) = 14px
     per side. Flattened to a single value here rather than reproducing the nesting. */
  padding: 14px 0;
  width: 100%;
  text-align: left;
  background: none;
  border: none;
  cursor: pointer;
  color: var(--evsh-neutral-1);
}
.evsh-support-dialog-row--divided {
  border-bottom: 1px solid var(--evsh-bg-light-blue);
}
.evsh-support-dialog-row-label {
  flex: 1;
}
.evsh-support-dialog-row-chevron {
  color: var(--evsh-neutral-blue-2);
  flex-shrink: 0;
}
.evsh-support-dialog-field {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.evsh-support-dialog-textarea {
  height: 72px;
  width: 100%;
  resize: none;
  box-sizing: border-box;
  border: 1.5px solid var(--evsh-neutral-4);
  border-radius: 12px;
  padding: 12px 16px;
  color: var(--evsh-neutral-1);
  font-family: var(--evsh-font);
}
.evsh-support-dialog-textarea::placeholder {
  color: var(--evsh-neutral-3);
}
.evsh-support-dialog-textarea:focus {
  outline: none;
  border-color: var(--evsh-blue-1);
}
/* (0,3,0) — beats the plain `:focus` rule above (0,2,0) regardless of declaration order, so an
   errored field that is focused stays red instead of reverting to the focus blue. Same shape and
   same reason as `.evsh-textfield-input[aria-invalid]` above (see ~line 1452); Coach gets the
   equivalent from Tailwind class ordering, which does not survive the port. NOTE: CSS specificity
   is not observable in happy-dom, so no unit test can catch the removal of the second selector —
   this comment and TextField's identical shape are the guard. */
.evsh-support-dialog-textarea[aria-invalid],
.evsh-support-dialog-textarea[aria-invalid]:focus {
  border-color: var(--evsh-red-3);
}
.evsh-support-dialog-error {
  display: flex;
  align-items: center;
  gap: 4px;
  color: var(--evsh-red-1);
}
.evsh-support-dialog-submit {
  width: 100%;
}
.evsh-support-dialog-success {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 16px;
  padding: 8px 16px;
  text-align: center;
}
/* Coach wraps the success title and description in `flex flex-col gap-2` (8px). Without it
   the two lines render FLUSH: `.evsh-subtitle-1` and `.evsh-body-3` carry `margin: 0` (type
   scale, top of this file) in every host, so this wrapper's gap is the only thing making it 8px. */
.evsh-support-dialog-success-text {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.evsh-support-dialog-success-close {
  position: absolute;
  top: -8px;
  right: -8px;
}

/* ─── ComboBox (evolve-suite, Billing "Reload balance to") ────────────────────────────────────
 *
 * Figma `fYvRupTfIiOEBXYGOcvZZZ` node `4-10747`: `INSTANCE: ComboBox` — Label / Input Field
 * (value text + Chevron) / Inline Error / Helper.
 *
 * ⭐ DELIBERATELY MIRRORS `.evsh-textfield-input`, not `.evsh-dropdown`. The spec calls this an
 * INPUT-style control, and `dropdown.tsx`'s own docblock says the pill trigger "does not inherit
 * ComboBox's Placeholder/Filled/Focused/Error states". Sharing the textfield's border, radius and
 * error rule is what makes a ComboBox sit in a form beside a TextField without looking foreign.
 *
 * ⚠️ `aria-invalid` drives the error colour, exactly as the TextField rule above does, so an
 * errored+focused field stays red rather than reverting to focus blue.
 *
 * ⛔ SH-02 (#653) / SH-07 FOLLOWED HERE, per Inputs `nE0g1ecWmE` rev 6 L142 ("shares all structural and styling
 * behaviors with TextField"). Re-read over REST: design-system ComboBox set `98:115` (file `3gwifp9Rkgj4R7FiQ8ac2l`),
 * all six variants draw a 38-tall Input Field with a 1.5 INSIDE stroke and pad 8/12/10/12 — so the stroke is an
 * inset shadow, not a border (a border made it 41, 3px off the TextField beside it). Disabled `260:634` fills and
 * strokes #EBEBEB with a #666 value; Error Text `98:112` is #B12031 on a 16.8 line (the code had red-3 at 18).
 */
/* SH-09: every variant in `98:115` is a VERTICAL auto-layout, itemSpacing 8 (label → field → message). */
.evsh-combobox {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.evsh-combobox-field {
  display: flex;
  align-items: center;
  justify-content: space-between;
  /* SH-14: Input Field `98:74` is HORIZONTAL with itemSpacing 0 — the 356-wide text box meets the 16px caret. */
  gap: 0;
  background: var(--evsh-white);
  border: 0;
  box-shadow: inset 0 0 0 1.5px var(--evsh-neutral-4);
  border-radius: var(--evsh-radius-card);
  padding: 8px 12px 10px;
  color: var(--evsh-neutral-1);
  font-family: var(--evsh-font);
  width: 100%;
  box-sizing: border-box;
  cursor: pointer;
  text-align: left;
}
.evsh-combobox-field:focus {
  box-shadow: inset 0 0 0 1.5px var(--evsh-blue-1);
}
/* ONE FOCUS INDICATOR (manual QA, Billing → Adjust limit): Chromium's own `outline: auto 1px` was drawn around the
   inset blue-1 ring on keyboard focus and after Escape. Figma's Focused variant (`98:93`) is the 1.5 blue-1 stroke with no
   effect, so the outline goes wherever that ring shows. NOT on an errored field: error beats focus, its ring stays red
   on focus, and the browser outline is then the only thing telling a keyboard user where focus is (WCAG 2.4.7). */
.evsh-combobox-field:focus:not([aria-invalid]) {
  outline: none;
}
.evsh-combobox-field[aria-invalid],
.evsh-combobox-field[aria-invalid]:focus {
  box-shadow: inset 0 0 0 1.5px var(--evsh-red-3);
}
.evsh-combobox-field:disabled {
  cursor: default;
  background: var(--evsh-neutral-5);
  box-shadow: inset 0 0 0 1.5px var(--evsh-neutral-5);
  color: var(--evsh-neutral-2);
}
/* SH-14: the value takes the room left of the caret (Figma's text box fills to it) and a value too long for it
   ends in an ellipsis before the caret; `min-width: 0` lets a flex item shrink below its text's width. */
.evsh-combobox-value {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* Placeholder `98:75`, Error `98:108` and Disabled+Placeholder `260:624` all paint #B2B2B2 (Inputs L149-L153).
   Set on the span itself, so the disabled field's #666 (above) does not reach it by inheritance. */
.evsh-combobox-value--placeholder {
  color: var(--evsh-neutral-3);
}
/* SH-11: every Chevron vector (`99:45` … `260:638`) strokes #A2B0B7 in all six states, disabled included
   (Inputs L143). The paths draw in `currentColor`, so this colour is the stroke. */
.evsh-combobox-chevron {
  display: inline-flex;
  flex: 0 0 auto;
  color: var(--evsh-neutral-blue-2);
}
/* SH-10: Inline Error `98:111` and every Helper frame pad 0/8, the same inset as the label. */
.evsh-combobox-error {
  color: var(--evsh-red-1);
  padding: 0 8px;
  line-height: 16.8px;
}
/* ComboBox set `98:115` helper nodes (`98:81`, `98:92`, `98:103`, `98:114`, `260:631`, `260:642`): 16.8, as TextField's. */
.evsh-combobox-helper {
  color: var(--evsh-neutral-2);
  padding: 0 8px;
  line-height: 16.8px;
}

/* ============ Accordion (wiki: Accordion DzX5GHd1Jh · Figma Evolve Components 276:695) ============
   Values read over the REST API from component set 276:695 (Expanded=True 276:694,
   Expanded=False 276:696). The wiki's link `278-31` is a dead node id.

   THE STROKE IS INSIDE THE PADDING. Figma measures padding 18/14/18/18 from the OUTER edge with a
   1px INSIDE stroke (the Suite screen's instance 1054:7099 starts at y=188, its Label Container at
   y=206). A CSS border sits outside the padding, so 1px + 17/13/17/17 lands the content where Figma
   draws it; 1px + 18 would sit one pixel in and make the card 2px taller.

   THE TITLE BOX IS 22, NOT 21.6. The text style is Lato 900 18 at "Auto" (120% = 21.6px) line
   height, but Figma sizes the text box to whole pixels (Title 276:678 is 22 tall) and lays out
   around that box. */
.evsh-accordion {
  display: flex;
  flex-direction: column;
  gap: 12px;                                   /* itemSpacing 12 */
  box-sizing: border-box;
  padding: 17px 13px 17px 17px;                /* + the 1px border = Figma's 18/14/18/18 */
  background: var(--evsh-white);               /* fill #ffffff */
  border: 1px solid var(--evsh-bg-light-blue); /* stroke #e8eef3, 1px INSIDE */
  border-radius: 24px;
}
/* <h3> has browser margins and Tailwind's preflight restyles headings: neither may move the header. */
.evsh-accordion-heading {
  margin: 0;
  font: inherit;
}
/* The header button is the Label Container: HORIZONTAL, itemSpacing 8, centred. */
.evsh-accordion-header {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  margin: 0;
  padding: 0;
  background: none;
  border: 0;
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
}
/* Expanded=True only: the Label Container carries paddingBottom 12 and a 1px bottom stroke. */
.evsh-accordion--expanded .evsh-accordion-header {
  padding-bottom: 12px;
  border-bottom: 1px solid var(--evsh-bg-light-blue);
}
.evsh-accordion-text {
  display: flex;
  flex-direction: column;
  gap: 4px;                                    /* Workspace Info itemSpacing 4 */
  flex: 1;
  min-width: 0;
}
.evsh-accordion-title {
  font-size: 18px;
  font-weight: 900;
  line-height: 22px;                           /* the Title box (276:678), not the 21.6 style */
  color: var(--evsh-neutral-1);
}
.evsh-accordion-description {
  font-size: 14px;
  font-weight: 500;
  line-height: 18px;
  color: #667380;                              /* Description 276:677 fill; no shell token carries it */
}
/* Icon Container: a 20x20 icon plus paddingRight 8. */
.evsh-accordion-icon {
  flex: none;
  width: 20px;
  height: 20px;
  margin-right: 8px;
  color: var(--evsh-neutral-1);               /* chevron stroke #00202f */
}
/* Content slot: VERTICAL, itemSpacing 20. */
.evsh-accordion-content {
  display: flex;
  flex-direction: column;
  gap: 20px;
}
/* `display: flex` above is an author rule and beats the browser's own `[hidden] { display: none }`,
   so a collapsed region would stay visible in any host that does not reset `[hidden]` itself. */
.evsh-accordion-content[hidden] {
  display: none;
}

/* ── evolve-suite#954 — the Create-workspace INTRO dialog ──────────────────────────────────────
   Spec `eqb7VfpiM2` rev 54 (2026-09-21T15:54:27.625Z) L106: the current page stays visible behind
   this. Figma file `fYvRupTfIiOEBXYGOcvZZZ` ("Evolve Suite"), Desktop frame `4:4286`, dialog
   `4:4287`.

   ⭐ FIGMA FILE VERSION `2401813641729628019` — RECORD THE VERSION, NOT JUST THE NODE IDS. A node
   id says WHERE someone looked; the version says WHEN, and it is the only thing that lets the next
   reader tell a stale number from a current one. This file moved tonight (lastModified 17:24:30Z
   when #928 was verified, 23:33:54Z for this read), so node ids alone would not have distinguished
   the two. Values below are from a COLD read of that version, taken independently of the port and
   then diffed against it — which is how the one wrong number below was caught before it shipped.

   ⛔ THE PORT'S CARRIED `18/18` FOR THE CLOSE CONTROL WAS WRONG, and it had travelled through three
   hands looking more measured at each hop. It is a 20x20 control positioned 18px IN — a SIZE and an
   OFFSET collapsed into one number. Recorded here because the next reader will otherwise re-derive
   the same collapse from the same source.

   ⛔ ELEVEN `itemSpacing` VALUES IN THIS FRAME PAINT NOTHING — every one sits on a frame with fewer
   than two VISIBLE children (`4:4301` Title 24; `4:4311`/`4:4322`/`4:4333`/`4:4344` 8;
   `4:4312`/`4:4323`/`4:4334`/`4:4345` 4; `4:4350` Button Row 12; `4:4351` 8). Four more nodes are
   `visible: false` (the per-benefit `info` icon, `4:4314`/`4:4325`/`4:4336`/`4:4347`). None of them
   is represented below, deliberately. And `4:4287` carries `strokeWeight: 2.0` with `strokes: []`
   — there is NO border; taking the weight without reading the array adds an outline Figma does not
   draw.

   This package has no Tailwind (0 files; 34 use `evsh-`) while Coach and Edge consume it as
   pre-built CSS, which is why the Suite's Tailwind original had to be re-expressed at all. */
.evsh-cw-intro-overlay {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: center;
  /* `#000000` at 0.38 — the settled scrim `.evsh-legal-dialog-overlay` already uses. The scrim node
     also has `strokeWeight: 0.699` with `strokes: []`: no border there either. */
  background: rgba(0, 0, 0, 0.38);
}
/* Dialog `4:4287`: 734 wide, radius 20, padding 24, a flex ROW with a 32px gap.
   ⛔ NO `height`. 382 is a HUG — the sum of the content, not an authored value. Hardcoding it
   would freeze a total that every child's padding contributes to.
   Shadow is the NAMED effect style `4:48` = `shadow/medium`, confirmed by name and not only by
   value: DROP_SHADOW (0, 4) blur 12, `#00202f` at 0.13.
   ⛔ THE `92vw` CAP IS DELIBERATE: 734 is the DESKTOP measurement and the mobile frames are a later
   pass, so dropping it would push the dialog off-screen to satisfy a number never measured there. */
.evsh-cw-intro-card {
  position: relative;
  display: flex;
  flex-direction: row;
  gap: 32px;
  width: min(734px, 92vw);
  padding: 24px;
  border-radius: 20px;
  background: var(--evsh-white);
  box-shadow: 0 4px 12px rgba(0, 32, 47, 0.13);
}
/* Illustration `459:25779`.
   ⛔ `flex: 1`, NOT `width: 327px`. Its 327 is DERIVED, not authored — the node is
   `layoutSizingHorizontal: FILL` with `layoutGrow: 1`, and 686 content − 32 gap − 327 fixed
   content column = 327. Pinning it as a width would turn a consequence into a constraint and
   break the moment the dialog's width changes. Only the CONTENT column is authored at 327. */
.evsh-cw-intro-illustration {
  flex: 1;
  min-width: 0;
  align-self: stretch;
  object-fit: cover;
}
/* Content Container `4:4300`: 327 wide (authored), paddingTop 12, paddingBottom 8.
   ⭐ `itemSpacing: 0` ON THIS COLUMN, AND THIS IS THE FACT MOST EASILY LOST IN A PORT. Every bit of
   vertical rhythm comes from each CHILD's own padding, never from a stack gap. Reaching for
   `gap` here is the natural move and it is wrong — it would double every spacing below. */
.evsh-cw-intro-content {
  flex: 0 0 327px;
  padding-top: 12px;
  padding-bottom: 8px;
}
/* Close control: frame `4:4353` is 20x20; glyph group `4:4354` is 10x10, two vectors, stroke 1.5,
   `#00202f`. The 18 is the INSET — 18 from the dialog's top and 18 from its right, confirmed by
   arithmetic against the dialog box, not by reading a padding field.
   ⛔ `4:4353` is `layoutPositioning: ABSOLUTE` — it is NOT in the dialog's auto-layout flow. Make
   it a third flex child and it pushes both columns sideways.
   ⚠️ The SVG's own box is 11.5 while the glyph measures 10: the paths run 0.75→10.75, so a 1.5
   stroke sits fully inside. Both numbers are right and they describe different things — geometry
   vs stroke-inclusive bounds. Neither is a correction of the other.
   Colour is `--evsh-neutral-1` (#00202f), NOT neutral-2: the glyph uses `currentColor`, so the
   button's colour is what renders. */
.evsh-cw-intro-close {
  position: absolute;
  top: 18px;
  right: 18px;
  width: 20px;
  height: 20px;
  padding: 0;
  border: none;
  background: none;
  color: var(--evsh-neutral-1);
  cursor: pointer;
}
.evsh-cw-intro-close-box {
  display: flex;
  align-items: center;
  justify-content: center;
}
/* Title `4:4302`, bound to the REMOTE text style `heading/heading-2` (`4:1490`): Lato Black 23/32.
   Raw values because this package has no `.evsh-heading-*` class to bind to — measured: it defines
   `.evsh-body-2/3/4` and `.evsh-subtitle-*` and no heading scale at all. If one is ever added,
   this is the rule that should adopt it.
   ⚠️ 23/32 IS NOT THE WIZARD'S 34/48, though both sites once named the same `evsh-heading-2`: the
   wizard's node binds `heading/heading-1`. One class name stood for two correct values. */
.evsh-cw-intro-title {
  margin: 0;
  font-family: var(--evsh-font);
  font-size: 23px;
  font-weight: 900;
  line-height: 32px;
  color: var(--evsh-neutral-1);
}
/* Description block `4:4303`: paddingTop 24, **itemSpacing 24**, paddingBottom 16.
   ⭐ THIS FRAME HAS TWO VISIBLE CHILDREN — the description text `4:4304` and the benefits list
   `4:4305` — so its `itemSpacing: 24` PAINTS, and it is the description→benefits gap.
   ⛔ DO NOT CONFUSE IT WITH `4:4301` Title's `itemSpacing: 24`, which is one of the eleven inert
   values (single visible child). Same number, adjacent nodes, opposite status. That pair is the
   reason the decoy enumeration had to be a LIST of which ones are dead rather than a general
   warning about itemSpacing.
   ⛔ AND ITS `paddingBottom: 16` IS NOT THAT GAP EITHER. It sits BELOW the benefits list and pairs
   with the button row's `paddingTop: 16` to make the 32 above the button. An earlier revision of
   this block collapsed the two 16s and rendered the description→benefits gap as 16.
   THE ARITHMETIC IS WHAT CATCHES THAT, and it is auditable in one line — the dialog's height is a
   HUG and measures 382:
     24 dialog-pt + 12 col-pt + 32 title + 24 desc-pt + 40 desc-text + 24 desc-gap + 128 benefits
     + 16 desc-pb + 16 btnrow-pt + 34 button + 8 col-pb + 24 dialog-pb = 382 ✓
   Substituting 16 for the desc-gap totals 374 and no longer closes against the measured frame.
   (128 benefits = 4 rows x 20 line + 3 gaps x 16.)
   Expressed as the block's own `gap`, the way Figma expresses it — not as a margin on the list,
   which would be right by coincidence rather than by mechanism. */
.evsh-cw-intro-description-block {
  display: flex;
  flex-direction: column;
  gap: 24px;
  padding-top: 24px;
  padding-bottom: 16px;
}
/* Text `4:4304`, bound to the remote style `body/body-2` (`4:51`) = Lato 500 15/20, which this
   package already tokenises as `.evsh-body-2` — applied as that class so a design-system change
   reaches it, rather than restated as numbers here. */
.evsh-cw-intro-description {
  margin: 0;
  color: var(--evsh-neutral-2);
}
/* Benefits list `4:4305`. Its spacing above comes from the block's `gap`, per the content column's
   own `itemSpacing: 0` — never from a margin here. */
.evsh-cw-intro-benefits {
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 16px;
}
/* `4:4306`: 12px icon-to-text gap, CENTRE cross-alignment. With single-line benefits the alignment
   is invisible today; it stops being invisible the moment a benefit wraps. */
.evsh-cw-intro-benefit {
  display: flex;
  align-items: center;
  gap: 12px;
}
.evsh-cw-intro-benefit-label {
  color: var(--evsh-neutral-1);
}
/* Button Row `4:4350`: paddingTop 16. Its `itemSpacing: 12` is inert (one visible child).
   ⚠️ `justify-content: flex-end` is Figma's declared alignment and is INERT while the button fills
   the row (`layoutGrow: 1`). Stated so the build says what the design says, and so a later reader
   does not add it again thinking it was missed. */
.evsh-cw-intro-actions {
  padding-top: 16px;
  display: flex;
  justify-content: flex-end;
}
/* The button itself is `.evsh-btn.evsh-btn--primary`, whose existing rule ALREADY matches the cold
   read exactly — padding 7/12, radius 10, Lato 600 15/20, fill `--evsh-neutral-1` (#00202f), white
   text, and its own comment derives the 34px height the same way Figma does. So nothing is
   re-specified here; only the fill-the-row width, which is `layoutGrow: 1` on `4:4351`. */
.evsh-cw-intro-primary {
  width: 100%;
}

/* ─── Credit Availability dialog (matchiq-for-sellers#1443) ────────────────────────────────────
   The shared dialog Coach (Upload transcript) and Edge (Prepare me) open when the platform refuses
   a credit-consuming action. Component: `credit-availability-dialog.tsx`.

   Figma "Evolve Coach" `d15UNnN4OKK64hgdshVD5D`, read over the REST API 2026-09-22: `879:7126`,
   `1505:72271`, `2728:10209`, `2728:10263`. All four share every value below; each rule names the
   node it came from (ids from `879:7126` unless noted). Pinned in
   `test/credit-availability-dialog-figma.test.ts`; the built bundle in `verify-built-css.mjs`.

   ⛔ APPEND-ONLY BLOCK. Nothing above this line is edited by #1443: two other lanes are changing
   this file at the same time, and a separate block rebases cleanly whoever merges second. */

/* The frame fill, #000000 at 0.38 — the scrim every shell dialog uses (dialog-scrim-figma.test.ts).
   The 24px padding is the phone gutter, MEASURED (evolve-suite#627, 2026-09-29): the Coach file's four
   mobile frames — `3007:59309`, `3007:59363`, `3007:60502`, `3007:60556`, each 430 wide — place their
   `Dialog` (`3007:59310` / `…59364` / `…60503` / `…60557`) at x=24, 382 wide, so 24 on both sides.
   ⛔ It was 16, recorded here as "not measured — there is no mobile frame"; those frames exist, and
   16 drew the card 398 wide. The desktop card is a fixed 482 (`min(482px, 100%)` below), so the gutter
   only engages under 530px and desktop is unchanged. */
.evsh-credit-dialog-overlay {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  padding: 24px;
  background: rgba(0, 0, 0, 0.38);
}
/* `Dialog` 879:7127 — FIXED 482 wide, HUG tall (646 / 590 / 502 / 446 by scenario, so no height).
   `clipsContent: true` + radius 32 is why the illustration's top corners round. `max-height` and the
   vertical scroll keep a short viewport from pushing the actions off-screen; they never engage at the
   900-tall frame size. */
.evsh-credit-dialog {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 12px;
  box-sizing: border-box;
  width: min(482px, 100%);
  max-height: 100%;
  overflow: hidden auto;
  padding-bottom: 24px;
  border-radius: 32px;
  background: var(--evsh-white);
  box-shadow: var(--evsh-shadow-med);
  font-family: var(--evsh-font);
}
/* The container takes focus on open (the package's dialog convention); it is not a control. */
.evsh-credit-dialog:focus {
  outline: none;
}
/* `Illustration Container` 1505:72172 — FILL x 258, fill #F5DADD (a Figma variable with no shell
   token) under an IMAGE fill with scaleMode FILL, i.e. cover. The image is the host's file. */
.evsh-credit-dialog-illustration {
  position: relative;
  flex: none;
  height: 258px;
  overflow: hidden;
  background: #f5dadd;
}
.evsh-credit-dialog-illustration-image {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
/* `Icon` 1505:72174 — 89.41 square at x 196.48, y 84.33. Centred rather than pinned at 196.48: the
   centred value is 196.30 at 482, and centring keeps it centred when the card narrows. */
.evsh-credit-dialog-glyph {
  position: absolute;
  top: 84.33px;
  left: 50%;
  transform: translateX(-50%);
}
/* `Frame 47792` 1546:41555 — pad L/R 24, gap 12. */
.evsh-credit-dialog-content {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 0 24px;
}
/* `Title` 880:7329 — pad top 16, gap 18. */
.evsh-credit-dialog-heading {
  display: flex;
  flex-direction: column;
  gap: 18px;
  padding-top: 16px;
}
/* 880:7266 — Lato 900 23/32 #00202F. */
.evsh-credit-dialog-title {
  margin: 0;
  font-size: 23px;
  line-height: 32px;
  font-weight: 900;
  color: var(--evsh-neutral-1);
}
/* 880:7327 — Lato 400 15/26 #666666. */
.evsh-credit-dialog-description {
  margin: 0;
  font-size: 15px;
  line-height: 26px;
  font-weight: 400;
  color: var(--evsh-neutral-2);
}
/* `Benefits (Hidden)` 1505:72182 — pad 20 top and bottom, gap 16. ⚠️ Despite its name the frame is
   `visible` in all four frames; the benefits show wherever the scenario includes them (spec L27). */
.evsh-credit-dialog-benefits {
  display: flex;
  flex-direction: column;
  gap: 16px;
  margin: 0;
  padding: 20px 0;
  list-style: none;
}
/* `Benefit` 1505:72183 — 18 bullet, gap 12, centred; text 1505:72190 Lato 700 15/20 #00202F. The
   row's `info` glyph (1505:72191) is `visible: false` and is not drawn. */
.evsh-credit-dialog-benefit {
  display: flex;
  align-items: center;
  gap: 12px;
  font-size: 15px;
  line-height: 20px;
  font-weight: 700;
  color: var(--evsh-neutral-1);
}
.evsh-credit-dialog-benefit svg {
  flex-shrink: 0;
}
/* The spec L19 line / the no-destination line. No frame draws it (PM ruling on #1443: after the
   benefits, before the actions), so it takes the description's type rather than inventing one. */
.evsh-credit-dialog-note {
  margin: 0;
  font-size: 15px;
  line-height: 26px;
  font-weight: 400;
  color: var(--evsh-neutral-2);
}
/* `Button Container` 880:7227 — a column, gap 8. */
.evsh-credit-dialog-actions {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
/* 880:7228 / 880:7230 — FILL x 48 (12 + 24 + 12), radius 10, Lato 600 16/24, centred. LOCAL frames
   (componentId null), not Button instances: the shell's `.evsh-btn` is 34 tall at 15/20. */
.evsh-credit-dialog-action {
  display: flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  width: 100%;
  padding: 12px;
  border: none;
  border-radius: 10px;
  font-family: var(--evsh-font);
  font-size: 16px;
  line-height: 24px;
  font-weight: 600;
  text-align: center;
  text-decoration: none;
  cursor: pointer;
}
/* 880:7228 — fill #00202F, text #FFFFFF. Hover is not in the frames: it reuses `.evsh-btn--primary`'s,
   the background dimmed rather than the element, so the label keeps its contrast. */
.evsh-credit-dialog-action--primary {
  background: var(--evsh-neutral-1);
  color: var(--evsh-white);
}
.evsh-credit-dialog-action--primary:hover {
  background: rgba(0, 32, 47, 0.8);
}
/* 880:7230 — no fill, no stroke, text #00202F. Hover is not in the frames: it keeps the tint Coach's
   own dialog shipped (`hover:bg-neutral-blue-5`), so moving to the shared dialog changes nothing. */
.evsh-credit-dialog-action--dismiss {
  background: none;
  color: var(--evsh-neutral-1);
}
.evsh-credit-dialog-action--dismiss:hover {
  background: var(--evsh-neutral-blue-5);
}

/* ============ Support widget (evsh-support-*) — evolve-suite#1086 ============ */
/* The in-app support widget (Chat + Feedback), shared by Coach and Edge. Ported from
   Coach's inline `SW_STYLE` (itself pitchiq's), retokenised onto --evsh-*. The widget's
   fixed elements sit outside any host wrapper, so each carries `evsh-root` itself and
   `.evsh-support` bridges the handful of widget-local names onto the shell's tokens.
   Where the shell has no token for a colour Coach drew (its gold-1 warning, the light
   gold / light green tints), the literal Figma value is stated here, widget-scoped, rather
   than minted on .evsh-root for one consumer. */
.evsh-support {
  --evsh-support-accent: var(--evsh-blue-1);
  --evsh-support-accent-soft: var(--evsh-neutral-blue-4);
  --evsh-support-surface: var(--evsh-white);
  --evsh-support-fg-1: var(--evsh-neutral-1);
  --evsh-support-fg-2: var(--evsh-neutral-2);
  --evsh-support-border-1: var(--evsh-neutral-5);
  --evsh-support-border: var(--evsh-neutral-4);
  --evsh-support-warning: #c19528;         /* Figma `gold/gold 1` — no shell token */
  --evsh-support-error: var(--evsh-red);
  --evsh-support-success: var(--evsh-green-1);
  --evsh-support-bg-light-gold: #f4f0e7;   /* Figma `background/light-gold` — no shell token */
  --evsh-support-bg-light-blue: var(--evsh-bg-light-blue);
  --evsh-support-bg-light-green: #e9f2eb;  /* Figma `background/light-green` — no shell token */
  --evsh-support-radius-sm: 6px;
  --evsh-support-radius-md: 8px;
  --evsh-support-radius-lg: var(--evsh-radius-card);
  --evsh-support-radius-full: 999px;
  --evsh-support-shadow: var(--evsh-shadow-med);
  --evsh-support-fs-1: 1rem;
  --evsh-support-fs-3: 0.8125rem;
  --evsh-support-transition: 120ms ease;
  --evsh-support-ease-out: cubic-bezier(0.25, 1, 0.5, 1);
  font-family: var(--evsh-font);
}
@keyframes evshSupportPop { from { opacity: 0; transform: translateY(6px) scale(.98); } to { opacity: 1; transform: none; } }
@keyframes evshSupportSpin { to { transform: rotate(360deg); } }
.evsh-support-fade { animation: evshSupportPop .18s var(--evsh-support-ease-out); }
.evsh-support-spinner { display: inline-block; width: 14px; height: 14px; border: 2px solid var(--evsh-support-accent-soft); border-top-color: var(--evsh-support-accent); border-radius: var(--evsh-support-radius-full); animation: evshSupportSpin .7s linear infinite; }
/* z-index 90: above the shell's popovers (20), below its dialog overlays (100). */
.evsh-support-dock { position: fixed; top: 0; right: 0; bottom: 0; z-index: 90; display: flex; background: var(--evsh-support-surface); border-left: 1px solid var(--evsh-support-border-1); box-shadow: -8px 0 24px -22px rgba(0, 0, 0, .4); }
.evsh-support-dock-inner { flex: 1; min-width: 0; display: flex; flex-direction: column; height: 100%; overflow: hidden; }
.evsh-support-resize { position: absolute; left: 0; top: 0; bottom: 0; width: 10px; margin-left: -5px; cursor: col-resize; z-index: 3; background: transparent; touch-action: none; }
.evsh-support-resize::before { content: ""; position: absolute; left: 4px; top: 0; bottom: 0; width: 2px; background: transparent; transition: background var(--evsh-support-transition); }
.evsh-support-resize:hover::before, .evsh-support-dock.is-resizing .evsh-support-resize::before, .evsh-support-resize:focus-visible::before { background: var(--evsh-support-accent); }
.evsh-support-resize:focus-visible { outline: none; }
body.evsh-support-resizing, body.evsh-support-resizing * { cursor: col-resize !important; user-select: none !important; }
.evsh-support-launcher { position: fixed; right: 20px; bottom: 20px; z-index: 90; width: 52px; height: 52px; border-radius: var(--evsh-support-radius-full); border: 0; cursor: pointer; display: flex; align-items: center; justify-content: center; background: var(--evsh-support-accent); color: var(--evsh-white); box-shadow: var(--evsh-support-shadow); transition: transform var(--evsh-support-transition), background var(--evsh-support-transition); }
.evsh-support-launcher:hover { transform: translateY(-1px); }
.evsh-support-panel { position: fixed; right: 20px; bottom: 84px; z-index: 90; width: 380px; max-width: calc(100vw - 40px); max-height: min(560px, calc(100vh - 120px)); display: flex; flex-direction: column; background: var(--evsh-support-surface); border: 1px solid var(--evsh-support-border-1); border-radius: var(--evsh-support-radius-lg); box-shadow: var(--evsh-support-shadow); overflow: hidden; animation: evshSupportPop .14s var(--evsh-support-ease-out); }
.evsh-support-head { display: flex; align-items: center; justify-content: space-between; padding: 14px 16px 10px; }
.evsh-support-head-title { display: inline-flex; align-items: center; gap: 8px; font-family: var(--evsh-font); font-weight: 900; font-size: var(--evsh-support-fs-1); color: var(--evsh-support-fg-1); }
.evsh-support-head-actions { display: inline-flex; align-items: center; gap: 2px; }
.evsh-support-iconbtn { display: flex; align-items: center; justify-content: center; width: 30px; height: 30px; border: 0; border-radius: var(--evsh-support-radius-md); background: transparent; color: var(--evsh-support-fg-2); cursor: pointer; }
.evsh-support-iconbtn:hover { background: var(--evsh-support-accent-soft); color: var(--evsh-support-accent); }
.evsh-support-tabs { display: flex; gap: 6px; padding: 0 16px 10px; border-bottom: 1px solid var(--evsh-support-border-1); }
.evsh-support-tabbtn { display: inline-flex; align-items: center; gap: 6px; border: 0; cursor: pointer; background: transparent; color: var(--evsh-support-fg-2); font-family: var(--evsh-font); font-weight: 700; font-size: var(--evsh-support-fs-3); padding: 7px 12px; border-radius: var(--evsh-support-radius-full); }
.evsh-support-tabbtn.is-active { background: var(--evsh-support-accent-soft); color: var(--evsh-support-accent); }
.evsh-support-body { display: flex; flex-direction: column; flex: 1; min-height: 0; }
.evsh-support-chat { display: flex; flex-direction: column; flex: 1; min-height: 0; }
.evsh-support-msgs { flex: 1; min-height: 220px; overflow-y: auto; padding: 14px 16px; display: flex; flex-direction: column; gap: 10px; }
.evsh-support-empty { display: flex; gap: 10px; align-items: flex-start; color: var(--evsh-support-fg-2); font-size: var(--evsh-support-fs-3); }
.evsh-support-empty-icon { display: flex; align-items: center; justify-content: center; width: 28px; height: 28px; border-radius: var(--evsh-support-radius-full); background: var(--evsh-support-accent-soft); color: var(--evsh-support-accent); flex-shrink: 0; }
.evsh-support-empty p { margin: 4px 0 0; line-height: 1.45; }
.evsh-support-msg { display: flex; gap: 8px; align-items: flex-end; }
.evsh-support-msg--user { justify-content: flex-end; }
.evsh-support-msg-icon { display: flex; align-items: center; justify-content: center; width: 26px; height: 26px; border-radius: var(--evsh-support-radius-full); background: var(--evsh-support-accent-soft); color: var(--evsh-support-accent); flex-shrink: 0; }
.evsh-support-msg-icon--human { background: var(--evsh-support-bg-light-green); color: var(--evsh-support-success); }
.evsh-support-msg-body { min-width: 0; max-width: 82%; }
.evsh-support-msg-who { font-family: var(--evsh-font); font-weight: 800; font-size: 0.72rem; color: var(--evsh-support-fg-2); margin: 0 0 3px 2px; }
.evsh-support-msg-bubble { max-width: 100%; padding: 9px 12px; border-radius: var(--evsh-support-radius-lg); font-size: var(--evsh-support-fs-3); line-height: 1.45; white-space: pre-wrap; overflow-wrap: break-word; }
.evsh-support-msg--agent .evsh-support-msg-bubble { background: var(--evsh-support-bg-light-blue); color: var(--evsh-support-fg-1); border-bottom-left-radius: 4px; }
.evsh-support-msg--user .evsh-support-msg-bubble { background: var(--evsh-support-accent); color: var(--evsh-white); border-bottom-right-radius: 4px; }
.evsh-support-msg--agent .evsh-support-msg-bubble--human { background: var(--evsh-support-bg-light-green); border: 1px solid var(--evsh-support-success); }
.evsh-support-msg-bubble--warn { background: var(--evsh-support-bg-light-gold) !important; border: 1px solid var(--evsh-support-warning); }
.evsh-support-msg-warnicon { display: inline-flex; vertical-align: -2px; margin-right: 6px; color: var(--evsh-support-warning); }
.evsh-support-msg-bubble--busy { display: inline-flex; align-items: center; gap: 8px; color: var(--evsh-support-fg-2); }
.evsh-support-esc { display: flex; gap: 10px; align-items: flex-start; padding: 11px 12px; border-radius: var(--evsh-support-radius-lg); background: var(--evsh-support-bg-light-gold); border: 1px solid var(--evsh-support-warning); }
.evsh-support-esc-icon { display: flex; align-items: center; justify-content: center; width: 28px; height: 28px; border-radius: var(--evsh-support-radius-full); background: var(--evsh-support-surface); color: var(--evsh-support-warning); flex-shrink: 0; }
.evsh-support-esc-title { font-family: var(--evsh-font); font-weight: 900; font-size: var(--evsh-support-fs-3); color: var(--evsh-support-fg-1); }
.evsh-support-esc-sub { font-size: var(--evsh-support-fs-3); color: var(--evsh-support-fg-2); margin-top: 2px; line-height: 1.4; }
.evsh-support-degrade { display: flex; gap: 10px; align-items: flex-start; padding: 11px 12px; border-radius: var(--evsh-support-radius-lg); background: var(--evsh-support-bg-light-blue); border: 1px solid var(--evsh-support-accent); }
.evsh-support-degrade-icon { display: flex; align-items: center; justify-content: center; width: 28px; height: 28px; border-radius: var(--evsh-support-radius-full); background: var(--evsh-support-surface); color: var(--evsh-support-accent); flex-shrink: 0; }
.evsh-support-degrade-title { font-family: var(--evsh-font); font-weight: 900; font-size: var(--evsh-support-fs-3); color: var(--evsh-support-fg-1); }
.evsh-support-degrade-sub { font-size: var(--evsh-support-fs-3); color: var(--evsh-support-fg-2); margin-top: 2px; line-height: 1.4; }
.evsh-support-error { display: flex; align-items: center; gap: 6px; color: var(--evsh-support-error); font-size: var(--evsh-support-fs-3); }
.evsh-support-inputrow { display: flex; gap: 8px; padding: 10px 16px 0; border-top: 1px solid var(--evsh-support-border-1); }
.evsh-support-input { flex: 1; min-width: 0; border: 1.5px solid var(--evsh-support-border); border-radius: var(--evsh-support-radius-md); padding: 8px 10px; font-family: var(--evsh-font); font-size: var(--evsh-support-fs-3); color: var(--evsh-support-fg-1); background: var(--evsh-support-surface); }
.evsh-support-input:focus { outline: none; border-color: var(--evsh-support-accent); }
.evsh-support-textarea { width: 100%; box-sizing: border-box; border: 1.5px solid var(--evsh-support-border); border-radius: var(--evsh-support-radius-md); padding: 8px 10px; font-family: var(--evsh-font); font-size: var(--evsh-support-fs-3); color: var(--evsh-support-fg-1); background: var(--evsh-support-surface); resize: vertical; }
.evsh-support-textarea:focus { outline: none; border-color: var(--evsh-support-accent); }
.evsh-support-btn { display: inline-flex; align-items: center; justify-content: center; gap: 6px; border: 0; cursor: pointer; border-radius: var(--evsh-support-radius-full); padding: 8px 14px; background: var(--evsh-support-accent); color: var(--evsh-white); font-family: var(--evsh-font); font-weight: 700; font-size: var(--evsh-support-fs-3); transition: opacity var(--evsh-support-transition); }
.evsh-support-btn:disabled { opacity: .5; cursor: default; }
.evsh-support-btn--secondary { background: var(--evsh-support-accent-soft); color: var(--evsh-support-accent); }
.evsh-support-btn--block { width: 100%; }
.evsh-support-foot { padding: 6px 16px 12px; }
.evsh-support-chat .evsh-support-inputrow + .evsh-support-foot { padding-top: 6px; }
.evsh-support-chat > .evsh-support-inputrow:last-child { padding-bottom: 14px; }
.evsh-support-linkbtn { display: inline-flex; align-items: center; gap: 5px; border: 0; background: none; cursor: pointer; color: var(--evsh-support-fg-2); font-family: var(--evsh-font); font-weight: 700; font-size: var(--evsh-support-fs-3); padding: 2px 0; }
.evsh-support-linkbtn:hover { color: var(--evsh-support-accent); }
.evsh-support-feedback { display: flex; flex-direction: column; gap: 12px; padding: 14px 16px 16px; }
.evsh-support-kinds { display: flex; gap: 8px; }
.evsh-support-kind { flex: 1; display: inline-flex; align-items: center; justify-content: center; gap: 6px; cursor: pointer; border-radius: var(--evsh-support-radius-full); padding: 8px 12px; font-family: var(--evsh-font); font-size: var(--evsh-support-fs-3); font-weight: 700; border: 1.5px solid var(--evsh-support-border); background: transparent; color: var(--evsh-support-fg-2); }
.evsh-support-kind.is-active { border-color: var(--evsh-support-accent); background: var(--evsh-support-accent-soft); color: var(--evsh-support-accent); }
.evsh-support-pagecheck { display: flex; align-items: center; gap: 8px; font-size: var(--evsh-support-fs-3); color: var(--evsh-support-fg-2); cursor: pointer; }
.evsh-support-pagecheck input { accent-color: var(--evsh-support-accent); }
.evsh-support-pagecheck code { font-size: 0.8em; background: var(--evsh-support-accent-soft); padding: 1px 6px; border-radius: var(--evsh-support-radius-sm); }
.evsh-support-thanks { display: flex; flex-direction: column; align-items: center; gap: 8px; text-align: center; padding: 30px 20px; }
.evsh-support-thanks-icon { display: flex; align-items: center; justify-content: center; width: 44px; height: 44px; border-radius: var(--evsh-support-radius-full); background: var(--evsh-support-bg-light-green); color: var(--evsh-support-success); }
.evsh-support-thanks h4 { margin: 2px 0 0; font-family: var(--evsh-font); font-weight: 900; color: var(--evsh-support-fg-1); }
.evsh-support-thanks p { margin: 0 0 8px; color: var(--evsh-support-fg-2); font-size: var(--evsh-support-fs-3); line-height: 1.45; }
/* Below the shell's desktop line (min-width: 1024px) the products show a fixed bottom bar:
   lift the launcher and the popover above it. 1023 is the exact complement.
   The two plain rules are the host-owned bar the products had before #627 (74 = its height plus a
   gap), and stay for any host that has not adopted the shell's Nav Bar.
   evolve-suite#627 — with the SHELL's Nav Bar on screen the geometry is known exactly, so the
   launcher stacks on it: 16px above the bar's top edge, the FAB's own gap. With the FAB there too,
   it goes one more step up — above the 40px pill plus another 16 — so launcher, FAB and bar never
   overlap (all three sit on the right edge). The panel keeps its existing 10px over the 52px
   launcher (136 - 74 = 62), and caps its height to what is left under the Tool Bar. */
@media (max-width: 1023px) {
  .evsh-support-launcher { bottom: calc(74px + env(safe-area-inset-bottom)); }
  .evsh-support-panel { bottom: calc(136px + env(safe-area-inset-bottom)); }
  body:has(.evsh-mobile-bottom-bar) .evsh-support-launcher { bottom: calc(var(--evsh-mobile-bottom-offset) + 16px); }
  body:has(.evsh-mobile-fab) .evsh-support-launcher { bottom: calc(var(--evsh-mobile-bottom-offset) + 72px); }
  body:has(.evsh-mobile-bottom-bar) .evsh-support-panel {
    bottom: calc(var(--evsh-mobile-bottom-offset) + 78px);
    max-height: min(560px, calc(100vh - var(--evsh-mobile-bottom-offset) - 78px - var(--evsh-mobile-top-offset) - 16px));
  }
  body:has(.evsh-mobile-fab) .evsh-support-panel {
    bottom: calc(var(--evsh-mobile-bottom-offset) + 134px);
    max-height: min(560px, calc(100vh - var(--evsh-mobile-bottom-offset) - 134px - var(--evsh-mobile-top-offset) - 16px));
  }
}
/* #627 review — the same two caps in `dvh`, the VISIBLE viewport: `vh` is the large one, taller than
   what a tablet or phone browser shows while its toolbars are up, so the panel's top could sit under
   them. The `vh` rules above stay as the fallback, but in an @supports block rather than as a second
   declaration: these values hold `var()`, which an engine accepts at parse time whatever else is in
   the line, so one without `dvh` would resolve a second line to `none` (no cap) instead of keeping
   the first — see the mobile menus' @supports block for the Chromium measurement. The band is
   stated (761–1023) rather than left to source order: below 761 the phone takeover owns the height. */
@supports (height: 100dvh) {
  @media (min-width: 761px) and (max-width: 1023px) {
    body:has(.evsh-mobile-bottom-bar) .evsh-support-panel {
      max-height: min(560px, calc(100dvh - var(--evsh-mobile-bottom-offset) - 78px - var(--evsh-mobile-top-offset) - 16px));
    }
    body:has(.evsh-mobile-fab) .evsh-support-panel {
      max-height: min(560px, calc(100dvh - var(--evsh-mobile-bottom-offset) - 134px - var(--evsh-mobile-top-offset) - 16px));
    }
  }
}
@media (max-width: 760px) {
  /* Phone: full-screen takeover. dvh tracks the keyboard-aware viewport so the input
     stays visible while typing. */
  .evsh-support-panel {
    left: 0; right: 0; top: 0; bottom: 0; width: auto; max-width: none;
    height: 100dvh; max-height: 100dvh; border-radius: 0; border: 0;
    padding-bottom: env(safe-area-inset-bottom);
  }
  /* #627 — the Nav Bar rules in the 1023px block above out-rank the takeover's plain selector, so the
     takeover restates its edges at their specificity (same selectors, later in the file). */
  body:has(.evsh-mobile-bottom-bar) .evsh-support-panel,
  body:has(.evsh-mobile-fab) .evsh-support-panel {
    bottom: 0;
    max-height: 100dvh;
  }
  /* The launcher is behind the open panel — hide it so the X in the header is the single
     close affordance. */
  .evsh-support-root--open .evsh-support-launcher { display: none; }
}

/* ============ Switching Loader (evolve-suite#1302, spec 2hT2HgPht5 r5) ============ */
/* Figma 1637:48609 (product) and 1637:48652 (workspace): a white full screen, the tile and its label
   centred. Tile 48 px, radius 14 (the loop's own 30% at rest), label Lato 700 15/20 #00202F.
   z-index: above the dialogs (1000) and below the toast layer (1010), so an error toast raised while
   the loader is up is still seen. */
.evsh-switching-loader {
  --evsh-switching-loader-size: 48px;
  /* The tile-to-label gap: 8 px (Figma 1637:48611 / 1637:48654 — container itemSpacing 8, 48 + 8 + 20 = 76 tall;
     re-measured 2026-09-28, it was 16 when ELI-060 first ruled). ONE token, so it changes in one place.
     It is measured from the tile as it RESTS at the centre of the stage (and from the static tile under reduced
     motion): the stage gives back the empty strip under that tile — see `.evsh-switching-loader-stage`. */
  --evsh-switching-loader-label-gap: 8px;
  position: fixed;
  inset: 0;
  z-index: 1005;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--evsh-switching-loader-label-gap);
  background: var(--evsh-white);
  outline: none;
}
/* The stage the two tiles travel in: 2.1 x 1.3 of the tile, clipped, like the playground's. */
.evsh-switching-loader-stage {
  position: relative;
  width: calc(var(--evsh-switching-loader-size) * 2.1);
  height: calc(var(--evsh-switching-loader-size) * 1.3);
  overflow: hidden;
  /* The centred tile's bottom edge sits at 1.1 of the tile (its top is 0.1: travel .5 of 1.2, minus the half tile),
     so the last 0.2 of the 1.3 stage is empty. Giving it back makes the flex gap the tile-to-label distance. */
  margin-bottom: calc(var(--evsh-switching-loader-size) * -0.2);
  /* ...and the centred tile's TOP sits 0.1 of the tile below the stage's top, so the visible group (tile + gap + label)
     would render half of that (2.4 px) low in its centring box. Lifting the stage by the 0.1 makes the resting tile's
     top the block's top, as in Figma. Reduced motion has no such offset (the stage IS the tile). */
  margin-top: calc(var(--evsh-switching-loader-size) * -0.1);
}
.evsh-switching-loader-tile {
  position: absolute;
  left: 0;
  top: 0;
  width: var(--evsh-switching-loader-size);
  height: var(--evsh-switching-loader-size);
  box-sizing: border-box;
  border-radius: 14px; /* Figma: 14 in every frame; the loop's own rounding (inline) matches it at rest. */
  background: var(--evsh-switching-loader-tile-color);
  color: var(--evsh-white);
  overflow: hidden;
  transform-origin: center;
  will-change: transform, opacity;
}
.evsh-switching-loader-content {
  position: absolute;
  inset: 0;
  /* Follows the tile's (animated, percentage) radius, so what is drawn inside it (the pending fill's border) is not clipped square. */
  border-radius: inherit;
}
/* The artwork slot: 36 px at 12.5% from the corner (the playground's `.logo-slot`). */
.evsh-switching-loader-mark {
  position: absolute;
  left: 12.5%;
  top: 12.5%;
  display: block;
  width: 36px;
  height: 36px;
}
.evsh-switching-loader-photo {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
/* Still fetching / decoding (or failed): held back, so the placeholder fill below is what shows. Kept in the DOM (not
   `display: none`) so the browser loads it and fires `load`. */
.evsh-switching-loader-photo--pending {
  position: absolute;
  inset: 0;
  opacity: 0;
}
/* The placeholder tile's own look (#F1F5F8, 1 px inside border #000 at 4%, Figma 1637:48719), drawn INSIDE the photo tile
   while its image is pending: the photo tile has no fill of its own (its tile colour is transparent). */
.evsh-switching-loader-photo-pending {
  position: absolute;
  inset: 0;
  box-sizing: border-box;
  background: #f1f5f8;
  border: 1px solid rgba(0, 0, 0, 0.04);
  /* The border follows the same curve as the tile (via `.evsh-switching-loader-content`), as the placeholder variant's does. */
  border-radius: inherit;
}
/* Figma 1637:48719: the placeholder tile has a 1 px INSIDE border, #000 at 4%. */
.evsh-switching-loader--placeholder .evsh-switching-loader-tile {
  border: 1px solid rgba(0, 0, 0, 0.04);
}
/* The building glyph is #7A8D97 (`--evsh-neutral-blue-1`) via currentColor, centred in the tile. */
.evsh-switching-loader-placeholder {
  position: absolute;
  top: 50%;
  left: 50%;
  display: block;
  width: 24px;
  height: 24px;
  transform: translate(-50%, -50%);
  color: var(--evsh-neutral-blue-1);
}
.evsh-switching-loader-placeholder svg,
.evsh-switching-loader-mark svg {
  display: block;
}
.evsh-switching-loader-label {
  margin: 0;
  max-width: 100%;
  padding: 0 24px;
  font-weight: 700;
  font-size: 15px;
  line-height: 20px;
  color: var(--evsh-neutral-1);
  text-align: center;
  overflow-wrap: anywhere;
}
/* Centring in every host: a page with a visible scrollbar (or `scrollbar-gutter: stable`) makes html's client width
   narrower than the viewport, so the fixed loader is too and its label sits off centre (measured: 1265 px in 1280 in Coach,
   1270 in Edge). While a loader is on the page the document neither scrolls nor reserves a gutter; the page behind is
   inert and covered anyway, and everything is restored when the loader goes. */
html:has(.evsh-switching-loader) {
  /* `clip`, not `hidden`: it removes the scrollbar without making html a scroll container, so scrollTop is untouched and
     nothing drifts when the loader goes. (Safari before 16 ignores `clip`: there the scrollbar stays and the label is a few px
     off centre, which is what shipped before this rule; nothing else changes.) */
  overflow: clip;
  scrollbar-gutter: auto;
}
/* Reduced motion: one static tile, no traveling stage. */
.evsh-switching-loader--reduced .evsh-switching-loader-stage {
  width: var(--evsh-switching-loader-size);
  height: var(--evsh-switching-loader-size);
  margin-top: 0;
  margin-bottom: 0;
}

/* ============ Error Slot (spec DwabVFr7XU rev 8 L14-L32 · Figma 311:3351 Detailed / 311:3353 Compact) ============
   Moved from the Suite's Tailwind utilities (evolve-suite#1038) so Edge and Coach draw the same slot (pitchiq#489).
   Every value is what those utilities computed: `mx-auto flex w-full max-w-[256px] flex-col items-center gap-[8px]
   px-[8px] py-[32px]`. The Suite loads a Tailwind preflight (`box-sizing: border-box` on everything, zeroed margins);
   Edge has none, so the box model and the label's margin are owned HERE rather than assumed. */
.evsh-error-slot {
  display: flex;
  box-sizing: border-box;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  width: 100%;
  max-width: 256px;
  margin-left: auto;
  margin-right: auto;
  padding: 32px 8px;
}
/* Detailed (311:3334): ONE centred row, gap 0 — the 4px between the words is the Ghost's own left padding.
   Compact (311:3357): the same box STACKED, gap 0 (the 12 declared on 311:3353 spaces one child, so it is not built). */
.evsh-error-slot-row {
  display: flex;
  box-sizing: border-box;
  align-items: center;
  justify-content: center;
  width: 100%;
}
.evsh-error-slot-row--compact { flex-direction: column; }
/* Size, leading and weight come from `.evsh-body-2` (15/20/500, margin 0) on the same element. */
.evsh-error-slot-message {
  color: var(--evsh-neutral-1);
  text-align: center;
}
/* "Try again" is the library Ghost (`.evsh-btn--ghost`, navy) painted red-1 (#b12031, an instance override in Figma).
   TWO classes, not one: the Ghost rule is declared far above and a single-class rule would win or lose on source
   order alone. Unlike the Suite's Tailwind overrides this needs no `!` — the shell sheet is unlayered and so is this.
   In flight it is the Ghost `Disabled` variant `287:720` (#b2b2b2). It is `aria-disabled`, not `disabled`, so the
   shell's `.evsh-btn:disabled { cursor: default }` does not match and the default cursor is restated here. */
.evsh-btn.evsh-error-slot-action { color: var(--evsh-red-1); }
.evsh-btn.evsh-error-slot-action[aria-disabled='true'] {
  color: var(--evsh-neutral-3);
  cursor: default;
}
