/* ============================================================================
 * Layout primitives — the page shell, the auth card, the landing hero.
 * These compose the example apps; each stays token-driven and themeable.
 * The hand-written .ui-side rail and .ui-shell grid are gone: appShell() draws
 * the rail with sidebarNav(), so nothing emitted that markup any more.
 * ========================================================================== */

/* -- The one page shell (appShell) --------------------------------------- */
/* A full-height rail beside one <main>. The rail's own rows are sidebarNav()'s
   (.ui-nav--side), so nothing here restyles a nav item — these rules place the
   rail, its brand head and its signed-in footer, and set the reading column. */
.ui-app {
  display: grid;
  grid-template-columns: max-content 1fr;
  min-height: calc(100vh - var(--ui-app-top, 0px));
  background: var(--bg); /* code-bg: inherit — page background inherits root overrides. */
  color: var(--text);
  font-family: var(--font-sans);
  /* The rail's width, and the one property the fold moves: the nav's own column
     plus the inset either side plus the rule, so the shell has no geometry of its
     own to drift from the rows inside it. why: docs/components.md#the-page-shell */
  --ui-rail-rule: 1px;
  --ui-rail-w: calc(var(--ui-nav-col) + 2 * var(--space-4) + var(--ui-rail-rule));
}
/* With a topbar above it, the shell starts below one. 52px is .topbar's height
   in topbar.css; the topbar sticks so the rail below it can too. */
.ui-app-page { --ui-app-top: 52px; }
.ui-app-page .topbar { position: sticky; top: 0; z-index: var(--z-sticky); }

/* The rail uses the card surface so its labels stay on a reading ground.
   Both themes draw its edge (--rail-edge); light also casts a faint --elev-rail, sideways,
   because the rail is flush to a screen edge. Decided on #730.
   why: docs/foundations.md#elevation
   overflow-y, because the box is pinned at the viewport's height for the whole
   scroll of .ui-app — without it a row past that height paints below the fold
   and no amount of document scrolling brings it back. The rail's own padding is
   what keeps a focus ring out of the clip, so it stays above --ring's spread. */
.ui-app__rail {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
  padding: var(--space-5) var(--space-4);
  border-right: var(--ui-rail-rule) solid var(--rail-edge);
  box-shadow: var(--elev-rail);
  background: var(--surface);
  position: sticky;
  top: var(--ui-app-top, 0px);
  height: calc(100vh - var(--ui-app-top, 0px));
  width: var(--ui-rail-w);
  /* hidden across, auto down: the fold clips what leaves the screen rather than
     laying it out again. Two longhands, not the shorthand — the gates' CSS engine
     does not read the pair. why: docs/components.md#the-page-shell */
  overflow-x: hidden;
  overflow-y: auto;
  transition: width var(--dur-med) var(--ease);
}
/* Each block of the rail keeps the open column while the box closes over it. The
   nav declares its own; these are the two that are not the nav. Scoped to the rail,
   because the topbar layout draws the reader's block in its band instead and a column
   the rail's width is no measurement for a box on a 52px line.
   why: docs/components.md#the-page-shell */
.ui-app__rail > .ui-app__head,
.ui-app__rail > .ui-app__user { width: var(--ui-nav-col); flex: none; }
/* The rail is itself --surface, so nav.css's plate has no step here: 1.000:1, with a
   hairline as the whole resting mark. Both marks go a rung further — the current row
   to the --surface-3 the folded rail already paints, a hovered row to half of it, so
   the pointer stays quieter than the reader's own row in both themes (#475 r34). The
   danger and current rows are named out: this rule outranks nav.css's states. */
.ui-app__rail .ui-nav__item:not(.is-danger):not(.is-active):hover { background: color-mix(in srgb, var(--surface-3) 50%, transparent); /* code-bg: inherit — The wash keeps the containing ground. */ }
.ui-app__rail .ui-nav__item.is-active { background: var(--surface-3); /* code-bg: inherit — The control keeps the containing ground. */ }
.ui-app__brand {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: 2px 6px;
  text-decoration: none;
  /* The lockup leaves on the words' clock, `visibility` with it so the link leaves
     the tab order — `linear`, as every discrete property in the kit is.
     why: docs/components.md#the-page-shell */
  transition: opacity var(--dur-fast) var(--ease), visibility var(--dur-fast) linear;
  /* display: brand — the rail's lockup is the same mark topbar.css sets, and the
     two have to be the same face or the shell reads as two products. */
  font-family: var(--font-display);
  font-weight: var(--weight-semibold);
  letter-spacing: var(--tracking-tight);
}
/* Carries the shell's class as well as its own, for the same reason the rail
   rows do (see nav.css): a host stylesheet's `a:link` is (0,1,1). */
.ui-app .ui-app__brand { color: var(--strong); }
.ui-app__brand:focus-visible { outline: var(--ring); outline-offset: var(--ring-offset); border-radius: var(--radius-xs); }
.ui-app__brand svg { display: block; flex: none; }

/* The head band: the product's mark, and the rail's own control at the far end of
   the same line. `min-width: 0` so a long word shrinks the lockup rather than pushing
   the control off the band. why: docs/components.md#the-page-shell */
