/* Sindhorn Midtown Internal — the primitives.

   Everything in this file is vocabulary any page could use: a page, a
   section, a card, a list row, a field, a metric, a dialog. What one page's
   content shapes out of these - the promotion card's meta row, the
   directory's search and avatar, the brand table, the business card - is
   a composition and lives in app-compositions.css, so this file is the
   transferable core of the library (see docs/idui).

   Written deliberately, not extracted. An earlier attempt lifted 57 rules out
   of three route stylesheets with a script and bolted route names on as
   co-selectors; it dropped resets, missed sub-elements and left components with
   no material, and each repair was a patch on a patch. This file is authored
   instead, and owns nothing but geometry and structure.

   Material comes from app-glass.css: .app-card, .app-control, .app-overlay.
   Values come from app-tokens.css. No fill, no blur, no color literals here.

   Nothing here references a page: a page is markup made of these names and
   the variant attributes they accept (data-tone, data-size, data-width,
   data-columns, data-mode, data-open, data-compact, data-direction,
   data-split, data-rule, data-icon, data-stagger), never a class of its own.
*/

/* ============================================================================
   BASE — applies wherever the library is loaded.
   ========================================================================== */

/* Mobile browsers paint a gray box over anything tappable on press. It is not
   part of this system: press feedback is the scale-down each control already
   declares. Keyboard focus rings are untouched, since those are how a keyboard
   user knows where they are. */
a,button,summary,[role="button"],input,select,textarea,label{-webkit-tap-highlight-color:transparent}

/* Text elements inside library structure carry no user-agent margin. Every
   component states the space it needs; a paragraph must never add its own.
   Zero-specificity so any component rule wins without a fight. */
:where(.app-page,.app-card,.app-surface,.app-overlay) :where(p,h1,h2,h3,h4,figure){margin:0}

/* ---- Actionable card -------------------------------------------------------
   A card whose whole face is the button. The button itself must reset the user
   agent's background, or it renders as a gray ButtonFace over the material. */
.app-action-card{overflow:clip;border-radius:var(--radius-surface)}
.app-action-card-button{
  width:100%;margin:0;padding:16px 17px;
  display:block;text-align:left;
  border:0;background:transparent;
  color:inherit;font:inherit;cursor:pointer;
  transition:transform var(--motion-fast) var(--motion-ease),opacity var(--motion-fast) ease;
}
.app-action-card-button:active{transform:scale(.995);opacity:.9}
.app-action-card-button:focus-visible{outline:2px solid var(--app-accent);outline-offset:-3px}
.app-action-card-head{display:flex;align-items:baseline;justify-content:space-between;gap:var(--space-5)}
.app-action-card-status{color:var(--app-accent);font-size:var(--type-small);font-weight:var(--weight-label);letter-spacing:var(--tracking-label);text-transform:uppercase}
.app-action-card-title{display:block;margin:8px 0 0;font-size:var(--type-lead);font-weight:var(--weight-regular);line-height:var(--leading-tight)}
/* A live card's title takes the success tone, as on the live route. */
.app-action-card[data-tone="success"] .app-action-card-title{color:var(--app-success)}
.app-action-card-copy{display:block;margin:5px 0 0;color:var(--app-muted);font-size:var(--type-body-s);line-height:var(--leading-body)}
.app-action-card-foot{margin-top:12px;display:flex;align-items:center;justify-content:space-between;gap:var(--space-5);color:var(--app-muted);font-size:var(--type-small)}
.app-action-card-foot svg{width:14px;height:14px;flex:0 0 auto;fill:none;stroke:currentColor;stroke-width:1.5}
/* Status reads as a tone, the same vocabulary a badge and a state use. */
.app-action-card-status[data-tone="success"],.app-hero-eyebrow[data-tone="success"]{color:var(--app-success)}
.app-action-card-status[data-tone="quiet"],.app-hero-eyebrow[data-tone="quiet"]{color:var(--app-muted)}

/* ---- Primary action --------------------------------------------------------
   The emphatic control on a page. .app-control supplies the material; the
   accent edge marks emphasis.

   Disabled dims the content, not the element. Fading the whole button with
   opacity also fades its backdrop-filter, so a disabled control stops looking
   like the same material as everything around it. */
.app-primary{
  min-height:var(--control);padding:0 16px;
  border:1px solid rgba(var(--app-accent-rgb),.28);border-radius:var(--radius-control);
  color:inherit;font:inherit;font-size:var(--type-body-s);cursor:pointer;
  transition:transform var(--motion-fast) var(--motion-ease),border-color var(--motion-ms) ease,color var(--motion-ms) ease;
}
.app-primary{display:inline-flex;align-items:center;justify-content:center;gap:var(--space-4);text-decoration:none}
.app-primary svg{width:16px;height:16px;flex:0 0 auto;fill:none;stroke:currentColor;stroke-width:1.5}
.app-primary[data-width="full"]{width:100%}
.app-primary:active{transform:scale(.975)}
.app-primary:focus-visible{outline:2px solid var(--app-accent);outline-offset:2px}
.app-primary:disabled{border-color:var(--app-line);color:var(--app-muted);cursor:default;transform:none}
/* A destructive confirm keeps the primary's shape and takes the danger tone,
   so the one button that removes something reads differently from the one
   that saves. Its edge is the same ink, faded, so no second literal appears. */
.app-primary[data-tone="danger"]{color:var(--app-danger);border-color:color-mix(in srgb,var(--app-danger) 32%,transparent)}

/* ---- Content surface -------------------------------------------------------
   A card whose content is the destination. Same material as the actionable
   card, deliberately without a press affordance. */
.app-surface{padding:15px 16px;border-radius:var(--radius-surface)}
.app-surface-label{color:var(--app-accent);font-size:var(--type-small);font-weight:var(--weight-label);letter-spacing:var(--tracking-label);text-transform:uppercase}
.app-surface-title{margin:8px 0 0;font-size:var(--type-lead);font-weight:var(--weight-regular);line-height:var(--leading-tight);text-wrap:balance}
.app-surface-copy{margin:8px 0 0;color:var(--app-muted);font-size:var(--type-body-s);line-height:var(--leading-body)}
/* Tone is the one thing a card says about its content: quiet steps the title back. */
.app-card[data-tone="quiet"] .app-surface-title{color:var(--app-muted)}

/* A quote reports what someone said: the copy reads in the text ink, on a
   dashed edge with a quiet label. A warning keeps the accent on its edge
   instead of its label, so the edge is what the eye catches. */
.app-surface[data-mode="quote"] .app-surface-copy{color:var(--app-text)}
.app-surface[data-mode="quote"]{border-style:dashed}
.app-surface[data-mode="quote"] .app-surface-label{color:var(--app-muted)}
.app-surface[data-tone="warn"] .app-surface-label{color:var(--app-text)}
/* Text inside a surface after its label: a list a card carries. */
.app-surface-label+.app-prose{margin-top:12px}

/* Card sections: one card, several groups. Each group after the first opens
   with the same hairline the list uses, so a card never needs a nested card
   to separate its parts. */
.app-card-section+.app-card-section{margin-top:16px;padding-top:16px;border-top:1px solid var(--app-line)}
/* The first section is the card's head, and the hairline that closes it runs
   the card's width, the way the head of an open disclosure closes over its
   panel; every hairline after it - later sections, list rows, ruled metrics -
   runs the content's width. A card had shown both widths under its head, and
   the owner chose the whole width for the head alone (r71). The section
   reaches under the surface padding to draw the line and pads its content
   back to where it was. The actionable card's progress line keeps to the
   same rule in app-compositions.css (r72). */
.app-card-section:first-child+.app-card-section{margin-inline:-16px;padding-inline:16px}
.app-card-section>.app-surface-label+*{margin-top:12px}
.app-card-section>.app-list>.app-list-row:first-child{padding-top:0}
.app-card-section>.app-list>.app-list-row:last-child{padding-bottom:0}

/* ---- Disclosure ------------------------------------------------------------
   Content stays in the DOM and the panel animates height, so a screen reader
   and Find-in-page still see it when collapsed. */
.app-disclosure{overflow:clip;border-radius:var(--radius-surface)}
.app-disclosure-button{
  width:100%;min-height:var(--control-row);margin:0;padding:15px 16px;
  display:flex;align-items:center;justify-content:space-between;gap:var(--space-6);
  border:0;background:transparent;
  color:inherit;font:inherit;text-align:left;cursor:pointer;
  transition:opacity var(--motion-fast) ease;
}
.app-disclosure-button:active{opacity:.9}
.app-disclosure-button:focus-visible{outline:2px solid var(--app-accent);outline-offset:-3px}
.app-disclosure-head{min-width:0;display:grid;gap:0}
/* A head sharing a row with its toggle takes the space that is left, and never
   pushes the toggle to a second line. min-width:0 alone is not enough: a
   wrapping row decides how many items fit from their content widths before any
   shrinking happens, so a long title wrapped the chevron underneath itself and
   left it sitting at the start of a line of its own. A flex-basis of zero means
   the head never claims more than the line has, and the title wraps inside it
   where it belongs. Visible from r61, when two columns halved the width a job
   title had. */
