/* @version 20261002e */
/* The UI layer: shadcn/ui's scale and shapes, on One Ticket's own colours.
 *
 * ---------------------------------------------------------------------------
 * Why this file exists
 *
 * ot-layout.css holds the tokens - colour, radius, elevation, easing - and
 * ot-app.css holds the handful of shapes they get assembled into. Between them
 * they answer "what colour" and "how round". Neither answers the two questions
 * the eye is most sensitive to: HOW BIG IS THE TEXT and HOW MUCH SPACE IS
 * BETWEEN THINGS.
 *
 * Nothing answered them, so every page answered for itself. A count across the
 * eight signed-in pages: 18 distinct font sizes, six of them inside a 3px band
 * (11.5, 12, 12.5, 13, 13.5, 14, 14.5). Twelve distinct gap values. .ot-card
 * was `padding: 15px 17px 16px` - three different numbers for one box.
 *
 * None of that is visible as a bug. It reads as slight inconsistency, which is
 * exactly what somebody means when they look at a competitor and say it "just
 * looks better". Steps that differ, but not differently enough to be read as
 * rank.
 *
 * ---------------------------------------------------------------------------
 * Why shadcn's numbers and not our own
 *
 * shadcn/ui is not a library - there is nothing to install. It is a set of
 * components you copy, made of Tailwind classes over Radix primitives. What
 * you actually adopt is its SCALE and its SHAPES, and those are the parts that
 * travel without a bundler.
 *
 * Tailwind's contribution is that you cannot type 12.5px. That constraint is
 * the whole of the effect, and it is the one thing a stylesheet cannot enforce
 * on its own - so test/the-scale-is-the-whole-of-it.test.js does it instead:
 * a raw font-size in pixels outside this file fails the suite. That is the
 * same job Tailwind's build would have done, without a build.
 *
 * ---------------------------------------------------------------------------
 * How to adopt it
 *
 *   <link rel="stylesheet" href="/ot-layout.css">
 *   <link rel="stylesheet" href="/ot-app.css">
 *   <link rel="stylesheet" href="/ot-ui.css">
 *
 * Then delete the page's own CSS for anything below. Half-adopting is worse
 * than not adopting: two definitions of a card, one of them local and stale,
 * is the bug this file is meant to end - the same argument ot-app.css makes at
 * the top of itself.
 *
 * Colour is deliberately NOT redefined here. Every value below reads a token
 * from ot-layout.css, so all fifty-two schemes in color-schemes.js keep
 * working and a dark page needs no second copy of any of this.
 * ------------------------------------------------------------------------ */