.ui-app__head {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding-bottom: var(--space-3);
  border-bottom: 1px solid var(--border);
}
.ui-app__head > .ui-app__brand { min-width: 0; }

/* The nav grows, so the reader block below it pins to the bottom of a full-height
   rail instead of hugging the last nav row. The nav's own footer slot is unused —
   sign out is in the reader's menu, because it ends a session rather than going
   anywhere — so the rail's foot is the account block alone. */
.ui-app__rail .ui-nav--side { flex: 1 1 auto; }

/* Which row is current is structural here, not chromatic: the resting glyphs step
   down so the current one is the brightest whatever --accent is doing, and the
   plate nav.css draws around it carries no hue at all. #475 took the accent bar
   that used to stand beside the glyph off every rail, so the line that grew it to
   the taller tile here went with it. The current glyph's own ink is nav.css's rule
   too, not a copy here: since #429 EVERY rail reads it that way, so the rail's copy
   was the same sentence in a second file. What is left here is the rail's own
   resting step. */
/* Re-tuned at #295, which took the light rail down a step with the page. Below 720px
   the glyph is the whole row, so it is held at text grade rather than at 1.4.11's bar. */
.ui-app__rail .ui-nav__ic svg { opacity: 0.66; }

/* A destructive row of the rail is a navigation row and rests like one; the danger
   intent is the step nav.css writes for it. Both of nav.css's danger states are
   named, not hover alone.
   */
.ui-app__rail .ui-nav__item.is-danger:not(:hover):not(:focus-visible) { color: var(--text); }

/* One rule closes the rail, and it is this one: the nav's footer slot is empty now
   that sign out is in the menu this block opens, so the hairline that fenced sign
   out off moved down onto the block it was fencing. Two of them twenty pixels apart
   read as a third region of the rail rather than as its foot. */
.ui-app__user {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding-top: var(--space-5);
  border-top: 1px solid var(--border);
}
/* A drawer's footer carries no rule above it, so the block keeps the rail's rule out. */
.ui-app__drawer .ui-app__user { padding-top: 0; border-top: 0; }
/* The avatar is the reader block's own glyph, so half the difference between the
   glyph column and itself is the inset that centres it on the line every glyph above
   it stands on. The arithmetic and not the 5.5 it comes to: both numbers are declared
   elsewhere. why: docs/components.md#the-page-shell */
.ui-app { --ui-app-av: 30px; }
.ui-app__av {
  width: var(--ui-app-av); height: var(--ui-app-av); flex: none;
  margin-inline-start: calc((var(--ui-nav-strip) - var(--ui-app-av)) / 2);
  display: grid;
}
/* The mark is the Avatar part in the reader's hue, filling this box like the top bar's. */
.ui-app__av > .ui-avatar { --ui-avatar-size: 100%; }
.ui-app__who { font-size: calc(var(--text-sm) - 0.5px); line-height: 1.3; min-width: 0; }
.ui-app__who b { display: block; color: var(--strong); font-weight: var(--weight-semibold); }
/* nowrap is what makes the ellipsis fire — without it the address wraps, and
   the rail column is max-content, so a long one widens the rail instead. */