.app-row>.app-disclosure-head{flex:1 1 0}
.app-disclosure-kicker{color:var(--app-accent);font-size:var(--type-small);font-weight:var(--weight-label);letter-spacing:var(--tracking-label);text-transform:uppercase}
.app-disclosure-title{margin:0;font-size:var(--type-lead);font-weight:var(--weight-regular);line-height:var(--leading-label)}
.app-disclosure-copy{margin:4px 0 0;color:var(--app-muted);font-size:var(--type-body-s);line-height:var(--leading-body)}
.app-disclosure-kicker+.app-disclosure-title{margin-top:6px}
.app-disclosure-end{flex:0 0 auto;display:flex;align-items:center;gap:var(--space-5);color:var(--app-muted);font-size:var(--type-body-s);font-variant-numeric:tabular-nums}
/* A completed disclosure carries the success tone on its leading edge and count. */
.app-disclosure[data-tone="success"]{border-left:2px solid var(--app-success)}
.app-disclosure[data-tone="success"] .app-disclosure-end{color:var(--app-success)}
.app-disclosure-chevron{width:18px;height:18px;flex:0 0 auto;fill:none;stroke:currentColor;stroke-width:1.5;transition:transform var(--motion-ms) var(--motion-ease)}
/* The panel and the chevron answer to the open variant alone, not to the
   surface class, so a surface whose head already carries a control - a job
   card with a status selector - can disclose without becoming a disclosure.
   Keyed on data-open, which is what actually changes, so the behavior hook
   stays in the modules where hooks belong (r35). */
[data-open="true"] .app-disclosure-chevron{transform:rotate(90deg)}
.app-disclosure-panel{display:grid;grid-template-rows:0fr;transition:grid-template-rows var(--motion-ms) var(--motion-ease)}
[data-open="true"] .app-disclosure-panel{grid-template-rows:1fr}
.app-disclosure-panel-inner{overflow:hidden}
/* Either the whole head is the button, or the head carries its own controls
   and the chevron alone is the toggle. Same rotation, same panel; only the
   hit area differs. Rounded, never circular. */
.app-disclosure-toggle{
  width:var(--size-icon-control);height:var(--size-icon-control);flex:0 0 auto;
  display:inline-grid;place-items:center;
  margin:0;padding:0;border:0;background:transparent;border-radius:var(--radius-control);
  color:var(--app-muted);cursor:pointer;
  transition:transform var(--motion-fast) var(--motion-ease),color var(--motion-ms) ease;
}
.app-disclosure-toggle:active{transform:scale(.94)}
.app-disclosure-toggle:focus-visible{outline:2px solid var(--app-accent);outline-offset:2px}
/* The open content sits under one hairline, so the head reads as a row of
   its own; the hairline is inside the clipped panel, so a closed disclosure
   shows none. The head is a group of its own whatever the panel holds: r65
   dropped this hairline above a ruled grid as a second divider, and the
   outlet cards read as headless - it is back for every panel (r69). */
.app-disclosure>.app-disclosure-panel .app-disclosure-panel-inner>*{padding:14px 16px 16px;border-top:1px solid var(--app-line)}
/* One divider or the other, never both. A group closed by its own hairline
   does not also need a section divider drawn across the whole card above it -
   that is the card dividing itself twice, and it reads as a bug even when
   every line is deliberate. Where a section rules its own groups, the section
   divider goes and the room made for it goes with it: the seam after a ruled
   section is the grid's own row gap, so two ruled sections read as one grid
   and a list under one hangs from its last hairline (r66). */
.app-card-section:has(.app-metric-grid[data-rule="true"])+.app-card-section{border-top:0;margin-top:0;padding-top:18px}
/* The same seam between two ruled grids that sit side by side with no
   section around them: the row gap, so the pair reads as one grid instead of
   the second's first row hanging from the first's last hairline (r69). */
.app-metric-grid[data-rule="true"]+.app-metric-grid[data-rule="true"]{margin-top:18px}
.app-disclosure-detail{color:var(--app-muted);font-size:var(--type-body-s);line-height:var(--leading-body)}
.app-disclosure-detail span{display:block;margin-bottom:6px;color:var(--app-accent);font-size:var(--type-small);font-weight:var(--weight-label);letter-spacing:var(--tracking-label);text-transform:uppercase}
.app-disclosure-detail ul{margin:0;padding-left:18px;display:grid;gap:var(--space-2)}

@media (prefers-reduced-motion:reduce){
}

/* ---- Form field ------------------------------------------------------------
   An input reads as a well, not a raised surface, so it takes the inset fill
   rather than the card material. */
.app-field{display:grid;gap:6px;min-width:0}
.app-field label{color:var(--app-muted);font-size:var(--type-small);font-weight:var(--weight-label);letter-spacing:var(--tracking-label);text-transform:uppercase}
.app-field label span{opacity:.72;text-transform:none}
/* The range exclusion sits in :where() so it adds no weight: .app-search
   input in app-compositions.css refines this well by sheet order alone, and a
   bare :not() (r37) outweighed it and took the well's padding for two days. */
.app-field input:where(:not([type="range"])),.app-field textarea,.app-field select{
  width:100%;min-height:var(--control-row);padding:10px 12px;
  border:1px solid var(--app-line);border-radius:var(--radius-inset);
  background:var(--app-inset-fill);
  color:var(--app-text);font:inherit;font-size:var(--type-body);
  outline:none;
  transition:border-color var(--motion-ms) ease,box-shadow var(--motion-ms) var(--motion-ease);
}
.app-field input:where(:not([type="range"])):focus,.app-field textarea:focus,.app-field select:focus{
  border-color:rgba(var(--app-accent-rgb),.55);
  box-shadow:0 0 0 3px rgba(var(--app-accent-rgb),.08);
}
.app-field input:disabled,.app-field textarea:disabled,.app-field select:disabled{opacity:.45;cursor:default}

/* ---- Range ------------------------------------------------------------------
   A number the hand sets by feel rather than by typing: the same well as a
   field, drawn as a track, with the value read back beside its label. The
   thumb is the accent because it is the thing being moved; the track is the
   inset fill every other input uses, so a range reads as one of them. The
   thumb is a grip, taller than it is wide: rounded, never circular, and a
   shape the finger reads as something to hold (r37). */
.app-field-head{display:flex;align-items:baseline;justify-content:space-between;gap:var(--space-5)}
.app-field-head output{color:var(--app-text);font-size:var(--type-body-s);font-variant-numeric:tabular-nums}
.app-field input[type="range"]{
  width:100%;height:var(--control-inline);margin:0;padding:0;
  appearance:none;-webkit-appearance:none;background:transparent;
  color:var(--app-accent);cursor:pointer;
}
.app-field input[type="range"]::-webkit-slider-runnable-track{
  height:4px;border-radius:var(--radius-tight);
  background:var(--app-inset-fill);border:1px solid var(--app-line);
}
.app-field input[type="range"]::-moz-range-track{
  height:4px;border-radius:var(--radius-tight);
  background:var(--app-inset-fill);border:1px solid var(--app-line);
}
.app-field input[type="range"]::-webkit-slider-thumb{
  appearance:none;-webkit-appearance:none;
  width:22px;height:22px;margin-top:-10px;
  border:0;border-radius:var(--radius-tight);
  background:var(--app-accent);
}
.app-field input[type="range"]::-moz-range-thumb{
  width:22px;height:22px;
  border:0;border-radius:var(--radius-tight);
  background:var(--app-accent);
}
.app-field input[type="range"]:focus-visible{outline:2px solid var(--app-accent);outline-offset:2px}
.app-field input[type="range"]:disabled{opacity:.45;cursor:default}
/* Longer text: the same well, tall enough for a paragraph, grown by the
   employee and never by the browser's corner grip alone. */
.app-field textarea{line-height:var(--leading-body);resize:vertical}
/* A moment is typed into the same well; WebKit's own picker chrome is dropped
   so the field keeps its edge and height. */
.app-field input[type="datetime-local"],.app-field input[type="date"],.app-field input[type="time"]{appearance:none;-webkit-appearance:none}
/* A field answered by chips rather than typed: one of a few, or several.
   Chips carry aria-pressed; the group is only a wrapping row. */
.app-chip-group{display:flex;flex-wrap:wrap;gap:var(--space-3)}
.app-field .app-chip-group{padding-top:2px}
.app-field-note{color:var(--app-muted);font-size:var(--type-body-s);line-height:var(--leading-body)}
.app-field[data-tone="error"] .app-field-note{color:var(--app-danger)}

/* A six-digit code is one field with six wells, one digit each, so what the
   employee typed is visible at a glance. Each well is an ordinary field
   input — same height, tint, focus and blur — only centered and larger. The
   group's behavior (advance, backspace, paste) lives in app-code.js. */