:root {
  /* ---- Type ------------------------------------------------------------
   * Tailwind's ramp, which is also shadcn's. Six steps, each with its line
   * height attached, because a size without a leading is half a decision and
   * the other half then gets made per page.
   *
   * Six, not eighteen. Anything that wants a size between two rungs wants one
   * of the two rungs. */
  --ui-text-xs: 12px;     --ui-leading-xs: 16px;    /* badges, eyebrows, meta */
  --ui-text-sm: 14px;     --ui-leading-sm: 20px;    /* body, rows, labels     */
  --ui-text-base: 16px;   --ui-leading-base: 24px;  /* card titles, inputs    */
  --ui-text-lg: 18px;     --ui-leading-lg: 28px;    /* a heading in a sheet   */
  --ui-text-xl: 20px;     --ui-leading-xl: 28px;    /* section heads          */
  --ui-text-2xl: 24px;    --ui-leading-2xl: 32px;   /* page title             */

  /* ---- Display ---------------------------------------------------------
   * Headline type, which is a different job from the six steps above and is
   * deliberately kept out of them. Those are UI text: a row, a label, a badge,
   * a card title - sizes that have to agree with each other across a whole
   * screen. A display size is set once, on one thing, and its job is to be the
   * largest thing on the page. The builder's event-name field is the case that
   * forced this: it is the title of the thing being made, typed at the size it
   * will be read at, and 24px is not that.
   *
   * Three rungs, which are Tailwind's 3xl/4xl/5xl, so a headline that has to
   * come down has somewhere to land instead of somebody typing 32.
   *
   * These are NOT a seventh, eighth and ninth text step, and the test keeps
   * them apart: the text scale stays six, and reaching for a display size is
   * a decision you have to spell out. */
  --ui-display-sm: 30px;  --ui-leading-display-sm: 34px;
  --ui-display-md: 36px;  --ui-leading-display-md: 40px;
  --ui-display-lg: 48px;  --ui-leading-display-lg: 52px;

  /* ---- Space -----------------------------------------------------------
   * Tailwind's 4px grid, named by its step so the name says the ratio. The
   * jump from 6 to 8 is deliberate: 28 exists in Tailwind and has never once
   * been the right answer here. */
  --ui-1: 4px;    /* icon to its own label            */
  --ui-2: 8px;    /* inside a control                 */
  --ui-3: 12px;   /* chip to text, row padding        */
  --ui-4: 16px;   /* page gutter, gap between cards   */
  --ui-5: 20px;   /* card padding                     */
  --ui-6: 24px;   /* between sections                 */
  --ui-8: 32px;   /* between parts of a page          */
  --ui-10: 40px;  /* control height                   */

  /* ---- Radius ----------------------------------------------------------
   * shadcn's --radius is 0.625rem and it derives the rest from it: sm is
   * radius-4, md is radius-2, lg is radius, xl is radius+4. ot-layout.css
   * already has this scale at 8/12/16/20; these are the shadcn values, and
   * the two coexist because a page adopts one file or the other, never half
   * of each. */
  --ui-radius: 10px;
  --ui-radius-sm: 6px;
  --ui-radius-md: 8px;
  --ui-radius-lg: 10px;
  --ui-radius-xl: 14px;

  /* ---- The one shadcn token we do not already have ----------------------
   * A card's shadow. shadcn's cards are borderless with a small shadow on
   * white; ours are bordered, because a tinted ground needs the edge. Both,
   * lightly: the border does the work and the shadow only lifts it. */
  --ui-shadow-card: 0 1px 2px rgba(16, 24, 40, 0.04);
  --ui-shadow-raised: 0 1px 3px rgba(16, 24, 40, 0.10), 0 8px 24px -8px rgba(16, 24, 40, 0.16);
}

/* ---------------------------------------------------------------------------
 * Icons
 *
 * lucide, as one sprite (/ot-icons.svg), referenced by name:
 *
 *   <svg class="ui-i" aria-hidden="true"><use href="/ot-icons.svg#calendar"/></svg>
 *
 * One set, one size, one stroke weight, on every row that has one. That
 * uniformity is a surprising amount of what reads as polish: a page with
 * icons on three rows out of eight reads as unfinished, and a page whose
 * icons are 16, 18, 20 and 21px reads as assembled from parts.
 *
 * currentColor throughout, so an icon is the colour of the text beside it and
 * never needs its own rule.
 * ------------------------------------------------------------------------ */