.ui-app__who span { display: block; color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* -- the reader's menu ---------------------------------------------------- */
/* The account block is a dropdown() trigger, so it takes the rail's own row skin
   rather than the dropdown's pill — in place it is the last row of the rail. The
   dropdown's own ring is left standing. why: docs/components.md#the-page-shell */
.ui-app__user .ui-dropdown { width: 100%; }
.ui-app .ui-app__user-trigger {
  width: 100%;
  gap: var(--space-2);
  /* As tall as the mark inside it plus its own padding — 30 + 4 + 4, written out
     rather than as the calc() it comes from, and held to that arithmetic by
     stories/apps/shell-states.test.js.
     */
  min-height: 38px;
  padding: var(--space-1) 0;
  border: 0;
  border-radius: var(--radius-sm);
  background: none;
  color: var(--text);
  font-size: inherit;
  font-weight: inherit;
  letter-spacing: normal;
}
.ui-app .ui-app__user-trigger:hover { background: var(--surface); /* code-bg: inherit — control paint; the containing surface is the ground, for a chip and for a band alike. */ }
.ui-app__user-trigger .ui-app__who { flex: 1 1 auto; text-align: left; }
/* The panel is portalled onto <body>, so nothing here may hang off .ui-app: by the
   time it is open it is no longer inside the rail that drew it.
   why: docs/components.md#the-dropdown-panel */
.ui-app__user-panel .ui-dropdown__head { display: flex; flex-direction: column; gap: 2px; }
.ui-app__user-panel .ui-dropdown__head b { display: block; color: var(--strong); font-size: calc(var(--text-sm) - 0.5px); font-weight: var(--weight-semibold); }
/* The address wraps rather than truncating: nothing bounds this line, so
   `text-overflow` would have nothing to fire against — the inert pair
   .ui-app__who's own comment warns about. `anywhere` keeps an address with no
   break in it from widening the panel instead. */
.ui-app__user-panel .ui-dropdown__head span { display: block; color: var(--text); font-size: var(--text-xs); overflow-wrap: anywhere; }

/* The toggle's cell, at the far end of the brand row: the auto margin puts it there
   with or without a wordmark beside it, and the offset below rides it back onto the
   glyph column as the rail closes. `position: relative` and not a transform, because
   the chip inside is `position: fixed`. why: docs/components.md#the-page-shell */
.ui-app__fold-row {
  flex: none;
  margin-inline-start: auto;
  position: relative;
  inset-inline-start: 0;
  transition: inset-inline-start var(--dur-med) var(--ease);
}
/* One box on the glyph column at both widths — --ui-nav-strip, the width nav.css
   derives the closed rail from. `gap: 0` leaves that column the whole of the
   content box, so the label below is squeezed to nothing until the chip rule lifts
   it out. why: docs/components.md#the-page-shell */
.ui-app__fold { width: var(--ui-nav-strip); gap: 0; }
/* The chip rule lifts the name out of the flow, and a 17px glyph is shorter than
   the line the name occupied — so the button lost 0.39px on hover and moved every
   row under it. The glyph box carries the line instead, in both states.
   why: docs/components.md#the-page-shell */
.ui-app__fold .ui-nav__ic { height: 1lh; }
.ui-app .ui-app__fold { color: var(--text); }
.ui-app .ui-app__fold:hover,
.ui-app .ui-app__fold:focus-visible { color: var(--strong); }
/* The seam holds at 9 at both widths, because it is the rail: the compartment it
   cuts off is the narrow one, on the side the rail is on. What travels is the
   chevron, and it says what the press will do rather than repeating the state —
   the same thing the control's own accessible name says. shell.js draws it at
   13..16, Lucide `panel-left-close`; mirrored about 15, the centre of the
   compartment it stands in, it lands at 14..17, which is Lucide
   `panel-left-open`. So the two states are two Lucide glyphs and the CSS between
   them is one reflection, not a hand-drawn position. `transform-origin` is
   written out because a scale, unlike the translate this replaces, is read from
   wherever the origin is — left to the initial 0 0 it would fold the mark
   through the viewBox's left edge.

   The width's own clock, not the words', so the mark and the closing edge arrive
   together; no `!important`, so the reduced-motion net still takes it.
   why: docs/components.md#the-page-shell */
.ui-app__fold .ui-app__fold-arrow {
  transform-box: view-box;
  transform-origin: 15px 12px;
  transition: transform var(--dur-med) var(--ease);
}
.ui-app.is-collapsed .ui-app__fold .ui-app__fold-arrow { transform: scaleX(-1); }

/* -- The collapsed rail --------------------------------------------------- */
/* The reader's own fold, at the widths that have a rail. why: docs/components.md#the-page-shell */
.ui-app.is-collapsed { --ui-rail-w: calc(var(--ui-nav-strip) + 2 * var(--space-4) + var(--ui-rail-rule)); }
:where(.ui-app.is-collapsed) .ui-app__rail .ui-nav__cap,
:where(.ui-app.is-collapsed) .ui-app__rail .ui-nav__label,
:where(.ui-app.is-collapsed) .ui-app__rail .ui-nav__badge,
:where(.ui-app.is-collapsed) .ui-app__rail .ui-nav__caret,
:where(.ui-app.is-collapsed) .ui-app__rail .ui-app__who,
:where(.ui-app.is-collapsed) .ui-app__rail .ui-dropdown__chevron,
:where(.ui-app.is-collapsed) .ui-app__rail .ui-app__brand span { opacity: 0; pointer-events: none; }
/* The toggle rides onto the glyph column — the two widths' own difference — and the
   lockup goes whole, since that column is where its mark stood. Below 720px there is
   no rail, so none of this is drawn. why: docs/components.md#the-page-shell */
:where(.ui-app.is-collapsed) .ui-app__head > .ui-app__fold-row { inset-inline-start: calc(var(--ui-nav-strip) - var(--ui-nav-col)); }
/* Both layouts put the toggle in the head (#732), so it lands where the mark stood.
   why: docs/components.md#the-page-shell */
:where(.ui-app.is-collapsed) .ui-app__rail .ui-app__brand { opacity: 0; visibility: hidden; pointer-events: none; }
:where(.ui-app.is-collapsed) .ui-app__rail .ui-nav__sub { margin-inline-start: 0; padding-inline-start: 0; border-inline-start-width: 0; }
:where(.ui-app.is-collapsed) .ui-app__rail .ui-nav__item:not(:has(.ui-nav__ic))::after { opacity: 0.62; }
/* The current row's plate on the glyph column, and beside it the one thing a bare rail
   does not do: an ordinary row drops its wash here, answered by the floating name below.
   nav.css's folded rules, a rung further. why: docs/components.md#the-page-shell */
:where(.ui-app.is-collapsed) .ui-app__rail .ui-nav__item.is-active { isolation: isolate; background: none; }
:where(.ui-app.is-collapsed) .ui-app__rail .ui-nav__item.is-active::before { content: ""; position: absolute; left: 0; top: 50%; width: var(--ui-nav-strip); height: 100%; border-radius: 10px; transform: translateY(-50%); /* motion: still — a mark on the current row, whose own highlight already transitions; this centres it, it does not travel */ background: var(--surface-3); /* code-bg: inherit — The plate contains no focus target. */ z-index: -1; }
:where(.ui-app.is-collapsed) .ui-app__rail .ui-nav__item:not(.is-danger):not(.is-active):hover { background: none; }
/* A row closes to the glyph column, so the rail's clip does not cut the right side of its
   focus ring. why: docs/components.md#the-page-shell */
:where(.ui-app.is-collapsed) .ui-app__rail .ui-nav__item,
:where(.ui-app.is-collapsed) .ui-app__rail .ui-app__user-trigger { width: var(--ui-nav-strip); }
/* The label IS the chip. `fixed` is the fallback placement, out of the rail's own
   clip; the @supports branch below pins it to the row and is the only one that
   survives a scroll. The toggle's selector is unscoped, because it is wordless at
   both widths.

   It floats by role — a readout placed over the page for as long as a pointer
   rests on a row — so it takes the floating treatment the hover readout takes,
   which is the same shape on the same step: the two-step edge and the drops.
   why: docs/components.md#the-page-shell
   why: docs/foundations.md#elevation */
:where(.ui-app.is-collapsed) .ui-app__rail .ui-nav__item:is(:hover, :focus-visible) > .ui-nav__label,
:where(.ui-app.is-collapsed) .ui-app__rail .ui-app__user-trigger:is(:hover, :focus-visible) > .ui-app__who,
.ui-app__rail .ui-app__fold:is(:hover, :focus-visible) > .ui-nav__label { position: fixed; left: calc(var(--ui-rail-w) + var(--space-2)); z-index: var(--z-dropdown); width: max-content; max-width: 15rem; margin-block: -7px; padding: 6px 10px; border: 1px solid var(--float-edge); box-shadow: inset 0 0 0 1px var(--elev-edge, var(--float-edge-inner)), var(--elev-drop); border-radius: var(--radius-xs); background: var(--surface); /* code-bg: inherit — decorative or text-only paint; nothing nested reads a ground from it. */ color: var(--strong); opacity: 1; pointer-events: none; }
@supports (anchor-name: --a) and (anchor-scope: --a) {
  .ui-app.is-collapsed,
  .ui-app:has(.ui-app__fold) { anchor-scope: --ui-rail; }
  :where(.ui-app.is-collapsed) .ui-app__rail,
  .ui-app__rail:has(.ui-app__fold) { anchor-name: --ui-rail; }
  :where(.ui-app.is-collapsed) .ui-app__rail .ui-nav__item,
  :where(.ui-app.is-collapsed) .ui-app__rail .ui-app__user-trigger,
  .ui-app__rail .ui-app__fold { anchor-name: --ui-rail-row; anchor-scope: --ui-rail-row; }
  :where(.ui-app.is-collapsed) .ui-app__rail .ui-nav__item:is(:hover, :focus-visible) > .ui-nav__label,
  :where(.ui-app.is-collapsed) .ui-app__rail .ui-app__user-trigger:is(:hover, :focus-visible) > .ui-app__who,
  .ui-app__rail .ui-app__fold:is(:hover, :focus-visible) > .ui-nav__label { position-anchor: --ui-rail-row; left: calc(anchor(--ui-rail right) + var(--space-2)); top: anchor(top); bottom: anchor(bottom); align-self: center; margin-block: 0; }
}

/* The reading column centres in the track it is given — without width: 100% it
   was hugging the rail, 519px of dead background on the right at 1728px wide.
   The width is what keeps the auto margins honest: an auto inline margin takes
   a grid item out of stretch, and Chrome then lays it out at its max-content
   width, which is how the payout ledger pushed the page 529px sideways at 375.
   --measure is the ONE source for this width now. shell.js used to carry its
   own copy of the number and a test held the two in step; it now omits the
   property rather than writing a default, so an absent or unparseable maxWidth
   lands here, on the token, instead of on a second literal. See issue #198. */
.ui-app__main {
  min-width: 0;
  width: 100%;
  max-width: var(--ui-app-main, var(--measure));
  margin-inline: auto;
  padding: 40px clamp(24px, 4vw, 56px) 72px;
}
/* The whole of the difference: `centered` is the rule above and `wide` takes its cap
   off. `maxWidth` is the number under either name. why: docs/foundations.md#widths */
.ui-app__main--wide { max-width: var(--ui-app-main, none); }

/* -- The topbar layout ----------------------------------------------------- */
/* The band stands BESIDE the rail, not across the top of both, which is the
   arrangement where the rail's head band and the band close at the same height — what
   the three heights below are for. Level, not one continuous stroke: the rail insets
   its own rule, and in light that half is 1.009:1 on the rail's ground.
   why: docs/components.md#the-second-layout */
.ui-app--topbar { --ui-app-band: 52px; }
.ui-app__well { display: flex; flex-direction: column; min-width: 0; }
.ui-app__bar {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  height: var(--ui-app-band);
  flex: none;
  padding-inline: clamp(24px, 4vw, 56px);
  background: var(--bg); /* code-bg: inherit — page background inherits root overrides. */
  border-bottom: 1px solid var(--border);
  /* Sticky for the reason the rail beside it is: chrome that scrolls away is chrome
     you scroll back up for. */
  position: sticky;
  top: 0;
  z-index: var(--z-sticky);
}
.ui-app__bar-gap { flex: 1 1 auto; }
/* The rail's head takes the band's height, so the rule under the head lands on
   the line the band's own rule lands on. The rail gives up its top inset to buy it. */
.ui-app--topbar > .ui-app__rail { padding-top: 0; }
.ui-app--topbar > .ui-app__rail > .ui-app__head { height: var(--ui-app-band); padding-bottom: 0; }

/* Drawn as a field, behaving as a button. The hairline and the radius are the
   field's — src/styles/input.css:20 `.ui-input,` sets both — because what it looks
   like is the point. The STEP is not, and that is the one thing this control does
   differently from every other field in the kit: the reason is on the declaration
   below. It gives way before anything else on the band.
   why: docs/components.md#the-page-shell */
.ui-app__search {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex: 0 1 var(--panel-sm);
  min-width: 0;
  padding: 7px 10px;
  /* A family alone leaves the browser's leading behind: `font: inherit` first, then
     the one step this row takes. */
  font: inherit;
  font-size: var(--text-sm);
  text-align: start;
  color: var(--text);
  /* The band search uses the same reading surface as other text controls.
     why: docs/foundations.md#elevation */
  background: var(--surface); /* code-bg: inherit — control paint; the containing surface is the ground, for a chip and for a band alike. */
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  cursor: pointer;
  transition: border-color var(--dur-med) var(--ease), color var(--dur-med) var(--ease);
}
.ui-app__search:hover { border-color: var(--border-strong); color: var(--strong); }
/* Every other control on the band takes the kit's ring. This one is a <button>
   without `.ui-btn`, so base.css's shared rule never reached it and focus fell
   through to the browser's own square outline, drawn around a 12px radius.
   why: docs/foundations.md#the-focus-ring */
.ui-app__search:focus-visible { outline: var(--ring); outline-offset: var(--ring-offset); }
/* The same glyph the palette draws in its own search box, at the same box and the
   same stroke: the trigger and the thing it opens are not two search marks.
   src/styles/command-palette.css:108 `.ui-cmdk__search-ic svg` */
.ui-app__search-ic { display: flex; flex: none; }
.ui-app__search-ic svg { width: 17px; height: 17px; stroke: currentColor; fill: none; stroke-width: 2.2; }
/* Part of the button's name, so it is squeezed rather than dropped: an ellipsis
   still leaves the whole string in the tree. */
.ui-app__search-txt { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* The other half of the name, so it never gives way. */
.ui-app__search kbd { flex: none; }
/* The mark alone — no room for two lines on a 52px row. No rule over it, since the
   band's hairline is under the whole of it, and no column, since this is not the rail.
   why: docs/components.md#the-page-shell */
.ui-app__user--bar { flex: none; padding-top: 0; border-top: 0; }
/* The switch is a 34px square and stays one: the trigger above is the thing on this
   band that gives way, and a flex item with the default shrink does not wait its
   turn. Measured at 390 with a control beside the trigger, the React band's switch
   drew 19x34 and the strategy topbar's drew 28.47x34 — a square glyph plate squashed,
   and a tap zone the sheet could only clamp to 29 or 39.
   Pinning the strategy topbar's half of this rule does not make its row narrower: it
   moves a 5.53px shortfall onto the account block's trailing gutter instead, because
   nothing before it in that row has width to spare. topbar.css's --space-2 comment
   names that choice and the gutter it leaves.
   why: docs/components.md#the-page-shell */
.ui-app__bar .toggle, .topbar__in .toggle { flex: none; }
.ui-app__user--bar .ui-app__av { margin-inline-start: 0; }
/* Crumb, then the title block. At --space-2 the crumb sat 8px off the h1 and
   the h1 8px off its subtitle, so all three ranks read as one. */
.ui-app__main .ui-nav--crumbs { margin-bottom: var(--space-5); }
.ui-app__main h1 {
  color: var(--strong);
  /* rank: page-title */
  font-size: var(--text-2xl);
  font-weight: var(--weight-bold);
  line-height: 1.1;
  letter-spacing: var(--tracking-tight);
  margin-bottom: 8px;
}
.ui-app__sub { color: var(--text); font-size: calc(var(--text-base) + 0.5px); line-height: 1.6; max-width: var(--prose-body); margin-bottom: var(--space-8); }
/* A page may open on its title alone; then the title carries the head's step. */
.ui-app__main > h1:not(:has(+ .ui-app__sub)) { margin-bottom: var(--space-8); }
.ui-app__body { display: flex; flex-direction: column; gap: var(--space-5); }
/* A flex child's min-width is its content by default, so one wide block — a
   ledger, a code sample — pushes the whole page sideways instead of scrolling
   inside itself. This is what keeps the shell narrower than the window. */
.ui-app__body > * { min-width: 0; }

/* -- A row of controls above a list --------------------------------------- */
/* Filter fields, a segmented switch, the button that applies them. The row is
   one line wherever the line holds and breaks where it does not, and the text
   field is what makes that one decision instead of two: an .ui-input is
   width:100%, so as a flex item its hypothetical size is the whole container
   and wrapping alone put every control beside it on a second line at 1280 as
   readily as at 375. Given a basis of its own the field shares the line, takes
   the slack when there is any, and the line breaks only once the field would be
   squeezed under that basis — 6rem, about as narrow as a placeholder plus a word
   of what you typed. min-width:0 keeps an input's automatic minimum (its `size`
   attribute, ~20 characters) from setting a floor well above the basis. */
.ui-toolbar { display: flex; flex-wrap: wrap; gap: 10px; }
/* A control row belongs to the block it narrows rather than standing beside it,
   so it sits closer than .ui-app__body's gap between two blocks that are each
   their own subject. Not on the last row: there the gap is the page's bottom
   and nothing is being narrowed. why: Artur's review of the Finance report, #505 */
.ui-app__body > .ui-toolbar:not(:last-child) { margin-bottom: calc(var(--space-3) - var(--space-5)); }
.ui-toolbar > .ui-input,
.ui-toolbar > .ui-input-group { flex: 1 1 6rem; min-width: 0; }
/* A row that offers the block two unrelated things — what to narrow it by, and
   what to do with it — puts the second at the far end instead of beside the
   first. The auto margin is the same one the rail's fold toggle rides to the end
   of the brand row, so the gap between the two groups is whatever the line has
   left and the row still wraps on its own. Above the phone step a wrapped line
   keeps the end it was sent to; below it there is no line, and the rule in the
   query further down takes the margin off.
   why: Artur's review of the Finance report, #505 */
.ui-toolbar--split > :last-child { margin-inline-start: auto; }
/* At one column the 6rem basis stops being a floor and becomes the field's size:
   a row with two narrow chips beside it keeps all three on the line and cuts the
   field's own placeholder off. why: docs/components.md#a-toolbar-at-one-column */
@media (max-width: 560px) {
  .ui-toolbar > .ui-input,
  .ui-toolbar > .ui-input-group { flex-basis: 100%; }
  /* The end control stays IN the row at this width rather than dropping under it.
     A flex line breaks on hypothetical sizes, so a group whose content is wider
     than the line takes that line alone however much it shrinks afterwards: at
     390 the report's filter group asks for about 340px of chips and clear in a
     284px row, so the 36px export went to a line of its own, left-aligned under
     the clear button — which is the reading `--split` exists to prevent, drawn
     vertically. Given a basis the group no longer claims the whole line, the end
     control fits beside it, and the group grows into what is left. 60% rather
     than half because at 560 half still left room for the group beside the
     period strip, which put the export back under the row.

     The auto margin goes: with the group grown there is no slack for it to take,
     and on the line it did have it left the control against the right edge of an
     otherwise empty row — a mark that came loose rather than what to do with the
     block, which is what Artur marked on round r31. `align-self` is what keeps
     the control its own height beside a group two lines tall; it sits at the
     row's top end, the corner it keeps above this step.
     why: Artur's review of the Finance report, rounds r31 and r34, #505 */
  .ui-toolbar--split > :nth-last-child(2) { flex: 1 1 60%; min-width: 0; }
  .ui-toolbar--split > :last-child { margin-inline-start: 0; align-self: start; }
}

/* -- The phone shell ------------------------------------------------------ */
/* Below this width there is no rail and no strip. A band at the top holds the menu
   control and the brand's mark, the control opens the same rail in the kit's left
   drawer, and the reading column takes the whole width. The band, the control and the
   drawer exist at every width and are not drawn above it, so the rail's rows above
   720px are the rows they were. why: docs/components.md#the-page-shell */
.ui-app__bar--phone { display: none; }
.ui-btn.ui-app__menu { display: none; }
.ui-app__brand--band { display: none; align-items: center; justify-content: center; min-width: 44px; min-height: 44px; /* a finger's box around a 24px mark */ }
.ui-app__drawer { display: none; }
/* The drawer panel is --bg-elevated, which --surface matches exactly in light, so nav.css's
   plate measures 1.00:1 there. The current row and the hover wash take the rung the rail
   takes (--surface-3), and the count keeps its --surface pill on that plate. Dark already
   has a step and keeps it. why: docs/components.md#the-page-shell */
:where(:root[data-theme="light"]) .ui-app__drawer .ui-nav__item:not(.is-danger):not(.is-active):hover { background: color-mix(in srgb, var(--surface-3) 50%, transparent); /* code-bg: inherit — The wash keeps the containing ground. */ }
:where(:root[data-theme="light"]) .ui-app__drawer .ui-nav__item.is-active { background: var(--surface-3); /* code-bg: inherit — The control keeps the containing ground. */ }
@media (max-width: 720px) {
  /* A column, not a grid: the band sticks to the top of the shell, and a sticky grid
     item sticks only inside its own row. */
  .ui-app { display: flex; flex-direction: column; --ui-app-band: 52px; }
  .ui-app > .ui-app__rail { display: none; }
  .ui-app__well { flex: 1 1 auto; }
  .ui-app > .ui-app__main { flex: 1 1 auto; }
  .ui-app > .ui-app__bar--phone { display: flex; }
  .ui-btn.ui-app__menu { display: inline-flex; }
  .ui-app__brand--band { display: flex; }
  /* The first visible item lines up with the page. A leading menu uses the smaller
     inset to keep its phone position in both shells. */
  .ui-app__bar { padding-inline: var(--space-4); }
  .ui-app__bar:has(> .ui-app__menu:first-child) { padding-inline-start: var(--space-2); }
  /* The drawer paints itself and sits out of the flow, so its wrapper takes no height. */
  .ui-app__drawer { display: block; }
  /* The rail's rows are the drawer's rows: the column fills the panel, and the foot
     starts at the panel's edge like the rows above it. */
  .ui-app__drawer .ui-nav--side,
  .ui-app__drawer .ui-nav--side > * { width: 100%; }
  /* The panel's title names the list, so the section's own caption stays out. */
  .ui-app__drawer .ui-nav__cap { display: none; }
  .ui-app__drawer .ui-drawer__footer { justify-content: flex-start; }
  .ui-app__drawer .ui-app__user { width: 100%; }
  /* A finger is the only pointer this width has, so a row and the account block are
     held to 44px. why: docs/components.md#the-page-shell */
  .ui-app__drawer .ui-nav__item,
  .ui-app__drawer .ui-app__user-trigger { min-height: 44px; }
  .ui-app__main { padding: 28px var(--space-4) 56px; }
  .ui-app__main h1 { font-size: calc(var(--text-xl) + 3px); }
  /* A settings row is label-beside-control, and below this step any control that does
     not fit beside its label drops under it. A row that holds a choice wraps this way at
     every width, in card.css. Scoped to the shell, because a card row outside a shell
     is somebody else's container to size. */
  .ui-app__main .ui-card__row { flex-wrap: wrap; gap: var(--space-3); }
}

/* -- The band's search at one column --------------------------------------- */
/* The band holds the trigger, the reader's mark and whatever the page puts between
   them, so the sentence broke mid-word — "Search or run a co…". The search glyph takes
   its place (#729). Neither the sentence nor the cap is `display: none`: both are inside
   the button's accessible name — the cap by Artur's call on #308 — so they are
   clipped the way `.ui-sr` in loading.css clips, and the name holds at every width.
   why: docs/components.md#the-page-shell */
@media (max-width: 560px) {
  .ui-app__search-txt,
  .ui-app__search kbd {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
  }
}

/* -- Auth card (sign-in / consent) — centered, glowing --------------------- */
.ui-auth {
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: 40px 20px;
  position: relative;
  overflow: hidden;
}
.ui-auth__card {
  position: relative;
  z-index: 1;
  width: 100%;
  max-width: var(--panel-md);
  /* Floats over a spotlit page, so it takes the floating step and the floating
     treatment: the two-step edge in dark, one line and the drop in light.
     why: docs/foundations.md#elevation */
  background: var(--bg-elevated);
  border: 1px solid var(--float-edge);
  box-shadow: inset 0 0 0 1px var(--elev-edge, var(--float-edge-inner)), var(--elev-drop);
  border-radius: 22px;
  padding: 38px 36px 32px;
}
.ui-auth__brand { display: inline-flex; align-items: center; gap: 10px; margin-bottom: 26px; }
.ui-auth__brand span { font-size: calc(var(--text-base) + 0.5px); font-weight: 600; color: var(--strong); }
.ui-auth__title { color: var(--strong); font-size: calc(var(--text-xl) + 2px); font-weight: 600; letter-spacing: -0.01em; margin-bottom: 7px; }
.ui-auth__sub { color: var(--text); font-size: var(--text-base); line-height: 1.55; margin-bottom: 26px; }
.ui-auth__foot { margin-top: 24px; text-align: center; font-size: var(--text-sm); color: var(--text); }
.ui-auth__foot a { color: var(--accent); }
.ui-divider { display: flex; align-items: center; gap: 14px; margin: 22px 0; color: var(--text); font-size: calc(var(--text-xs) + 1px); }
.ui-divider::before, .ui-divider::after { content: ""; flex: 1; height: 1px; background: var(--border); }

/* App scope grant list (OAuth consent) */
.ui-scopes { display: flex; flex-direction: column; gap: 2px; margin: 4px 0 8px; }
.ui-scope { display: flex; gap: 13px; padding: 13px 4px; }
.ui-scope + .ui-scope { border-top: 1px solid var(--border); }
.ui-scope__icon {
  width: 38px; height: 38px; flex: none;
  border-radius: 11px;
  display: grid; place-items: center;
  background: var(--glow-purple); /* code-bg: inherit — a wash paints over a ground and behind a chip, so the opaque ground below it is the step. */
  color: var(--accent);
}
.ui-scope__icon svg { width: 21px; height: 21px; stroke: currentColor; fill: none; stroke-width: 1.8; }
.ui-scope__t { color: var(--strong); font-size: calc(var(--text-base) - 0.5px); font-weight: 500; }
.ui-scope__d { color: var(--text); font-size: var(--text-sm); margin-top: 2px; line-height: 1.5; }

/* -- Landing hero -------------------------------------------------------- */
.ui-hero {
  position: relative;
  overflow: hidden;
  padding: clamp(60px, 12vh, 130px) 0 clamp(50px, 9vh, 100px);
  text-align: center;
}
.ui-hero__eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: calc(var(--text-xs) + 1px);
  letter-spacing: 0.06em;
  color: var(--purple-mid);
  background: var(--glow-purple); /* code-bg: inherit — a wash paints over a ground and behind a chip, so the opaque ground below it is the step. */
  border-radius: 999px;
  padding: 6px 14px;
  margin-bottom: 26px;
  white-space: nowrap;
}
.ui-hero__title {
  font-size: clamp(38px, 6.4vw, 66px);
  line-height: 1.04;
  letter-spacing: -0.025em;
  font-weight: 700;
  color: var(--strong);
  max-width: var(--prose-display);
  margin: 0 auto 22px;
}
.ui-hero__title .grad {
  background: linear-gradient(120deg, var(--grad-from), var(--grad-to));
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
}
.ui-hero__sub {
  font-size: clamp(16px, 2.1vw, 20px);
  color: var(--text);
  max-width: var(--prose-lede);
  margin: 0 auto 34px;
  line-height: 1.6;
}
.ui-hero__cta { display: inline-flex; gap: 12px; flex-wrap: wrap; justify-content: center; }

/* Feature grid */
.ui-features {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 20px;
  /* Was 1080px — a third page-scale number with nothing arguing for it. The
     feature row is a top-level band and lines up with the page it sits on. */
  max-width: var(--container);
  margin: 0 auto;
  padding: 0 26px;
}
.ui-feature__icon {
  width: 46px; height: 46px;
  border-radius: 13px;
  display: grid; place-items: center;
  background: var(--glow-purple); /* code-bg: inherit — a wash paints over a ground and behind a chip, so the opaque ground below it is the step. */
  color: var(--accent);
  margin-bottom: 16px;
}
.ui-feature__icon svg { width: 25px; height: 25px; stroke: currentColor; fill: none; stroke-width: 1.8; }
.ui-feature h3 { color: var(--strong); font-size: calc(var(--text-lg) - 1px); font-weight: 600; margin-bottom: 7px; }
.ui-feature p { color: var(--text); font-size: var(--text-base); line-height: 1.6; }

.ui-section-head { text-align: center; max-width: var(--panel-lg); margin: 0 auto clamp(40px, 6vh, 64px); }
.ui-section-head h2 { color: var(--strong); font-size: clamp(28px, 4vw, 40px); font-weight: 700; letter-spacing: -0.02em; margin-bottom: 14px; }
.ui-section-head p { color: var(--text); font-size: calc(var(--text-lg) - 1px); line-height: 1.6; }

/* Forced colours replace every author background, so a selected item takes the system
   pair as its fill. `forced-color-adjust: none` is what keeps the pair from sitting on
   Chromium's Canvas backplate, which would leave the label unreadable. Focus keeps its
   own ring, drawn in CanvasText so the fill is never mistaken for it. The pair is
   `!important` so the shell's danger ink and every hover or focus state yield to it. */
@media (forced-colors: active) {
  .ui-app__rail .ui-nav__item:is(.is-active, .is-current):not(.is-disabled) { forced-color-adjust: none; background: Highlight !important; color: HighlightText !important; --ring: var(--ring-width) solid CanvasText; }
  .ui-app__rail .ui-nav__item:is(.is-active, .is-current).is-disabled { forced-color-adjust: none; background: GrayText !important; color: Canvas !important; /* muted-ink: state — unavailable control */ }
  .ui-app__rail .ui-nav__item.is-active:not(.is-disabled)::before { background: Highlight; }
  .ui-app__rail .ui-nav__item.is-active.is-disabled::before { background: GrayText; }
  /* A folded row is only an icon strip: the plate carries the fill, so the row paints none. */
  .ui-app.is-collapsed .ui-app__rail .ui-nav__item:is(.is-active, .is-current) { isolation: isolate; background: none !important; }
  .ui-app.is-collapsed .ui-app__rail .ui-nav__item.is-current:not(.is-active)::before {
    content: ""; position: absolute; left: 0; top: 50%; width: var(--ui-nav-strip); height: 100%; border-radius: 10px;
    transform: translateY(-50%); /* motion: still — a mark on the current group, centred and not travelling */
    z-index: -1;
  }
  .ui-app.is-collapsed .ui-app__rail .ui-nav__item.is-current:not(.is-active):not(.is-disabled)::before { background: Highlight; }
  .ui-app.is-collapsed .ui-app__rail .ui-nav__item.is-current.is-disabled:not(.is-active)::before { background: GrayText; }
  /* The floating name of a selected folded row inherits the row's adjust, so it is painted in system colours. */
  .ui-app.is-collapsed .ui-app__rail .ui-nav__item:is(.is-active, .is-current):is(:hover, :focus-visible) > .ui-nav__label {
    forced-color-adjust: auto; background: Canvas; color: CanvasText; border-color: CanvasText;
  }
}