.app-code{display:grid;grid-template-columns:repeat(6,minmax(0,1fr));gap:var(--space-3)}
.app-field .app-code input{padding-inline:0;text-align:center;font-size:var(--type-lead);font-variant-numeric:tabular-nums}

/* ---- Code block -------------------------------------------------------------
   A command or a value shown verbatim, in a well like a field (same fill,
   same edge, same inset radius), in the code face, with room at the top
   right for one chip - Copy. The chip's behavior is a page's; the well is not. */
.app-code-block{position:relative;margin:0;padding:14px 60px 14px 16px;border:1px solid var(--app-line);border-radius:var(--radius-inset);background:var(--app-inset-fill);overflow-x:auto}
.app-code-block code{display:block;color:var(--app-accent);font-family:var(--font-code);font-size:var(--type-body-s);line-height:var(--leading-body);white-space:pre}
.app-code-block>.app-chip{position:absolute;top:8px;right:8px}

/* ---- Selector --------------------------------------------------------------
   Behavior lives in app-select.js. The trigger is a control; the menu floats
   above content, so it is an overlay rather than a card. Inside a card the
   library lifts the open menu to the document (glass never nests): the same
   element as position:fixed, anchored to the trigger through the three
   custom properties app-select.js sets - the one runtime geometry in the
   library, and the library's, never a page's. */
.app-select{position:relative;min-width:0;display:grid;gap:6px}
.app-select-label{color:var(--app-muted);font-size:var(--type-small);font-weight:var(--weight-label);letter-spacing:var(--tracking-label);text-transform:uppercase}
.app-select-trigger{
  width:100%;min-height:var(--control);padding:0 12px;
  display:flex;align-items:center;justify-content:space-between;gap:var(--space-4);
  border-radius:var(--radius-control);
  color:inherit;font:inherit;font-size:var(--type-body);text-align:left;cursor:pointer;
}
.app-select-trigger svg{width:14px;height:14px;flex:0 0 auto;fill:none;stroke:currentColor;stroke-width:1.5;transition:transform var(--motion-fast) var(--motion-ease)}
.app-select[data-open="true"] .app-select-trigger svg{transform:rotate(180deg)}
.app-select-menu{
  position:absolute;z-index:60;top:calc(100% + 6px);left:0;right:0;
  max-height:260px;overflow:auto;padding:0;
  border-radius:var(--radius-inset);
  display:none;
}
.app-select[data-open="true"] .app-select-menu{display:block}
.app-select-menu[data-mode="floating"]{
  display:block;position:fixed;
  top:var(--app-select-top);left:var(--app-select-left);right:auto;width:var(--app-select-width);
}
/* Compact: the same selector as a badge. No visible label, the trigger a
   badge grown to a tappable height that carries the chosen option's tone,
   the menu hanging from the trigger's right edge at a readable width. The
   status on a job card (23 Job) is this. */
.app-select[data-compact="true"]{display:inline-grid;gap:0}
.app-select[data-compact="true"] .app-select-trigger{
  width:auto;height:auto;min-height:var(--control-inline);padding:0 8px 0 10px;
  gap:5px;justify-content:center;
  border:0;border-radius:var(--radius-control);
  font-size:var(--type-small);line-height:1;
  transition:transform var(--motion-fast) var(--motion-ease);
}
.app-select[data-compact="true"] .app-select-trigger svg{width:12px;height:12px;stroke-width:1.8}
.app-select[data-compact="true"] .app-select-trigger:active{transform:scale(.96)}
.app-select[data-compact="true"] .app-select-trigger:focus-visible{outline:2px solid var(--app-accent);outline-offset:2px}
.app-select[data-compact="true"] .app-select-menu{min-width:200px;left:auto;right:0}
/* A compact menu opens toward the free side: at the start of a split row it
   hangs from the trigger's left edge, anywhere else from its right. */
.app-row[data-split="true"]>.app-select[data-compact="true"]:first-child .app-select-menu{left:0;right:auto}
.app-select-option{
  width:100%;min-height:var(--control);padding:0 12px;
  display:flex;align-items:center;
  border:0;border-radius:var(--radius-none);background:transparent;
  color:inherit;font:inherit;font-size:var(--type-body-s);text-align:left;cursor:pointer;
}
/* A separator between options, never trailing after the last. */
.app-select-option+.app-select-option{border-top:1px solid var(--app-line)}
.app-select-option[aria-selected="true"]{color:var(--app-accent)}
.app-select-option:focus-visible{outline:2px solid var(--app-accent);outline-offset:-2px}

/* ---- Utility action ---------------------------------------------------------
   Text and an optional icon. No frame, no border, no material: Sign out, Share
   and Back to top are actions, not surfaces. app-controls.css carries the same
   intent behind a pile of cascade overrides, and is not loaded outside the app
   shell — which is why these rendered as bare browser buttons here. A word
   as short as Call is narrower than a finger, so the action keeps a 24px
   minimum target with the word centred in it - a target, not a frame (r69). */
.app-utility-action{
  min-height:var(--control-inline);min-width:24px;padding:0;
  display:inline-flex;align-items:center;justify-content:center;gap:var(--space-3);
  border:0;background:transparent;
  color:var(--app-muted);font:inherit;font-size:var(--type-body-s);line-height:1;
  text-decoration:none;
  cursor:pointer;
  transition:color var(--motion-ms) ease,transform var(--motion-fast) var(--motion-ease);
}
.app-utility-action:active{transform:scale(.975)}
.app-utility-action:hover,.app-utility-action:focus-visible{color:var(--app-accent)}
.app-utility-action:focus-visible{outline:2px solid var(--app-accent);outline-offset:2px}
.app-utility-action svg{width:14px;height:14px;flex:0 0 auto;fill:none;stroke:currentColor;stroke-width:1.5}
/* Utility actions stand at the end of the line. Sign out, Share, Refresh and
   Back to top belong to the right edge wherever they appear, so a row of
   them is right-aligned by definition; a page never places them itself. */
.app-utility-row{display:flex;flex-wrap:wrap;align-items:center;justify-content:flex-end;gap:4px 20px}
*+.app-utility-row{margin-top:16px}
/* A count or a caption in the row - "42 employees" - reads at the left
   edge while the actions keep the right; it is text, not a control. */
.app-utility-note{margin-right:auto;color:var(--app-muted);font-size:var(--type-body-s);line-height:1;font-variant-numeric:tabular-nums}

/* ---- Back / close control ---------------------------------------------------
   A detail view needs a way out. app-controls.css carries these for the current
   app behind cascade overrides; this is the clean form for pages built on the
   library. .app-control supplies the material. */
.app-back-control,.app-close-control{
  width:var(--size-icon-control);height:var(--size-icon-control);flex:0 0 auto;
  display:inline-grid;place-items:center;
  border-radius:var(--radius-control);
  color:inherit;font:inherit;cursor:pointer;
  transition:transform var(--motion-fast) var(--motion-ease),color var(--motion-ms) ease;
}
.app-back-control:active,.app-close-control:active{transform:scale(.94)}
.app-back-control:focus-visible,.app-close-control:focus-visible{outline:2px solid var(--app-accent);outline-offset:2px}
.app-back-control svg,.app-close-control svg{width:18px;height:18px;fill:none;stroke:currentColor;stroke-width:1.6}

/* ---- Metric -----------------------------------------------------------------
   A number that carries meaning: today's occupancy, an outlet's revenue. The
   value is tabular so columns of them line up, and the delta states its own
   direction rather than relying on color alone. */
.app-metric{min-width:0;display:grid;gap:6px;align-content:start}
.app-metric-label{color:var(--app-muted);font-size:var(--type-small);font-weight:var(--weight-label);letter-spacing:var(--tracking-label);text-transform:uppercase}
.app-metric-value{font-size:var(--type-title);font-weight:var(--weight-regular);line-height:1;font-variant-numeric:tabular-nums}
.app-metric-note{color:var(--app-muted);font-size:var(--type-body-s);line-height:var(--leading-label)}
.app-metric-delta{display:inline-flex;align-items:center;gap:var(--space-2);font-size:var(--type-body-s);font-variant-numeric:tabular-nums}
.app-metric-delta[data-direction="up"]{color:var(--app-success)}
.app-metric-delta[data-direction="down"]{color:var(--app-danger)}
.app-metric-delta[data-direction="flat"]{color:var(--app-muted)}
.app-metric[data-tone="danger"] .app-metric-value{color:var(--app-danger)}
.app-metric-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(132px,1fr));gap:18px 20px}
/* Fixed columns keep a strip on one line at phone width; text-scale values
   let the grid carry facts (an outlet, a time) as well as numbers; ruled
   metrics close with the same hairline the list row uses. */