.ui-i {
  width: 18px;
  height: 18px;
  flex: none;
  display: block;
  color: inherit;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.ui-i-sm { width: 16px; height: 16px; }
.ui-i-lg { width: 20px; height: 20px; }

/* ---------------------------------------------------------------------------
 * Card
 *
 * shadcn's Card is a bordered box with a head (title + description) and a
 * body, and EQUAL padding on all four sides. The equal padding is not a
 * detail: .ot-card is 15px 17px 16px, and three numbers for one box is the
 * kind of thing nobody can name and everybody can see.
 * ------------------------------------------------------------------------ */

.ui-card {
  background: var(--ot-surface);
  border: 1px solid var(--ot-border);
  border-radius: var(--ui-radius-xl);
  box-shadow: var(--ui-shadow-card);
  min-width: 0;
}

/* A card lifts off the ground it sits on.
 *
 * shadcn keeps --background and --card apart, and that gap is the whole reason
 * a card reads as an OBJECT rather than as a fenced-off region of the page.
 * color-schemes.js sets --ot-surface equal to --ot-bg on every light scheme,
 * on the stated reasoning that "the ground and the panels sit at the same
 * tint... with the borders doing the separating, is what light actually means
 * here". That reasoning is sound for a panel that holds a table. It is what
 * makes a page of small cards read flat, because a border alone cannot say
 * "above" - only lighter can.
 *
 * Scoped to .ui-card, so nothing else in fifty-two schemes moves. On the white
 * and gradient surfaces the ground is already white and this changes nothing.
 * A dark scheme keeps --ot-surface, which is already lighter than its ground.
 */
:root[data-ot-surface="light"] .ui-card { background: #ffffff; }

/* Unless something nearer says otherwise.
 *
 * The rule above reads the surface off <html>, on the assumption that a page
 * has one scheme. new-event.html does not: the builder paints .ne-shell with
 * applyColorScheme so the organiser sees the event's own colours while making
 * it, and deliberately stops there - the shell contains the nav rail and every
 * other route, and a dark event used to turn the whole app dark and leave it
 * that way. So <html> stays on the ORGANISER's light scheme while a subtree of
 * it is dark, the rule above matches anyway, and the cards came out white with
 * the dark scheme's pale ink on them. Unreadable, and only on the one page
 * that does the right thing with scoping.
 *
 * Same specificity as the rule above - :root, an attribute, a class against an
 * attribute, a :not() taking its argument's, a class - so source order decides
 * and this one is second on purpose. */
[data-ot-surface]:not([data-ot-surface="light"]) .ui-card { background: var(--ot-surface); }
.ui-card-pad { padding: var(--ui-5); }
.ui-card-raised { box-shadow: var(--ui-shadow-raised); }

.ui-card-head {
  display: flex;
  align-items: flex-start;
  gap: var(--ui-3);
  padding: var(--ui-5) var(--ui-5) 0;
}
.ui-card-head + .ui-card-body { padding-top: var(--ui-4); }
.ui-card-body { padding: var(--ui-5); }

.ui-card-title {
  font-size: var(--ui-text-base);
  line-height: var(--ui-leading-base);
  font-weight: 600;
  color: var(--ot-text);
  margin: 0;
}
.ui-card-desc {
  font-size: var(--ui-text-sm);
  line-height: var(--ui-leading-sm);
  color: var(--ot-muted);
  margin: 2px 0 0;
}

/* ---------------------------------------------------------------------------
 * Row
 *
 * The object this whole file is really for. A list row is an icon chip, a
 * title, a line under it, and something on the right.
 *
 * The CHIP IS A FIXED SIZE, and that is the point. Because it never changes
 * width, the text after it starts at the same x on every row of every list on
 * every page - and that single vertical edge is most of what somebody is
 * reading when they say a competitor's screens look "evenly spaced". It is
 * not the spacing between the rows. It is that the rows agree with each other.
 * ------------------------------------------------------------------------ */

.ui-list {
  display: flex;
  flex-direction: column;
}
/* Between the rows, not around them: a list inside a card wants its rule to
   run the full width of the card, and a border-bottom on the last row draws a
   line against the card's own edge. */
.ui-list > * + * { border-top: 1px solid var(--ot-border); }

.ui-row {
  display: flex;
  align-items: center;
  gap: var(--ui-3);
  padding: var(--ui-3) var(--ui-4);
  min-height: 44px;
  color: var(--ot-text);
  text-decoration: none;
  background: transparent;
  border: 0;
  font-family: inherit;
  text-align: left;
  width: 100%;
  box-sizing: border-box;
}
a.ui-row, button.ui-row { cursor: pointer; }
@media (hover: hover) {
  /* A row in a list of rows takes a quiet fill: an outline round one row
     of a stack read as a box drawn in the middle of the card ("I don't like
     the hover outline on this section"). */
  a.ui-row:hover, button.ui-row:hover { background: var(--ot-surface-2); }
  /* A row that is the whole card - About's "Add a description" - takes the
     card's outline instead, like a ticket card or a settings tile ("I prefer
     the outline hover effect"). Drawn inside, so nothing around it moves. */
  .ui-list > a.ui-row:only-child:hover, .ui-list > button.ui-row:only-child:hover {
    background: transparent; box-shadow: inset 0 0 0 1.5px var(--ot-accent); border-radius: inherit;
  }
}
a.ui-row:focus-visible, button.ui-row:focus-visible {
  outline: 2px solid var(--ot-accent);
  outline-offset: -2px;
}

.ui-row-chip {
  width: 36px;
  height: 36px;
  flex: none;
  border-radius: var(--ui-radius-lg);
  background: var(--ot-surface-2);
  color: var(--ot-muted);
  display: flex;
  align-items: center;
  justify-content: center;
}
.ui-row-chip.is-accent { background: var(--ot-accent-soft); color: var(--ot-link); }

.ui-row-main {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.ui-row-title {
  font-size: var(--ui-text-sm);
  line-height: var(--ui-leading-sm);
  font-weight: 600;
  overflow-wrap: anywhere;
}
/* Two text roles per row and never a third. The moment a row carries three
   sizes it stops being one object. */
.ui-row-sub {
  font-size: var(--ui-text-sm);
  line-height: var(--ui-leading-sm);
  color: var(--ot-muted);
  overflow-wrap: anywhere;
}
.ui-row-end {
  flex: none;
  display: flex;
  align-items: center;
  gap: var(--ui-2);
  font-size: var(--ui-text-sm);
  line-height: var(--ui-leading-sm);
  color: var(--ot-muted);
  font-variant-numeric: tabular-nums;
}
.ui-row-chev { color: var(--ot-faint); }

/* ---------------------------------------------------------------------------
 * Badge
 * ------------------------------------------------------------------------ */

.ui-badge {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  height: 24px;
  padding: 0 10px;
  border-radius: 999px;
  /* A ring in the badge's own ink, a quarter strength, so the tint has an
     edge and every tone gets one without saying it again. Drawn from
     currentColor, which is why the tones below only name a background and
     a colour. The transparent line is for a browser without color-mix. */
  border: 1px solid transparent;
  border-color: color-mix(in srgb, currentColor 26%, transparent);
  font-size: var(--ui-text-xs);
  line-height: var(--ui-leading-xs);
  font-weight: 700;
  white-space: nowrap;
  /* Never narrower than what it holds. Safari on a phone could size the tag
     to a squeezed table column, so the box stopped short and the label ran
     out through its right edge ("Active" in Subscriptions). */
  min-width: max-content;
  flex-shrink: 0;
}
/* The icon is the tag's own drawing: the line in the ink and the shapes it
   closes filled with a wash of the same ink, so a person, a flame or a clock
   reads as a small picture rather than a wire. Pick a glyph with a body -
   users, flame, hourglass, a circle - over a bare tick or chevron, whose
   fill is a wedge nobody drew. */
.ui-badge > svg {
  width: 14px;
  height: 14px;
  flex: none;
  margin-left: -3px;
  fill: color-mix(in srgb, currentColor 24%, transparent);
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}
/* The size a tag takes on its own, outside a table: on a card or beside a
   title, where it is one of two or three things rather than one of three
   hundred. */
.ui-badge.is-lg { height: 32px; padding: 0 14px; gap: 7px; font-size: var(--ui-text-sm); }
.ui-badge.is-lg > svg { width: 17px; height: 17px; margin-left: -4px; }
.ui-badge.is-outline { border-color: var(--ot-border); color: var(--ot-muted); }
.ui-badge.is-quiet { background: var(--ot-surface-2); color: var(--ot-muted); }
.ui-badge.is-accent { background: var(--ot-accent-soft); color: var(--ot-link); }
/* A tint, like is-danger beside it. This was --ot-success-surface, a solid
   mint meant for a button: the only place it was used was the specimen page,
   and the moment a real status column took it, three hundred rows of "Paid"
   would have been three hundred solid mint pills. A badge states a fact
   quietly; a button asks for a press. */
.ui-badge.is-ok { background: var(--ot-success-bg); color: var(--ot-success); }
.ui-badge.is-danger { background: var(--ot-danger-bg); color: var(--ot-danger); }
/* Waiting, held, marked as spam - neither good nor bad. */
.ui-badge.is-warn { background: var(--ot-warn-bg); color: var(--ot-warn); }

/* ---------------------------------------------------------------------------
 * Button
 *
 * shadcn's four variants, at its default height of 36 - except that 36 is
 * under the 44 a thumb reliably hits, so the touch floor wins and the default
 * is 40 with a 44 minimum tap area on the icon-only one.
 * ------------------------------------------------------------------------ */

.ui-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--ui-2);
  height: var(--ui-10);
  padding: 0 var(--ui-4);
  border-radius: var(--ui-radius-md);
  border: 1px solid transparent;
  font-family: inherit;
  font-size: var(--ui-text-sm);
  line-height: var(--ui-leading-sm);
  font-weight: 600;
  cursor: pointer;
  text-decoration: none;
  white-space: nowrap;
  transition: background .15s ease, border-color .15s ease, color .15s ease;
}
.ui-btn:focus-visible { outline: 2px solid var(--ot-accent); outline-offset: 2px; }
.ui-btn[disabled] { opacity: .5; cursor: default; }

.ui-btn.is-primary { background: var(--ot-cta); color: var(--ot-cta-ink); }
.ui-btn.is-outline { background: var(--ot-surface); border-color: var(--ot-border-strong); color: var(--ot-text); }
/* Cancel beside a primary: quiet, and never the browser's own blue - a
   <button> with no colour of its own is link-blue on iOS. */
.ui-btn.is-secondary { background: var(--ot-pop-surface-2, var(--ot-surface-2)); border-color: var(--ot-border); color: var(--ot-text); }
.ui-btn.is-secondary:hover { border-color: var(--ot-border-strong); }
.ui-btn.is-ghost { background: transparent; color: var(--ot-muted); }
.ui-btn.is-danger { background: var(--ot-danger); color: #ffffff; }
/* Square, and 44 wide whatever is in it. An icon-only control is the one that
   most often ends up a 32px target, and it is also the one with no label to
   aim at. */
.ui-btn.is-icon { width: 44px; height: 44px; padding: 0; }

@media (hover: hover) {
  .ui-btn.is-primary:hover { background: var(--ot-cta-hover); }
  .ui-btn.is-outline:hover { border-color: var(--ot-accent); }
  .ui-btn.is-ghost:hover { background: var(--ot-surface-2); color: var(--ot-text); }
  .ui-btn[disabled]:hover { background: var(--ot-cta); }
}

/* ---------------------------------------------------------------------------
 * Input
 *
 * 16px, not 14. Safari on iPhone zooms the page in whenever a focused field's
 * text is under 16px and does not zoom back out - ot-layout.css says this at
 * length already. It is the one place the scale's rung is chosen by the
 * platform rather than by the design.
 * ------------------------------------------------------------------------ */

.ui-input {
  width: 100%;
  height: var(--ui-10);
  padding: 0 var(--ui-3);
  background: var(--ot-input-bg);
  border: 1px solid var(--ot-field-border);
  border-radius: var(--ui-radius-md);
  color: var(--ot-text);
  font-family: inherit;
  font-size: var(--ui-text-base);
  line-height: var(--ui-leading-base);
}
.ui-input:focus { outline: 2px solid var(--ot-accent); outline-offset: -1px; border-color: var(--ot-accent); }
.ui-input::placeholder { color: var(--ot-faint); }

/* A field with an icon inside it: the box takes the border, the input takes
   none, or the two edges sit one pixel apart and read as a mistake. */
.ui-field {
  display: flex;
  align-items: center;
  gap: var(--ui-2);
  height: var(--ui-10);
  padding: 0 var(--ui-3);
  background: var(--ot-input-bg);
  border: 1px solid var(--ot-field-border);
  border-radius: var(--ui-radius-md);
}
.ui-field:focus-within { outline: 2px solid var(--ot-accent); outline-offset: -1px; border-color: var(--ot-accent); }
.ui-field > .ui-input { border: 0; height: auto; padding: 0; background: transparent; }
.ui-field > .ui-input:focus { outline: none; }
.ui-field > .ui-i { color: var(--ot-faint); }

.ui-label {
  display: block;
  font-size: var(--ui-text-sm);
  line-height: var(--ui-leading-sm);
  font-weight: 600;
  color: var(--ot-text);
  margin: 0 0 var(--ui-2);
}
.ui-hint {
  font-size: var(--ui-text-sm);
  line-height: var(--ui-leading-sm);
  color: var(--ot-muted);
  margin: var(--ui-2) 0 0;
}

/* ---------------------------------------------------------------------------
 * Tabs
 *
 * shadcn's TabsList: a muted track with the live segment raised out of it.
 * Full width on a phone, where it is a filter over the list below and wants
 * to start where the list starts.
 * ------------------------------------------------------------------------ */

.ui-tabs {
  display: flex;
  gap: var(--ui-1);
  padding: var(--ui-1);
  background: var(--ot-surface-2);
  border-radius: var(--ui-radius-lg);
}
.ui-tab {
  flex: 1 1 auto;
  min-width: 0;
  height: 36px;
  border: 0;
  border-radius: var(--ui-radius-md);
  background: transparent;
  color: var(--ot-muted);
  font-family: inherit;
  font-size: var(--ui-text-sm);
  line-height: var(--ui-leading-sm);
  font-weight: 600;
  cursor: pointer;
  white-space: nowrap;
}
.ui-tab.is-on {
  background: var(--ot-surface);
  color: var(--ot-text);
  box-shadow: 0 1px 2px rgba(16, 24, 40, 0.10);
}
.ui-tab:focus-visible { outline: 2px solid var(--ot-accent); outline-offset: -2px; }

/* ---------------------------------------------------------------------------
 * Dialog
 *
 * shadcn's Dialog: a scrim, a panel, a head, a body and a foot with its
 * actions to the right. Four pages had built one; none of them had built it
 * the same, and the one that mattered - the scrim's z-index - was three
 * different numbers.
 *
 * Aligned to the top of the screen rather than centred. A form whose fields
 * grow as it is filled in moves under the cursor when it is centred, and on a
 * phone a centred panel taller than the viewport has no top to scroll back to.
 *
 * A PAGE THAT OPENS ONE MUST SET html.ot-overlay-open WHILE IT IS OPEN.
 *
 * No z-index can do this for you. The shell draws its header and its tab bar
 * OUTSIDE the routed page, so a veil that belongs to the route cannot cover
 * them however high it sits - they paint over the top of the dialog, the head
 * of a tall form is clipped under one and the actions under the other, and
 * both stay lit while everything between them is dimmed. The class is what
 * takes them out of the way and dims the strips where their boxes were.
 *
 * app.html clears it on every route change, so set it from componentDidUpdate
 * and clear it in componentWillUnmount - never only from the handler that
 * opens the dialog. test/a-dialog-covers-the-whole-screen.test.js holds every
 * page that draws a .ui-veil to this.
 * ------------------------------------------------------------------------ */

.ui-veil {
  position: fixed;
  inset: 0;
  z-index: 10050;
  /* The shell's own value, not a new one.
   *
   * app.html dims the page under html.ot-overlay-open with
   * rgba(4,8,20,0.55), and the header keeps its box while a dialog is open -
   * it is hidden, not removed - so the strip along the top of the screen is
   * dimmed by the SHELL and the rest by this. Two different values meet as a
   * visible seam across the top of every dialog; orders.html found that and
   * says so above its own veil. A shared veil that picked its own number
   * would have put the seam on every page that adopted it. */
  background: rgba(4, 8, 20, 0.55);
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding: var(--ui-6) var(--ui-4);
  overflow-y: auto;
  /* A tall dialog scrolls in here; it does not hand the scroll on to the page. */
  overscroll-behavior: contain;
}
.ui-dialog {
  background: var(--ot-surface);
  border: 1px solid var(--ot-border);
  border-radius: var(--ui-radius-xl);
  box-shadow: var(--ui-shadow-raised);
  width: min(520px, 100%);
  display: flex;
  flex-direction: column;
  gap: var(--ui-4);
  padding: var(--ui-6);
}
.ui-dialog-head { display: flex; align-items: flex-start; gap: var(--ui-3); }
.ui-dialog-title {
  flex: 1 1 auto;
  min-width: 0;
  margin: 0;
  font-size: var(--ui-text-lg);
  line-height: var(--ui-leading-lg);
  font-weight: 600;
  color: var(--ot-text);
}
.ui-dialog-body { display: flex; flex-direction: column; gap: var(--ui-3); }
/* Actions to the right, and wrapping rather than squashing - a two-button
   foot at 320px is two buttons under each other, not two half-width ones. */
.ui-dialog-foot {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: var(--ui-2);
}

/* ---------------------------------------------------------------------------
 * Progress, separator, eyebrow
 * ------------------------------------------------------------------------ */

.ui-progress {
  height: 8px;
  width: 100%;
  border-radius: 999px;
  background: var(--ot-surface-2);
  overflow: hidden;
}
.ui-progress-bar {
  height: 100%;
  border-radius: 999px;
  background: var(--ot-accent);
}

.ui-sep { height: 1px; background: var(--ot-border); border: 0; margin: 0; }

.ui-eyebrow {
  font-size: var(--ui-text-xs);
  line-height: var(--ui-leading-xs);
  font-weight: 600;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--ot-muted);
}

/* A label / value line, which is most of what a settings or usage card is. */
.ui-kv {
  display: flex;
  align-items: baseline;
  gap: var(--ui-3);
  font-size: var(--ui-text-sm);
  line-height: var(--ui-leading-sm);
}
.ui-kv > :first-child { flex: 1 1 auto; min-width: 0; font-weight: 500; }
.ui-kv > :last-child { flex: none; color: var(--ot-muted); font-variant-numeric: tabular-nums; }

/* ---------------------------------------------------------------------------
 * Stacks
 *
 * Named gaps, so a page never types a number to separate two things. Every
 * value is a rung of the space scale above.
 * ------------------------------------------------------------------------ */

.ui-stack { display: flex; flex-direction: column; }
.ui-stack-1 { gap: var(--ui-1); }
.ui-stack-2 { gap: var(--ui-2); }
.ui-stack-3 { gap: var(--ui-3); }
.ui-stack-4 { gap: var(--ui-4); }
.ui-stack-6 { gap: var(--ui-6); }
.ui-stack-8 { gap: var(--ui-8); }

.ui-inline { display: flex; align-items: center; }
.ui-inline-2 { gap: var(--ui-2); }
.ui-inline-3 { gap: var(--ui-3); }
.ui-grow { flex: 1 1 auto; min-width: 0; }
