/* ─────────────────────────────────────────────────────────────────────────
   Magic UI components

   Load order: vendor/bootstrap/bootstrap.min.css → tokens/tokens.css →
   this file → css/magic-ui-bootstrap.css → css/magic-ui-bento.css. Every rule reads tokens; nothing here names
   a colour. Class names are .ms-<component>[-part], state is .is-<state> or
   an ARIA attribute (aria-selected, aria-pressed, aria-expanded,
   aria-current, aria-disabled) — prefer the ARIA attribute when one exists,
   so the look and what assistive technology hears cannot disagree.

   Sections: base · shell (rail, toolbar, workspace, inspector) · controls ·
   data display · feedback · overlays · standalone pages · responsive.
   See 04-components.md for anatomy, states and behaviour of each.
   ───────────────────────────────────────────────────────────────────────── */

/* ── Base ─────────────────────────────────────────────────────────────── */

*, *::before, *::after { box-sizing: border-box; }
html, body { margin: 0; }
body {
  font-family: var(--font-sans); font-size: var(--fs-base); font-weight: var(--fw-regular); line-height: var(--lh-base);
  background: var(--bg); color: var(--text); -webkit-font-smoothing: antialiased;
}
input, textarea, button, select { font: inherit; color: inherit; color-scheme: inherit; }
select option { background: var(--surface); color: var(--text); }
input::placeholder, textarea::placeholder { color: var(--faint); }
a { color: var(--accent); text-decoration: none; }
a:hover { color: var(--accent-strong); }
* { scrollbar-width: thin; scrollbar-color: var(--faint) transparent; }
[hidden] { display: none !important; }
body.is-locked { overflow: hidden; }            /* while a dialog is open */

/* Keyboard focus is always visible; mouse focus never draws a ring. */
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: var(--r-sm); }