.app-metric-grid[data-columns="2"]{grid-template-columns:repeat(2,1fr)}
.app-metric-grid[data-columns="3"]{grid-template-columns:repeat(3,1fr);gap:18px 12px}
.app-metric-grid[data-mode="text"] .app-metric-value{font-size:var(--type-lead);line-height:var(--leading-tight);font-variant-numeric:tabular-nums}
.app-metric-grid[data-rule="true"] .app-metric{padding-bottom:14px;border-bottom:1px solid var(--app-line)}
/* A rule closes a group. Inside a card the card's own edge already closes the
   last one, so drawing another a few pixels above it says the same thing twice.
   On the ground there is no edge to borrow and the rule stays. The even-count
   guard is the second column of a last row, which only exists while the grid
   resolves to two tracks - phone width, where these are read. */
/* Only the grid at the bottom edge: last among its siblings, in a group that is
   last, in a wrapper that is last - three levels, as deep as a card composes.
   r64 reached it with a descendant match, which also took every grid inside a
   wrapper that happened to be last: the outlet card lost all of its hairlines,
   not only the one above its edge (r66). A grid of exactly two is its own final
   pair, so the even guard already covers it. */
:where(.app-card,.app-surface,.app-disclosure-panel-inner)>.app-metric-grid[data-rule="true"]:last-child .app-metric:last-child,
:where(.app-card,.app-surface,.app-disclosure-panel-inner)>:last-child>.app-metric-grid[data-rule="true"]:last-child .app-metric:last-child,
:where(.app-card,.app-surface,.app-disclosure-panel-inner)>:last-child>:last-child>.app-metric-grid[data-rule="true"]:last-child .app-metric:last-child,
:where(.app-card,.app-surface,.app-disclosure-panel-inner)>.app-metric-grid[data-rule="true"]:last-child:has(>.app-metric:nth-child(even):last-child) .app-metric:nth-last-child(2),
:where(.app-card,.app-surface,.app-disclosure-panel-inner)>:last-child>.app-metric-grid[data-rule="true"]:last-child:has(>.app-metric:nth-child(even):last-child) .app-metric:nth-last-child(2),
:where(.app-card,.app-surface,.app-disclosure-panel-inner)>:last-child>:last-child>.app-metric-grid[data-rule="true"]:last-child:has(>.app-metric:nth-child(even):last-child) .app-metric:nth-last-child(2){border-bottom:0;padding-bottom:0}

.app-track{display:block;width:100%;height:10px;margin-top:6px;overflow:visible}
.app-track-rail{fill:var(--app-line)}
.app-track-mark{fill:var(--app-text);fill-opacity:.55}
.app-track-bar{
  fill:var(--app-accent);
  transform:scaleX(0);transform-box:fill-box;transform-origin:left center;
  transition:transform var(--motion-reveal) var(--motion-ease);
  transition-delay:calc(var(--app-stagger,0) * var(--motion-stagger));
}
/* The bars fill in sequence rather than together. One rule owns the timing -
   the step is the constitution's, not a number repeated seven times - and the
   attribute carries only the index. A literal single rule is not reachable in
   CSS: attr() into calc() is too new to rely on, and a page may not set a
   custom property inline, so the seven lines below are index plumbing and
   nothing else. Adding an eighth costs one line and no arithmetic. */
.app-track[data-stagger="1"]{--app-stagger:1}
.app-track[data-stagger="2"]{--app-stagger:2}
.app-track[data-stagger="3"]{--app-stagger:3}
.app-track[data-stagger="4"]{--app-stagger:4}
.app-track[data-stagger="5"]{--app-stagger:5}
.app-track[data-stagger="6"]{--app-stagger:6}
.app-track[data-stagger="7"]{--app-stagger:7}
[data-ready="true"] .app-track-bar{transform:scaleX(1)}

/* ---- Sheet ------------------------------------------------------------------
   A bottom sheet, not a centered dialog: it belongs to the thumb. Same native
   <dialog> element and the same overlay material, different geometry, so the
   two cannot drift into separate scroll-lock or backdrop behavior. */
.app-sheet{
  width:100%;max-width:100%;max-height:82dvh;
  margin:auto auto 0;padding:0;
  border-radius:var(--radius-surface) var(--radius-surface) 0 0;
  color:var(--app-text);
}
.app-sheet-grip{width:38px;height:4px;margin:10px auto 0;border-radius:var(--radius-pill);background:var(--app-line)}
.app-sheet-body{padding:16px max(20px,env(safe-area-inset-left)) calc(20px + env(safe-area-inset-bottom));overflow:auto}
.app-sheet-title{margin:6px 0 0;font-size:var(--type-lead);font-weight:var(--weight-regular);line-height:var(--leading-tight)}
.app-sheet-title+.app-list,.app-sheet-title+.app-stack,.app-sheet-title+.app-field{margin-top:14px}

/* ---- Check ------------------------------------------------------------------
   A task or artwork item that can be marked done. The label is the hit target,
   so the whole row is tappable rather than just the box. */
.app-check{
  min-height:var(--control-row);padding:8px 0;
  display:flex;align-items:center;gap:var(--space-5);
  cursor:pointer;
}
.app-check input{position:absolute;width:1px;height:1px;opacity:0;pointer-events:none}
.app-check-box{
  width:20px;height:20px;flex:0 0 auto;
  display:grid;place-items:center;
  border:1px solid var(--app-line);border-radius:var(--radius-tight);
  transition:border-color var(--motion-ms) ease,background-color var(--motion-ms) ease;
}
.app-check-box svg{width:12px;height:12px;fill:none;stroke:var(--app-accent);stroke-width:2.2;opacity:0;transition:opacity var(--motion-fast) ease}
.app-check input:checked+.app-check-box{border-color:rgba(var(--app-accent-rgb),.55)}
.app-check input:checked+.app-check-box svg{opacity:1}
.app-check input:focus-visible+.app-check-box{outline:2px solid var(--app-accent);outline-offset:2px}
.app-check-label{min-width:0;font-size:var(--type-body-s);line-height:var(--leading-label)}
.app-check input:checked~.app-check-label{color:var(--app-muted);text-decoration:line-through}
/* A check that chooses rather than completes - "show this field" - keeps its
   label upright when checked; only a task strikes through. */
.app-check[data-mode="option"] input:checked~.app-check-label{color:inherit;text-decoration:none}

/* ---- Badge ------------------------------------------------------------------
   A count or a status marker. Never the only carrier of meaning: pair it with
   text a screen reader can read. It draws a fill, so by the glass membership
   rule it needs the atmosphere behind it too — the tint stays its own, the
   blur is the one everything else uses. */
.app-badge{
  min-width:18px;height:18px;padding:0 5px;
  display:inline-flex;align-items:center;justify-content:center;
  border-radius:var(--radius-pill);
  background:rgba(var(--app-accent-rgb),.16);
  color:var(--app-accent);font-size:var(--type-small);font-variant-numeric:tabular-nums;line-height:1;
}
.app-badge[data-tone="quiet"]{background:var(--app-inset-fill);color:var(--app-muted)}
.app-badge[data-tone="danger"]{background:color-mix(in srgb,var(--app-danger) 18%,transparent);color:var(--app-danger)}
.app-badge[data-tone="success"]{background:color-mix(in srgb,var(--app-success) 18%,transparent);color:var(--app-success)}
.app-badge[data-tone="accent"]{background:color-mix(in srgb,var(--app-accent) 18%,transparent);color:var(--app-accent)}

/* ---- List -------------------------------------------------------------------
   Rows separated by a hairline, never by a gap that pretends to be a card each.
   The separator sits between rows and never after the last. */
.app-list{display:grid}
.app-list-row{
  min-height:var(--control-row);padding:12px 0;
  display:flex;align-items:center;justify-content:space-between;gap:14px;
}
.app-list-row+.app-list-row{border-top:1px solid var(--app-line)}
a.app-list-row{color:inherit;text-decoration:none}
.app-list-row-main{min-width:0;display:grid;gap:3px}
.app-list-row-title{font-size:var(--type-body);line-height:var(--leading-label)}
.app-list-row-meta{color:var(--app-muted);font-size:var(--type-body-s)}
/* Meta set down a step, where a row is narrow and the fact is a stamp rather
   than a sentence - a paper's number and date beside each other in a column
   half a phone wide. Existing token, existing variant word (r41). */
.app-list-row-meta[data-size="micro"]{font-size:var(--type-micro)}
/* An inbox row shows the first lines of its message under the meta, clamped
   to two so the list stays a list; the full text opens in the dialog. */
.app-list-row-copy{color:var(--app-text);font-size:var(--type-body-s);line-height:var(--leading-body);display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;overflow-wrap:anywhere}
.app-list-row-end{flex:0 0 auto;display:flex;align-items:center;gap:var(--space-3);color:var(--app-muted);font-size:var(--type-body-s);font-variant-numeric:tabular-nums}
button.app-list-row{width:100%;border:0;background:transparent;color:inherit;font:inherit;text-align:left;cursor:pointer}
button.app-list-row:active{opacity:.9}
button.app-list-row:focus-visible{outline:2px solid var(--app-accent);outline-offset:-2px}
/* A row that is a link is the same row: it takes the same ring rather than the
   user agent's, which arrives in a different colour and geometry on the same
   shape. Reaches the artwork links in F&B, the Brand links and the document
   pages' row links (r76). */
a.app-list-row:focus-visible{outline:2px solid var(--app-accent);outline-offset:-2px}
/* A picker row (the status sheet): the chosen row carries the accent and a
   check at its end; the others stay the text color. */
button.app-list-row[aria-pressed="true"]{color:var(--app-accent)}
button.app-list-row[aria-pressed="true"] .app-list-row-end{color:var(--app-accent)}
.app-list-row-end svg{width:16px;height:16px;fill:none;stroke:currentColor;stroke-width:1.8}
/* ---- Prose ------------------------------------------------------------------
   Long-form editorial copy: the History and Brand narratives. Constrained
   measure, because a full-width line of body text on a tablet is unreadable. */
/* Running text a person reads at length is its own role. Most products set it
   in the interface face and say so; one that sets long-form prose in a serif
   while its chrome stays a sans needs somewhere to say that, and this is it.
   Sindhorn and Flipgazine answer var(--font-ui), and render unchanged (r41).

   Size travels with the face for the same reason: text meant to be read at
   length is set larger than the interface around it, and by how much is the
   constitution's to say. Both answer var(--type-body), which is what they
   were already doing implicitly. */
/* ---- Text stays inside its box --------------------------------------------
   A word with nothing to break on - a hash, a URL, a Thai or Japanese phrase,
   which have no spaces at all - is wider than its container, and the container
   is wider than the page. The core already knew this and had answered it three
   times, ad hoc, wherever someone hit it: on a list row title, on a business
   card name, on a card link. Test 02.3 found the other places by handing the
   archive a 90-character token and watching the document grow 459px (r47).

   Stated once, over the roles that carry author text, at zero specificity so
   nothing it touches changes precedence. This is an invariant, not a fix: no
   text a product is given may leave the box the layout gave it. */
:where(.app-hero-title,.app-hero-copy,.app-hero-eyebrow,
       .app-surface-label,.app-surface-title,.app-surface-copy,
       .app-prose p,.app-prose li,.app-prose h3,
       .app-list-row-title,.app-list-row-meta,.app-list-row-copy,
       .app-metric-label,.app-metric-value,.app-metric-note,
       .app-disclosure-title,.app-disclosure-copy,
       .app-state-title,.app-state-copy){overflow-wrap:anywhere}

/* A badge and a chip are one short label, not author text. `anywhere` shrinks
   an element's min-content to a single character, so the layout gives it a box
   narrower than its own word and it breaks mid-word - "Implemented" as three
   stacked fragments, live since r47. They keep the safety net for a genuinely
   unbreakable token and lose the eager breaking: break-word does not change
   min-content sizing, so the label gets the width it needs. They also stop
   shrinking - a badge beside longer content in a row was being squeezed two
   pixels under its own word and breaking there. A label is not the part of a
   row that gives way. */
.app-badge,.app-chip{overflow-wrap:break-word;flex-shrink:0}

.app-prose{max-width:var(--measure-prose);display:grid;gap:14px;font-family:var(--font-prose)}
.app-prose p{margin:0;font-size:var(--type-prose);line-height:var(--leading-body)}
.app-prose h3{margin:8px 0 0;font-size:var(--type-lead);font-weight:var(--weight-regular);line-height:var(--leading-tight)}
.app-prose>.app-code-block,.app-prose>.app-table-wrap,.app-prose>.app-surface{margin:0}
.app-prose ul,.app-prose ol{margin:0;padding-left:var(--space-7);display:grid;gap:6px;font-size:var(--type-prose);line-height:var(--leading-body)}
/* A miniature of a document: the same prose, set at caption scale because it
   is a depiction rather than something to read. The constitution already had
   the size and the vocabulary already had the word (r41). */
.app-prose[data-size="caption"] p,.app-prose[data-size="caption"] li{font-size:var(--type-caption)}
.app-prose strong,.app-prose b{font-weight:var(--weight-medium)}
/* A link is the accent wherever a page puts it - running text, a note, a table
   cell, a row's title. Until r75 only prose and notes were covered, so the
   route links inside /idui's gate table and the titles on the transfer rows
   came out in the user agent's blue at 1.3:1 on the atmosphere: the one place
   left where a browser default could reach a reader (r34's comment said as
   much, and was wrong by omission).
   The condition is the absence of a class, not a list of the places a link may
   sit: a link that belongs to a composition names itself - a row that is
   itself a link, the card's link line - and keeps the ink that composition
   gave it. :where() holds this at one class of specificity so those rules
   still win, and the navbar and masthead are outside .app-page entirely. */
.app-prose a,.app-note a,.app-page a:where(:not([class])){color:var(--app-accent);text-underline-offset:3px}
.app-prose[data-mode="verbatim"]{white-space:pre-line}
/* A ruled prose closes each entry with the list hairline, so a run of
   heading + paragraph pairs reads as a list, not one long column. */
.app-prose[data-rule="true"]>p+h3{margin-top:0;padding-top:16px;border-top:1px solid var(--app-line)}
/* Quiet prose is a manual's running text: paragraphs and lists in the muted
   ink, emphasis and a lead paragraph in the text ink, a subhead in the text
   ink at the medium weight. */
/* Zero-specificity on the prose so any component inside it (a note's
   label, a step, a table cell) keeps its own ink. */
:where(.app-prose[data-tone="quiet"]) p,:where(.app-prose[data-tone="quiet"]) li{color:var(--app-muted)}
:where(.app-prose[data-tone="quiet"]) p[data-tone="lead"],:where(.app-prose[data-tone="quiet"]) b,:where(.app-prose[data-tone="quiet"]) strong,:where(.app-prose[data-tone="quiet"]) i{color:var(--app-text)}
.app-prose[data-tone="quiet"] h3{font-weight:var(--weight-medium)}
/* A long list reads in two columns where there is room. */
/* A list in columns flows as a block, not a grid: grid rows cannot break across columns. */
@media (min-width:640px){.app-prose[data-columns="2"] ol,.app-prose[data-columns="2"] ul{display:block;columns:2;column-gap:34px}.app-prose[data-columns="2"] li{break-inside:avoid;margin:6px 0}}

/* Long copy opens collapsed to a readable height and fades out at the bottom;
   the toggle is a utility action, and open removes the limit rather than
   animating to a guessed height. */