/* Read by screen readers, not shown. */
.ms-sr { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
.ms-muted { color: var(--faint); }
.ms-dim { color: var(--dim); }
.ms-mono { font-family: var(--font-mono); }
.ms-num { font-variant-numeric: tabular-nums; }
.ms-num.is-end { display: block; text-align: right; }   /* figures in a column line up on the right */
.ms-spacer { flex: 1; }

@keyframes ms-fade { from { opacity: 0; } to { opacity: 1; } }
@keyframes ms-rise { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
@keyframes ms-drop { from { opacity: 0; transform: translateY(-8px); } to { opacity: 1; transform: none; } }
@keyframes ms-menu { from { opacity: 0; transform: translateY(5px) scale(.97); } to { opacity: 1; transform: none; } }
@keyframes ms-pane { from { transform: translateX(26px); opacity: 0; } to { transform: none; opacity: 1; } }
@keyframes ms-row-in { from { opacity: 0; transform: translateX(10px); } to { opacity: 1; transform: none; } }
@keyframes ms-shimmer { from { background-position: 200% 0; } to { background-position: -200% 0; } }
@keyframes ms-spin { to { transform: rotate(360deg); } }
@keyframes ms-new { 0% { background: color-mix(in srgb, var(--accent) 22%, transparent); } 100% { background: transparent; } }

/* Every animation decorates a change that has already happened, so
   removing them costs nothing. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: .001ms !important; animation-iteration-count: 1 !important; transition-duration: .001ms !important; }
}

/* ── Shell ────────────────────────────────────────────────────────────── */

.ms-app { display: flex; min-height: 100vh; padding-top: var(--banner-h, 0px); }   /* room for fixed banners */
.ms-main { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.ms-body { flex: 1; min-height: 0; display: flex; }
.ms-workspace { flex: 1; min-width: 0; padding: var(--sp-4) var(--sp-7) 90px; }   /* 90px: room for the selection bar */

/* Navigation rail: 80px of icons with labels under them, or a 240px drawer. */
.ms-rail {
  width: var(--rail-w); flex: none; display: flex; flex-direction: column; gap: 2px;
  padding: 12px 10px 10px; background: var(--rail-bg); color: var(--rail-fg); border-right: 1px solid var(--rail-border, transparent);
  height: calc(100vh - var(--banner-h, 0px)); position: sticky; top: var(--banner-h, 0px); z-index: var(--z-rail);
  transition: width .22s var(--ease-rail), padding .22s ease;
}
.ms-rail.is-open { width: var(--rail-w-open); padding-inline: 12px; }
.ms-rail :focus-visible { outline: 2px solid var(--rail-fg); outline-offset: -2px; }
.ms-rail-head { height: 48px; flex: none; display: flex; align-items: center; gap: 10px; margin-bottom: 10px; }
/* Collapsed, the head usually holds nothing but the toggle — the brand and the
   mark are hidden — so it centres rather than clinging to the left edge where
   the brand used to be. */
.ms-rail:not(.is-open) .ms-rail-head { justify-content: center; gap: 0; }
.ms-rail:not(.is-open) .ms-rail-head > .ms-rail-btn { width: 44px; height: 40px; border-radius: var(--r-lg); }
/* Under a top bar the rail has no brand of its own, so a fixed 48px head is a
   blank row with one chevron adrift in it. Let it size to what is actually in
   it — and put the collapse control in the foot (.ms-rail-toggle-row), with
   the other things that are about the rail rather than about the app. */
.ms-app.has-topbar .ms-rail-head { height: auto; min-height: 0; margin-bottom: 0; }
.ms-app.has-topbar .ms-rail-head:empty { display: none; }
.ms-app.has-topbar .ms-rail-scroll { padding-top: 4px; }
/* The collapse control belongs with the brand, in the bar: they are both "the
   app", it is where people look for it, and it leaves the rail with no head at
   all — so the navigation starts at the top of the rail rather than 58px down
   it. The button is the bar's own colours, not the rail's: on a coloured rail
   the rail's foreground is white, which would vanish here. */
.ms-topbar-toggle {
  width: 34px; height: 34px; flex: none; border: 0; border-radius: var(--r-md); padding: 0;
  background: transparent; color: var(--dim); cursor: pointer; display: grid; place-items: center;
}
.ms-topbar-toggle:hover { background: var(--surface-2); color: var(--text); }
.ms-topbar-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
/* Kept for a rail that wants its toggle at the foot instead. */
.ms-rail-toggle-row { display: flex; justify-content: flex-end; padding-top: 6px; margin-top: 6px; border-top: 1px solid var(--rail-divider); }
.ms-rail:not(.is-open) .ms-rail-toggle-row { justify-content: center; }
.ms-rail-toggle-row > .ms-rail-btn { width: 44px; height: 36px; border-radius: var(--r-lg); }
.ms-rail-logo { width: 40px; height: 40px; border-radius: 11px; flex: none; display: grid; place-items: center; background: var(--rail-card-bg); color: var(--rail-fg); }
.ms-rail-toggle {
  position: relative; width: 60px; height: 48px; border: 0; padding: 0; background: transparent; cursor: pointer;
  border-radius: var(--r-xl); display: grid; place-items: center; color: var(--rail-fg);
}
.ms-rail-toggle > * { grid-area: 1 / 1; transition: opacity var(--t-base) ease, transform var(--t-base) ease; }
.ms-rail-expand { width: 40px; height: 40px; border-radius: 11px; display: grid; place-items: center; background: var(--rail-hover-bg); opacity: 0; transform: scale(.9); }
.ms-rail-toggle:hover .ms-rail-logo, .ms-rail-toggle:focus-visible .ms-rail-logo { opacity: 0; transform: scale(.9); }
.ms-rail-toggle:hover .ms-rail-expand, .ms-rail-toggle:focus-visible .ms-rail-expand { opacity: 1; transform: none; }
.ms-rail-brand { flex: 1; min-width: 0; font-size: 15.5px; font-weight: var(--fw-heavy); letter-spacing: -.02em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ms-rail-btn {
  width: 32px; height: 32px; border: 0; border-radius: var(--r-md); background: transparent; cursor: pointer; padding: 0;
  display: grid; place-items: center; color: var(--rail-fg-muted); flex: none;
}
.ms-rail-btn:hover { background: var(--rail-hover-bg); color: var(--rail-fg); }
.ms-rail-scroll { flex: 1 1 auto; min-height: 0; overflow-y: auto; overflow-x: hidden; display: flex; flex-direction: column; scrollbar-width: none; }
.ms-rail nav { display: flex; flex-direction: column; gap: 2px; }
.ms-rail-item {
  /* 2px of margin, not padding: collapsed, the items are tall enough that a
     hovered one's fill met the current item's pill above or below it and the
     two read as one block. The gap keeps each one its own shape. */
  display: flex; flex-direction: column; align-items: center; gap: 4px;
  margin-block: 2px; padding: 9px 2px 7px; border-radius: var(--r-xl);
  /* Collapsing changes flex-direction and text-align, which cannot be
     animated — they snap whatever you do. Everything else that changes can
     be, and carrying those across the same .22s as the rail's own width is
     what keeps the snap from reading as a jolt. (prefers-reduced-motion is
     handled once at the top of this file, for everything.) */
  transition: padding .22s var(--ease-rail), gap .22s var(--ease-rail),
              font-size .22s var(--ease-rail), border-radius .22s var(--ease-rail);
  color: var(--rail-fg-muted); font-size: var(--fs-2xs); font-weight: var(--fw-bold); line-height: 1.15;
  text-align: center; white-space: nowrap; min-width: 0; text-decoration: none; cursor: pointer; border: 0; background: transparent;
}
.ms-rail-item:hover { background: var(--rail-hover-bg); color: var(--rail-fg); }
.ms-rail-item[aria-current="page"] { background: var(--rail-active-bg); color: var(--rail-active-fg, var(--rail-fg)); }
.ms-rail-item > span { max-width: 100%; overflow: hidden; text-overflow: ellipsis; }
/* The label's own transition, so the text settling into its new place reads
   as a move rather than a redraw. */
.ms-rail-item > span:not(.ms-rail-badge):not(.ms-dot) { transition: font-size .22s var(--ease-rail); }
.ms-rail.is-open .ms-rail-item { flex-direction: row; gap: 11px; padding: 9px 10px; font-size: var(--fs-lg); text-align: left; border-radius: var(--r-lg); }
.ms-rail.is-open .ms-rail-item > span { flex: 1; }
.ms-rail-divider { height: 1px; background: var(--rail-divider); margin: 8px 6px; flex: none; }
.ms-rail-sub { display: flex; flex-direction: column; gap: 1px; padding: 2px 0 4px 20px; }
.ms-rail-sub a {
  display: flex; align-items: center; gap: 9px; padding: 6px 10px; border-radius: var(--r-sm); min-width: 0;
  font-size: var(--fs-md); font-weight: var(--fw-medium); color: var(--rail-fg-muted); white-space: nowrap;
}
.ms-rail-sub a span { overflow: hidden; text-overflow: ellipsis; }
.ms-rail-sub a:hover { background: var(--rail-hover-bg); color: var(--rail-fg); }
.ms-rail-sub a[aria-current="page"] { background: var(--rail-active-bg); color: var(--rail-active-fg, var(--rail-fg)); }
.ms-rail:not(.is-open) .ms-rail-sub, .ms-rail:not(.is-open) .ms-rail-only-open { display: none; }
/* A named group in the open drawer ("Queues +"), and a coloured dot for a
   label people chose. The dot always sits beside the label's name. */
.ms-rail-label { display: flex; align-items: center; gap: 6px; margin: 14px 4px 4px 10px; font-size: var(--fs-sm); font-weight: var(--fw-heavy); color: var(--rail-fg-muted); }
.ms-rail-label > span { flex: 1; min-width: 0; }
.ms-rail:not(.is-open) .ms-rail-label { display: none; }
/* Collapsed, the label that used to hold the groups apart is gone, so the
   groups do it themselves — otherwise the last item of one and the first of
   the next sit a pixel apart, and a hover on either one touches the other. */
.ms-rail:not(.is-open) .ms-rail-label + * { margin-top: 10px; }
.ms-rail:not(.is-open) .ms-rail-scroll + .ms-rail-foot,
.ms-rail:not(.is-open) .ms-rail-foot > nav:first-child { margin-top: 8px; }
.ms-dot { width: 10px; height: 10px; flex: none; border-radius: 3px; background: var(--tone, var(--accent)); }
.ms-rail-item > .ms-dot { margin-inline: 4.5px; }                 /* centred in the 19px icon slot */
.ms-rail.is-open .ms-rail-item > span.ms-dot, .ms-rail.is-open .ms-rail-item > span.ms-rail-badge { flex: none; }
.ms-rail-badge.is-count { background: var(--chip-success-bg); color: var(--chip-success-fg); }
.ms-rail:not(.is-open) .ms-rail-item > .ms-rail-badge { display: none; }
/* A count has no room in the narrow rail, but the signal should survive it:
   it becomes a dot on the icon, and the badge's aria-label still reads it. */
.ms-rail:not(.is-open) .ms-rail-item { position: relative; }
.ms-rail:not(.is-open) .ms-rail-item > .ms-rail-badge.is-count {
  display: block; position: absolute; top: 5px; right: 16px; width: 10px; height: 10px; min-width: 0;
  padding: 0; border-radius: 50%; font-size: 0; overflow: hidden; border: 2px solid var(--rail-bg);
  /* The chip fill is made to sit BEHIND text, so as a solid dot with no text
     it all but disappears — in dark it was a near-black green on a near-black
     rail. A dot has to be the vivid tone, and a ring of the rail's own colour
     keeps it legible on a coloured rail too. */
  background: var(--rail-dot, var(--success));
}
.ms-rail-foot { flex: none; display: flex; flex-direction: column; padding-top: 8px; }
.ms-rail-card { border-radius: var(--r-xl); background: var(--rail-card-bg); padding: 11px 12px 10px; margin-top: 6px; overflow: hidden; }
.ms-rail-card-head { display: flex; align-items: center; justify-content: space-between; font-size: var(--fs-sm); font-weight: var(--fw-bold); margin-bottom: 8px; white-space: nowrap; }
.ms-rail-card-meta { display: flex; align-items: center; gap: 6px; font-size: var(--fs-xs); color: var(--rail-fg-muted); margin-top: 7px; font-variant-numeric: tabular-nums; white-space: nowrap; }
.ms-rail-bar { height: 6px; border-radius: 3px; background: var(--rail-divider); overflow: hidden; }
.ms-rail-bar i { display: block; height: 100%; border-radius: 3px; background: var(--rail-fg); transition: width .4s ease; }
.ms-rail-bar i.is-warn { background: var(--status-warn-bg); }
.ms-rail-bar i.is-crit { background: var(--status-crit-bg); }
.ms-rail-badge { display: inline-flex; align-items: center; gap: 3px; height: 20px; padding: 0 7px; border-radius: 10px; font-size: var(--fs-xs); font-weight: var(--fw-heavy); font-variant-numeric: tabular-nums; white-space: nowrap; }
.ms-rail-badge.is-warn { background: var(--status-warn-bg); color: var(--status-warn-fg); }
.ms-rail-badge.is-crit { background: var(--status-crit-bg); color: var(--status-crit-fg); }
.ms-rail-ring { display: grid; justify-items: center; gap: 5px; margin-top: 6px; padding: 6px 0 4px; color: var(--rail-fg); border-radius: var(--r-xl); text-decoration: none; }
/* Restated: a global a:hover would otherwise recolour it to the accent. */
.ms-rail-ring:hover, .ms-rail-ring:focus-visible { background: var(--rail-hover-bg); color: var(--rail-fg); }
.ms-rail-version { font: 500 var(--fs-2xs)/1.3 var(--font-mono); color: var(--rail-fg-muted); padding: 8px 4px 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ms-rail:not(.is-open) .ms-rail-version { text-align: center; font-size: 9.5px; }
/* Tooltips beside the rail come from MSUX's fixed layer, not an ::after: the
   scroller clips sideways and the toolbar sits above the rail, so a tooltip
   drawn inside the rail is cut off or painted over. */
.ms-rail [data-tip]::after,
.ms-rail [data-tip]:hover::after,
.ms-rail [data-tip]:focus-visible::after { content: none; display: none; }
.ms-tip {
  position: fixed; z-index: var(--z-floating-menu); transform: translateY(-50%);
  background: var(--tooltip-bg); color: var(--tooltip-fg); font: 700 11.5px/1 var(--font-sans); padding: 7px 8px; border-radius: 7px;
  white-space: pre; pointer-events: none; opacity: 0; transition: opacity var(--t-fast) ease;
}
.ms-tip.is-on { opacity: 1; }

/* Toolbar: where you are, then what you can do here. */
.ms-toolbar {
  height: var(--toolbar-h); flex: none; display: flex; align-items: center; gap: var(--sp-5);
  padding: 0 var(--sp-8) 0 var(--sp-10); border-bottom: 1px solid var(--border); background: var(--bg);
  position: sticky; top: var(--banner-h, 0px); z-index: var(--z-sticky);
}
.ms-crumbs { flex: 1; min-width: 0; display: flex; align-items: center; gap: 6px; }
.ms-crumbs a, .ms-crumbs button { font-size: var(--fs-xl); font-weight: var(--fw-bold); color: var(--dim); white-space: nowrap; background: none; border: 0; padding: 0; cursor: pointer; }
.ms-crumbs a:hover, .ms-crumbs button:hover { color: var(--text); }
.ms-crumbs svg { color: var(--faint); flex: none; }
.ms-title { margin: 0; font-size: 16px; font-weight: var(--fw-heavy); letter-spacing: var(--tracking-title); color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* Inspector (the Details pane): what is selected, and what can be done with it. */
.ms-inspector {
  width: var(--inspector-w); flex: none; border-left: 1px solid var(--border); background: var(--surface);
  height: calc(100vh - var(--banner-h, 0px) - var(--toolbar-h)); position: sticky; top: calc(var(--banner-h, 0px) + var(--toolbar-h));
  display: flex; flex-direction: column; animation: ms-pane .2s ease both;
}
.ms-insp-tabs { flex: none; display: flex; align-items: flex-end; gap: 4px; height: 49px; padding: 0 12px 0 18px; border-bottom: 1px solid var(--border); }
.ms-insp-tab { height: 48px; display: flex; align-items: center; margin-bottom: -1px; font-size: var(--fs-base); font-weight: var(--fw-heavy); border-bottom: 2px solid var(--accent); }
.ms-insp-tabs.is-drill { align-items: center; }
.ms-insp-back { width: 30px; height: 30px; margin-left: -8px; border-radius: var(--r-sm); border: 0; background: transparent; color: var(--dim); display: grid; place-items: center; cursor: pointer; }
.ms-insp-back:hover { background: var(--surface-2); color: var(--text); }
.ms-insp-heading { margin: 0; font-size: var(--fs-lg); font-weight: var(--fw-heavy); }
.ms-insp-close { margin-bottom: 10px; width: 28px; height: 28px; border-radius: var(--r-sm); border: none; background: transparent; color: var(--faint); cursor: pointer; display: grid; place-items: center; }
.ms-insp-tabs.is-drill .ms-insp-close { margin-bottom: 0; }
.ms-insp-close:hover { background: var(--surface-2); color: var(--text); }
.ms-insp-body { flex: 1; min-height: 0; overflow: auto; padding: 16px 18px 24px; }
.ms-insp-preview {
  position: relative; display: flex; align-items: center; justify-content: center; width: 100%; aspect-ratio: 16 / 10;
  border-radius: var(--r-xl); overflow: hidden; padding: 0; border: 1px solid var(--border); background: var(--surface-2); color: inherit;
}
button.ms-insp-preview { cursor: pointer; }
.ms-insp-preview-type {
  width: 100%; height: 100%; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 8px;
  background: color-mix(in srgb, var(--tone, var(--accent)) 12%, var(--surface-2)); color: var(--tone, var(--accent));
  font: 800 11px/1 var(--font-mono); letter-spacing: .08em;
}
.ms-insp-toolbar { display: flex; align-items: center; gap: 4px; margin: 12px 0 18px; min-height: 34px; }
.ms-insp-title { margin: 0 0 12px; font-size: var(--fs-base); font-weight: var(--fw-heavy); letter-spacing: -.005em; }
.ms-insp-hint { margin: 18px 0 0; font-size: var(--fs-sm); line-height: 1.55; color: var(--faint); text-align: center; }
.ms-drill-row {
  width: 100%; display: flex; align-items: center; gap: 10px; padding: 10px 11px; margin: 0 0 18px; border-radius: 11px;
  border: 1px solid var(--border); background: var(--surface); color: var(--text); text-align: left; cursor: pointer;
}
.ms-drill-row:hover { background: var(--surface-2); }
.ms-drill-row > svg { color: var(--dim); flex: none; }
.ms-drill-row-text { flex: 1; min-width: 0; display: flex; flex-wrap: wrap; align-items: baseline; gap: 0 5px; }
.ms-drill-row-text b { font-size: var(--fs-base); font-weight: var(--fw-heavy); }
.ms-drill-row-text span { font-size: var(--fs-md); color: var(--dim); font-weight: var(--fw-medium); }

/* Properties: the label above its value, so long values never squeeze. */
.ms-props { display: flex; flex-direction: column; gap: 14px; }
.ms-prop { position: relative; display: flex; flex-direction: column; gap: 5px; min-width: 0; }
.ms-prop-label { font-size: var(--fs-xs); font-weight: var(--fw-bold); letter-spacing: var(--tracking-label); text-transform: uppercase; color: var(--faint); }
.ms-prop-value { font-size: var(--fs-base); line-height: var(--lh-base); overflow-wrap: anywhere; }
.ms-prop-value time { border-bottom: 1px dotted var(--border-strong); cursor: help; }
.ms-prop-value small { color: var(--faint); font-size: 11.5px; }
.ms-person { display: inline-flex; align-items: center; gap: 8px; }

/* ── Controls ─────────────────────────────────────────────────────────── */

/* Buttons. One primary per view; the rest quiet. */
.ms-btn {
  height: var(--h-md); padding: 0 13px; border-radius: var(--r-md); border: 1px solid var(--border-strong);
  background: var(--surface); color: var(--text); font-size: var(--fs-base); font-weight: var(--fw-bold); cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center; gap: 7px; white-space: nowrap; text-decoration: none; position: relative;
}
.ms-btn:hover:not([disabled]):not([aria-disabled="true"]) { background: var(--surface-2); color: var(--text); }
.ms-btn.is-primary { background: var(--accent); color: var(--on-accent); border-color: transparent; }
.ms-btn.is-primary:hover:not([disabled]):not([aria-disabled="true"]) { background: var(--accent-strong); color: var(--on-accent); }
.ms-btn.is-danger { color: var(--danger); border-color: color-mix(in srgb, var(--danger) 40%, transparent); }
.ms-btn.is-danger:hover:not([disabled]) { background: var(--danger-soft); color: var(--danger); }
.ms-btn.is-danger-solid { background: var(--danger); color: var(--bg); border-color: transparent; }
.ms-btn.is-ghost { background: transparent; border-color: transparent; color: var(--dim); }
.ms-btn.is-ghost:hover:not([disabled]) { background: var(--surface-2); color: var(--text); }
.ms-btn.is-sm { height: var(--h-sm); padding: 0 11px; border-radius: var(--r-sm); font-size: var(--fs-md); }
.ms-btn.is-lg { height: var(--h-lg); padding: 0 16px; border-radius: var(--r-lg); }
.ms-btn.is-xl { height: var(--h-xl); padding: 0 18px; border-radius: 11px; font-size: 14px; font-weight: var(--fw-heavy); }
.ms-btn.is-block { width: 100%; }
.ms-btn[disabled], .ms-btn[aria-disabled="true"] { opacity: .45; cursor: not-allowed; }
.ms-link-btn { border: 0; background: transparent; padding: 2px 0; color: var(--accent); font-size: var(--fs-md); font-weight: var(--fw-bold); cursor: pointer; text-decoration: underline; text-underline-offset: 3px; }
/* Without this a disabled one looks exactly like a live one and people keep
   pressing it. Every other button in the kit says so; this one has to as well. */
.ms-link-btn:disabled, .ms-link-btn[aria-disabled="true"] { color: var(--faint); cursor: not-allowed; text-decoration-color: transparent; }

/* Icon buttons: the tool (framed) and the bare kind that sits inside a row. */
.ms-icon-btn {
  position: relative; width: 38px; height: var(--h-md); border-radius: var(--r-md); border: 1px solid var(--border);
  background: var(--surface-2); color: var(--dim); cursor: pointer; padding: 0; display: grid; place-items: center; flex: none;
}
.ms-icon-btn:hover { color: var(--text); background: var(--surface-3); }
.ms-icon-btn.is-square { width: 36px; height: 36px; border-radius: var(--r-lg); background: var(--surface); }
.ms-icon-btn[aria-pressed="true"], .ms-icon-btn.is-on { background: var(--accent-soft); color: var(--accent); border-color: transparent; }
.ms-icon-btn.is-danger { color: var(--danger); }
.ms-icon-btn.is-bare { width: 30px; height: 30px; border: 0; border-radius: var(--r-sm); background: transparent; }
.ms-icon-btn.is-bare:hover { background: var(--surface-2); }
/* Not now: still focusable, so its tooltip can say why (principle 6). */
.ms-icon-btn[aria-disabled="true"] { opacity: .45; cursor: not-allowed; }

/* Tooltips: [data-tip] on anything positioned. Below by default. */
[data-tip] { position: relative; }
[data-tip]:hover::after, [data-tip]:focus-visible::after {
  content: attr(data-tip); position: absolute; top: calc(100% + 6px); left: 50%; transform: translateX(-50%);
  white-space: nowrap; padding: 5px 8px; border-radius: 7px; background: var(--tooltip-bg); color: var(--tooltip-fg);
  font: 700 11px/1.3 var(--font-sans); letter-spacing: 0; text-transform: none; z-index: 40; pointer-events: none;
}
[data-tip-align="end"]:hover::after, [data-tip-align="end"]:focus-visible::after { left: auto; right: 0; transform: none; }
[data-tip-pos="above"]:hover::after, [data-tip-pos="above"]:focus-visible::after { top: auto; bottom: calc(100% + 6px); }

/* Text fields. */
.ms-label { display: block; font-size: var(--fs-sm); font-weight: var(--fw-heavy); color: var(--dim); margin-bottom: 6px; }
.ms-req { color: var(--danger); margin-left: 2px; }
.ms-input, .ms-select, .ms-textarea {
  width: 100%; min-width: 0; height: 40px; border-radius: var(--r-lg); border: 1.5px solid var(--border-strong);
  background: var(--bg); color: var(--text); padding: 0 12px; font-size: var(--fs-lg); outline: none;
}
.ms-textarea { height: auto; min-height: 74px; padding: 9px 12px; resize: vertical; line-height: var(--lh-base); }
.ms-input.is-sm, .ms-select.is-sm { height: var(--h-md); border-width: 1px; border-radius: var(--r-md); font-size: var(--fs-base); font-weight: var(--fw-medium); padding: 0 10px; }
.ms-input:focus, .ms-select:focus, .ms-textarea:focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
/* Field states: the tone is on the border and a soft ring, whether or not the
   field has focus, so a form can be read at a glance. Error is aria-invalid,
   because ARIA has a word for it; the other two are classes, because it does
   not. Every one of them is paired with a message — never colour alone. */
.ms-input[aria-invalid="true"], .ms-select[aria-invalid="true"], .ms-textarea[aria-invalid="true"],
.ms-affix.is-invalid { border-color: var(--danger); box-shadow: 0 0 0 3px var(--danger-soft); }
.ms-input[aria-invalid="true"]:focus, .ms-select[aria-invalid="true"]:focus, .ms-textarea[aria-invalid="true"]:focus,
.ms-affix.is-invalid:focus-within { border-color: var(--danger); box-shadow: 0 0 0 3px var(--danger-soft); }
.ms-input.is-success, .ms-select.is-success, .ms-textarea.is-success, .ms-affix.is-success { border-color: var(--success); box-shadow: 0 0 0 3px var(--success-soft); }
.ms-input.is-warn, .ms-select.is-warn, .ms-textarea.is-warn, .ms-affix.is-warn { border-color: var(--warn); box-shadow: 0 0 0 3px var(--warn-soft); }
.ms-error { min-height: 1.35em; margin: 7px 0 0; font-size: var(--fs-md); font-weight: var(--fw-bold); color: var(--danger); line-height: 1.4; display: flex; align-items: center; gap: 5px; }
.ms-error:empty { display: none; }
/* The same shape in the other two tones, so a form's messages line up. */
.ms-ok, .ms-warn-text { min-height: 1.35em; margin: 7px 0 0; font-size: var(--fs-md); font-weight: var(--fw-bold); line-height: 1.4; display: flex; align-items: center; gap: 5px; }
.ms-ok { color: var(--success); }
.ms-warn-text { color: var(--warn); }
.ms-ok svg, .ms-warn-text svg, .ms-error svg { flex: none; }
/* Dates and times are the browser's own controls; all the kit does is make
   them the same height and weight as every other field, and make the picker's
   little glyph visible in both themes — left alone it is near-black on a dark
   ground. The value is always shown alongside in words, because the field's
   own format follows the reader's locale and a bare 03/04 says nothing. */
.ms-input[type="date"], .ms-input[type="time"], .ms-input[type="datetime-local"],
.ms-input[type="month"], .ms-input[type="week"] { padding-right: 9px; font-variant-numeric: tabular-nums; }
.ms-input[type="date"]::-webkit-calendar-picker-indicator,
.ms-input[type="time"]::-webkit-calendar-picker-indicator,
.ms-input[type="datetime-local"]::-webkit-calendar-picker-indicator,
.ms-input[type="month"]::-webkit-calendar-picker-indicator,
.ms-input[type="week"]::-webkit-calendar-picker-indicator {
  opacity: .55; cursor: pointer; border-radius: var(--r-sm); padding: 2px;
}
.ms-input[type="date"]::-webkit-calendar-picker-indicator:hover,
.ms-input[type="time"]::-webkit-calendar-picker-indicator:hover,
.ms-input[type="datetime-local"]::-webkit-calendar-picker-indicator:hover { opacity: 1; background: var(--surface-2); }
[data-theme="dark"] .ms-input::-webkit-calendar-picker-indicator { filter: invert(1); }

/* Range. One value on a line, or two fields for a from–to. The number beside
   it is not decoration: a slider with no readout is a control you cannot set
   deliberately, and it is what a screen reader has to agree with. */
.ms-range-row { display: flex; align-items: center; gap: 12px; }
.ms-range-value {
  flex: none; min-width: 4.5ch; text-align: right; font-size: var(--fs-lg);
  font-weight: var(--fw-heavy); font-variant-numeric: tabular-nums; color: var(--text);
}
.ms-range {
  flex: 1; min-width: 0; height: 20px; margin: 0; padding: 0;
  background: transparent; -webkit-appearance: none; appearance: none; cursor: pointer;
}
.ms-range:focus { outline: none; }
.ms-range::-webkit-slider-runnable-track {
  height: 6px; border-radius: 3px; background: var(--surface-2); border: 1px solid var(--border);
}
.ms-range::-moz-range-track { height: 6px; border-radius: 3px; background: var(--surface-2); border: 1px solid var(--border); }
.ms-range::-moz-range-progress { height: 6px; border-radius: 3px 0 0 3px; background: var(--accent); }
.ms-range::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none; width: 18px; height: 18px; margin-top: -7px;
  border-radius: 50%; background: var(--accent); border: 2px solid var(--surface);
  box-shadow: 0 1px 3px rgba(0, 0, 0, .28);
}
.ms-range::-moz-range-thumb {
  width: 18px; height: 18px; border-radius: 50%; background: var(--accent);
  border: 2px solid var(--surface); box-shadow: 0 1px 3px rgba(0, 0, 0, .28);
}
.ms-range:focus-visible::-webkit-slider-thumb { box-shadow: 0 0 0 3px var(--accent-soft); }
.ms-range:focus-visible::-moz-range-thumb { box-shadow: 0 0 0 3px var(--accent-soft); }
.ms-range:disabled { cursor: not-allowed; opacity: .5; }
.ms-range-scale { display: flex; justify-content: space-between; margin: 4px 0 0; font-size: var(--fs-xs); color: var(--faint); }

/* A field with something at its end: a button that acts on the value (copy,
   generate, clear) or a mark that reports on it (a tick once it is checked).
   The same geometry the password field uses for its eye, given a name of its
   own so every field can have it. A mark is never focusable; a button always
   has a real accessible name, because an icon alone is not one. */
.ms-with-icon { position: relative; display: flex; align-items: center; }
.ms-with-icon > .ms-input { padding-right: 42px; }
.ms-with-icon.has-two > .ms-input { padding-right: 76px; }
.ms-input-btn, .ms-input-mark {
  position: absolute; right: 5px; width: 32px; height: 32px; display: grid; place-items: center;
  border-radius: var(--r-md); flex: none;
}
/* Two at the end of a field: the FIRST in the source sits further in, so
   reading order, tab order and visual order agree. Putting the second one
   on the left reversed them. */
.ms-with-icon.has-two > .ms-input-btn:first-of-type,
.ms-with-icon.has-two > .ms-input-mark:first-of-type { right: 39px; }
.ms-input-btn { border: 0; background: transparent; color: var(--dim); cursor: pointer; padding: 0; }
.ms-input-btn:hover { color: var(--text); background: var(--surface-2); }
.ms-input-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.ms-input-btn:disabled { opacity: .45; cursor: not-allowed; }
.ms-input-btn.is-done { color: var(--success); }
/* The tick lands rather than fades: a copy you cannot see happen reads as a
   click that did nothing. Motion is small and skipped where it is not wanted. */
.ms-input-btn.is-done > svg { animation: ms-pop .26s var(--ease-out) both; }
@keyframes ms-pop { from { transform: scale(.55); opacity: 0; } 60% { transform: scale(1.12); } to { transform: none; opacity: 1; } }
.ms-input-mark { pointer-events: none; color: var(--faint); }
.ms-input-mark.is-success { color: var(--success); }
.ms-input-mark.is-warn { color: var(--warn); }
.ms-input-mark.is-danger { color: var(--danger); }

/* File field. The native control cannot be styled, so it is hidden inside a
   label and the label is the control — which keeps the click target, the
   keyboard and the accessible name all real. The chosen name is shown, because
   "No file chosen" in the browser's own words is the part people miss. */
.ms-file { display: flex; align-items: center; gap: 10px; min-width: 0; }
.ms-file input[type="file"] {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
.ms-file-btn {
  flex: none; display: inline-flex; align-items: center; gap: 6px; height: var(--h-lg, 34px);
  padding: 0 12px; border-radius: var(--r-lg); border: 1.5px solid var(--border-strong);
  background: var(--surface); color: var(--text); font-size: var(--fs-base); font-weight: var(--fw-bold); cursor: pointer;
}
.ms-file:hover .ms-file-btn { background: var(--surface-2); }
.ms-file input[type="file"]:focus-visible + .ms-file-btn { outline: 2px solid var(--accent); outline-offset: 2px; }
.ms-file-name { min-width: 0; font-size: var(--fs-md); color: var(--dim); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ms-file-name.is-empty { color: var(--faint); }
.ms-file-clear { flex: none; }
/* The roomier variant: a bordered area you can also drop onto. It is the small
   sibling of .ms-dropzone, which takes a whole screen. */
.ms-file.is-area {
  flex-direction: column; gap: 8px; justify-content: center; text-align: center;
  min-height: 132px; padding: 18px 16px; border: 1.5px dashed var(--border-strong);
  border-radius: var(--r-xl); background: var(--surface-2); cursor: pointer;
}
.ms-file.is-area.is-over { border-color: var(--accent); border-style: solid; background: var(--accent-soft); }
.ms-file.is-area .ms-file-name { white-space: normal; }

/* Timer. One button that starts and stops, with the elapsed time in it, and a
   reset beside it. The digits are tabular so the button does not twitch each
   time a 1 becomes a 0, and the running state is in aria-pressed, not colour. */
.ms-timer { display: inline-flex; align-items: center; gap: 6px; }
.ms-timer-btn { min-width: 122px; justify-content: flex-start; font-variant-numeric: tabular-nums; }
.ms-timer-btn[aria-pressed="true"] { border-color: var(--danger); color: var(--danger); }
.ms-timer-time { margin-left: auto; font-weight: var(--fw-heavy); font-variant-numeric: tabular-nums; }
/* A quiet pulse while it runs, so a glance says "still going" without reading
   the digits. It is decoration: the state is in aria-pressed and the label. */
.ms-timer-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--danger); flex: none; }
.ms-timer-btn[aria-pressed="true"] .ms-timer-dot { animation: ms-pulse 1.6s ease-in-out infinite; }
@keyframes ms-pulse { 0%, 100% { opacity: 1; } 50% { opacity: .25; } }
@media (prefers-reduced-motion: reduce) {
  .ms-timer-btn[aria-pressed="true"] .ms-timer-dot { animation: none; }
  .ms-input-btn.is-done > svg { animation: none; }
}

/* Dismissal, for the things that say something rather than ask something:
   the info panel, the callout, the banner. It is opt-in — a note that is part
   of a page's content should not be closable, because closing it loses content
   nobody can get back. Esc is scoped to focus inside the element, so it never
   competes with the Escape stack that closes dialogs and menus. */
.ms-dismiss {
  flex: none; width: 26px; height: 26px; margin: -3px -4px -3px 0; display: grid; place-items: center;
  border: 0; border-radius: var(--r-md); background: transparent; color: var(--faint); cursor: pointer; padding: 0;
}
.ms-dismiss:hover { background: color-mix(in srgb, var(--text) 8%, transparent); color: var(--text); }
.ms-dismiss:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.ms-note.is-dismissible, .ms-callout.is-dismissible { position: relative; }
.ms-note.is-dismissible > .ms-dismiss, .ms-callout.is-dismissible > .ms-dismiss { position: absolute; top: 9px; right: 9px; margin: 0; }
.ms-note.is-dismissible > .ms-note-body, .ms-callout.is-dismissible { padding-right: 34px; }
.ms-banner.is-dismissible > .ms-dismiss { margin-left: 4px; }
.ms-going { animation: ms-fade-out .18s var(--ease-out) both; }
@keyframes ms-fade-out { to { opacity: 0; transform: translateY(-4px); } }
@media (prefers-reduced-motion: reduce) { .ms-going { animation-duration: 1ms; } }

.ms-hint { margin: 2px 0 0; font-size: var(--fs-sm); color: var(--dim); line-height: 1.45; }
.ms-count { text-align: right; font-size: 11.5px; color: var(--dim); margin: 4px 0 2px; font-variant-numeric: tabular-nums; }
.ms-count.is-over { color: var(--danger); font-weight: var(--fw-bold); }
/* A field with a fixed part beside what is typed: an extension, a unit, a domain. */
.ms-affix {
  display: flex; align-items: center; gap: 4px; height: 42px; padding: 0 12px; border-radius: var(--r-lg);
  border: 1.5px solid var(--border-strong); background: var(--bg);
}
.ms-affix:focus-within { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
.ms-affix.is-invalid { border-color: var(--danger); }
.ms-affix.is-invalid:focus-within { box-shadow: 0 0 0 3px var(--danger-soft); }
.ms-affix input { flex: 1; min-width: 0; height: 100%; border: 0; outline: none; background: transparent; font-size: 14px; }
.ms-affix-part { flex: none; color: var(--faint); font-size: 14px; font-weight: var(--fw-medium); }

/* Search: a pill, with its shortcut showing until you type. */
/* Sized by its input, so the shortcut hint and the clear button stay beside
   the text whether the box sits in a toolbar or on its own. */
.ms-search { position: relative; display: flex; align-items: center; width: max-content; max-width: 100%; }
.ms-search.is-wide { width: auto; }
.ms-search > svg { position: absolute; left: 14px; color: var(--faint); pointer-events: none; }
.ms-search input {
  height: var(--h-lg); width: 300px; max-width: 34vw; border-radius: 19px; border: 1px solid var(--border);
  background: var(--surface-2); font-size: var(--fs-lg); padding: 0 36px 0 38px; outline: none;
}
.ms-search input:focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); background: var(--bg); }
.ms-search input::-webkit-search-cancel-button { display: none; }
.ms-kbd { font: 600 11px/1 var(--font-mono); padding: 4px 6px; border-radius: 5px; border: 1px solid var(--border); background: var(--surface); color: var(--faint); }
.ms-search .ms-kbd { position: absolute; right: 9px; pointer-events: none; }
.ms-search input:not(:placeholder-shown) ~ .ms-kbd, .ms-search input:focus ~ .ms-kbd { display: none; }
.ms-search-clear { position: absolute; right: 8px; width: 20px; height: 20px; border: none; border-radius: 10px; background: transparent; color: var(--faint); cursor: pointer; display: grid; place-items: center; padding: 0; }

/* Expanding search: an icon's worth of room until it is wanted, then the full
   field. It grows rightwards from its own edge, so nothing beside it moves —
   give the toolbar the space with `.ms-search.is-expanding { width: 300px }`
   where the room exists, or let it overlay where it does not. */
.ms-search.is-expanding { width: var(--h-lg); transition: width var(--t-base) var(--ease-rail); }
.ms-search.is-expanding input {
  width: 100%; max-width: none; padding-inline: 38px 12px; cursor: pointer;
  background: transparent; border-color: transparent;
}
.ms-search.is-expanding input::placeholder { opacity: 0; transition: opacity .12s ease; }
.ms-search.is-expanding:hover input { background: var(--surface-2); }
.ms-search.is-expanding.is-open { width: min(var(--search-w, 300px), 60vw); }
.ms-search.is-expanding.is-open input { cursor: text; background: var(--surface-2); border-color: var(--border); }
.ms-search.is-expanding.is-open input::placeholder { opacity: 1; }
.ms-search.is-expanding.is-open input:focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); background: var(--bg); }
/* Overlay, for a toolbar with no room to give: it grows over what is beside it. */
.ms-search.is-expanding.is-overlay.is-open { position: absolute; right: 0; z-index: var(--z-sticky); background: var(--bg); }
@media (prefers-reduced-motion: reduce) { .ms-search.is-expanding { transition: none; } }

/* Switch, checkbox, radio. */
.ms-switch {
  appearance: none; width: 38px; height: 22px; border-radius: 11px; background: var(--border-strong); position: relative;
  cursor: pointer; margin: 0; flex: none; transition: background .15s ease;
}
.ms-switch::after { content: ""; position: absolute; top: 3px; left: 3px; width: 16px; height: 16px; border-radius: 50%; background: #fff; box-shadow: 0 1px 2px rgba(0, 0, 0, .3); transition: transform .15s ease; }
.ms-switch:checked { background: var(--accent); }
.ms-switch:checked::after { transform: translateX(16px); }
.ms-switch-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; font-size: var(--fs-base); font-weight: var(--fw-bold); cursor: pointer; }
.ms-check, .ms-radio { accent-color: var(--accent); width: 15px; height: 15px; margin: 0; flex: none; cursor: pointer; }
/* A checkbox or radio beside its words. The box does not shrink, the words
   wrap under themselves rather than under the box, and the whole row is the
   hit area — which is the difference between a tickable line and two things
   jammed together. Pages were inlining this; it belongs in the kit. */
.ms-check-row, .ms-radio-row {
  display: grid; grid-template-columns: auto minmax(0, 1fr); align-items: start;
  gap: 4px 9px; cursor: pointer; font-size: var(--fs-lg); line-height: var(--lh-base);
}
.ms-check-row > .ms-check, .ms-radio-row > .ms-radio { margin-top: 2px; }
.ms-check-row > span, .ms-radio-row > span { min-width: 0; }
/* A second line under the words, for what the option means. */
.ms-check-row .ms-hint, .ms-radio-row .ms-hint { grid-column: 2; margin: 0; }
.ms-check-row:has(> .ms-check:disabled), .ms-radio-row:has(> .ms-radio:disabled) { cursor: not-allowed; color: var(--faint); }
/* A set of them, spaced like form rows rather than crammed. */
.ms-check-set { display: grid; gap: 9px; }
.ms-check-set > legend, .ms-field > legend { padding: 0; }

/* Segmented control: a small set of exclusive options, all visible. */
.ms-seg { display: inline-flex; padding: 3px; gap: 2px; border-radius: var(--r-lg); background: var(--surface-2); border: 1px solid var(--border); }
.ms-seg button {
  border: 0; background: transparent; color: var(--dim); font-size: var(--fs-md); font-weight: var(--fw-heavy);
  height: 28px; padding: 0 11px; border-radius: var(--r-sm); cursor: pointer; font-variant-numeric: tabular-nums;
}
.ms-seg button[aria-pressed="true"] { background: var(--surface); color: var(--text); box-shadow: 0 1px 2px rgba(0, 0, 0, .14); }
.ms-seg button[aria-pressed="true"].is-accent { background: var(--accent); color: var(--on-accent); }
.ms-seg-count { color: var(--faint); margin-left: 2px; }

/* Filter chips: any number on at once. */
.ms-chips { display: flex; gap: 5px; flex-wrap: wrap; }
.ms-chips button {
  height: 28px; padding: 0 11px; border-radius: var(--r-pill); border: 1px solid var(--border);
  background: transparent; color: var(--dim); font-size: var(--fs-sm); font-weight: var(--fw-medium); cursor: pointer;
}
.ms-chips button[aria-pressed="true"] { border-color: var(--accent); color: var(--accent); background: var(--accent-soft); }

/* ── Data display ─────────────────────────────────────────────────────── */

/* Status pill: a state word in a row or a panel. */
.ms-pill {
  display: inline-flex; align-items: center; gap: 5px; height: 24px; padding: 0 9px; border-radius: 12px;
  font-size: var(--fs-sm); font-weight: var(--fw-bold); white-space: nowrap; font-variant-numeric: tabular-nums;
  background: var(--surface-2); color: var(--dim);
}
.ms-pill.is-ok { background: color-mix(in srgb, var(--success) 10%, var(--bg)); color: var(--success); }
.ms-pill.is-accent { background: var(--accent-soft); color: var(--accent-strong); }
.ms-pill.is-warn { background: color-mix(in srgb, var(--warn) 7%, var(--bg)); color: var(--warn); }
.ms-pill.is-crit { background: color-mix(in srgb, var(--danger) 6%, var(--bg)); color: var(--danger); }
.ms-pill.is-plain { background: transparent; padding: 0; }

/* Tag: a short code in mono — a revision (v3), an id, a count. Clickable when it leads somewhere. */
.ms-tag {
  flex: none; position: relative; height: 21px; min-width: 28px; padding: 0 6px; border-radius: 7px;
  border: 1px solid var(--border-strong); background: var(--surface); color: var(--dim);
  font: 700 11px/1 var(--font-mono); display: inline-flex; align-items: center; justify-content: center; gap: 4px;
}
button.ms-tag { cursor: pointer; }
button.ms-tag:hover, button.ms-tag:focus-visible { color: var(--accent-strong); border-color: var(--accent); }
.ms-tag.is-behind { border-style: dashed; color: var(--warn); border-color: color-mix(in srgb, var(--warn) 60%, transparent); }
.ms-tag.is-behind::before { content: ""; width: 5px; height: 5px; border-radius: 50%; background: currentColor; }

/* Marker: a small round badge beside a name (starred, locked, linked). */
.ms-marker { flex: none; width: 22px; height: 22px; border-radius: 50%; display: inline-flex; align-items: center; justify-content: center; background: var(--surface-2); color: var(--dim); }
.ms-marker.is-star { color: var(--warn); }

/* The fill is the opaque --accent-subtle, not the 10%-translucent
   --accent-soft: an avatar is a solid disc standing in for a photograph, and a
   translucent one lets whatever it sits on show through — on a cover strip or a
   coloured row it reads as a hole rather than a face. */
.ms-avatar {
  width: 22px; height: 22px; flex: none; border-radius: 50%; background: var(--accent-subtle); color: var(--accent-strong);
  font-size: 9px; font-weight: var(--fw-heavy); display: inline-grid; place-items: center; text-transform: uppercase;
}
.ms-avatar.is-md { width: 30px; height: 30px; font-size: var(--fs-sm); }
.ms-avatar.is-lg { width: 40px; height: 40px; font-size: 14px; }
/* A photo instead of initials: the initials stay underneath as the fallback,
   so a broken or slow image never leaves an empty circle. */
.ms-avatar img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; border-radius: inherit; }
/* Presence: is this person here? A dot on the avatar's corner, with a ring in
   the surface colour so it reads against hair, photos and fills alike. It is
   never the only sign — the label beside it says the same in words. */
.ms-avatar { position: relative; }
.ms-presence {
  position: absolute; right: -1px; bottom: -1px; width: 34%; height: 34%; min-width: 8px; min-height: 8px;
  border-radius: 50%; background: var(--faint); box-shadow: 0 0 0 2px var(--surface);
}
.ms-presence.is-online { background: var(--success); }
.ms-presence.is-busy { background: var(--danger); }
.ms-presence.is-away { background: var(--warn); }
/* Away is a ring rather than a fill, so neither rests on a fill alone. */
.ms-presence.is-away { background: var(--surface); box-shadow: 0 0 0 2px var(--surface), inset 0 0 0 2.5px var(--warn); }
.ms-presence.is-offline { background: var(--surface); box-shadow: 0 0 0 2px var(--surface), inset 0 0 0 2px var(--border-strong); }
button.ms-avatar { border: 0; cursor: pointer; padding: 0; box-shadow: 0 0 0 1.5px transparent; }
button.ms-avatar:hover, button.ms-avatar[aria-expanded="true"] { box-shadow: 0 0 0 2px var(--accent); }

/* List: flat rows split by one hairline. Hover and selection change colour
   and shadow only — never a size — so nothing below ever moves. */
.ms-list-head {
  display: flex; align-items: center; gap: 12px; padding: 0 14px; height: 40px; border-bottom: 1px solid var(--border);
  font-size: var(--fs-xs); font-weight: var(--fw-bold); color: var(--faint); text-transform: uppercase; letter-spacing: var(--tracking-label);
}
.ms-row {
  display: flex; align-items: center; gap: 12px; padding: 0 14px; min-height: var(--row-h);
  border-bottom: 1px solid var(--border); cursor: pointer; outline: none;
  transition: background-color var(--t-fast) ease, box-shadow var(--t-fast) ease;
}
.ms-row:hover { background: color-mix(in srgb, var(--text) 4%, transparent); }
.ms-row:focus-visible { box-shadow: inset 0 0 0 2px var(--accent); }
.ms-row[aria-selected="true"], .ms-row.is-selected { background: var(--accent-soft); box-shadow: inset 3px 0 0 var(--accent); }
.ms-row[aria-selected="true"]:focus-visible { box-shadow: inset 3px 0 0 var(--accent), inset 0 0 0 2px var(--accent); }
.ms-row.is-new { animation: ms-new 2.4s ease; }
.ms-row.is-entering { animation: ms-row-in .18s ease both; }
.ms-col-check { flex: none; width: 20px; display: flex; }
.ms-col-icon { flex: none; width: 32px; display: flex; align-items: center; justify-content: center; }
.ms-col-main { flex: 1; min-width: 0; display: flex; align-items: center; gap: 8px; }
.ms-col { flex: none; width: var(--w, 140px); font-size: var(--fs-md); color: var(--dim); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-variant-numeric: tabular-nums; }
.ms-col-actions { flex: none; width: var(--w, 132px); display: flex; justify-content: flex-end; gap: 2px; }
.ms-row-name { font-size: var(--fs-lg); font-weight: var(--fw-medium); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; cursor: pointer; }
.ms-row-name:hover { text-decoration: underline; text-underline-offset: 3px; }
.ms-row-sub { font-size: var(--fs-xs); color: var(--faint); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ms-row-line { display: flex; align-items: center; gap: 6px; min-width: 0; }
.ms-row-stack { flex: 1; min-width: 0; display: flex; flex-direction: column; }
/* Actions appear on hover, focus or selection; the quick edit pencil too. */
.ms-row-actions, .ms-row-quick { opacity: 0; transition: opacity var(--t-fast) ease; }
.ms-row:hover .ms-row-actions, .ms-row:focus-within .ms-row-actions, .ms-row[aria-selected="true"] .ms-row-actions,
.ms-row:hover .ms-row-quick, .ms-row:focus-within .ms-row-quick { opacity: 1; }
/* Checkboxes: visible but quiet in a list, so they are discoverable. */
.ms-row .ms-check, .ms-list-head .ms-check { opacity: .3; transition: opacity .14s ease; }
.ms-row:hover .ms-check, .ms-row[aria-selected="true"] .ms-check, .ms-check:focus-visible, .has-selection .ms-check, .ms-list-head .ms-check:hover { opacity: 1; }

/* Sortable column heads: the active one keeps its arrow; others show a faint
   one on hover. Room for the arrow is always reserved. */
.ms-sort { display: inline-flex; align-items: center; gap: 5px; background: none; border: none; padding: 0; cursor: pointer; color: inherit; font: inherit; letter-spacing: inherit; text-transform: inherit; }
.ms-sort:hover, .ms-sort[aria-sort] { color: var(--text); }
.ms-sort-arrow { display: inline-flex; opacity: 0; transition: opacity var(--t-fast) ease; }
.ms-sort:hover .ms-sort-arrow, .ms-sort:focus-visible .ms-sort-arrow { opacity: .45; }
.ms-sort[aria-sort] .ms-sort-arrow { opacity: 1; color: var(--accent); }

/* Data table for dense admin screens: grid columns set per table with --cols. */
.ms-table { border-top: 1px solid var(--border); }
.ms-trow {
  display: grid; grid-template-columns: var(--cols, minmax(0, 1fr) 160px 120px); align-items: center; gap: 0 12px;
  min-height: 52px; padding: 0 12px; border-bottom: 1px solid var(--border); font-size: var(--fs-base);
}
.ms-trow.is-head { min-height: 38px; font-size: var(--fs-xs); font-weight: var(--fw-heavy); letter-spacing: var(--tracking-label); text-transform: uppercase; color: var(--dim); }
.ms-trow.is-link { cursor: pointer; }
.ms-trow.is-link:hover { background: var(--surface-2); }
.ms-trow[aria-selected="true"] { background: var(--accent-soft); }   /* the same fill a list row uses */
.ms-trow > * { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Selectable table rows behave like list rows: a focus ring, the accent edge
   when selected, and row actions that appear without moving anything. */
.ms-trow.is-link { outline: none; transition: background-color var(--t-fast) ease, box-shadow var(--t-fast) ease; }
.ms-trow.is-link:focus-visible { box-shadow: inset 0 0 0 2px var(--accent); }
.ms-trow.is-link[aria-selected="true"] { box-shadow: inset 3px 0 0 var(--accent); }
.ms-trow.is-link[aria-selected="true"]:focus-visible { box-shadow: inset 3px 0 0 var(--accent), inset 0 0 0 2px var(--accent); }
.ms-trow:hover .ms-row-actions, .ms-trow:focus-within .ms-row-actions, .ms-trow[aria-selected="true"] .ms-row-actions,
.ms-row-actions:has(.show) { opacity: 1; }
.ms-trow > .ms-row-actions { overflow: visible; display: flex; justify-content: flex-end; }

/* Zebra: for a wide table of numbers, where the eye has to travel along a row
   without slipping into the next one. Not for lists — there, the hairline and
   the row's own two lines do that job. Hover and selection still win, so the
   stripe never argues with state. */
.ms-table.is-zebra .ms-trow:nth-child(2n) { background: var(--surface-2); }
.ms-table.is-zebra .ms-trow.is-head { background: inherit; }
.ms-table.is-zebra .ms-trow.is-link:hover { background: var(--surface-3); }
.ms-table.is-zebra .ms-trow[aria-selected="true"] { background: var(--accent-soft); }
/* Inside an elevated card the head has its own filled ground; keep it. */
.ms-card.is-elevated .ms-table.is-zebra .ms-trow.is-head { background: var(--surface-2); }

/* Column filter: the funnel in a head, beside the sort. It is quiet until it
   is doing something, and then it says so — a filtered column must never look
   like an unfiltered one. */
.ms-col-filter {
  display: inline-grid; place-items: center; width: 22px; height: 22px; margin-left: 2px; padding: 0;
  border: 0; border-radius: var(--r-sm); background: transparent; color: var(--faint); cursor: pointer;
  opacity: 0; transition: opacity var(--t-fast) ease, color var(--t-fast) ease;
}
.ms-trow.is-head:hover .ms-col-filter, .ms-list-head:hover .ms-col-filter,
.ms-col-filter:focus-visible, .ms-col-filter.is-on { opacity: 1; }
.ms-col-filter:hover { color: var(--text); background: var(--surface-3); }
.ms-col-filter:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.ms-col-filter.is-on { color: var(--accent); }
.ms-filter-menu { padding: 6px; min-width: 216px; }
.ms-filter-menu .ms-filter-search { padding: 4px 6px 8px; }
.ms-filter-menu .ms-filter-list { max-height: 240px; overflow: auto; display: grid; gap: 1px; }
.ms-filter-opt { display: flex; align-items: center; gap: 9px; padding: 7px 8px; border-radius: var(--r-md); cursor: pointer; font-size: var(--fs-base); }
.ms-filter-opt:hover { background: var(--surface-2); }
.ms-filter-opt input { margin: 0; }
.ms-filter-opt .ms-filter-count { margin-left: auto; color: var(--faint); font-size: var(--fs-xs); font-variant-numeric: tabular-nums; }
.ms-filter-foot { display: flex; gap: 6px; align-items: center; padding: 8px 6px 2px; margin-top: 4px; border-top: 1px solid var(--border); }


/* Grid of tiles: a framed visual, the name, and a line that gives way to
   actions on hover — in the same space, so nothing moves. */
.ms-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 6px; }
.ms-tile { position: relative; padding: 10px 10px 6px; border-radius: var(--r-3xl); cursor: pointer; outline: none; transition: background-color .15s ease, box-shadow var(--t-base) ease; }
.ms-tile:hover, .ms-tile:focus-within { background: var(--surface); box-shadow: var(--elevation-raised), 0 0 0 1px var(--border); }
/* Hover and focus must not look the same: a tile is selectable like a row, so
   it gets a row's ring. */
.ms-tile:focus-visible { box-shadow: 0 0 0 2px var(--accent); }
.ms-tile[aria-selected="true"]:focus-visible { box-shadow: 0 0 0 1.5px var(--accent), 0 0 0 4px var(--accent-soft); }
.ms-tile[aria-selected="true"] { background: var(--accent-soft); box-shadow: 0 0 0 1.5px var(--accent); }
.ms-tile-visual { position: relative; aspect-ratio: 4 / 3; border-radius: var(--r-lg); overflow: hidden; border: 1px solid var(--border); background: var(--surface); display: grid; place-items: center; }
.ms-tile-name { margin-top: 10px; text-align: center; font-size: var(--fs-base); font-weight: var(--fw-medium); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ms-tile-foot { position: relative; height: 32px; }
.ms-tile-meta, .ms-tile-actions { position: absolute; inset: 0; display: flex; align-items: center; transition: opacity var(--t-fast) ease; }
.ms-tile-meta { justify-content: center; font-size: 11.5px; color: var(--faint); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ms-tile-actions { justify-content: space-around; opacity: 0; }
.ms-tile:hover .ms-tile-meta, .ms-tile:focus-within .ms-tile-meta, .ms-tile[aria-selected="true"] .ms-tile-meta { opacity: 0; }
.ms-tile:hover .ms-tile-actions, .ms-tile:focus-within .ms-tile-actions, .ms-tile[aria-selected="true"] .ms-tile-actions { opacity: 1; }

.ms-card { border: 1px solid var(--border); border-radius: var(--r-3xl); background: var(--surface); padding: 20px; display: flex; flex-direction: column; gap: 14px; }
.ms-card-title { margin: 0; font-size: 14px; font-weight: var(--fw-heavy); }

/* Meters: one bar, or a stacked bar of parts. */
.ms-meter { display: flex; height: 10px; border-radius: 6px; overflow: hidden; background: var(--surface-3); gap: 2px; }
.ms-meter > span { display: block; height: 100%; background: var(--accent); }
.ms-meter.is-thin { height: 6px; }
.ms-meter > span.is-warn { background: var(--warn); }
.ms-meter > span.is-crit { background: var(--danger); }
.ms-ring { width: 44px; height: 44px; position: relative; display: grid; place-items: center; }
.ms-ring svg { position: absolute; inset: 0; transform: rotate(-90deg); }
.ms-ring b { font-size: var(--fs-xs); font-weight: var(--fw-heavy); font-variant-numeric: tabular-nums; }

/* Timeline: the history of one record, newest first, grouped by day.
   Exactly one entry is "current" — the card with the accent on it. */
.ms-tl { display: grid; gap: 4px; }
.ms-tl-group { font-size: var(--fs-xs); font-weight: var(--fw-heavy); letter-spacing: var(--tracking-label); text-transform: uppercase; color: var(--faint); margin: 12px 2px 2px; }
.ms-tl-group:first-child { margin-top: 2px; }
.ms-tl-item { position: relative; display: flex; align-items: flex-start; gap: 10px; padding: 9px 8px 9px 9px; border-radius: var(--r-xl); border: 1.5px solid transparent; outline: none; cursor: pointer; }
.ms-tl-item:hover { background: var(--surface-2); }
.ms-tl-item:focus-visible { box-shadow: 0 0 0 2px var(--accent); }
.ms-tl-item.is-current { border-color: var(--accent); background: var(--accent-soft); }
.ms-tl-item[aria-current="true"]:not(.is-current) { background: var(--surface-2); border-color: var(--border-strong); }
.ms-tl-badge { flex: none; width: 32px; height: 32px; border-radius: var(--r-md); display: grid; place-items: center; font: 800 11.5px/1 var(--font-mono); background: var(--surface-3); color: var(--dim); border: 1px solid var(--border); }
.ms-tl-item.is-current .ms-tl-badge { background: var(--accent); color: var(--on-accent); border-color: var(--accent); }
.ms-tl-text { flex: 1; min-width: 0; display: grid; gap: 1px; }
.ms-tl-text > :first-child { padding-right: 26px; }
.ms-tl-label { font-size: var(--fs-sm); font-weight: var(--fw-heavy); color: var(--accent-strong); }
.ms-tl-label.is-muted { color: var(--dim); font-weight: var(--fw-bold); font-style: italic; }
.ms-tl-by { font-size: var(--fs-md); font-weight: var(--fw-medium); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ms-tl-item.is-current .ms-tl-by { font-weight: var(--fw-bold); }
.ms-tl-when { font-size: 11.5px; color: var(--dim); font-variant-numeric: tabular-nums; }
.ms-tl-when .ms-nowrap { white-space: nowrap; }
.ms-tl-note { margin-top: 4px; padding: 5px 8px; border-radius: var(--r-sm); background: var(--surface-2); border: 1px solid var(--border); font-size: var(--fs-sm); line-height: 1.4; display: flex; gap: 6px; align-items: flex-start; overflow-wrap: anywhere; }
.ms-tl-item.is-current .ms-tl-note { background: var(--surface); }
.ms-tl-note svg { flex: none; margin-top: 2px; color: var(--dim); }
.ms-tl-more { position: absolute; top: 6px; right: 5px; }

/* Stepper: a flow too long for one dialog — import and map, resolve, publish.
   The numbers are counted by CSS, so adding a step never renumbers by hand. */
.ms-steps { display: flex; flex-wrap: wrap; gap: 6px; list-style: none; margin: 0; padding: 0; counter-reset: ms-step; }
.ms-step {
  flex: 1; min-width: 130px; display: flex; align-items: center; gap: 8px; padding: 8px 10px;
  border-radius: var(--r-lg); background: var(--surface-2); color: var(--dim);
  font-size: var(--fs-md); font-weight: var(--fw-bold);
}
.ms-step::before {
  counter-increment: ms-step; content: counter(ms-step); flex: none; width: 22px; height: 22px;
  border-radius: 50%; display: grid; place-items: center; background: var(--surface-3); color: var(--dim);
  font: var(--fw-heavy) 11px/1 var(--font-mono);
}
/* Where you are, in fill and in words — aria-current carries it, not a class. */
.ms-step[aria-current="step"] { background: var(--accent-soft); color: var(--text); }
.ms-step[aria-current="step"]::before { background: var(--accent); color: var(--on-accent); }
.ms-step.is-done::before { content: "✓"; background: var(--success); color: var(--bg); }
.ms-step.is-done { color: var(--text); }
/* A step you may return to is a button; one you may not is plain text. */
button.ms-step { border: 0; cursor: pointer; font-family: inherit; text-align: left; }
button.ms-step:hover { background: var(--surface-3); }
button.ms-step:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
@media (max-width: 560px) { .ms-steps { flex-direction: column; } .ms-step { min-width: 0; } }

/* Wizard: the whole flow the stepper maps — head, one step's body, and a foot
   that never moves. Back on the left, the one primary action on the right. */
.ms-wizard { display: flex; flex-direction: column; gap: 16px; min-width: 0; }
.ms-wizard-head { display: grid; gap: 12px; }
.ms-wizard-title { margin: 0; font-size: var(--fs-2xl); font-weight: var(--fw-heavy); letter-spacing: var(--tracking-title); }
.ms-wizard-body { display: grid; gap: 14px; min-height: 0; align-content: start; }
.ms-wizard-step-head { display: grid; gap: 3px; }
.ms-wizard-heading { margin: 0; font-size: var(--fs-xl); font-weight: var(--fw-bold); outline: none; }
.ms-wizard-hint { margin: 0; font-size: var(--fs-md); line-height: 1.5; color: var(--dim); }
.ms-wizard-foot { display: flex; align-items: center; gap: 8px; padding-top: 14px; border-top: 1px solid var(--border); }
.ms-wizard-error { flex: 1; min-width: 0; font-size: var(--fs-md); font-weight: var(--fw-medium); color: var(--danger); }
.ms-wizard-error:empty { display: none; }
.ms-wizard-error:empty + .ms-spacer, .ms-wizard-foot > .ms-spacer { flex: 1; }
@media (max-width: 560px) {
  .ms-wizard-foot { flex-wrap: wrap; }
  .ms-wizard-foot .ms-btn { flex: 1 1 auto; }
}

/* Form layout: a column of fields, rows that split in two where there is room,
   and a foot that carries the one primary action. */
.ms-form { display: grid; gap: var(--bento-gap, 16px); max-width: 760px; }
.ms-form-row { display: grid; gap: 14px; grid-template-columns: repeat(2, minmax(0, 1fr)); }
.ms-form-row.is-single { grid-template-columns: minmax(0, 1fr); }
.ms-field { display: block; min-width: 0; }
.ms-form-foot { display: flex; align-items: center; gap: 8px; }
.ms-form-foot .ms-hint { margin: 0 0 0 auto; }
@media (max-width: 900px) { .ms-form-row { grid-template-columns: minmax(0, 1fr); } }

/* Command palette: search that goes anywhere and does anything, from the
   keyboard. It borrows the dialog's scrim and focus trap; the list is a
   roving one, so arrows move without Tab. */
.ms-palette { display: flex; flex-direction: column; min-height: 0; }
.ms-palette-search { display: flex; align-items: center; gap: 10px; padding: 4px 2px 12px; border-bottom: 1px solid var(--border); margin-bottom: 8px; }
.ms-palette-search > svg { flex: none; color: var(--faint); }
.ms-palette-search input { flex: 1; min-width: 0; height: 30px; border: 0; outline: none; background: transparent; color: var(--text); font-size: var(--fs-2xl); font-weight: var(--fw-medium); }
.ms-palette-search input::placeholder { color: var(--faint); font-weight: var(--fw-medium); }
.ms-palette-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 1px; max-height: min(52vh, 420px); overflow: auto; }
.ms-palette-group { font-size: var(--fs-xs); font-weight: var(--fw-heavy); letter-spacing: var(--tracking-label); text-transform: uppercase; color: var(--faint); padding: 10px 10px 4px; }
.ms-palette-item {
  display: flex; align-items: center; gap: 10px; width: 100%; padding: 9px 10px; border: 0; border-radius: var(--r-lg);
  background: transparent; color: var(--text); font-size: var(--fs-lg); font-weight: var(--fw-medium); text-align: left; cursor: pointer; outline: none;
}
.ms-palette-item > svg { flex: none; color: var(--dim); }
.ms-palette-item:hover, .ms-palette-item[aria-selected="true"] { background: var(--surface-2); }
.ms-palette-item[aria-selected="true"] { box-shadow: inset 2px 0 0 var(--accent); }
.ms-palette-text { flex: 1; min-width: 0; display: grid; gap: 1px; }
.ms-palette-text small { font-size: var(--fs-sm); color: var(--dim); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ms-palette-empty { padding: 22px 10px; text-align: center; color: var(--dim); font-size: var(--fs-md); }
.ms-palette-foot { display: flex; align-items: center; gap: 12px; padding-top: 10px; margin-top: 8px; border-top: 1px solid var(--border); font-size: var(--fs-sm); color: var(--faint); }

/* Thumbnails and media: a picture of a record, at a size that says how much
   it matters. Every one reserves its space before the image arrives, so a
   list never jumps as pictures load. */
.ms-thumb {
  --thumb: 56px;
  width: var(--thumb); height: var(--thumb); flex: none; position: relative; overflow: hidden;
  border-radius: var(--r-lg); border: 1px solid var(--border); background: var(--surface-2);
  display: grid; place-items: center; color: var(--dim);
}
.ms-thumb.is-sm { --thumb: 36px; border-radius: var(--r-md); }
.ms-thumb.is-lg { --thumb: 96px; border-radius: var(--r-xl); }
.ms-thumb.is-round { border-radius: 50%; }
/* A wide thumbnail keeps a picture's shape rather than cropping it square. */
.ms-thumb.is-wide { width: auto; aspect-ratio: 4 / 3; height: var(--thumb); }
.ms-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* No picture: the record's type on a tinted ground, never an empty grey box. */
.ms-thumb.is-placeholder { background: color-mix(in srgb, var(--tone, var(--accent)) 12%, var(--surface)); color: var(--tone, var(--accent)); }
.ms-thumb-badge { position: absolute; right: 3px; bottom: 3px; }

/* Media row: a thumbnail beside a name and its meta. The building block of a
   list of things with pictures. */
.ms-media { display: flex; align-items: center; gap: 12px; min-width: 0; }
.ms-media-text { flex: 1; min-width: 0; display: grid; gap: 1px; }
.ms-media-name { font-size: var(--fs-lg); font-weight: var(--fw-medium); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ms-media-meta { font-size: var(--fs-xs); color: var(--faint); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* Figure: a picture that is the content, with its caption. */
.ms-figure { margin: 0; display: grid; gap: 8px; min-width: 0; }
.ms-figure-frame { border-radius: var(--radius-card); border: 1px solid var(--border); background: var(--surface-2); overflow: hidden; aspect-ratio: var(--ratio, 16 / 9); }
.ms-figure-frame img { width: 100%; height: 100%; object-fit: cover; display: block; }
.ms-figure figcaption { font-size: var(--fs-md); color: var(--dim); line-height: 1.45; }

/* Gallery: thumbnails on a grid that fills the width it is given. */
.ms-gallery { display: grid; grid-template-columns: repeat(auto-fill, minmax(var(--gallery-min, 132px), 1fr)); gap: var(--bento-gap, 16px); }
.ms-gallery .ms-thumb { width: 100%; height: auto; aspect-ratio: var(--ratio, 1); --thumb: auto; }

/* Board: work that moves through stages. The columns are the stages, and a
   card is the same record the list shows — so the verbs, the selection and
   the ⋯ menu are the list's, not a second set. Moving is a menu item
   ("Move to…"), never only a drag. */
.ms-board { display: grid; grid-auto-flow: column; grid-auto-columns: minmax(var(--board-col, 230px), 1fr); gap: var(--bento-gap, 16px); overflow-x: auto; padding-bottom: 4px; }
.ms-board-col { display: grid; gap: 8px; align-content: start; padding: 10px; border-radius: var(--r-xl); background: var(--surface-2); min-width: var(--board-col, 230px); }
.ms-board-head { display: flex; align-items: center; gap: 8px; padding: 2px 2px 4px; font-size: var(--fs-xs); font-weight: var(--fw-heavy); letter-spacing: var(--tracking-label); text-transform: uppercase; color: var(--faint); }
.ms-board-count { margin-left: auto; font-variant-numeric: tabular-nums; }
.ms-board-card {
  display: grid; gap: 6px; padding: 10px 11px; border-radius: var(--r-lg); background: var(--surface);
  border: 1px solid var(--border); font-size: var(--fs-base); text-align: left; cursor: pointer; outline: none;
}
.ms-board-card:hover { border-color: var(--border-strong); }
.ms-board-card:focus-visible { box-shadow: 0 0 0 2px var(--accent); }
.ms-board-card[aria-selected="true"] { background: var(--accent-soft); border-color: var(--accent); }
.ms-board-card-name { font-weight: var(--fw-medium); }
.ms-board-card-meta { display: flex; align-items: center; gap: 8px; font-size: var(--fs-xs); color: var(--faint); }
.ms-board-col.is-over { outline: 2px dashed var(--accent); outline-offset: -2px; }
.ms-board-empty { padding: 14px 6px; text-align: center; font-size: var(--fs-md); color: var(--faint); }
@media (max-width: 820px) { .ms-board { grid-auto-columns: minmax(78vw, 1fr); } }

/* Password field: the reveal, and the strength meter the docs have promised
   since 1.0. The meter is segments rather than one bar, so progress is
   countable at a glance, and it always says its word — colour alone would
   leave the same meter meaningless to a lot of people. */
.ms-password { position: relative; display: flex; align-items: center; }
.ms-password input { flex: 1; min-width: 0; padding-right: 42px; }
.ms-reveal {
  position: absolute; right: 6px; width: 30px; height: 30px; display: grid; place-items: center;
  border: 0; border-radius: var(--r-md); background: transparent; color: var(--faint); cursor: pointer; padding: 0;
}
.ms-reveal:hover { color: var(--text); background: var(--surface-2); }
.ms-reveal:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

.ms-strength { display: grid; gap: 6px; margin-top: 8px; }
.ms-strength-bars { display: flex; gap: 4px; }
.ms-strength-bars i {
  flex: 1; height: 4px; border-radius: 2px; background: var(--surface-3);
  transition: background-color var(--t-base) ease;
}
.ms-strength-line { display: flex; align-items: baseline; gap: 6px; font-size: var(--fs-sm); color: var(--dim); }
.ms-strength-word { font-weight: var(--fw-heavy); }
/* One tone per score, on the filled segments and on the word together. */
.ms-strength[data-score="0"] i.is-on, .ms-strength[data-score="1"] i.is-on { background: var(--danger); }
.ms-strength[data-score="2"] i.is-on { background: var(--warn); }
.ms-strength[data-score="3"] i.is-on { background: var(--warn); }
.ms-strength[data-score="4"] i.is-on { background: var(--success); }
.ms-strength[data-score="0"] .ms-strength-word, .ms-strength[data-score="1"] .ms-strength-word { color: var(--danger); }
.ms-strength[data-score="2"] .ms-strength-word, .ms-strength[data-score="3"] .ms-strength-word { color: var(--warn); }
.ms-strength[data-score="4"] .ms-strength-word { color: var(--success); }

/* Code: a block to read, copy or hand to someone. Sunken rather than raised,
   because it is content, not a control; it scrolls sideways on its own so a
   long line never widens the page. */
.ms-code { border: 1px solid var(--border); border-radius: var(--r-xl); background: var(--surface-2); overflow: hidden; }
.ms-code-head {
  display: flex; align-items: center; gap: 10px; padding: 7px 8px 7px 12px; border-bottom: 1px solid var(--border);
  font-size: var(--fs-sm); font-weight: var(--fw-bold); color: var(--dim); background: var(--surface);
}
.ms-code-name { font-family: var(--font-mono); color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ms-code-lang { font-size: var(--fs-2xs); font-weight: var(--fw-heavy); letter-spacing: var(--tracking-label); text-transform: uppercase; color: var(--faint); }
.ms-code-body { margin: 0; padding: 12px 14px; overflow-x: auto; font: 500 12.5px/1.65 var(--font-mono); color: var(--text); tab-size: 2; }
.ms-code-body code { font: inherit; }
.ms-code.is-wrap .ms-code-body { white-space: pre-wrap; overflow-wrap: anywhere; }
/* Line numbers are a reading aid, never selectable with the code. */
.ms-code.is-numbered .ms-code-body { counter-reset: ms-line; }
.ms-code.is-numbered .ms-code-line::before {
  counter-increment: ms-line; content: counter(ms-line); display: inline-block; width: 2.2em; margin-right: 12px;
  text-align: right; color: var(--faint); user-select: none;
}
.ms-code-line.is-marked { background: var(--accent-soft); box-shadow: inset 2px 0 0 var(--accent); }
/* One line, inside a sentence. */
.ms-code-inline { font: 500 .92em/1.4 var(--font-mono); background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--r-sm); padding: 1px 5px; }
/* Tokens, for whichever highlighter an app uses. The kit only names them. */
.ms-tok-key { color: var(--accent-strong); font-weight: var(--fw-bold); }
.ms-tok-str { color: var(--success); }
.ms-tok-num { color: var(--warn); }
.ms-tok-com { color: var(--faint); font-style: italic; }
.ms-tok-tag { color: var(--accent); }
.ms-tok-attr { color: var(--label-pink); }

/* Quote: somebody else's words, kept as theirs — a customer's reply, a note
   left on a record, an excerpt from an email. It is content, not a callout:
   nothing here asks to be acted on. */
.ms-quote { margin: 0; display: grid; gap: 10px; padding: 12px 0 12px 16px; border-left: 3px solid var(--border-strong); }
.ms-quote-text { margin: 0; font-size: var(--fs-lg); line-height: 1.6; color: var(--text); overflow-wrap: anywhere; }
.ms-quote-text > p { margin: 0 0 8px; }
.ms-quote-text > p:last-child { margin-bottom: 0; }
.ms-quote-by { display: flex; align-items: center; gap: 8px; font-size: var(--fs-md); color: var(--dim); }
.ms-quote-by cite { font-style: normal; font-weight: var(--fw-bold); color: var(--text); }
/* On a surface of its own, for an excerpt that needs separating from the page. */
.ms-quote.is-boxed { border-left-width: 0; padding: 14px 16px; border-radius: var(--r-xl); background: var(--surface-2); border: 1px solid var(--border); }
.ms-quote.is-boxed.is-accent { border-left: 3px solid var(--accent); }
/* Quieter, for a one-line note inside a row or a card. */
.ms-quote.is-compact { padding: 6px 0 6px 12px; gap: 5px; }
.ms-quote.is-compact .ms-quote-text { font-size: var(--fs-md); }
/* A block of someone's message, as it arrived: preserves their line breaks. */
.ms-quote.is-verbatim .ms-quote-text { white-space: pre-wrap; }

/* Info panel: a paragraph of content worth noticing — the note in a help
   page, the caveat on a settings screen. Unlike a callout it asks for nothing:
   no buttons, no dismissal. Tone says how much to care; the icon and the words
   say what it is. */
.ms-note {
  display: flex; gap: 12px; padding: 14px 16px; border-radius: var(--r-xl);
  background: color-mix(in srgb, var(--tone, var(--info)) 10%, var(--surface));
  border: 1px solid color-mix(in srgb, var(--tone, var(--info)) 22%, var(--surface));
  color: var(--text); font-size: var(--fs-lg); line-height: 1.6;
}
/* The chip fills are tuned for a chip; at this size the same colour shouts,
   so a panel is a wash of its tone over the surface and keeps ordinary text. */
.ms-note > svg, .ms-note > [data-icon] { flex: none; margin-top: 2px; color: var(--tone, var(--info)); }
.ms-note-body { min-width: 0; display: grid; gap: 4px; }
.ms-note-body > p { margin: 0; }
.ms-note-body > b { font-weight: var(--fw-heavy); }
.ms-note.is-success { --tone: var(--success); }
.ms-note.is-warn { --tone: var(--warn); }
.ms-note.is-danger { --tone: var(--danger); }
.ms-note.is-accent { --tone: var(--accent); }
.ms-note.is-neutral { --tone: var(--dim); }

/* Prose: content people read rather than operate — a help page, release notes,
   a long description on a record. The app's chrome has its own type; this is
   for the words inside it. */
.ms-prose { max-width: var(--content-max, 72ch); color: var(--text); font-size: var(--fs-lg); line-height: 1.65; }
.ms-prose > * + * { margin-top: 14px; }
.ms-prose h1, .ms-prose h2, .ms-prose h3, .ms-prose h4 { margin: 0; font-weight: var(--fw-heavy); letter-spacing: var(--tracking-title); text-wrap: balance; }
.ms-prose h1 { font-size: var(--fs-hero); }
.ms-prose h2 { font-size: var(--fs-3xl); }
.ms-prose h3 { font-size: var(--fs-2xl); }
.ms-prose h4 { font-size: var(--fs-xl); }
.ms-prose > h2 { margin-top: 28px; }
.ms-prose > h3, .ms-prose > h4 { margin-top: 22px; }
.ms-prose > h2 + *, .ms-prose > h3 + *, .ms-prose > h4 + * { margin-top: 8px; }
/* :where() so these reset nested paragraphs and lists without outranking the
   flow rule below — a plain `.ms-prose p` did, and left prose with no gaps. */
.ms-prose :where(p) { margin: 0; }
.ms-prose a { color: var(--accent); text-underline-offset: 2px; }
.ms-prose :where(ul, ol) { margin: 0; padding-left: 22px; display: grid; gap: 6px; }
/* Restated after the resets: same specificity, so source order decides,
   and the flow between blocks has to be the thing that wins. */
.ms-prose > * + * { margin-top: 14px; }
.ms-prose > h2 + *, .ms-prose > h3 + *, .ms-prose > h4 + * { margin-top: 8px; }
.ms-prose > h2 { margin-top: 28px; }
.ms-prose > h3 { margin-top: 22px; }
.ms-prose li::marker { color: var(--faint); }
.ms-prose hr { margin: 24px 0; border: 0; border-top: 1px solid var(--border); }
.ms-prose img { max-width: 100%; height: auto; border-radius: var(--r-xl); }
.ms-prose > .ms-note, .ms-prose > .ms-quote, .ms-prose > .ms-code, .ms-prose > .ms-figure { margin-top: 18px; }
.ms-prose small { font-size: var(--fs-md); color: var(--dim); }

/* Back to the top: it appears once there is something above you, and it goes
   where the floating bars go rather than over the content. */
.ms-to-top {
  position: fixed; right: 20px; bottom: 20px; z-index: var(--z-sticky);
  width: 38px; height: 38px; display: grid; place-items: center; padding: 0;
  border: 1px solid var(--border); border-radius: 50%; background: var(--surface); color: var(--dim);
  box-shadow: var(--shadow-sm); cursor: pointer;
  /* visibility, not just opacity: pointer-events stops the mouse and does
     nothing for the keyboard, so without this the button sits in the tab order
     while invisible and a screen reader announces "Back to the top" at the top
     of a page, for a control that is not there yet. */
  opacity: 0; transform: translateY(8px); pointer-events: none; visibility: hidden;
  transition: opacity var(--t-base) ease, transform var(--t-base) ease, visibility var(--t-base);
}
.ms-to-top.is-on { opacity: 1; transform: none; pointer-events: auto; visibility: visible; }
.ms-to-top:hover { color: var(--text); border-color: var(--border-strong); }
.ms-to-top:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* Where a floating bar already sits at the bottom, stack above it. */
.ms-to-top.is-above-bar { bottom: calc(20px + var(--floatbar-h, 56px)); }
/* Inside a scrolling region rather than the page, it belongs to that region:
   absolute, and inset far enough to clear the frame's border and its
   scrollbar. Fixed here would pin it to the viewport's corner, where it lands
   on top of whatever the region's edge happens to be. */
.ms-to-top.is-contained { position: absolute; right: 22px; bottom: 18px; }
.ms-to-top.is-contained.is-above-bar { bottom: calc(18px + var(--floatbar-h, 56px)); }

/* Floating bar: a strip that stays put while the page scrolls, carrying the
   actions for the thing being worked on — Save, Save and close, Discard on a
   record too long to reach the bottom of. A preferences bar (the appearance
   switches in the Northwind example) is the same component, used differently. */
.ms-floatbar {
  position: fixed; left: 50%; bottom: 18px; transform: translateX(-50%); z-index: var(--z-selection-bar);
  display: flex; align-items: center; gap: 12px; max-width: min(960px, calc(100vw - 24px));
  padding: 8px 12px; border: 1px solid var(--border); border-radius: var(--r-2xl);
  background: var(--surface); box-shadow: var(--shadow);
}
.ms-floatbar-body { display: flex; align-items: center; gap: 18px; overflow-x: auto; scrollbar-width: none; flex-wrap: nowrap; min-width: 0; }
.ms-floatbar-body::-webkit-scrollbar { display: none; }
.ms-floatbar-group { display: flex; align-items: center; gap: 8px; }
.ms-floatbar-label { font-size: var(--fs-xs); font-weight: var(--fw-heavy); letter-spacing: var(--tracking-label); text-transform: uppercase; color: var(--faint); white-space: nowrap; }
.ms-floatbar.is-collapsed .ms-floatbar-body { display: none; }

/* The bar's usual job is the actions for the thing you are working on, kept
   to hand while the screen scrolls: Save, Save and close, Discard on a long
   invoice or a long form. The actions sit at the right, the state of the work
   at the left, so the primary button is always in the same place. */
.ms-floatbar-actions { display: flex; align-items: center; gap: 8px; flex: none; margin-left: auto; }
/* The state is one line and never wraps. Left to wrap it turns a 52px bar
   into a four-line slab that covers the thing being edited. */
.ms-floatbar-state {
  display: flex; align-items: center; gap: 7px; min-width: 0; flex: 0 1 auto;
  font-size: var(--fs-md); color: var(--dim); white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis;
}
.ms-floatbar-state > svg { flex: none; }
.ms-floatbar-state > span { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.ms-floatbar-state b { color: var(--text); font-weight: var(--fw-heavy); flex: none; }
/* One row, always. */
.ms-floatbar { min-height: 52px; }
/* The handle: collapse it out of the way and bring it back. */
.ms-floatbar-toggle { flex: none; }
.ms-floatbar.is-collapsed { padding: 6px; gap: 6px; min-height: 0; }
.ms-floatbar.is-collapsed .ms-floatbar-body,
.ms-floatbar.is-collapsed .ms-floatbar-state,
.ms-floatbar.is-collapsed .ms-floatbar-sep { display: none; }
/* Collapsed, it keeps the primary action and drops the rest — the point of
   collapsing is to get out of the way, not to lose the way to save. */
.ms-floatbar.is-collapsed .ms-floatbar-actions > .ms-btn:not(.is-primary) { display: none; }

/* Where it sits. Centred is the default, because a bar about the work in
   front of you belongs under it. A corner is for something running alongside
   the work — a chat, an announcement — which should not sit over the middle
   of what you are reading. */
.ms-floatbar.is-left { left: 20px; right: auto; transform: none; }
.ms-floatbar.is-right { left: auto; right: 20px; transform: none; }
/* Inside a pane rather than the page — a demo stage, a split view, an editor
   that is only part of the screen. Fixed would pin it to the viewport, where
   it covers whatever else is there and swallows its clicks. */
.ms-floatbar.is-contained { position: absolute; }
.ms-floatbar.is-contained.is-left { left: 14px; }
.ms-floatbar.is-contained.is-right { right: 14px; }
.ms-floatbar.is-contained:not(.is-left):not(.is-right) { left: 50%; transform: translateX(-50%); }

/* A bar that opens upwards into a panel: a chat, a stack of announcements.
   The row stays the bar; the panel grows above it, so the handle never moves
   under the cursor as the content changes. */
.ms-floatbar.is-panel { flex-direction: column; align-items: stretch; gap: 0; padding: 0; overflow: hidden; width: min(360px, calc(100vw - 24px)); }
.ms-floatbar.is-panel > .ms-floatbar-row { display: flex; align-items: center; gap: 10px; padding: 8px 10px 8px 14px; min-height: 52px; }
.ms-floatbar.is-panel.is-open > .ms-floatbar-row { order: -1; border-bottom: 1px solid var(--border); }
.ms-floatbar-panel {
  display: none; flex-direction: column; min-height: 0;
  max-height: min(420px, 60vh); overflow: auto; padding: 14px;
  background: var(--surface-2); gap: 10px;
}
.ms-floatbar.is-open .ms-floatbar-panel { display: flex; }
.ms-floatbar-foot { display: flex; align-items: center; gap: 8px; padding: 10px; border-top: 1px solid var(--border); }
.ms-floatbar.is-panel:not(.is-open) .ms-floatbar-foot { display: none; }
/* An unread count on the handle, so a collapsed chat still says something. */
.ms-floatbar-count {
  min-width: 19px; height: 19px; padding: 0 5px; border-radius: 10px; flex: none;
  display: inline-grid; place-items: center; background: var(--danger); color: #fff;
  font-size: 11px; font-weight: var(--fw-heavy); font-variant-numeric: tabular-nums;
}

/* Chat bubbles inside the panel. Theirs on the left, yours on the right, and
   the name only on theirs — you know who you are. */
.ms-chat { display: flex; flex-direction: column; gap: 10px; }
.ms-chat-msg { display: grid; gap: 3px; max-width: 84%; }
.ms-chat-msg > b { font-size: var(--fs-xs); font-weight: var(--fw-heavy); color: var(--faint); }
.ms-chat-msg > p {
  margin: 0; padding: 8px 11px; border-radius: var(--r-xl); font-size: var(--fs-md);
  line-height: var(--lh-base); background: var(--surface); border: 1px solid var(--border); color: var(--text);
}
.ms-chat-msg.is-mine { margin-left: auto; }
.ms-chat-msg.is-mine > p { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
.ms-chat-msg > time { font-size: 11px; color: var(--faint); }
.ms-chat-msg.is-mine > time { text-align: right; }

@media (max-width: 640px) {
  .ms-floatbar.is-left, .ms-floatbar.is-right { left: 12px; right: 12px; }
  .ms-floatbar.is-panel { width: auto; }
}
/* Unsaved work is the one state worth a colour of its own. */
.ms-floatbar.is-dirty { border-color: color-mix(in srgb, var(--warn) 45%, var(--border)); }
.ms-floatbar.is-dirty .ms-floatbar-state { color: var(--warn); }
.ms-floatbar.is-saving .ms-floatbar-state { color: var(--dim); }
.ms-floatbar.is-saved .ms-floatbar-state { color: var(--success); }
/* A separator between a group of secondary actions and the primary one. */
.ms-floatbar-sep { width: 1px; align-self: stretch; margin: 2px 2px; background: var(--border); flex: none; }

@media (max-width: 640px) {
  /* .is-contained is (0,4,0) and this block was (0,1,0), so a contained bar
     kept left:50% here and — once it wrapped — pushed its primary action off
     the edge. Both forms have to be named. */
  .ms-floatbar,
  .ms-floatbar.is-contained,
  .ms-floatbar.is-contained:not(.is-left):not(.is-right) { left: 12px; right: 12px; transform: none; max-width: none; }
  /* On a phone the actions come first and fill the width; the state moves
     above them rather than being pushed off the end. */
  .ms-floatbar { flex-wrap: wrap; }
  .ms-floatbar-actions { margin-left: 0; width: 100%; }
  .ms-floatbar-actions > .ms-btn { flex: 1; justify-content: center; }
  .ms-floatbar-actions > .ms-icon-btn { flex: none; }
}

/* Jump to a page, beside the pager. */
.ms-pager { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.ms-pager-count { font-size: var(--fs-md); color: var(--dim); font-variant-numeric: tabular-nums; }
.ms-pager-jump { display: flex; align-items: center; gap: 6px; font-size: var(--fs-md); color: var(--dim); }
.ms-pager-jump input { width: 62px; text-align: center; font-variant-numeric: tabular-nums; }

/* ── Feedback states ──────────────────────────────────────────────────── */

.ms-empty { display: grid; justify-items: center; gap: 8px; padding: 48px 20px; color: var(--dim); text-align: center; font-size: var(--fs-base); }
.ms-empty > svg { color: var(--faint); }
.ms-empty b { color: var(--text); font-size: 15px; }
.ms-empty p { margin: 0; max-width: 46ch; line-height: var(--lh-base); }
.ms-empty.is-small { padding: 22px; }
.ms-dropzone {
  min-height: 320px; display: flex; align-items: center; justify-content: center; padding: 32px 20px;
  border: 2px dashed color-mix(in srgb, var(--accent) 45%, transparent); border-radius: 16px; background: color-mix(in srgb, var(--accent) 5%, transparent);
}
/* Skeletons are for arriving somewhere new; a refresh updates in place. */
.ms-skel {
  display: block; border-radius: 7px;
  background: linear-gradient(100deg, var(--surface-2) 30%, var(--surface-3) 50%, var(--surface-2) 70%);
  background-size: 300% 100%; animation: ms-shimmer 1.4s ease infinite;
}
.ms-skel-row { display: flex; align-items: center; gap: 12px; padding: 0 14px; min-height: var(--row-h); border-bottom: 1px solid var(--border); }
.ms-spinner { width: 14px; height: 14px; flex: none; border-radius: 50%; border: 2px solid var(--surface-3); border-top-color: var(--accent); animation: ms-spin .8s linear infinite; }
.ms-error-state { display: grid; justify-items: center; gap: 8px; padding: 26px 8px; text-align: center; color: var(--dim); font-size: var(--fs-md); }
.ms-error-state b { color: var(--text); font-size: var(--fs-lg); }
/* Callout: an inline note that needs attention but is not an error. */
.ms-callout { display: grid; gap: 7px; padding: 11px 12px; border-radius: var(--r-xl); font-size: var(--fs-md); line-height: var(--lh-base); background: var(--surface-2); border: 1px solid var(--border); }
.ms-callout b { display: flex; align-items: center; gap: 6px; font-size: var(--fs-base); }
.ms-callout.is-warn { background: var(--warn-soft); border-color: color-mix(in srgb, var(--warn) 45%, transparent); }
.ms-callout.is-danger { background: var(--danger-soft); border-color: color-mix(in srgb, var(--danger) 40%, transparent); }
.ms-callout.is-info { background: var(--accent-soft); border-color: color-mix(in srgb, var(--accent) 35%, transparent); }
.ms-callout > .ms-btn { justify-self: start; }
/* State bar: a strip above content that says the view is not the usual one
   ("Viewing revision 2 of 5 · Not current") with the ways out. */
.ms-statebar { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; padding: 9px 10px 9px 14px; border-radius: var(--r-xl); font-size: var(--fs-base); font-weight: var(--fw-bold); background: var(--warn-soft); border: 1px solid color-mix(in srgb, var(--warn) 45%, transparent); }
.ms-statebar-text { flex: 1; min-width: 220px; display: flex; align-items: center; gap: 8px; }
.ms-statebar-text > svg { color: var(--warn); flex: none; }
.ms-statebar-actions { display: flex; flex-wrap: wrap; gap: 6px; }

/* Banners: announcements across the top of every page. They push the page
   down; set --banner-h on <html> to their measured height. */
.ms-banners { position: fixed; top: 0; left: 0; right: 0; z-index: var(--z-banner); display: flex; flex-direction: column; }
.ms-banner { display: flex; align-items: center; gap: 11px; padding: 10px 16px; border-bottom: 1px solid var(--border); background: var(--surface); box-shadow: 0 2px 10px rgba(0, 0, 0, .12); }
.ms-banner > svg { flex: none; color: var(--tone, var(--info)); }
.ms-banner-text { flex: 1; min-width: 0; font-size: var(--fs-base); font-weight: var(--fw-medium); line-height: var(--lh-base); }
.ms-banner.is-warn { --tone: var(--warn); } .ms-banner.is-crit { --tone: var(--danger); } .ms-banner.is-ok { --tone: var(--success); }

/* ── Overlays ─────────────────────────────────────────────────────────── */

/* Dialog: centred by the scrim with flex (never by translate, which lands on
   half pixels and blurs text); rises 8px while fading in; no backdrop blur. */
.ms-scrim { position: fixed; inset: 0; z-index: var(--z-dialog); display: flex; align-items: center; justify-content: center; padding: 4vh 16px; background: var(--overlay); animation: ms-fade .14s ease both; }
.ms-dialog { position: relative; width: min(var(--w, var(--dialog-w)), 100%); max-height: 86vh; overflow: auto; border-radius: var(--r-dialog); border: 1px solid var(--border); background: var(--surface); box-shadow: var(--shadow); padding: 22px; animation: ms-rise .18s var(--ease-out) both; }
/* The dialog pads itself, so its body only needs room from the head and the
   foot — but it is a real element the docs name, so it has a real rule. */
.ms-dialog-body { min-width: 0; }
.ms-dialog-body > :first-child { margin-top: 0; }
.ms-dialog-body > :last-child { margin-bottom: 0; }
.ms-dialog-head { display: flex; align-items: flex-start; gap: 12px; margin-bottom: 16px; }
.ms-dialog-title { flex: 1; margin: 0; font-size: var(--fs-2xl); font-weight: var(--fw-heavy); letter-spacing: var(--tracking-title); }
.ms-dialog-text { margin: 0 0 8px; font-size: var(--fs-lg); line-height: var(--lh-loose); }
.ms-dialog-sub { margin: 0; font-size: var(--fs-md); color: var(--dim); line-height: 1.55; }
.ms-dialog-foot { display: flex; gap: 9px; justify-content: flex-end; margin-top: 18px; }
.ms-dialog-list { list-style: none; margin: 12px 0 0; padding: 10px 12px; border-radius: var(--r-lg); background: var(--surface-2); display: grid; gap: 5px; }
.ms-dialog-list li { display: flex; align-items: center; gap: 8px; font-size: var(--fs-base); font-weight: var(--fw-medium); min-width: 0; }

/* Menus: dropdowns, the ⋯ menu, context menus. */
.ms-menu { position: absolute; top: calc(100% + 6px); z-index: var(--z-menu); min-width: 190px; padding: 6px; border: 1px solid var(--border); border-radius: var(--r-xl); background: var(--surface); box-shadow: var(--shadow); display: flex; flex-direction: column; gap: 1px; animation: ms-menu .14s ease both; }
.ms-menu.is-floating { position: fixed; top: auto; z-index: var(--z-floating-menu); min-width: 232px; }
.ms-menu.is-end { right: 0; }
.ms-menu [role="menuitem"], .ms-menu [role="menuitemradio"] {
  position: relative; display: flex; align-items: center; gap: 10px; width: 100%; min-height: 36px; padding: 6px 11px;
  border: none; border-radius: var(--r-sm); background: transparent; color: var(--text); font-size: var(--fs-base); font-weight: var(--fw-medium);
  text-align: left; cursor: pointer; outline: none; text-decoration: none;
}
/* :focus, not :focus-visible: the menu moves focus itself as the arrows walk
   the list, so the highlight has to follow it however the menu was opened. */
.ms-menu [role^="menuitem"]:hover, .ms-menu [role^="menuitem"]:focus { background: var(--surface-2); }
.ms-menu [role^="menuitem"] svg { color: var(--dim); flex: none; }
.ms-menu .is-danger, .ms-menu .is-danger svg { color: var(--danger); }
.ms-menu [aria-disabled="true"], .ms-menu [aria-disabled="true"] svg { color: var(--faint); cursor: not-allowed; }
.ms-menu [aria-disabled="true"][data-tip]:hover::after, .ms-menu [aria-disabled="true"][data-tip]:focus::after {
  content: attr(data-tip); position: absolute; left: 8px; right: 8px; top: auto; bottom: calc(100% + 4px); transform: none; white-space: normal; line-height: 1.35;
}
.ms-mi { display: grid; gap: 1px; }
.ms-mi small { font-size: var(--fs-xs); color: var(--dim); font-weight: var(--fw-medium); }
.ms-menu-rule { height: 1px; background: var(--border); margin: 4px 2px; }
.ms-menu-wrap { position: relative; }

/* Toasts: top centre under the toolbar; three at most. Success is always the
   success colour, whatever the accent. Corners match the selection bar. */
.ms-toasts { position: fixed; left: 0; right: 0; top: calc(var(--banner-h, 0px) + 70px); z-index: var(--z-toast); display: flex; flex-direction: column; align-items: center; gap: 8px; padding: 0 16px; pointer-events: none; }
.ms-toast {
  --tone: var(--info); pointer-events: auto; display: flex; align-items: center; gap: 11px; max-width: min(560px, 100%);
  padding: 10px 10px 10px 16px; border-radius: var(--r-2xl); border: 1px solid color-mix(in srgb, var(--tone) 55%, transparent);
  background: color-mix(in srgb, var(--tone) 12%, var(--surface)); box-shadow: 0 10px 30px rgba(0, 0, 0, .22); animation: ms-drop .22s var(--ease-out) both;
}
.ms-toast.is-success { --tone: var(--success); } .ms-toast.is-error, .ms-toast.is-danger { --tone: var(--danger); } .ms-toast.is-warning { --tone: var(--warn); }
.ms-toast-icon { flex: none; display: flex; color: var(--tone); }
.ms-toast-msg { flex: 1; min-width: 0; font-size: var(--fs-lg); font-weight: var(--fw-bold); line-height: 1.35; overflow-wrap: anywhere; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.ms-toast-action { flex: none; height: var(--h-sm); padding: 0 14px; border-radius: var(--r-md); border: 1px solid var(--tone); background: transparent; color: var(--text); font-size: var(--fs-md); font-weight: var(--fw-bold); cursor: pointer; }
.ms-toast-action:hover { background: color-mix(in srgb, var(--tone) 18%, transparent); }
.ms-toast-close { flex: none; width: 30px; height: 30px; border-radius: var(--r-md); border: none; background: transparent; color: var(--dim); cursor: pointer; display: grid; place-items: center; }
.ms-toast-close:hover { background: color-mix(in srgb, var(--text) 8%, transparent); color: var(--text); }

/* Task tray: background work (imports, exports, sends, syncs) with
   per-item progress. Bottom right; leaves on its own once everything settled. */
.ms-tray { position: fixed; right: 20px; bottom: 20px; width: 340px; max-width: calc(100vw - 32px); z-index: var(--z-task-tray); border-radius: var(--r-3xl); overflow: hidden; border: 1px solid var(--border); background: var(--surface); box-shadow: var(--shadow); animation: ms-rise .22s ease both; transition: opacity var(--t-slow) ease, transform var(--t-slow) ease; }
.ms-tray.is-leaving { opacity: 0; transform: translateY(14px); pointer-events: none; }
.ms-tray-head { display: flex; align-items: center; gap: 9px; padding: 11px 10px 11px 14px; }
.ms-tray-title { flex: 1; min-width: 0; font-size: var(--fs-base); font-weight: var(--fw-bold); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ms-tray-overall { height: 3px; background: var(--surface-3); }
.ms-tray-overall span { display: block; height: 100%; background: var(--accent); transition: width .2s linear; }
.ms-tray-list { max-height: 260px; overflow: auto; border-top: 1px solid var(--border); }
.ms-tray-row { padding: 10px 12px 10px 14px; border-bottom: 1px solid var(--border); outline: none; }
.ms-tray-row:last-child { border-bottom: none; }
.ms-tray-row.is-failed { background: color-mix(in srgb, var(--danger) 7%, transparent); }
.ms-tray-line { display: flex; align-items: center; gap: 8px; }
.ms-tray-name { flex: 1; min-width: 0; font-size: var(--fs-md); font-weight: var(--fw-medium); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ms-tray-state { flex: none; font-size: 11.5px; font-weight: var(--fw-bold); color: var(--faint); display: inline-flex; align-items: center; gap: 4px; }
.ms-tray-state.is-done { color: var(--success); }
.ms-tray-state.is-failed { color: var(--danger); }
.ms-tray-bar { margin-top: 7px; height: 4px; border-radius: 3px; background: var(--surface-3); overflow: hidden; }
.ms-tray-bar span { display: block; height: 100%; background: var(--accent); transition: width .2s linear; }
.ms-tray-sub { font-size: 10.5px; color: var(--faint); margin-top: 5px; font-variant-numeric: tabular-nums; }
.ms-tray-error { font-size: 11.5px; color: var(--danger); margin-top: 5px; line-height: 1.4; }

/* Selection bar: what can be done with everything selected. Bottom centre;
   steps aside while the inspector (which offers the same) is open. */
.ms-selbar { position: fixed; left: 50%; bottom: 22px; transform: translateX(-50%); z-index: var(--z-selection-bar); display: flex; align-items: center; gap: 9px; padding: 10px 14px; border-radius: var(--r-2xl); border: 1px solid var(--border); background: var(--surface); box-shadow: var(--shadow); animation: ms-rise .2s var(--ease-spring) both; }
.ms-selbar-count { font-size: var(--fs-base); font-weight: var(--fw-bold); padding-right: 4px; white-space: nowrap; }

/* Full-window viewer: a record's content with its side panes. */
.ms-viewer { position: fixed; inset: 0; z-index: var(--z-viewer); display: flex; flex-direction: column; background: var(--bg); animation: ms-fade .16s ease both; }
.ms-viewer-head { flex: none; display: flex; align-items: center; gap: 12px; padding: 12px 16px; border-bottom: 1px solid var(--border); }
.ms-viewer-title { flex: 1; min-width: 0; }
.ms-viewer-name { display: flex; align-items: center; gap: 8px; min-width: 0; font-size: 14px; font-weight: var(--fw-bold); }
.ms-viewer-meta { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; font-size: 11.5px; color: var(--faint); margin-top: 2px; }
.ms-viewer-body { flex: 1; min-height: 0; display: flex; }
.ms-viewer-main { flex: 1; min-width: 0; display: flex; flex-direction: column; overflow: auto; padding: 16px 22px 22px; gap: 12px; }
.ms-pane { width: var(--pane-w); flex: none; border-left: 1px solid var(--border); background: var(--surface); display: flex; flex-direction: column; min-height: 0; animation: ms-pane .2s ease both; }

/* ── Standalone pages (sign-in, errors, public links) ─────────────────── */

.ms-standalone { min-height: 100vh; display: flex; flex-direction: column; background: color-mix(in srgb, var(--surface-2) 70%, var(--bg)); }
.ms-standalone-head { display: flex; align-items: center; gap: 10px; padding: 14px 18px; }
.ms-standalone-main { flex: 1; display: flex; justify-content: center; align-items: flex-start; padding: 6vh 14px 28px; }
.ms-panel { width: 100%; max-width: 400px; background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-dialog); box-shadow: 0 1px 2px rgba(20, 30, 40, .06), 0 10px 30px rgba(20, 30, 40, .08); padding: 24px 22px; display: grid; gap: 14px; }
.ms-panel.is-wide { max-width: 560px; }
.ms-panel.is-center { text-align: center; justify-items: center; }
.ms-panel h1 { margin: 0; font-size: var(--fs-3xl); font-weight: var(--fw-heavy); letter-spacing: var(--tracking-title); text-wrap: balance; }
.ms-big-icon { width: 52px; height: 52px; border-radius: 15px; display: grid; place-items: center; background: var(--surface-2); color: var(--dim); }
.ms-big-icon.is-warn { background: var(--warn-soft); color: var(--warn); }
.ms-big-icon.is-danger { background: var(--danger-soft); color: var(--danger); }

/* ── Overview (soft bento) ────────────────────────────────────────────────
   Home screens and dashboards: white cards float on a grey canvas, a page
   header sits above the grid, density is comfortable. List + inspector
   screens keep the flat, compact look above. The interaction rules do not
   change: rows in a card still select to see and open to work. Grid spans
   live in magic-ui-bento.css; see docs/10-bento-layouts.md. */

.ms-app.is-canvas, .ms-app.is-canvas .ms-main, .ms-app.is-canvas .ms-toolbar { background: var(--canvas); }
.ms-app.is-canvas .ms-toolbar { border-bottom-color: transparent; padding-inline: var(--page-pad); }
/* Something new behind a button (unread notifications). The count is in the
   button's label; the dot is only the visual cue. */
.ms-unread-dot { position: absolute; top: 7px; right: 8px; width: 8px; height: 8px; border-radius: 50%; background: var(--danger); box-shadow: 0 0 0 2px var(--surface); pointer-events: none; }
.ms-page { flex: 1; min-width: 0; display: grid; grid-template-columns: minmax(0, 1fr); align-content: start; gap: var(--bento-gap); padding: 4px var(--page-pad) 56px; }

/* Page header: the date or context, a title, and this page's own actions. */
.ms-page-head { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 12px 16px; padding-top: 6px; }
.ms-page-head-text { flex: 1 1 320px; min-width: 0; display: grid; gap: 4px; }
.ms-page-eyebrow { font-size: var(--fs-lg); font-weight: var(--fw-medium); color: var(--dim); }
.ms-page-title { margin: 0; font-size: var(--fs-display); font-weight: var(--fw-heavy); letter-spacing: -.02em; line-height: 1.15; text-wrap: balance; }
/* Focus lands on the title when a page opens, so screen readers announce it;
   it is not a control, so it draws no ring. */
.ms-page-title[tabindex="-1"]:focus { outline: none; }
.ms-page-actions { display: flex; flex-wrap: wrap; gap: 8px; }

/* Stat strip: three or four numbers in one pill, in place of a row of
   number tiles. Each number says what it counts. */
.ms-stats { justify-self: start; display: inline-flex; flex-wrap: wrap; align-items: center; max-width: 100%; margin: 0; padding: 4px; list-style: none; border-radius: var(--radius-chip); background: var(--surface); border: 1px solid var(--border); box-shadow: var(--elevation-card); }
.ms-stat { display: inline-flex; align-items: center; gap: 8px; padding: 6px 18px 6px 14px; font-size: var(--fs-base); color: var(--dim); white-space: nowrap; }
.ms-stat + .ms-stat { border-left: 1px solid var(--border); }
.ms-stat > svg { flex: none; color: var(--dim); }
.ms-stat.is-warn > svg { color: var(--warn); }
.ms-stat b { font-size: var(--fs-3xl); font-weight: var(--fw-heavy); color: var(--text); font-variant-numeric: tabular-nums; }
/* .is-boxed: the strip takes the card corner (--radius-card) instead of the
   pill, to sit square with the cards under it. */
.ms-stats.is-boxed { border-radius: var(--radius-card); }

/* Soft card: a bento cell or any panel on the canvas. Hairline plus a soft
   shadow; only floating layers get the big one. */
.ms-card.is-elevated { border-radius: var(--radius-card); box-shadow: var(--elevation-card); padding: var(--card-pad); }
.ms-card-head { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 10px; min-height: 34px; }
.ms-card-head > svg { flex: none; color: var(--dim); }
.ms-card-head .ms-card-title { font-size: var(--fs-2xl); white-space: nowrap; }
.ms-card-meta { font-size: var(--fs-md); font-weight: var(--fw-bold); color: var(--dim); white-space: nowrap; }
/* Flush: a table runs edge to edge inside the card; rows keep their hairlines. */
.ms-card.is-flush { padding: 0; gap: 0; }
.ms-card.is-flush > .ms-card-head { padding: calc(var(--card-pad) * .7) var(--card-pad); }
.ms-card.is-flush > .ms-table .ms-trow { padding-inline: var(--card-pad); min-height: var(--row-h); }
.ms-card.is-flush > .ms-table .ms-trow:last-child { border-bottom: 0; border-radius: 0 0 calc(var(--radius-card) - 1px) calc(var(--radius-card) - 1px); padding-bottom: 8px; }
/* Rows fill their whole width, so the ones at the card's edges have to carry
   its corners: without this a selected row's fill and its accent edge square
   off the rounded bottom. The card itself can't clip — a row's ⋯ menu has to
   escape it. */
/* A table at the top of a flush card has no line of its own to draw: the
   card's edge is already there, and the extra border pushed the head down so
   its rounded corners missed the card's. */
.ms-card.is-flush > .ms-table:first-child { border-top: 0; }
.ms-card.is-flush > .ms-list-head:first-child,
.ms-card.is-flush > .ms-table:first-child > .ms-trow.is-head:first-child { border-radius: calc(var(--radius-card) - 1px) calc(var(--radius-card) - 1px) 0 0; }
.ms-card.is-flush > .ms-row:last-child,
.ms-card.is-flush > [role="grid"]:not(:has(~ *:not([hidden]))) > .ms-row:last-child { border-bottom: 0; border-radius: 0 0 calc(var(--radius-card) - 1px) calc(var(--radius-card) - 1px); padding-bottom: 8px; }
/* Collapsible (MagicUI.collapse): the card folds down to its head. The
   chevron sits last in the head and turns to point sideways when shut. */
.ms-card.is-collapsible > .ms-card-head { cursor: pointer; }
.ms-card-toggle { margin-left: auto; }
.ms-card-head > .ms-spacer ~ .ms-card-toggle { margin-left: 0; }
.ms-card-toggle > svg { transition: transform .18s var(--ease-out); }
.ms-card.is-collapsed .ms-card-toggle > svg { transform: rotate(-90deg); }
.ms-card.is-collapsed > :not(.ms-card-head) { display: none !important; }
@media (prefers-reduced-motion: reduce) { .ms-card-toggle > svg { transition: none; } }
.ms-card.is-elevated .ms-trow.is-head { min-height: 44px; background: var(--surface-2); font-size: var(--fs-base); font-weight: var(--fw-bold); letter-spacing: 0; text-transform: none; color: var(--dim); }
.ms-th { display: inline-flex; align-items: center; gap: 8px; }
.ms-th > svg { flex: none; }
.ms-cell-stack { display: grid; gap: 2px; min-width: 0; }
.ms-cell-stack > * { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ms-cell-meta { display: inline-flex; align-items: center; gap: 10px; font-size: var(--fs-sm); color: var(--dim); font-variant-numeric: tabular-nums; }
.ms-cell-meta > span { display: inline-flex; align-items: center; gap: 4px; }
.ms-type-icon { width: 34px; height: 34px; flex: none; border-radius: 10px; display: grid; place-items: center; color: var(--text); background: color-mix(in srgb, var(--tone, var(--accent)) 22%, var(--surface)); }

/* Status chip: where a record stands, as an icon and a word. Tones are the
   colour brief's five (success, pending, info, warning, danger) plus neutral;
   they never follow the accent (except .is-accent, for "active").
   .is-ok / .is-warn / .is-crit are the kit's older names for the same tones. */
.ms-status {
  display: inline-flex; align-items: center; gap: 5px; height: 24px; padding: 0 10px 0 8px; border-radius: var(--radius-chip);
  font-size: var(--fs-sm); font-weight: var(--fw-bold); white-space: nowrap; background: var(--chip-neutral-bg); color: var(--chip-neutral-fg);
}
.ms-status > svg { flex: none; }
.ms-status.is-accent { background: var(--chip-accent-bg); color: var(--chip-accent-fg); }
.ms-status.is-success, .ms-status.is-ok { background: var(--chip-success-bg); color: var(--chip-success-fg); }
.ms-status.is-pending { background: var(--chip-pending-bg); color: var(--chip-pending-fg); }
.ms-status.is-info { background: var(--chip-info-bg); color: var(--chip-info-fg); }
.ms-status.is-warning, .ms-status.is-warn { background: var(--chip-warning-bg); color: var(--chip-warning-fg); }
.ms-status.is-danger, .ms-status.is-crit { background: var(--chip-danger-bg); color: var(--chip-danger-fg); }

/* The global search, wide, with its shortcut showing until you type. */
.ms-search.is-wide { flex: 1 1 auto; max-width: 560px; }
.ms-search.is-wide input { width: 100%; max-width: none; height: 42px; border-radius: var(--radius-control); background: var(--surface); box-shadow: var(--elevation-card); }

/* Avatars that overlap: the people on a meeting or a record. */
.ms-avatars { display: inline-flex; flex: none; }
.ms-avatars > .ms-avatar { box-shadow: 0 0 0 2px var(--surface); }
.ms-avatars > .ms-avatar + .ms-avatar { margin-left: -6px; }

/* Week strip: seven days; the chosen one wears the accent, today is marked. */
.ms-week { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: 4px; }
.ms-day {
  position: relative; display: grid; justify-items: center; gap: 2px; padding: 7px 0 10px; border: 0; border-radius: var(--radius-control);
  background: transparent; color: var(--dim); font-size: var(--fs-xs); font-weight: var(--fw-bold); cursor: pointer;
}
.ms-day b { font-size: var(--fs-xl); font-weight: var(--fw-heavy); color: var(--text); font-variant-numeric: tabular-nums; }
.ms-day:hover { background: var(--surface-2); }
.ms-day[aria-pressed="true"] { background: var(--accent); color: var(--on-accent); }
.ms-day[aria-pressed="true"] b { color: var(--on-accent); }
.ms-day.is-today:not([aria-pressed="true"]) b { color: var(--accent-strong); }
.ms-day.has-items::after { content: ""; position: absolute; bottom: 4px; width: 4px; height: 4px; border-radius: 50%; background: currentColor; opacity: .7; }

/* Events: a coloured edge for the calendar or queue (named in the meta line). */
.ms-events { display: grid; gap: 2px; }
.ms-event { position: relative; display: flex; align-items: center; gap: 12px; padding: 10px 4px 10px 16px; }
.ms-event::before { content: ""; position: absolute; left: 0; top: 10px; bottom: 10px; width: 3px; border-radius: 2px; background: var(--tone, var(--accent)); }
.ms-event + .ms-event { border-top: 1px solid var(--border); }
.ms-event-text { flex: 1; min-width: 0; display: grid; gap: 3px; }
.ms-event-title { display: flex; align-items: center; gap: 7px; min-width: 0; font-size: var(--fs-lg); font-weight: var(--fw-bold); }
.ms-event-title > svg, .ms-event-title > [data-icon] { flex: none; color: var(--dim); }
.ms-event-title > span { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ms-event-meta { font-size: var(--fs-md); color: var(--dim); font-variant-numeric: tabular-nums; }

/* Checklist: round checks; a done item keeps its place, struck through. */
.ms-checklist { list-style: none; margin: 0; padding: 0; display: grid; }
.ms-checklist li { display: flex; align-items: flex-start; gap: 12px; padding: 12px 2px; }
.ms-checklist li + li { border-top: 1px solid var(--border); }
.ms-checklist input[type="checkbox"] {
  appearance: none; margin: 1px 0 0; width: 20px; height: 20px; flex: none; border-radius: 50%; border: 1.5px solid var(--border-strong);
  background: var(--surface); display: grid; place-items: center; cursor: pointer;
}
.ms-checklist input[type="checkbox"]:checked { background: var(--accent); border-color: var(--accent); }
.ms-checklist input[type="checkbox"]:checked::after { content: ""; width: 9px; height: 5px; margin-top: -2px; border: 2px solid var(--on-accent); border-top: 0; border-right: 0; transform: rotate(-45deg); }
.ms-checklist label { flex: 1; min-width: 0; display: grid; gap: 3px; margin: 0; cursor: pointer; }
.ms-checklist label b { font-size: var(--fs-lg); font-weight: var(--fw-bold); }
.ms-checklist label span { font-size: var(--fs-md); color: var(--dim); line-height: var(--lh-base); }
.ms-checklist input:checked + label b { text-decoration: line-through; color: var(--dim); }

/* Peek: on an overview the inspector floats over the canvas instead of
   taking a column, so selecting a row never reflows the cards (principle 4). */
.ms-inspector.is-peek {
  position: fixed; z-index: var(--z-menu); right: 16px; top: calc(var(--banner-h, 0px) + var(--toolbar-h) + 8px); bottom: 16px; height: auto;
  width: min(var(--pane-w), calc(100vw - 32px)); border: 1px solid var(--border); border-radius: var(--radius-card); box-shadow: var(--shadow); overflow: hidden;
}

@media (max-width: 820px) {
  .ms-page { padding: 4px 16px 40px; }
  .ms-page-title { font-size: var(--fs-hero); }
  .ms-rail-label { display: none; }
  .ms-stats { border-radius: var(--radius-card); flex-direction: column; align-items: stretch; width: 100%; }
  .ms-stat + .ms-stat { border-left: 0; border-top: 1px solid var(--border); }
  .ms-search.is-wide { max-width: none; }
}

/* ── Responsive ───────────────────────────────────────────────────────── */

/* Below 1000px the inspector steps aside (as does its switch); the context
   menu and the selection bar still carry every action it offered. */
@media (max-width: 1000px) { .ms-inspector, .ms-inspector-toggle { display: none !important; } }
/* Secondary columns give way first. */
@media (max-width: 900px) { .ms-col.is-secondary { display: none; } }
/* Phones: the rail becomes a bar across the top; the toolbar wraps. */
@media (max-width: 820px) {
  .ms-app { flex-direction: column; }
  .ms-rail, .ms-rail.is-open { position: sticky; top: var(--banner-h, 0px); width: 100%; height: auto; flex-direction: row; align-items: center; padding: 6px 8px; gap: 4px; overflow-x: auto; scrollbar-width: none; }
  .ms-rail-head, .ms-rail-divider, .ms-rail-sub, .ms-rail-card, .ms-rail-version { display: none; }
  .ms-rail-scroll, .ms-rail-foot { flex: none; flex-direction: row; overflow: visible; padding: 0; }
  .ms-rail nav { flex-direction: row; }
  .ms-rail-item, .ms-rail.is-open .ms-rail-item { flex-direction: row; gap: 6px; padding: 7px 10px; font-size: var(--fs-md); border-radius: var(--r-md); }
  .ms-toolbar { flex-wrap: wrap; height: auto; padding: 10px 12px; gap: 8px; }
  /* .ms-app.is-canvas .ms-toolbar is (0,3,0) and sets padding-inline to the
     full 28px --page-pad, so it beat the 12px above no matter where this block
     sat. On a 360px phone that left 56px of gutter and wrapped the bar onto a
     third row. Same shape as the .is-contained bug below; name both forms. */
  .ms-app.is-canvas .ms-toolbar { padding-inline: 12px; }
  .ms-toolbar > .ms-crumbs { flex-basis: 100%; }
  .ms-search { flex: 1 1 90px; min-width: 0; }
  .ms-search input { width: 100%; max-width: none; }
  .ms-hide-phone { display: none !important; }
}
/* Forced colours (Windows high contrast) drop box-shadow, and that is how
   selection and focus are drawn here — so redraw them as outlines. */
@media (forced-colors: active) {
  .ms-row:focus-visible, .ms-trow.is-link:focus-visible, .ms-tile:focus-visible,
  .ms-rail-item:focus-visible, .ms-btn:focus-visible, .ms-icon-btn:focus-visible { outline: 2px solid Highlight; outline-offset: -2px; }
  .ms-row[aria-selected="true"], .ms-trow.is-link[aria-selected="true"], .ms-tile[aria-selected="true"] { outline: 2px solid Highlight; outline-offset: -2px; }
  .ms-card, .ms-card.is-elevated, .ms-inspector, .ms-dialog, .ms-menu, .ms-toast { border: 1px solid CanvasText; }
  .ms-skel { border: 1px solid GrayText; }
  /* The tone edge is a pseudo-element background, which forced colours
     flatten — so every queue would read the same. Draw it as a border
     instead; the meta line names the queue in words either way. */
  .ms-event::before { background: CanvasText; }
}

/* Nothing to hover with on a touch screen: show what hover would reveal. */
@media (hover: none) {
  .ms-row-actions, .ms-row-quick, .ms-tile-actions { opacity: 1; }
  .ms-tile-meta { opacity: 0; }
  .ms-row .ms-check { opacity: 1; }
}

/* ── Hero ──────────────────────────────────────────────────────────────────
   The band at the top of a screen that says what the screen is for: a sign-in
   page, a product page, a first-run welcome. It is not a toolbar and it is not
   a banner — nothing here reports a condition, and nothing here is urgent.
   One primary action, at most one beside it. */
.ms-hero {
  display: grid; gap: 14px; align-content: center;
  padding: clamp(24px, 5vw, 52px) clamp(20px, 4vw, 44px);
  border-radius: var(--radius-card); background: var(--surface); border: 1px solid var(--border);
}
.ms-hero.is-tinted {
  background: linear-gradient(140deg, color-mix(in srgb, var(--accent) 10%, var(--surface)), var(--surface) 62%);
  border-color: color-mix(in srgb, var(--accent) 22%, var(--border));
}
.ms-hero.is-compact { padding: clamp(18px, 3vw, 28px) clamp(16px, 3vw, 28px); gap: 10px; }
.ms-hero-eyebrow { margin: 0; font-size: var(--fs-sm); font-weight: var(--fw-heavy); letter-spacing: var(--tracking-label); text-transform: uppercase; color: var(--accent); }
.ms-hero-title { margin: 0; font-size: clamp(24px, 3.4vw, 38px); font-weight: var(--fw-heavy); letter-spacing: var(--tracking-title); line-height: 1.15; text-wrap: balance; }
.ms-hero-lead { margin: 0; max-width: 62ch; font-size: var(--fs-xl); line-height: 1.55; color: var(--dim); }
.ms-hero-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 4px; }
/* margin: 0 because a <p> here would otherwise take Bootstrap reboot's
   1rem bottom margin and grow the hero. Its siblings all reset theirs. */
.ms-hero-meta { display: flex; flex-wrap: wrap; gap: 6px 14px; margin: 0; font-size: var(--fs-md); color: var(--faint); }
/* Text on one side, a picture or a demo on the other. Below 860px it stacks,
   text first — the words are the point, the picture is support. */
.ms-hero.is-split { grid-template-columns: minmax(0, 1fr) minmax(0, 0.9fr); gap: clamp(20px, 4vw, 44px); align-items: center; }
.ms-hero-body { display: grid; gap: 14px; min-width: 0; align-content: center; }
.ms-hero-media { min-width: 0; border-radius: var(--r-xl); overflow: hidden; background: var(--surface-2); aspect-ratio: var(--ratio, 4 / 3); }
.ms-hero-media > img, .ms-hero-media > video { width: 100%; height: 100%; object-fit: cover; display: block; }
@media (max-width: 860px) { .ms-hero.is-split { grid-template-columns: minmax(0, 1fr); } }

/* ── Profile card ──────────────────────────────────────────────────────────
   A person, summarised: who they are, how to reach them, and the one thing
   you came to do. It is a card, not a record — the moment it needs tabs or a
   history it has become an inspector or a record screen. */
.ms-profile { display: grid; gap: 14px; padding: 18px; min-width: 0; }
.ms-profile-top { display: grid; gap: 12px; justify-items: center; text-align: center; }
.ms-profile .ms-avatar { width: 72px; height: 72px; font-size: 22px; }
.ms-profile-name { margin: 0; font-size: var(--fs-2xl); font-weight: var(--fw-heavy); letter-spacing: var(--tracking-title); }
.ms-profile-role { margin: 0; font-size: var(--fs-lg); color: var(--dim); }
.ms-profile-meta { display: grid; gap: 7px; min-width: 0; }
.ms-profile-meta > div { display: flex; align-items: center; gap: 9px; min-width: 0; font-size: var(--fs-md); color: var(--dim); }
.ms-profile-meta svg { flex: none; color: var(--faint); }
.ms-profile-meta span { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ms-profile-actions { display: flex; gap: 8px; }
.ms-profile-actions > .ms-btn { flex: 1; justify-content: center; }
/* An icon-only button is a fixed square beside the verb, never half the row. */
.ms-profile-actions > .ms-icon-btn { flex: none; }
.ms-profile-stats { display: grid; grid-auto-flow: column; gap: 4px; padding-top: 12px; border-top: 1px solid var(--border); text-align: center; }
.ms-profile-stat { display: grid; gap: 1px; min-width: 0; }
.ms-profile-stat b { font-size: var(--fs-xl); font-weight: var(--fw-heavy); font-variant-numeric: tabular-nums; }
.ms-profile-stat span { font-size: var(--fs-xs); color: var(--faint); }
/* A cover strip, with the avatar straddling its edge. */
.ms-profile.has-cover { padding-top: 0; }
.ms-profile-cover { height: 84px; margin: 0 -18px 0; border-radius: calc(var(--radius-card) - 1px) calc(var(--radius-card) - 1px) 0 0; background: linear-gradient(120deg, var(--accent), color-mix(in srgb, var(--accent) 45%, var(--surface-3))); }
.ms-profile.has-cover .ms-profile-top { margin-top: -40px; }
.ms-profile.has-cover .ms-avatar { box-shadow: 0 0 0 4px var(--surface); }
/* The row form: in a list, a menu, a mention. Left-aligned, no stats. */
.ms-profile.is-row { grid-template-columns: auto minmax(0, 1fr) auto; align-items: center; gap: 12px; padding: 12px 14px; }
.ms-profile.is-row .ms-profile-top { display: contents; }
.ms-profile.is-row .ms-avatar { width: 44px; height: 44px; font-size: var(--fs-xl); }
.ms-profile.is-row .ms-profile-text { display: grid; gap: 1px; min-width: 0; text-align: left; }
.ms-profile.is-row .ms-profile-name { font-size: var(--fs-lg); }
.ms-profile.is-row .ms-profile-role { font-size: var(--fs-md); }

/* ── Appearance switcher ───────────────────────────────────────────────────
   Light/dark, the accent, the rail style and the density — the four switches
   an app built on this kit has to expose somewhere. It had been rewritten
   three times (the Northwind example, the docs site, a prototype) before it
   became a component, which is usually the sign that it should be one.

   The swatches are the only place a colour is the whole control, so the
   chosen one carries a tick as well: colour is never the only cue. */
.ms-appearance { display: flex; align-items: center; gap: 18px; flex-wrap: wrap; }
/* Inside a floating bar it is one row: the bar is 52px, and a switcher
   that wraps turns it into a slab over the work. */
.ms-floatbar .ms-appearance { flex-wrap: nowrap; gap: 14px; }
.ms-appearance-group { display: flex; align-items: center; gap: 8px; min-width: 0; }
.ms-appearance-label {
  font-size: var(--fs-xs); font-weight: var(--fw-heavy); letter-spacing: var(--tracking-label);
  text-transform: uppercase; color: var(--faint); white-space: nowrap;
}
.ms-swatches { display: flex; gap: 6px; }
.ms-swatch {
  width: 22px; height: 22px; border-radius: 50%; border: 0; padding: 0; cursor: pointer;
  display: grid; place-items: center; color: #fff; flex: none;
}
.ms-swatch > svg { opacity: 0; transition: opacity var(--t-fast) ease; }
/* The ring sits off the colour rather than on it: a border drawn on the
   swatch itself eats into the colour and reads as a smudge at this size. */
.ms-swatch[aria-pressed="true"] { box-shadow: 0 0 0 2px var(--surface), 0 0 0 4px var(--accent); }
.ms-swatch[aria-pressed="true"] > svg { opacity: 1; }
.ms-swatch:focus-visible { outline: none; box-shadow: 0 0 0 2px var(--surface), 0 0 0 4px var(--accent); }
@media (max-width: 720px) {
  .ms-appearance { gap: 12px; overflow-x: auto; scrollbar-width: none; }
  .ms-appearance::-webkit-scrollbar { display: none; }
}

/* ── Launcher ──────────────────────────────────────────────────────────────
   A circle in a corner that opens something: a panel of announcements, a rail
   of things you can start, or nothing at all beyond its own click. It is the
   one piece of furniture allowed to sit over the content permanently, so it
   is small, it is in a corner, and it never covers the middle of the screen.

   Three modes, and the difference matters:
     · plain   — it is a button. One job, done on click.
     · menu    — click opens a panel above it. For things to READ.
     · actions — hover or focus expands a rail of verbs. For things to START.
   A rail that opens on hover must also open on focus and on click, or it does
   not exist for a keyboard or a touch screen. */
/* The launcher's own box is the circle and nothing else. The rail and the
   panel are positioned against it rather than sitting in its flow — a rail
   that took up width even while closed dragged the whole thing off the edge
   of whatever it was in, and moved the circle every time it opened. */
.ms-launcher {
  position: fixed; bottom: 20px; z-index: var(--z-selection-bar);
  width: 52px; height: 52px;
}
.ms-launcher.is-right { right: 20px; }
.ms-launcher.is-left { left: 20px; }
.ms-launcher.is-contained { position: absolute; }

.ms-launcher-btn {
  width: 52px; height: 52px; flex: none; border-radius: 50%; border: 0; padding: 0; cursor: pointer;
  display: grid; place-items: center; background: var(--accent); color: var(--on-accent);
  /* Not --shadow: that is 0 16px 48px, and a 16px downward offset under a 52px
     circle blooms below it hard enough that the circle reads as hanging lower
     than the pill it shares a centre with. A small round control wants a
     shadow in proportion to itself. */
  box-shadow: 0 4px 14px rgba(20, 30, 40, .24);
  transition: transform var(--t-base) var(--ease-out), box-shadow var(--t-base) ease;
}
.ms-launcher-btn:hover { box-shadow: 0 4px 14px rgba(20, 30, 40, .28), 0 0 0 5px var(--accent-soft); }
.ms-launcher-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
/* The hand waves once when it arrives, and again on hover — a greeting, not a
   loop. Anything that moves forever in the corner of the eye is a tax. */
.ms-launcher-btn > svg { transform-origin: 70% 80%; }
.ms-launcher-btn.is-waving > svg { animation: ms-wave 1.1s var(--ease-out) 1; }
@keyframes ms-wave {
  0%, 100% { transform: rotate(0); }
  20% { transform: rotate(16deg); }
  40% { transform: rotate(-12deg); }
  60% { transform: rotate(12deg); }
  80% { transform: rotate(-6deg); }
}
/* A count on the circle, for a launcher that holds unread things. */
.ms-launcher-count {
  position: absolute; top: -2px; right: -2px; min-width: 20px; height: 20px; padding: 0 5px;
  border-radius: 10px; display: grid; place-items: center; background: var(--danger); color: #fff;
  font-size: 11px; font-weight: var(--fw-heavy); font-variant-numeric: tabular-nums;
  box-shadow: 0 0 0 2px var(--surface);
}
.ms-launcher-btn { position: relative; }

/* The rail of verbs. It grows from the circle's own edge, so the circle does
   not move — the thing you are pointing at stays where you pointed. */
/* One pill that unrolls from the circle, not a separate box beside it. The
   rail is the circle's own height with a matching radius, its far end is the
   same semicircle, and it is anchored so the button sits over its near end —
   so what grows is one continuous shape, and the circle never moves.
   clip-path does the growing: it reveals the pill from the button outward,
   which is the motion people mean by "it slides out of the button". */
.ms-launcher-rail {
  position: absolute; top: 50%; transform: translateY(-50%);
  display: flex; align-items: center; gap: 2px; height: 52px; white-space: nowrap;
  border-radius: 26px; border: 1px solid var(--border); background: var(--surface);
  box-shadow: 0 4px 14px rgba(20, 30, 40, .16);   /* matched to the circle's */
  pointer-events: none;
  transition: clip-path .3s var(--ease-out);
  /* It must never grow past what it is in. Fixed, that is the window; inside a
     pane, the pane sets --launcher-rail-max. Past that the verbs scroll within
     the pill rather than the pill running off the edge — it stays one shape. */
  max-width: var(--launcher-rail-max, calc(100vw - 28px));
  overflow-x: auto; scrollbar-width: none;
}
.ms-launcher-rail::-webkit-scrollbar { display: none; }
/* Padding on the button's side keeps the items clear of the circle. */
.ms-launcher.is-right .ms-launcher-rail {
  right: 0; padding: 0 58px 0 8px;
  clip-path: inset(0 0 0 calc(100% - 52px) round 26px);
}
.ms-launcher.is-left .ms-launcher-rail {
  left: 0; padding: 0 8px 0 58px;
  clip-path: inset(0 calc(100% - 52px) 0 0 round 26px);
}
.ms-launcher.is-open > .ms-launcher-rail { pointer-events: auto; clip-path: inset(0 round 26px); }
/* The circle sits over the pill's near end, so the two read as one object. */
.ms-launcher-btn { position: relative; z-index: 1; }
@media (prefers-reduced-motion: reduce) {
  .ms-launcher-rail { transition: none; }
}
.ms-launcher-item {
  display: flex; align-items: center; gap: 7px; height: 40px; padding: 0 12px;
  border: 0; border-radius: 20px; background: transparent; color: var(--text);
  font-size: var(--fs-base); font-weight: var(--fw-bold); cursor: pointer; text-align: left;
}
.ms-launcher-item:hover { background: var(--surface-2); }
.ms-launcher-item:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.ms-launcher-item > svg { flex: none; color: var(--dim); }
.ms-launcher-item:hover > svg { color: var(--accent); }

/* The panel, for things to read rather than start. */
.ms-launcher-panel {
  position: absolute; bottom: calc(100% + 10px);
  /* A container can set --launcher-panel-w; contained in a narrow pane, the
     viewport is the wrong thing to measure against. */
  width: var(--launcher-panel-w, min(340px, calc(100vw - 40px)));
  max-height: var(--launcher-panel-h, min(420px, 60vh));
  overflow: auto; display: none; flex-direction: column;
  border-radius: var(--radius-card); border: 1px solid var(--border);
  background: var(--surface); box-shadow: var(--shadow);
}
.ms-launcher.is-right .ms-launcher-panel { right: 0; }
.ms-launcher.is-left .ms-launcher-panel { left: 0; }
.ms-launcher.is-open > .ms-launcher-panel { display: flex; animation: ms-drop .2s var(--ease-out) both; }
.ms-launcher-head { display: flex; align-items: center; gap: 8px; padding: 12px 14px; border-bottom: 1px solid var(--border); }
.ms-launcher-title { margin: 0; font-size: var(--fs-lg); font-weight: var(--fw-heavy); }
.ms-launcher-list { display: flex; flex-direction: column; }
.ms-launcher-news {
  display: grid; gap: 3px; padding: 12px 14px; text-align: left; border: 0; background: none;
  border-bottom: 1px solid var(--border); cursor: pointer; color: inherit;
}
.ms-launcher-news:last-child { border-bottom: 0; }
.ms-launcher-news:hover { background: var(--surface-2); }
.ms-launcher-news:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.ms-launcher-news b { font-size: var(--fs-lg); font-weight: var(--fw-bold); }
.ms-launcher-news span { font-size: var(--fs-md); color: var(--dim); line-height: var(--lh-base); }
.ms-launcher-news time { font-size: 11px; color: var(--faint); }
.ms-launcher-news.is-unread b::after {
  content: ""; display: inline-block; width: 7px; height: 7px; margin-left: 7px;
  border-radius: 50%; background: var(--accent); vertical-align: middle;
}

@media (prefers-reduced-motion: reduce) {
  .ms-launcher-btn.is-waving > svg { animation: none; }
  /* The pill centres itself with translateY(-50%); a blanket
     `transform: none` from the old sliding rail dropped it 26px. Only
     the transition is dropped here — the positioning has to stand. */
  .ms-launcher-rail { transition: none; }
}
@media (max-width: 640px) {
  .ms-launcher { bottom: 14px; }
  .ms-launcher.is-right { right: 14px; }
  .ms-launcher.is-left { left: 14px; }
  /* A phone has even less width to unroll into — but a launcher inside a
     pane still answers to that pane, so the knob stays in the formula
     rather than being overridden exactly where room is tightest. */
  .ms-launcher .ms-launcher-rail { max-width: min(var(--launcher-rail-max, 100vw), calc(100vw - 24px)); }
}

/* ── Terms, read before accepting ──────────────────────────────────────────
   A box of terms that has to be scrolled to the end before the accept control
   comes alive. It is an honesty device, not a security one: it stops the
   thoughtless tick, and it makes the length of the thing visible. Use it only
   where somebody genuinely has to have seen the words — never as a hurdle on
   an ordinary form.

   The "Scroll down" hint sits over the foot of the box, centred, and goes as
   soon as the end is reached. It is over the text rather than under the box so
   it cannot be mistaken for the page's own furniture. */
.ms-terms { display: grid; gap: 10px; min-width: 0; }
/* The frame is what the fade and the hint are positioned against. They used to
   hang off .ms-terms, which also contains the foot — so the hint landed on the
   foot instead of over the words it belongs to. */
.ms-terms-frame { position: relative; min-width: 0; }
.ms-terms-box {
  max-height: var(--terms-h, 240px); overflow-y: auto; overflow-x: hidden;
  padding: 14px 16px; border: 1.5px solid var(--border-strong); border-radius: var(--r-xl);
  background: var(--surface); font-size: var(--fs-md); line-height: 1.6; color: var(--dim);
  scroll-behavior: smooth;
  /* Reserve the scrollbar's lane inside the padding, so the bar sits within
     the border rather than riding on it and clipping against the radius —
     and so the text never runs underneath it. */
  scrollbar-gutter: stable;
  scrollbar-width: thin;
  scrollbar-color: var(--border-strong) transparent;
}
.ms-terms-box::-webkit-scrollbar { width: 10px; }
.ms-terms-box::-webkit-scrollbar-track { background: transparent; }
.ms-terms-box::-webkit-scrollbar-thumb { background: var(--border-strong); border-radius: 5px; border: 3px solid var(--surface); }
.ms-terms-box::-webkit-scrollbar-thumb:hover { background: var(--neutral-8); }
.ms-terms-box > * { margin: 0 0 10px; }
.ms-terms-box > *:last-child { margin-bottom: 0; }
.ms-terms-box h4 { color: var(--text); font-size: var(--fs-lg); font-weight: var(--fw-heavy); margin-top: 16px; }
.ms-terms-box h4:first-child { margin-top: 0; }
.ms-terms-box:focus-visible { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
.ms-terms.is-read .ms-terms-box { border-color: var(--success); }

/* The hint, centred over the foot of the box. */
/* The hint is a real button: pressing it takes you to the end, which is the
   whole point of it. Leaving it as decoration would mean the only way through
   is a scroll gesture, and that is the one thing some people cannot do. */
.ms-terms-hint {
  position: absolute; left: 50%; transform: translateX(-50%);
  bottom: 12px; z-index: 2; display: inline-flex; align-items: center; gap: 6px;
  height: 30px; padding: 0 13px; border: 0; border-radius: 15px; cursor: pointer;
  background: var(--tooltip-bg); color: var(--tooltip-fg);
  font-size: var(--fs-md); font-weight: var(--fw-bold); white-space: nowrap;
  box-shadow: var(--shadow-sm); transition: opacity var(--t-base) ease, transform var(--t-base) var(--ease-out);
}
.ms-terms-hint:hover { background: var(--text); }
.ms-terms-hint:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.ms-terms.is-read .ms-terms-hint { pointer-events: none; }
.ms-terms-hint > svg { animation: ms-nudge 1.6s var(--ease-out) infinite; }
@keyframes ms-nudge { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(3px); } }
.ms-terms.is-read .ms-terms-hint { opacity: 0; transform: translateX(-50%) translateY(6px); }
/* A fade at the foot, so it is visible that there is more below. */
.ms-terms-box::after { content: ""; }
.ms-terms-fade {
  position: absolute; left: 2px; right: 2px; bottom: 2px; height: 56px; pointer-events: none; z-index: 1;
  border-radius: 0 0 calc(var(--r-xl) - 2px) calc(var(--r-xl) - 2px);
  background: linear-gradient(to bottom, transparent, var(--surface));
  transition: opacity var(--t-base) ease;
}
.ms-terms.is-read .ms-terms-fade { opacity: 0; }
/* How far through it you are. */
/* No progress bar. How far down a box of terms somebody has got is not a
   number anyone wants; what they want is to get to the end and carry on. */
.ms-terms-foot { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.ms-terms-said:empty { display: none; }
.ms-terms-said { font-size: var(--fs-md); color: var(--dim); }
.ms-terms.is-read .ms-terms-said { color: var(--success); font-weight: var(--fw-bold); }
@media (prefers-reduced-motion: reduce) {
  .ms-terms-hint > svg { animation: none; }
  .ms-terms-box { scroll-behavior: auto; }
}

/* ── Placeholder rows ──────────────────────────────────────────────────────
   Stand-in content: rows with a bar where the words go. Two jobs from one
   shape, and the difference is whether anything is on its way.

     .ms-placeholder            — nothing is coming. A demo stage, a preview,
                                  a screen behind a dialog in a specimen.
                                  It is still and quiet, and never pretends.
     .ms-placeholder.is-loading — something IS coming. The bars take the
                                  skeleton's shimmer, so the stillness of the
                                  first case stays meaningful.

   Widths come from --w on each bar, so a block of them looks like text rather
   than like a barcode. Give the rows the real component's height, or the
   screen jumps when the content lands — which is the whole point of a
   skeleton and the commonest way of getting one wrong. */
.ms-placeholder { display: grid; gap: var(--placeholder-gap, 10px); }
.ms-placeholder-row {
  display: flex; align-items: center; gap: 12px;
  min-height: var(--placeholder-h, 46px); padding: 0 14px;
  border-radius: var(--r-lg); background: var(--surface-2);
}
.ms-placeholder-bar {
  height: 9px; border-radius: 5px; background: var(--surface-3);
  width: var(--w, 60%); flex: none;
}
.ms-placeholder-dot { width: 26px; height: 26px; border-radius: 50%; background: var(--surface-3); flex: none; }
.ms-placeholder-bar.is-tall { height: 12px; }
.ms-placeholder-bar.is-thin { height: 7px; }
/* Stacked lines inside one row: a name over a line of metadata. */
.ms-placeholder-lines { display: grid; gap: 6px; flex: 1; min-width: 0; }
.ms-placeholder-lines > .ms-placeholder-bar { flex: none; }
/* Flush against a card's edges, like a list of rows rather than tiles. */
.ms-placeholder.is-flush { gap: 0; }
.ms-placeholder.is-flush .ms-placeholder-row { border-radius: 0; background: transparent; border-bottom: 1px solid var(--border); }
.ms-placeholder.is-flush .ms-placeholder-row:last-child { border-bottom: 0; }
/* Loading: the same shape, shimmering, so "still" keeps meaning "nothing is coming". */
.ms-placeholder.is-loading .ms-placeholder-bar,
.ms-placeholder.is-loading .ms-placeholder-dot {
  background: linear-gradient(100deg, var(--surface-2) 30%, var(--surface-3) 50%, var(--surface-2) 70%);
  background-size: 300% 100%; animation: ms-shimmer 1.4s ease infinite;
}
.ms-placeholder.is-loading.is-flush .ms-placeholder-bar,
.ms-placeholder.is-loading.is-flush .ms-placeholder-dot {
  background: linear-gradient(100deg, var(--surface-2) 30%, var(--surface-3) 50%, var(--surface-2) 70%);
  background-size: 300% 100%;
}
@media (prefers-reduced-motion: reduce) {
  .ms-placeholder.is-loading .ms-placeholder-bar,
  .ms-placeholder.is-loading .ms-placeholder-dot { animation: none; background: var(--surface-3); }
}

/* ── Card payment ──────────────────────────────────────────────────────────
   The one form where people are most likely to give up, so it asks for the
   fewest things in the order printed on the card, and it never blames.
   The kit styles the fields; it does not take card numbers — in a real app
   these are the payment provider's iframes wearing these classes. */
.ms-card-form { display: grid; gap: 14px; max-width: 460px; }
.ms-card-number { position: relative; }
.ms-card-number .ms-input { padding-right: 84px; font-variant-numeric: tabular-nums; letter-spacing: .04em; }
/* The network, where the number ends, and only once it is known. One uniform
   badge for all of them rather than eight approximated logos: the real marks
   are trademarks, an imitation is a poor likeness, and a badge that is the
   same shape every time is easier to read than eight that are not. Drop the
   provider's own artwork in here when you have it. */
.ms-card-brand {
  position: absolute; right: 9px; top: 50%; transform: translateY(-50%);
  /* A tighter radius than --r-sm: the badge sits inside a field whose own
     corners are far away, so a rounded chip here reads as a pill floating
     in a box rather than part of it. */
  height: 22px; padding: 0 7px; border-radius: 4px;
  display: grid; place-items: center;
  background: var(--surface-2); border: 1px solid var(--border);
  font-size: 10px; font-weight: var(--fw-heavy); letter-spacing: .05em;
  font-variant-numeric: normal; color: var(--dim); white-space: nowrap;
  opacity: 0; transition: opacity var(--t-base) ease;
}
.ms-card-brand.is-on { opacity: 1; }
.ms-card-brand > svg, .ms-card-brand > img { display: block; height: 20px; width: auto; }
.ms-card-row { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
.ms-card-cvc { position: relative; }
/* The total is the thing people check last, so it is the thing they can see. */
.ms-pay-total {
  display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
  padding: 12px 14px; border-radius: var(--r-xl); background: var(--surface-2); border: 1px solid var(--border);
}
.ms-pay-total b { font-size: var(--fs-2xl); font-weight: var(--fw-heavy); font-variant-numeric: tabular-nums; }
.ms-pay-total span { font-size: var(--fs-md); color: var(--dim); }
.ms-pay-secure { display: flex; align-items: center; gap: 6px; font-size: var(--fs-md); color: var(--dim); }
.ms-pay-secure > svg { flex: none; color: var(--success); }

/* ── Vote ──────────────────────────────────────────────────────────────────
   Was this helpful? Two buttons, one answer, and it never moves or vanishes
   once pressed — a control that disappears at the moment you use it leaves
   people wondering whether it registered. */
.ms-vote { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.ms-vote-q { font-size: var(--fs-md); color: var(--dim); }
.ms-vote-buttons { display: inline-flex; gap: 6px; }
.ms-vote-btn {
  display: inline-flex; align-items: center; gap: 6px; height: var(--h-md, 30px); padding: 0 11px;
  border-radius: var(--r-lg); border: 1px solid var(--border-strong); background: var(--surface);
  color: var(--dim); font-size: var(--fs-base); font-weight: var(--fw-bold); cursor: pointer;
}
.ms-vote-btn:hover { color: var(--text); background: var(--surface-2); }
.ms-vote-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.ms-vote-btn[aria-pressed="true"].is-up { border-color: var(--success); color: var(--success); background: var(--success-soft); }
.ms-vote-btn[aria-pressed="true"].is-down { border-color: var(--danger); color: var(--danger); background: var(--danger-soft); }
.ms-vote-btn > svg { flex: none; }
.ms-vote-btn.is-down > svg { transform: rotate(180deg); }
.ms-vote-count { font-variant-numeric: tabular-nums; }
.ms-vote-said { font-size: var(--fs-md); color: var(--success); font-weight: var(--fw-bold); }
/* The follow-up: once someone says no, ask why — but never make it compulsory. */
.ms-vote-why { display: grid; gap: 8px; margin-top: 10px; max-width: 460px; }

/* ── Timeline, laid out along the page ─────────────────────────────────────
   The same entries on a rail that runs left to right. It is for a sequence
   with an obvious shape and few steps — an order's progress, a release train,
   a claim moving through stages — where seeing the whole run at once is the
   point. A history with a dozen entries and a paragraph each stays vertical:
   sideways, it becomes a thing people scroll past rather than read.

   The rail is drawn behind the markers with a gradient stop, so it is one
   continuous line rather than a border on each item that breaks at every gap. */
.ms-tl.is-horizontal {
  display: grid; grid-auto-flow: column; grid-auto-columns: minmax(var(--tl-step, 150px), 1fr);
  gap: 0; padding: 4px 0 2px; position: relative;
  overflow-x: auto; scrollbar-width: thin; scroll-snap-type: x proximity;
}
.ms-tl.is-horizontal > .ms-tl-item {
  flex-direction: column; align-items: center; text-align: center;
  gap: 8px; padding: 6px 10px 4px; border-radius: var(--r-xl);
  scroll-snap-align: start; min-width: 0;
}
/* The rail: a line at the markers' centre, behind them, stopping at each end
   so it does not run off into nothing. */
.ms-tl.is-horizontal > .ms-tl-item::before {
  content: ""; position: absolute; top: calc(6px + 16px); left: 0; right: 0; height: 2px;
  background: var(--border-strong); z-index: 0;
}
.ms-tl.is-horizontal > .ms-tl-item:first-child::before { left: 50%; }
.ms-tl.is-horizontal > .ms-tl-item:last-child::before { right: 50%; }
/* Done is behind you, so the rail up to the current step is the accent. */
.ms-tl.is-horizontal > .ms-tl-item.is-done::before { background: var(--accent); }
.ms-tl.is-horizontal > .ms-tl-badge, .ms-tl.is-horizontal .ms-tl-badge { position: relative; z-index: 1; }
.ms-tl.is-horizontal .ms-tl-badge {
  width: 32px; height: 32px; border-radius: 50%; box-shadow: 0 0 0 3px var(--surface);
}
.ms-tl.is-horizontal .ms-tl-item.is-done .ms-tl-badge { background: var(--accent); color: var(--on-accent); border-color: var(--accent); }
.ms-tl.is-horizontal .ms-tl-text { flex: none; justify-items: center; gap: 2px; width: 100%; }
.ms-tl.is-horizontal .ms-tl-text > :first-child { padding-right: 0; }
.ms-tl.is-horizontal .ms-tl-by { white-space: normal; text-overflow: clip; line-height: 1.35; }
.ms-tl.is-horizontal .ms-tl-more { display: none; }
.ms-tl.is-horizontal .ms-tl-note { text-align: left; }
/* The current step is marked by weight and a ring, not by size: nothing moves
   as the sequence advances. */
.ms-tl.is-horizontal > .ms-tl-item.is-current { background: transparent; border-color: transparent; }
.ms-tl.is-horizontal > .ms-tl-item.is-current .ms-tl-badge { box-shadow: 0 0 0 3px var(--surface), 0 0 0 5px var(--accent-soft); }
/* Below this there is no room to lay a sequence out sideways, so it becomes
   the vertical one again rather than a row of five-pixel columns. */
@media (max-width: 720px) {
  .ms-tl.is-horizontal { grid-auto-flow: row; grid-auto-columns: auto; gap: 4px; overflow-x: visible; }
  .ms-tl.is-horizontal > .ms-tl-item { flex-direction: row; align-items: flex-start; text-align: left; padding: 9px 8px 9px 9px; }
  .ms-tl.is-horizontal > .ms-tl-item::before { display: none; }
  .ms-tl.is-horizontal .ms-tl-text { justify-items: start; }
  .ms-tl.is-horizontal .ms-tl-badge { border-radius: var(--r-md); box-shadow: none; }
}

/* ── Glass ─────────────────────────────────────────────────────────────────
   A translucent, blurred surface for the few bars that sit OVER content: a
   sticky header, a floating bar. What it buys is orientation — you can see
   that there is more above or below, and watch it pass under the bar, instead
   of the bar reading as the edge of the world.

   Two rules keep it honest. It is only for chrome that covers content, never
   for a card or a panel, which has nothing behind it to show. And it always
   has a solid fallback: backdrop-filter is unsupported or disabled often
   enough (older browsers, some Linux builds, GPU blocklists, reduced
   transparency settings) that shipping without one means shipping a bar whose
   text sits on whatever is underneath. */
.ms-glass {
  background: var(--glass-bg);
  -webkit-backdrop-filter: saturate(var(--glass-saturate)) blur(var(--glass-blur));
  backdrop-filter: saturate(var(--glass-saturate)) blur(var(--glass-blur));
}
.ms-glass.is-edged { border-color: var(--glass-edge); }
/* No blur available, or the reader has asked for less transparency: give them
   the solid surface rather than unreadable text. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .ms-glass { background: var(--surface); }
}
@media (prefers-reduced-transparency: reduce) {
  .ms-glass { background: var(--surface); -webkit-backdrop-filter: none; backdrop-filter: none; }
}

/* The floating bar takes it as a variant, because a bar that carries Save for
   unsaved work should be allowed to be solid and emphatic instead. */
.ms-floatbar.is-glass {
  background: var(--glass-bg);
  border-color: var(--glass-edge);
  -webkit-backdrop-filter: saturate(var(--glass-saturate)) blur(var(--glass-blur));
  backdrop-filter: saturate(var(--glass-saturate)) blur(var(--glass-blur));
}
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .ms-floatbar.is-glass { background: var(--surface); border-color: var(--border); }
}
@media (prefers-reduced-transparency: reduce) {
  .ms-floatbar.is-glass {
    background: var(--surface); border-color: var(--border);
    -webkit-backdrop-filter: none; backdrop-filter: none;
  }
}
/* Dirty is the one state that overrules the glass: unsaved work is not
   something to look through. */
.ms-floatbar.is-glass.is-dirty { background: color-mix(in srgb, var(--surface) 94%, transparent); }

/* ── Top bar shell ─────────────────────────────────────────────────────────
   The other common arrangement: a bar across the whole width, with the rail
   under it rather than beside it. Most web apps are one or the other, and
   which you want depends on what the bar is for.

     rail beside, toolbar inside   — the default. The toolbar belongs to the
                                     SCREEN: where you are, and what you can
                                     do here. It changes as you navigate.
     top bar across, rail under    — this. The bar belongs to the PRODUCT:
                                     the brand, search everything, the one
                                     global verb, you. It does not change.

   Putting screen actions in a product bar is the usual mistake: they end up
   miles from the thing they act on, and the bar stops meaning anything.

     <div class="ms-app has-topbar">
       <header class="ms-topbar is-glass">…</header>
       <div class="ms-shell">
         <aside class="ms-rail">…</aside>
         <main class="ms-main">…</main>
       </div>
     </div> */
.ms-app.has-topbar { flex-direction: column; }
.ms-shell { display: flex; flex: 1; min-width: 0; min-height: 0; }

.ms-topbar {
  flex: none; height: var(--topbar-h, 58px); display: flex; align-items: center; gap: var(--sp-5, 14px);
  padding: 0 var(--sp-6, 16px); border-bottom: 1px solid var(--border); background: var(--surface);
  position: sticky; top: var(--banner-h, 0px); z-index: var(--z-topbar, 12);
}
.ms-topbar-brand { display: flex; align-items: center; gap: 10px; flex: none; font-size: 16px; font-weight: var(--fw-heavy); letter-spacing: var(--tracking-title); color: var(--text); }
.ms-topbar-brand .ms-rail-logo { width: 32px; height: 32px; border-radius: 9px; }
.ms-topbar-search { flex: 1; min-width: 0; display: flex; justify-content: center; }
.ms-topbar-search .ms-search { width: min(420px, 100%); }
.ms-topbar-actions { display: flex; align-items: center; gap: 8px; flex: none; }
/* Glass: the bar stays put while the page moves under it, and you can see it
   move. The tokens carry the solid fallback with them. */
.ms-topbar.is-glass {
  background: var(--glass-bg); border-bottom-color: var(--glass-edge);
  -webkit-backdrop-filter: saturate(var(--glass-saturate)) blur(var(--glass-blur));
  backdrop-filter: saturate(var(--glass-saturate)) blur(var(--glass-blur));
}
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .ms-topbar.is-glass { background: var(--surface); border-bottom-color: var(--border); }
}
@media (prefers-reduced-transparency: reduce) {
  .ms-topbar.is-glass {
    background: var(--surface); border-bottom-color: var(--border);
    -webkit-backdrop-filter: none; backdrop-filter: none;
  }
}
/* The screen's own toolbar can take the glass too: it is sticky, so content
   passes under it exactly as it does under a product bar. */
.ms-toolbar.is-glass {
  background: var(--glass-bg); border-bottom-color: var(--glass-edge);
  -webkit-backdrop-filter: saturate(var(--glass-saturate)) blur(var(--glass-blur));
  backdrop-filter: saturate(var(--glass-saturate)) blur(var(--glass-blur));
}
/* .is-canvas makes the toolbar the canvas colour and drops its border; glass
   has to win that, or it tints towards a surface that is not behind it. */
.ms-app.is-canvas .ms-toolbar.is-glass {
  background: color-mix(in srgb, var(--canvas) 82%, transparent);
  border-bottom-color: var(--glass-edge);
}
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .ms-toolbar.is-glass { background: var(--bg); border-bottom-color: var(--border); }
  .ms-app.is-canvas .ms-toolbar.is-glass { background: var(--canvas); border-bottom-color: transparent; }
}
@media (prefers-reduced-transparency: reduce) {
  .ms-toolbar.is-glass {
    background: var(--bg); border-bottom-color: var(--border);
    -webkit-backdrop-filter: none; backdrop-filter: none;
  }
  .ms-app.is-canvas .ms-toolbar.is-glass { background: var(--canvas); border-bottom-color: transparent; }
}

/* Under a top bar the rail starts below it and is that much shorter. */
.ms-app.has-topbar .ms-rail {
  height: calc(100vh - var(--topbar-h, 58px) - var(--banner-h, 0px));
  top: calc(var(--topbar-h, 58px) + var(--banner-h, 0px));
}
.ms-app.has-topbar .ms-toolbar { top: calc(var(--topbar-h, 58px) + var(--banner-h, 0px)); }
/* The rail's own brand is in the top bar now, so it does not repeat it —
   the mark as well as the word. The mark inside the expand control stays,
   because that is a button, not branding. */
.ms-app.has-topbar .ms-rail .ms-rail-brand,
.ms-app.has-topbar .ms-rail-head > .ms-rail-logo { display: none; }

@media (max-width: 720px) {
  .ms-topbar { padding-inline: 12px; gap: 10px; }
  .ms-topbar-search { justify-content: flex-end; }
  .ms-topbar-search .ms-search { width: auto; }
}
/* Narrow, the kit turns the rail into a horizontal strip and stacks the app.
   Under a top bar the rail lives in .ms-shell, which that rule never touched —
   so the rail stayed a full-height column beside the content and the page
   scrolled sideways. The shell has to stack too, and the rail has to give up
   its sticky offset: sticky at --topbar-h collides with the toolbar, which is
   pinned to exactly the same line. */
@media (max-width: 820px) {
  .ms-app.has-topbar .ms-shell { flex-direction: column; min-width: 0; }
  .ms-app.has-topbar .ms-rail {
    position: static; width: 100%; height: auto; top: auto;
  }
}