.app-clamp{position:relative;max-height:170px;overflow:hidden;-webkit-mask-image:linear-gradient(180deg,#000 0%,#000 62%,transparent 100%);mask-image:linear-gradient(180deg,#000 0%,#000 62%,transparent 100%)}
.app-clamp[data-open="true"]{max-height:none;-webkit-mask-image:none;mask-image:none}
.app-clamp+.app-chip{margin-top:14px}
.app-chip[data-size="control"]{min-height:var(--control);padding-inline:22px;font-size:var(--type-body-s)}

/* ---- Toast ------------------------------------------------------------------
   Transient confirmation. It floats above content, so it is overlay material,
   declared in markup like every other overlay. */
.app-toast{
  position:fixed;z-index:160;left:50%;
  bottom:calc(var(--app-navbar-height) + 16px + env(safe-area-inset-bottom));
  transform:translateX(-50%);
  max-width:calc(100vw - 32px);padding:10px 14px;
  border-radius:var(--radius-control);
  font-size:var(--type-body-s);
}
.app-toast[hidden]{display:none}

/* ---- Chip ------------------------------------------------------------------
   Metadata or an explicit filter, never decoration on a short label.
   .app-control supplies the material. */
.app-chip{
  min-height:var(--control-inline);padding:0 12px;
  display:inline-flex;align-items:center;gap:6px;
  border-radius:var(--radius-inset);
  color:var(--app-muted);font:inherit;font-size:var(--type-small);text-decoration:none;cursor:pointer;
  transition:color var(--motion-ms) ease,border-color var(--motion-ms) ease,transform var(--motion-fast) var(--motion-ease);
}
.app-chip:active{transform:scale(.97)}
.app-chip:disabled{opacity:.45;cursor:default;transform:none}
.app-chip[aria-pressed="true"],.app-chip[aria-current],.app-chip.is-active{color:var(--app-accent);border-color:rgba(var(--app-accent-rgb),.38)}
.app-chip:focus-visible{outline:2px solid var(--app-accent);outline-offset:2px}
/* A rail is a row of chips that stays put under the masthead while a long
   detail scrolls. The rail itself has no edge and no material — each chip
   frosts what passes under it on its own, so nothing nests. Chips grow
   from their labels and share the rest of the row, so a longer label
   ("Artwork copy") takes the room it needs and a short one gives it up;
   five chips fit one phone row. */
.app-rail{position:sticky;top:calc(var(--app-masthead-height) + 10px);z-index:60;display:flex;gap:var(--space-3);margin-top:20px}
.app-rail .app-chip{flex:1 1 auto;min-width:0;justify-content:center;padding-inline:6px;white-space:nowrap}
/* Fixed columns wrap a longer rail into rows of equal chips. */
.app-rail[data-columns="3"]{display:grid;grid-template-columns:repeat(3,minmax(0,1fr))}
/* A section reached through the rail lands below it, not under it. */
.app-rail~.app-section,.app-page.is-shell>.app-rail~.app-section{scroll-margin-top:calc(var(--app-masthead-height) + 62px)}
.app-rail[data-columns="3"]~.app-section,.app-page.is-shell>.app-rail[data-columns="3"]~.app-section{scroll-margin-top:calc(var(--app-masthead-height) + 102px)}

/* ---- Dialog ----------------------------------------------------------------
   The dialog declares .app-overlay in markup; the scrim behind it is the
   material's own (app-glass.css). .app-sheet is the same native <dialog>,
   only the geometry differs, so the two share one scrim and one scroll lock. */
.app-dialog{
  width:min(94vw,620px);max-height:min(88dvh,760px);
  padding:0;border-radius:var(--radius-dialog);
  color:var(--app-text);
}
.app-dialog-body{padding:22px}
.app-dialog-head{display:flex;align-items:flex-start;justify-content:space-between;gap:18px}
.app-dialog-kicker{margin:0 0 4px;color:var(--app-accent);font-size:var(--type-small);font-weight:var(--weight-label);letter-spacing:var(--tracking-label);text-transform:uppercase}
.app-dialog-title{margin:0;font-size:var(--type-title);font-weight:var(--weight-thin);line-height:var(--leading-tight)}
.app-dialog-copy{margin:8px 0 0;color:var(--app-muted);font-size:var(--type-body-s);line-height:var(--leading-body)}
.app-dialog-head+.app-stack,.app-dialog-copy+.app-stack{margin-top:18px}
/* 16px between actions (r23): a text action beside a filled button needs
   the air, or Cancel reads as part of Save. */
.app-dialog-actions{margin-top:22px;display:flex;align-items:center;justify-content:flex-end;gap:var(--space-6)}
.app-dialog-actions-split{justify-content:space-between}
/* A form inside a dialog lays its fields on two columns; a field that needs
   the width spans. Under a phone's width the columns collapse. A section
   line groups fields the way a card section does, with the same hairline. */
.app-dialog-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:14px;margin-top:18px}
.app-dialog-grid>[data-width="full"]{grid-column:1/-1}
.app-dialog-section{grid-column:1/-1;display:grid;gap:3px;margin-top:6px;padding-top:14px;border-top:1px solid var(--app-line)}
.app-dialog-section>span{color:var(--app-accent);font-size:var(--type-small);font-weight:var(--weight-label);letter-spacing:var(--tracking-label);text-transform:uppercase}
.app-dialog-section>small{color:var(--app-muted);font-size:var(--type-body-s);line-height:var(--leading-body)}
.app-dialog-status{margin:14px 0 0;color:var(--app-muted);font-size:var(--type-body-s);line-height:var(--leading-body)}
.app-dialog-status[data-tone="error"]{color:var(--app-danger)}
.app-dialog-status:empty{margin:0}
/* The dialog's body scrolls when a form outgrows the viewport; the head and
   the actions stay in the flow so the whole form is one scroll. */
.app-dialog{overflow:auto}
.app-dialog-actions .app-utility-action[data-tone="danger"]{color:var(--app-danger)}
.app-dialog-actions>.app-row{gap:var(--space-6)}
@media (max-width:480px){.app-dialog-grid{grid-template-columns:1fr}}

/* Reduced motion belongs with the components that declare the motion. Equal
   specificity later in the same file wins, so no cascade override is needed. */
@media(prefers-reduced-motion:reduce){
  .app-action-card-button,.app-disclosure-button,.app-disclosure-chevron,
  .app-disclosure-panel,.app-field input,.app-field textarea,.app-field select,
  .app-select-trigger svg,.app-primary,.app-chip,
  .app-masthead-home,.app-masthead-account,.app-masthead-action,.app-navbar-set{transition-duration:.001ms;transition-delay:0s}
  .app-track-bar{transform:scaleX(1);transition:none}
}

/* ---- Shell ------------------------------------------------------------------
   A sticky header and footer that frost whatever scrolls beneath them. Both
   draw a single edge rather than a box, so they take the material from
   app-glass.css and declare only that one border here.

   Named .app-masthead / .app-navbar since r10, when the legacy shell's own
   header and tab bar still existed beside them.

   --app-navbar-height is exposed so .app-page can reserve room for the footer
   instead of every page guessing at it. */
/* Header and footer are the same height: one band top, one band bottom. The
   band's height is the constitution's (--app-shell-band). */
:root{--app-masthead-height:var(--app-shell-band);--app-navbar-height:var(--app-shell-band)}

.app-masthead{
  position:sticky;top:0;z-index:80;
  min-height:var(--app-masthead-height);
  padding:0 max(16px,env(safe-area-inset-left));
  display:flex;align-items:center;justify-content:space-between;gap:var(--space-5);
  border-bottom:1px solid var(--app-glass-border);
}
.app-masthead-logo{display:block;height:30px;width:auto}

/* The hotel logo is the Home button: wherever the employee is, one tap on
   the masthead returns to Today. It is frameless — the mark is the control,
   so it draws no edge and takes no material. */
.app-masthead-home{
  display:block;padding:0;border:0;background:transparent;color:inherit;
  border-radius:var(--radius-tight);cursor:pointer;
  transition:transform var(--motion-fast) var(--motion-ease);
}
.app-masthead-home:active{transform:scale(.96)}
.app-masthead-home:focus-visible{outline:2px solid var(--app-accent);outline-offset:4px}
/* The public share (r30) is the shell with no navbar and the logo as a
   mark, not Home: the page reserves no room for a footer, the toast sits on
   the safe area, and the mark takes no press. One body attribute, set by
   the generated page, is the whole difference. */
/* A shared page has no navbar and reserves nothing. A document page carries
   the transfer-test navbar and reserves it like any app route (r43). */
body[data-public]:not([data-public="doc"]){--app-navbar-height:0px}
body[data-public] .app-masthead-home{cursor:default}
body[data-public] .app-masthead-home:active{transform:none}

/* The account chip carries the employee's initials and opens Settings; while
   Settings is open it becomes the way back, so the same control shows a
   close mark instead. Rounded, never circular. .app-control supplies the
   material; it sits inside the glass masthead, so app-glass.css drops its
   blur there. Hidden until an employee is signed in. */
.app-masthead-account{
  width:var(--size-icon-control);height:var(--size-icon-control);flex:0 0 auto;
  display:inline-grid;place-items:center;
  border-radius:var(--radius-control);
  color:var(--app-text);font:inherit;font-size:var(--type-small);font-weight:var(--weight-bold);letter-spacing:var(--tracking-label);line-height:1;text-transform:uppercase;
  cursor:pointer;
  transition:transform var(--motion-fast) var(--motion-ease),color var(--motion-ms) ease;
}
.app-masthead-account:active{transform:scale(.94)}
.app-masthead-account:focus-visible{outline:2px solid var(--app-accent);outline-offset:2px}
.app-masthead-account svg{display:none;width:18px;height:18px;fill:none;stroke:currentColor;stroke-width:1.6}
.app-masthead-account[data-mode="close"]{color:var(--app-accent)}
.app-masthead-account[data-mode="close"] span{display:none}
.app-masthead-account[data-mode="close"] svg{display:block}

/* The masthead's right side is one row of icon controls: Messages, then the
   account chip. Messages left the footer in r20 - the footer's fourth tab
   is Jobs - and lives here as an icon-only control the same square as the
   chip, carrying the unread count. It is the only way into Messages; the
   page beneath keeps the app footer with no tab current, and the icon
   itself turns into the way back instead. Hidden with the chip until signed in. */
.app-masthead-tools{display:flex;align-items:center;gap:var(--space-3)}
.app-masthead-action{
  position:relative;
  width:var(--size-icon-control);height:var(--size-icon-control);flex:0 0 auto;
  display:inline-grid;place-items:center;
  border-radius:var(--radius-control);
  color:var(--app-text);font:inherit;cursor:pointer;
  transition:transform var(--motion-fast) var(--motion-ease),color var(--motion-ms) ease;
}
.app-masthead-action:active{transform:scale(.94)}
.app-masthead-action:focus-visible{outline:2px solid var(--app-accent);outline-offset:2px}
.app-masthead-action svg{display:block;width:18px;height:18px;fill:none;stroke:currentColor;stroke-width:1.6}
/* Two states, exactly like the chip: data-mode="messages" shows the icon and
   opens Messages; data-mode="close" (r23) shows a close mark in the accent
   while Messages is open, and tapping it returns to the page beneath. The
   unread count is not shown on the close mark - the employee is looking at
   the list it counts. */
.app-masthead-action svg[data-icon="close"]{display:none}
.app-masthead-action[data-mode="close"]{color:var(--app-accent)}
.app-masthead-action[data-mode="close"] svg[data-icon="messages"]{display:none}
.app-masthead-action[data-mode="close"] svg[data-icon="close"]{display:block}
.app-masthead-action[data-mode="close"] .app-masthead-badge{display:none}
/* The unread count sits on the control's top-right corner. It is a badge, so
   it keeps the badge tint; the masthead beneath is already glass, so
   app-glass.css drops its blur. */
.app-masthead-badge{position:absolute;top:-6px;right:-7px}
.app-masthead-action[aria-disabled="true"]{opacity:.34;pointer-events:none}

/* Fixed, not sticky. A sticky footer can only stick inside its containing
   block, so it depends on body actually reaching the bottom of the screen -
   which depends on 100dvh resolving to the visible height. Where it resolves
   short, the bar floats above the ground with the page's own background
   below it, and a short page (the frame before content mounts) is where that
   shows. Fixed to the viewport it cannot float, whatever dvh says; .app-page
   already reserves its height in padding, so nothing moves (r36). */
.app-navbar{
  position:fixed;left:0;right:0;bottom:0;z-index:80;
  min-height:var(--app-navbar-height);
  padding:0 max(8px,env(safe-area-inset-left)) env(safe-area-inset-bottom);
  display:grid;grid-auto-flow:column;grid-auto-columns:1fr;align-items:center;
  overflow:hidden;
  border-top:1px solid var(--app-glass-border);
}
.app-navbar-button{
  min-height:var(--control);padding:0 4px;
  display:flex;flex-direction:column;align-items:center;justify-content:center;gap:3px;
  border:0;background:transparent;
  color:var(--app-muted);font:inherit;font-size:var(--type-small);line-height:1;text-decoration:none;
  cursor:pointer;
  transition:color var(--motion-ms) ease,transform var(--motion-fast) var(--motion-ease);
}
/* A navbar item is a <button> for a shell route or an <a> for a page outside the shell; both read the same. */
.app-navbar-button:active{transform:scale(.96)}
.app-navbar-button[aria-current="page"]{color:var(--app-accent)}
.app-navbar-button:focus-visible{outline:2px solid var(--app-accent);outline-offset:-2px}
.app-navbar-button svg{width:20px;height:20px;fill:none;stroke:currentColor;stroke-width:1.5}
.app-navbar-button:disabled{cursor:default}

/* The footer holds two sets of buttons in the same cell: the app set (Today,
   F&B, Jobs, Brand) and the settings set (Me, Admin, Broadcast, System).
   The account chip switches the navbar's mode and the sets push: the settings
   set slides in from the right as the app set leaves to the left, and the
   reverse on the way back - the same ease-in-out the page uses. The navbar
   clips the set that is off to the side; the hidden set is invisible,
   untappable and inert, so the navbar is only ever one row of four. Both
   sets sit at grid area 1/1 of the navbar's own column grid, so a navbar
   holding bare buttons still lays out as before. */
.app-navbar-set{
  grid-area:1/1;min-width:0;
  display:grid;grid-auto-flow:column;grid-auto-columns:1fr;align-items:center;
  visibility:hidden;transform:translateX(100%);
  transition:transform var(--motion-ms) var(--motion-ease-inout),visibility 0s linear var(--motion-ms);
}
.app-navbar[data-mode="settings"] .app-navbar-set[data-set="app"]{transform:translateX(-100%)}
.app-navbar:not([data-mode="settings"]) .app-navbar-set[data-set="app"],
.app-navbar[data-mode="settings"] .app-navbar-set[data-set="settings"]{visibility:visible;transform:none;transition-delay:0s}

/* Signed out, the routes stay visible but dormant: the shell shows where the
   app goes without letting anyone go there. The router disables the
   buttons; data-locked lets a specimen show the state without a script. */
/* A destination that does not exist yet is not a link, so it cannot be
   disabled the way a button is; aria-disabled says the same thing about a
   span and reads the same (r43). */
.app-navbar-button:disabled,.app-navbar-button[aria-disabled="true"],.app-navbar[data-locked="true"] .app-navbar-button{color:var(--app-muted);opacity:.4;cursor:default}
.app-navbar-button:disabled:active,.app-navbar-button[aria-disabled="true"]:active,.app-navbar[data-locked="true"] .app-navbar-button:active{transform:none}

/* A page inside the shell reserves room for the sticky footer. */
/* flex:1 0 auto is the other half of the sticky-footer fix in app-shell.css:
   body is a flex column, so this is what actually consumes the leftover
   viewport height instead of leaving the footer to float after short
   content. */
/* Auto margins in a column flex box shrink the page to its content, which
   a skeleton of percentage-width lines makes visibly narrow. */
.app-page.is-shell{flex:1 0 auto;width:100%;padding-bottom:calc(var(--app-navbar-height) + 28px)}
/* Under a masthead the page's own 28px top plus the hero's 22px read as a
   hole; the hero starts one 28px band below the masthead instead. */
.app-page.is-shell{padding-top:0}
.app-page.is-shell>.app-hero:first-child{padding-top:28px}
/* Under the sticky masthead a jumped-to section lands below it, not behind it. A rail-led section keeps its own, larger offset. */
.app-page.is-shell>.app-section{scroll-margin-top:calc(var(--app-masthead-height) + 20px)}

/* ============================================================================
   LAYOUT — every page needs these, so no page should define them.

   These were sitting in the UI Library page's private stylesheet, which meant
   the next page built would have re-invented a reading column, a section and a
   heading. A module that every page needs is a library module by definition.
   ========================================================================== */
.app-page{position:relative;z-index:1;max-width:var(--measure-page);margin:0 auto;padding:28px max(20px,env(safe-area-inset-left)) calc(64px + env(safe-area-inset-bottom))}
.app-section{padding:44px 0 0;scroll-margin-top:20px}
.app-section-kicker{display:flex;align-items:center;gap:var(--space-4);margin:0;color:var(--app-accent);font-size:var(--type-small);font-weight:var(--weight-label);letter-spacing:var(--tracking-label);text-transform:uppercase}
.app-section-kicker::after{content:"";flex:1 1 auto;height:1px;background:var(--app-line)}
/* A count or status at the far end of the kicker rule, after the line. */
.app-section-kicker-end{order:1;color:var(--app-muted);font-variant-numeric:tabular-nums}
.app-section-title{margin:8px 0 0;font-size:var(--type-section);font-weight:var(--weight-thin);letter-spacing:var(--tracking-display);line-height:var(--leading-tight)}
.app-section-lede{margin:10px 0 0;max-width:60ch;color:var(--app-muted);font-size:var(--type-body);line-height:var(--leading-body)}
.app-row{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-5)}
.app-row[data-split="true"]{justify-content:space-between}
/* Peers in a row rather than a column. The count is the author's semantic
   claim - "these belong side by side, two across" - not a measurement, and it
   does not collapse: a product that wants two on a phone gets two, the way an
   archive of paper thumbnails does. Existing stacks name no count and are
   unchanged (r41). */
.app-stack{display:grid;gap:var(--space-5)}
/* A declared column count applies where there is room for it. Two columns on a
   390px screen is two 165px columns, which is not what "two columns" was ever
   asked for - the page is saying "these read side by side", and on a phone
   nothing does. Same 700px the reference sheet has always used. */
@media (min-width:700px){
  .app-stack[data-columns="2"]{grid-template-columns:repeat(2,minmax(0,1fr))}
  .app-stack[data-columns="3"]{grid-template-columns:repeat(3,minmax(0,1fr))}
}
/* A grid item is min-width:auto by default, so one long unbroken word makes an
   item wider than its track and a two-across row stops being equal. A stack
   arranges peers; a peer that will not fit its share is not arranged. This
   holds for every stack, not only the ones in columns, because a single-column
   stack inside a column is where it actually bit (r46). */
.app-stack>*{min-width:0}
.app-section>.app-stack,
.app-section>.app-card,
.app-section>.app-row,
.app-section>.app-metric-grid,
.app-section>.app-figure,
.app-section>.app-table-wrap,
.app-section>.app-prose,
.app-section>.app-disclosure{margin-top:var(--space-7)}
/* 20px is the distance from a title to its content; a section that opens
   straight into content has no title to be 20px from. */
.app-section>.app-stack:first-child,.app-section>.app-card:first-child,.app-section>.app-metric-grid:first-child,.app-section>.app-figure:first-child{margin-top:0}
/* Two selectors side by side share the row equally; a compact one is a badge
   and keeps its own width. */
.app-row>.app-select{flex:1 1 140px}
.app-row>.app-select[data-compact="true"]{flex:0 0 auto}
.app-section-subhead{margin:28px 0 0;font-size:var(--type-lead);font-weight:var(--weight-regular);line-height:var(--leading-label)}
.app-section-subhead+.app-stack{margin-top:12px}
/* A code block sets its own margin to 0 so a page can place it; inside a
   section it still follows a heading or a lede, and needs the gap the rest of
   the section's children get. */
:where(.app-section-subhead,.app-section-lede)+.app-code-block{margin-top:12px}
.app-note{margin:12px 0 0;color:var(--app-muted);font-size:var(--type-body-s);line-height:var(--leading-body)}

/* ---- Route hero -------------------------------------------------------------
   route-hero-standard.css is a compatibility shim: cascade overrides throughout, route
   aliases, and a duplicate token set. This is the clean form. Routes adopt it
   when they are rebuilt; until then the shim keeps them working. */
.app-hero{padding:22px 0 20px;border-bottom:1px solid var(--app-line)}
/* The first section after the hero reads as its continuation, not a new chapter. */
.app-hero+.app-section,.app-rail+.app-section{padding-top:var(--space-8)}
.app-hero-eyebrow{margin:0;color:var(--app-accent);font-size:var(--type-small);font-weight:var(--weight-label);letter-spacing:var(--tracking-label);text-transform:uppercase}
.app-hero-title{margin:10px 0 0;font-size:var(--type-display);font-weight:var(--weight-thin);letter-spacing:var(--tracking-display);line-height:var(--leading-tight);text-transform:var(--case-display)}
/* Emphasis inside a display title is the bold face; a voice that sets a
   heavier bold gets it here without a second selector. */
.app-hero-title b{font-weight:var(--weight-bold)}
.app-hero-copy{margin:12px 0 0;max-width:56ch;color:var(--app-muted);font-size:var(--type-lead);line-height:var(--leading-body)}
.app-hero>.app-back-control{margin-bottom:16px}
.app-hero>.app-row{margin-top:16px}
/* Hero head row: the eyebrow or the back control on the left, one frameless
   action on the right, so Share sits where the live routes keep it. */
.app-hero-head{display:flex;align-items:center;justify-content:space-between;gap:var(--space-5)}
/* An action beside the eyebrow keeps its tap height without adding to the
   row, so a hero with Share sits exactly where a hero without one does. */
.app-hero-head>.app-hero-eyebrow+.app-utility-action{margin-block:-8px}
.app-hero-head+.app-hero-eyebrow{margin-top:16px}
.app-hero-head+.app-hero-title{margin-top:10px}
.app-hero>.app-note{margin-top:6px}
/* A hero may carry a strip of figures under its copy - the headline numbers a
   document leads with. The hero gives every child it knows its own top margin,
   so a child it had never met sat flush against the paragraph above it. */
.app-hero>.app-metric-grid{margin-top:16px}
.app-hero>.app-skeleton{margin-top:16px}

/* ---- Operational states -----------------------------------------------------
   Loading, empty, success and error explain what happened and what the employee
   can do next, without changing the product's visual language. One component,
   four tones. */
.app-state{padding:16px 17px;border-radius:var(--radius-surface)}
.app-state-label{color:var(--app-muted);font-size:var(--type-small);font-weight:var(--weight-label);letter-spacing:var(--tracking-label);text-transform:uppercase}
.app-state-title{margin:6px 0 0;font-size:var(--type-body);font-weight:var(--weight-regular);line-height:var(--leading-label)}
.app-state-copy{margin:5px 0 0;color:var(--app-muted);font-size:var(--type-body-s);line-height:var(--leading-body)}
.app-state[data-tone="success"] .app-state-label{color:var(--app-success)}
.app-state[data-tone="error"] .app-state-label{color:var(--app-danger)}
.app-state[data-tone="loading"] .app-state-label{color:var(--app-accent)}

/* ---- Skeleton ---------------------------------------------------------------
   Reserve the final geometry so nothing jumps when the content arrives. A
   skeleton is a placeholder, not a surface: it takes the inset fill. */
.app-skeleton{display:grid;gap:var(--space-3)}
.app-skeleton-line,.app-skeleton-block{background:var(--app-inset-fill);border-radius:var(--radius-tight);animation:app-skeleton-pulse 1.4s var(--motion-ease) infinite}
.app-skeleton-line{height:12px}
.app-skeleton-line[data-width="short"]{width:38%}
.app-skeleton-line[data-width="medium"]{width:64%}
.app-skeleton-line[data-width="tiny"]{width:22%}
.app-skeleton-line[data-width="half"]{width:50%}
/* Sizes so a skeleton can take the exact shape of the page it stands in for. */
.app-skeleton-line[data-size="title"]{height:30px}
.app-skeleton-line[data-size="lead"]{height:16px}
.app-skeleton-line[data-size="control"]{height:44px;border-radius:var(--radius-inset)}
.app-skeleton-line[data-size="track"]{height:3px}
.app-skeleton-line[data-size="square"]{width:44px;height:44px;border-radius:var(--radius-inset)}
.app-skeleton[data-compact="true"]{gap:6px}
.app-action-card>.app-skeleton{padding:16px 17px}
.app-row>.app-skeleton{flex:1 1 140px}
.app-skeleton-block{height:74px;border-radius:var(--radius-inset)}
@keyframes app-skeleton-pulse{0%,100%{opacity:.5}50%{opacity:.9}}

/* ---- Figure -----------------------------------------------------------------
   Official imagery keeps an intentional aspect ratio and labels its provenance. */
/* A machine-readable mark. It is not a surface and takes no material: it must
   stay opaque and high-contrast to be scannable whatever the interface is made
   of, so the constitution names its ink and its ground rather than deriving
   them from the page. The module that draws it declares no colour at all. */
.app-qr{display:block;width:100%;height:auto}
.app-qr .app-qr-ground{fill:var(--app-qr-ground)}
.app-qr .app-qr-ink{fill:var(--app-qr-ink)}
.app-qr .app-qr-frame{fill:none;stroke:var(--app-qr-ink);stroke-width:1}

.app-figure{margin:0;overflow:hidden;border-radius:var(--radius-surface)}
.app-figure img{display:block;width:100%;height:auto}
/* A caption frames the figure. The photo and its provenance are one object,
   so a captioned figure is an inset well - the image flush at the top, the
   caption inside the well's padding, the same well a field draws. A caption
   set loose under the image read as two things (r69). An uncaptioned figure
   is the image alone and draws nothing. */
.app-figure:has(figcaption){background:var(--app-inset-fill)}
.app-figure figcaption{padding:10px 12px;color:var(--app-muted);font-size:var(--type-small)}
/* Inside a card or disclosure the figure takes the inset radius. */
:where(.app-card,.app-disclosure) .app-figure{border-radius:var(--radius-inset)}

@media(prefers-reduced-motion:reduce){
  .app-skeleton-line,.app-skeleton-block{animation:none}
}

/* ---- View transitions -------------------------------------------------------
   One page gives way to the next with a cut and a settle: the outgoing page
   leaves at once, the incoming page arrives a short way off its place and
   eases in over --motion-view on the plain ease-in-out curve. Nothing
   recedes, scales or parallaxes - one moving surface, so the frosted cards
   on it stay cheap to draw mid-move - and the direction still carries the
   spatial meaning app-view.js chooses:

     push / pop      siblings on one layer arrive from the right / the left
     cover / dismiss a deeper layer arrives from the foot; the page beneath
                     returns from the head

   Every keyframe is a transform. Opacity, filter, mask or clip-path on the
   host or anything above it defeats backdrop-filter and the glass flashes
   flat mid-move, so a crossfade is not an option here. The host holds its
   start pose hidden until the swap lands (data-view), then runs (data-run).

   Attribute selectors only: the shell's host and the library's specimen host
   share these without sharing a class. */
[data-view]{visibility:hidden;animation:var(--motion-view) var(--motion-ease-inout) both paused}
[data-view][data-run="true"]{visibility:visible;animation-play-state:running}
[data-view="push"]{animation-name:app-view-push-in}
[data-view="pop"]{animation-name:app-view-pop-in}
[data-view="cover"]{animation-name:app-view-cover-in}
[data-view="dismiss"]{animation-name:app-view-dismiss-in}
@keyframes app-view-spring-in{
  0%{opacity:0;transform:translateY(var(--motion-spring-travel)) scale(.965)}
  62%{opacity:1;transform:translateY(-5px) scale(1.006)}
  100%{opacity:1;transform:none}
}
@media (prefers-reduced-motion:reduce){}

@keyframes app-view-push-in{from{transform:translateX(var(--motion-view-travel))}to{transform:none}}
@keyframes app-view-pop-in{from{transform:translateX(calc(-1 * var(--motion-view-travel)))}to{transform:none}}
@keyframes app-view-cover-in{from{transform:translateY(var(--motion-view-travel))}to{transform:none}}
@keyframes app-view-dismiss-in{from{transform:translateY(calc(-1 * var(--motion-view-travel)))}to{transform:none}}