/* ============================================================================
   Components: the control vocabulary every screen composes from. Buttons,
   fields, cards, tables, chips, meters, avatars, toasts, modals, empty and
   loading states.

   Two rules govern every colour here.

   1. Everything in this file lives in the CONTENT area, a modal, a toast or a
      popover, never inside #nav. So it uses --c-bg / --c-surface* / --c-text*
      and never --c-nav-*. The nav has its own file for the same reason it has
      its own tokens.

   2. Ink on a solid fill is --c-text-inverse, not --c-accent-text, everywhere
      except a control whose fill is literally --c-accent. In the dark looks the
      accent is a LIGHT hue, so white-on-accent measures around 3:1 and fails
      AA; --c-text-inverse is dark ink in a dark look and light ink in a light
      one, which is the direction that contrasts with a saturated fill in both.
      Tinted fills get the same self-correction through color-mix: mixing a hue
      with --c-text darkens it on a light ground and lightens it on a dark one,
      so a single declaration passes in all ten blocks.
   ========================================================================= */

/* ============================================================================
   BUTTONS
   ========================================================================= */

/* The bare element is the primary action, so a form always has one obvious way
   forward without anybody having to remember a class name. */
button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-3);
  min-height: 34px;
  padding: var(--s-4) var(--s-6);
  border: var(--bw) solid transparent;
  border-radius: var(--r-md);
  background: var(--c-accent);
  color: var(--c-accent-text);
  font: inherit;
  font-size: var(--t-base);
  font-weight: var(--t-semibold);
  line-height: var(--t-tight);
  letter-spacing: var(--t-track-normal);
  white-space: nowrap;
  cursor: pointer;
  box-shadow: var(--e-1);
  transition:
    background var(--m-fast) var(--m-ease),
    border-color var(--m-fast) var(--m-ease),
    color var(--m-fast) var(--m-ease),
    box-shadow var(--m-fast) var(--m-ease),
    transform var(--m-fast) var(--m-ease);
}
button:hover { background: var(--c-accent-hover); }
button:active { transform: translateY(1px); box-shadow: none; }

button.ghost {
  background: var(--c-surface-2);
  color: var(--c-text);
  border-color: var(--c-border);
  box-shadow: none;
}
button.ghost:hover { background: var(--c-surface-3); border-color: var(--c-border-strong); }

button.quiet {
  background: transparent;
  color: var(--c-text-2);
  border-color: transparent;
  box-shadow: none;
  font-weight: var(--t-medium);
}
button.quiet:hover { background: var(--c-surface-2); color: var(--c-text); }

button.danger { background: var(--c-danger); color: var(--c-text-inverse); }
button.danger:hover { background: color-mix(in srgb, var(--c-danger) 86%, var(--c-text)); }

button.sm { min-height: 32px; padding: var(--s-3) var(--s-5); font-size: var(--t-sm); }
button.wide { width: 100%; min-height: 42px; }

/* The icon button floor is 32px and not a pixel less, because that is the
   number scripts/audit-geometry.mjs fails the build on. A 28px square is
   comfortable for a mouse and a genuine miss for anybody with a tremor or a
   trackpad, and it is the single most common way an otherwise careful
   interface becomes unusable. */
button.icon {
  width: 32px;
  height: 32px;
  min-height: 32px;
  padding: 0;
  flex: none;
  border: var(--bw) solid transparent;
  border-radius: var(--r-sm);
  background: transparent;
  color: var(--c-text-2);
  font-weight: var(--t-normal);
  box-shadow: none;
}
button.icon:hover { background: var(--c-surface-2); color: var(--c-text); border-color: var(--c-border); }
button.icon svg { width: 17px; height: 17px; }

button.on {
  background: var(--c-accent-quiet);
  border-color: color-mix(in srgb, var(--c-accent) 45%, transparent);
  color: var(--c-accent);
  box-shadow: none;
}

button:disabled,
button:disabled:hover,
button:disabled:active {
  opacity: 0.5;
  cursor: not-allowed;
  transform: none;
  background: var(--c-surface-2);
  color: var(--c-text-3);
  border-color: var(--c-border);
  box-shadow: none;
}

/* Touch: 32px is a miss with a finger. Grow the control rather than hanging an
   invisible target off it, because an invisible 44px box that overlaps its
   neighbour steals the neighbour's taps. */
@media (hover: none) {
  button { min-height: 42px; }
  button.sm { min-height: 38px; }
  button.icon { width: 42px; height: 42px; min-height: 42px; }
}
:root[data-input="touch"] button { min-height: 42px; }
:root[data-input="touch"] button.sm { min-height: 38px; }
:root[data-input="touch"] button.icon { width: 42px; height: 42px; min-height: 42px; }

/* ============================================================================
   FORM CONTROLS
   ========================================================================= */

input, textarea, select {
  width: 100%;
  min-height: 38px;
  padding: var(--s-4) var(--s-5);
  border: var(--bw) solid var(--c-border-strong);
  border-radius: var(--r-md);
  /* --c-bg rather than a surface, so a field reads as a well cut into whatever
     card it sits on, in every look. */
  background: var(--c-bg);
  color: var(--c-text);
  font: inherit;
  font-size: var(--t-base);
  line-height: var(--t-snug);
  transition:
    border-color var(--m-fast) var(--m-ease),
    box-shadow var(--m-fast) var(--m-ease);
}
textarea { resize: vertical; line-height: var(--t-body); min-height: 76px; }
input:hover:not(:disabled), textarea:hover:not(:disabled), select:hover:not(:disabled) {
  border-color: var(--c-text-3);
}
/* :focus and not :focus-visible: a field you are typing into must always show
   where the caret lives, mouse or not. The halo replaces the base.css outline
   because a border plus a halo hugs a rounded corner better than an offset
   outline does. */
input:focus, textarea:focus, select:focus {
  outline: none;
  border-color: var(--c-accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--c-accent) 22%, transparent);
}
input:disabled, textarea:disabled, select:disabled {
  opacity: 0.55;
  cursor: not-allowed;
  background: var(--c-surface-2);
}
::placeholder {
  color: color-mix(in srgb, var(--c-text-2) 86%, var(--c-bg));
  opacity: 1;
}
input[type="checkbox"], input[type="radio"] {
  width: 18px; height: 18px; min-height: 0;
  flex: none; padding: 0;
  accent-color: var(--c-accent);
  cursor: pointer;
}

/* iOS Safari zooms the page when it focuses a field drawn under 16px. max()
   keeps --t-base as the declared role and treats 16 as what it is: a browser
   threshold, not a rung on the type scale. */
@media (max-width: 860px) {
  input, textarea, select { min-height: 44px; font-size: max(16px, var(--t-base)); }
  input[type="checkbox"], input[type="radio"] { width: 22px; height: 22px; }
}

.field { display: block; }
.field-label {
  display: block;
  margin-bottom: var(--s-3);
  color: var(--c-text);
  font-size: var(--t-base);
  font-weight: var(--t-semibold);
  line-height: var(--t-snug);
}
.field-hint {
  display: block;
  margin-top: var(--s-3);
  color: var(--c-text-2);
  font-size: var(--t-sm);
  line-height: var(--t-snug);
}
.field-error { color: var(--c-danger); }
.form { display: flex; flex-direction: column; gap: var(--s-6); }

/* The one-time-code box. This is the one control allowed above --t-base,
   because it is not chrome around the screen's content, it IS the content: the
   sign-in step asks for exactly one thing, and digits being copied across from
   a phone have to be checkable at a glance. */
.codeinput {
  min-height: 58px;
  padding: var(--s-5) var(--s-4);
  font-family: var(--t-mono);
  font-size: var(--t-2xl);
  font-weight: var(--t-bold);
  text-align: center;
  letter-spacing: 0.34em;
  /* letter-spacing also trails the last glyph; nudge back to optical centre. */
  text-indent: 0.34em;
}

/* ============================================================================
   CARD
   The one container. A card is a surface with a hairline, not a shadow: five
   looks disagree about shadows (editorial has effectively none and no radius at
   all) and every one of them agrees about a rule.
   ========================================================================= */

.card {
  border: var(--bw) solid var(--c-border);
  border-radius: var(--r-xl);
  background: var(--c-surface);
  min-width: 0;
}
.card-pad { padding: var(--s-7); }
.card-head {
  display: flex;
  align-items: center;
  gap: var(--s-5);
  padding: var(--s-5) var(--s-7);
  border-bottom: var(--bw) solid var(--c-border);
  min-width: 0;
}
.card-title {
  font-size: var(--t-base);
  font-weight: var(--t-semibold);
  letter-spacing: var(--t-track-display);
  min-width: 0;
}
.card-body { padding: var(--s-7); min-width: 0; }
.card-foot {
  display: flex;
  align-items: center;
  gap: var(--s-5);
  padding: var(--s-5) var(--s-7);
  border-top: var(--bw) solid var(--c-border);
  color: var(--c-text-2);
  font-size: var(--t-sm);
  flex-wrap: wrap;
}
.card-quiet { background: var(--c-bg); }

/* A responsive card grid that never needs a breakpoint. auto-fit plus a floor
   of 260px is what makes the same markup a four-up row at 1920 and a single
   column at 400 without a media query per screen, and min(100%, ...) is what
   stops the floor itself overflowing at 320. */
.grid {
  display: grid;
  gap: var(--s-6);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr));
  min-width: 0;
}
.grid-2 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 340px), 1fr)); }
.grid-wide { grid-template-columns: repeat(auto-fit, minmax(min(100%, 420px), 1fr)); }

/* ============================================================================
   THE NUMBER WITH ITS CONTEXT

   Arc has one absolute rule about figures: a number never appears without what
   it is out of and what it covers. "87" is a rumour; "87 / 100, over 9
   evaluated days" is a fact somebody can argue with. So the stat is a component
   with three slots and js/ui.mjs refuses to build one without the third.
   ========================================================================= */

.stat { display: flex; flex-direction: column; gap: var(--s-2); min-width: 0; }
.stat-label {
  font-size: var(--t-2xs);
  font-weight: var(--t-semibold);
  letter-spacing: var(--t-track-caps);
  text-transform: uppercase;
  color: var(--c-text-3);
}
.stat-figure { display: flex; align-items: baseline; gap: var(--s-3); flex-wrap: wrap; min-width: 0; }
.stat-value {
  font-family: var(--t-display);
  font-size: var(--t-2xl);
  font-weight: var(--t-bold);
  letter-spacing: var(--t-track-tight);
  line-height: 1.05;
  color: var(--c-text);
}
.stat-of {
  font-size: var(--t-md);
  font-weight: var(--t-medium);
  color: var(--c-text-3);
  font-variant-numeric: tabular-nums;
}
.stat-context {
  color: var(--c-text-2);
  font-size: var(--t-sm);
  line-height: var(--t-snug);
}
.stat-xl .stat-value { font-size: var(--t-3xl); }

/* The hero figure on a score card. It is the only place in Arc where a number
   is allowed to be the largest thing on screen, and even here the denominator
   sits next to it at a smaller step rather than being hidden in a caption. */
.figure-hero { display: flex; align-items: baseline; gap: var(--s-4); flex-wrap: wrap; }
.figure-hero-value {
  font-family: var(--t-display);
  font-size: var(--t-3xl);
  font-weight: var(--t-black);
  letter-spacing: var(--t-track-tight);
  line-height: 1;
  font-variant-numeric: tabular-nums;
}
.figure-hero-of {
  font-size: var(--t-lg);
  font-weight: var(--t-medium);
  color: var(--c-text-3);
  font-variant-numeric: tabular-nums;
}

/* ============================================================================
   CHIPS, BANDS, PILLS
   ========================================================================= */

/* One chip, five tones, and the tone is DATA. A band label comes out of the
   policy pack and so does its position in the list, so the stylesheet cannot
   know that "Outstanding" is the good one; js/policy.mjs maps a band's index to
   a tone and sets the attribute. Hardcoding band names here would be exactly
   the tenant assumption the audit exists to catch. */
.chip {
  display: inline-flex;
  align-items: center;
  gap: var(--s-3);
  max-width: 100%;
  padding: var(--s-2) var(--s-5);
  border: var(--bw) solid var(--c-border);
  border-radius: var(--r-full);
  background: var(--c-surface-2);
  color: var(--c-text-2);
  font-size: var(--t-xs);
  font-weight: var(--t-semibold);
  line-height: 1.6;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.chip[data-tone="good"] {
  background: var(--c-success-quiet);
  border-color: color-mix(in srgb, var(--c-success) 34%, transparent);
  color: color-mix(in srgb, var(--c-success) 80%, var(--c-text));
}
.chip[data-tone="warn"] {
  background: var(--c-warn-quiet);
  border-color: color-mix(in srgb, var(--c-warn) 34%, transparent);
  color: color-mix(in srgb, var(--c-warn) 80%, var(--c-text));
}
.chip[data-tone="bad"] {
  background: var(--c-danger-quiet);
  border-color: color-mix(in srgb, var(--c-danger) 34%, transparent);
  color: color-mix(in srgb, var(--c-danger) 80%, var(--c-text));
}
.chip[data-tone="accent"] {
  background: var(--c-accent-quiet);
  border-color: color-mix(in srgb, var(--c-accent) 40%, transparent);
  color: color-mix(in srgb, var(--c-accent) 82%, var(--c-text));
}
/* "Not evaluated" is not a bad result, it is the absence of one, and it must
   never be dressed as a failure. A dashed outline with no fill says "there is
   nothing here yet" in a way a grey pill does not. */
.chip[data-tone="none"] {
  background: transparent;
  border-style: dashed;
  border-color: var(--c-border-strong);
  color: var(--c-text-3);
  font-weight: var(--t-medium);
}
.chip-dot { width: 6px; height: 6px; border-radius: var(--r-full); background: currentColor; flex: none; }
.chip-sq {
  border-radius: var(--r-xs);
  font-family: var(--t-mono);
  font-size: var(--t-2xs);
  letter-spacing: 0.02em;
}

/* ============================================================================
   METER
   A bar is the only honest way to show "how much of what was possible".
   The track is always drawn full width so the empty part is visible: a fill
   with no track is a number pretending to be a picture.
   ========================================================================= */

.meter {
  position: relative;
  height: 6px;
  border-radius: var(--r-full);
  background: var(--c-surface-3);
  overflow: hidden;
}
.meter-fill {
  position: absolute;
  inset: 0 auto 0 0;
  border-radius: var(--r-full);
  background: var(--c-accent);
  transition: width var(--m-slow) var(--m-out);
}
.meter[data-tone="good"] .meter-fill { background: var(--c-success); }
.meter[data-tone="warn"] .meter-fill { background: var(--c-warn); }
.meter[data-tone="bad"]  .meter-fill { background: var(--c-danger); }
.meter-sm { height: 4px; }

/* A component that can go negative gets a zero line and fills outward from it,
   because a task-completion range of -5 to +5 rendered as a 0-to-100 bar tells
   somebody who scored -2 that they scored 30 percent. */
.meter-signed { background: var(--c-surface-3); }
.meter-zero {
  position: absolute;
  top: 0;
  bottom: 0;
  width: var(--bw);
  background: var(--c-border-strong);
  z-index: 1;
}

/* ============================================================================
   AVATAR
   ========================================================================= */

.avatar {
  flex: none;
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  border-radius: var(--r-full);
  background: var(--c-surface-3);
  color: var(--c-text-2);
  font-size: var(--t-xs);
  font-weight: var(--t-semibold);
  line-height: 1;
  overflow: hidden;
  user-select: none;
}
.avatar-sm { width: 24px; height: 24px; font-size: var(--t-2xs); }
.avatar-lg { width: 46px; height: 46px; font-size: var(--t-md); }
.avatar-xl { width: 68px; height: 68px; font-size: var(--t-xl); border-radius: var(--r-full); }
.avatar img { width: 100%; height: 100%; object-fit: cover; }

.person {
  display: flex;
  align-items: center;
  gap: var(--s-5);
  min-width: 0;
}
.person-text { min-width: 0; }
.person-name {
  /* Block, because these are used as both <div> and <span> depending on the
     screen, and an inline pair runs "Aiko" and "Associate" together into
     "AikoAssociate" the first time somebody reaches for a span. */
  display: block;
  font-weight: var(--t-semibold);
  color: var(--c-text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.person-sub {
  display: block;
  color: var(--c-text-2);
  font-size: var(--t-sm);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
a.person:hover { text-decoration: none; }
a.person:hover .person-name { text-decoration: underline; }

/* ============================================================================
   TABLE
   Ruled, not striped. Stripes are a crutch for rows that are too tall and too
   loosely aligned; a hairline under each row plus tabular figures does the same
   job and survives the editorial look, which has no fills at all.
   ========================================================================= */

.table { width: 100%; font-size: var(--t-base); }
.table th {
  padding: var(--s-4) var(--s-5);
  border-bottom: var(--bw-strong) solid var(--c-border-strong);
  color: var(--c-text-3);
  font-size: var(--t-2xs);
  font-weight: var(--t-semibold);
  letter-spacing: var(--t-track-caps);
  text-transform: uppercase;
  text-align: left;
  white-space: nowrap;
  vertical-align: bottom;
}
.table td {
  padding: var(--s-5);
  border-bottom: var(--bw) solid var(--c-border-subtle);
  vertical-align: middle;
  color: var(--c-text);
}
.table tbody tr:last-child td { border-bottom: 0; }
.table tbody tr:hover td { background: var(--c-hover); }
.table .num-cell { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.table caption {
  caption-side: top;
  padding: 0 var(--s-5) var(--s-5);
  color: var(--c-text-2);
  font-size: var(--t-sm);
  text-align: left;
}

/* ============================================================================
   TOASTS
   ========================================================================= */

.toasts {
  position: fixed;
  right: calc(var(--s-6) + var(--safe-r));
  bottom: calc(var(--s-6) + var(--safe-b));
  z-index: var(--z-toast);
  display: flex;
  flex-direction: column;
  gap: var(--s-4);
  align-items: flex-end;
  max-width: min(420px, calc(100vw - var(--s-9)));
  pointer-events: none;
}
.toast {
  display: flex;
  align-items: flex-start;
  gap: var(--s-5);
  max-width: 100%;
  padding: var(--s-5) var(--s-6);
  border: var(--bw) solid var(--c-border);
  border-radius: var(--r-lg);
  background: var(--c-overlay);
  color: var(--c-text);
  font-size: var(--t-sm);
  line-height: var(--t-snug);
  box-shadow: var(--e-3);
  pointer-events: auto;
  animation: toast-in var(--m-slow) var(--m-out);
}
.toast[data-leaving="1"] { animation: toast-out var(--m-base) var(--m-ease) forwards; }
.toast-mark { flex: none; width: 16px; height: 16px; margin-top: 1px; }
.toast-text { min-width: 0; }
.toast[data-tone="success"] .toast-mark { color: var(--c-success); }
.toast[data-tone="error"]   .toast-mark { color: var(--c-danger); }
.toast[data-tone="info"]    .toast-mark { color: var(--c-accent); }
@keyframes toast-in  { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }
@keyframes toast-out { to { opacity: 0; transform: translateY(6px); } }

/* ============================================================================
   MODAL
   Centred card on a desktop, bottom sheet on a phone, because a sheet is what a
   phone expects and it puts the actions under the thumb.
   ========================================================================= */

.modal-back {
  position: fixed;
  inset: 0;
  z-index: var(--z-modal);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--s-6);
  background: var(--c-scrim);
  animation: fade-in var(--m-base) var(--m-ease);
}
.modal {
  display: flex;
  flex-direction: column;
  width: min(520px, 100%);
  max-height: calc(100% - var(--s-8));
  min-height: 0;
  border: var(--bw) solid var(--c-border);
  border-radius: var(--r-2xl);
  background: var(--c-overlay);
  box-shadow: var(--e-4);
  overflow: hidden;
  animation: pop-in var(--m-slow) var(--m-out);
}
.modal-wide { width: min(760px, 100%); }
.modal-head {
  display: flex;
  align-items: center;
  gap: var(--s-5);
  flex: none;
  padding: var(--s-6) var(--s-7);
  border-bottom: var(--bw) solid var(--c-border);
}
.modal-title {
  font-size: var(--t-md);
  font-weight: var(--t-semibold);
  letter-spacing: var(--t-track-display);
  min-width: 0;
}
.modal-body { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: var(--s-7); }
.modal-foot {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--s-4);
  flex: none;
  flex-wrap: wrap;
  padding: var(--s-6) var(--s-7);
  border-top: var(--bw) solid var(--c-border);
  background: var(--c-surface);
}
@media (max-width: 600px) {
  .modal-back { align-items: flex-end; padding: 0; }
  .modal {
    width: 100%;
    max-height: 88%;
    border-radius: var(--r-2xl) var(--r-2xl) 0 0;
    border-bottom: 0;
    padding-bottom: var(--safe-b);
    animation: sheet-in var(--m-slow) var(--m-out);
  }
}
@keyframes sheet-in { from { transform: translateY(16px); opacity: 0; } to { transform: none; opacity: 1; } }

/* ============================================================================
   POPOVER (theme picker, menus)
   ========================================================================= */

.popover {
  position: fixed;
  z-index: var(--z-popover);
  min-width: 200px;
  max-width: min(320px, calc(100vw - var(--s-8)));
  padding: var(--s-4);
  border: var(--bw) solid var(--c-border);
  border-radius: var(--r-lg);
  background: var(--c-overlay);
  box-shadow: var(--e-3);
  animation: pop-in var(--m-base) var(--m-out);
}
.menu-item {
  display: flex;
  align-items: center;
  gap: var(--s-5);
  width: 100%;
  min-height: 34px;
  padding: var(--s-3) var(--s-5);
  border: 0;
  border-radius: var(--r-sm);
  background: transparent;
  color: var(--c-text);
  font: inherit;
  font-size: var(--t-base);
  font-weight: var(--t-normal);
  text-align: left;
  cursor: pointer;
  box-shadow: none;
}
.menu-item:hover { background: var(--c-surface-2); }
.menu-item[aria-checked="true"] { font-weight: var(--t-semibold); }
.menu-tick { margin-left: auto; flex: none; width: 15px; height: 15px; color: var(--c-accent); }
.menu-item[aria-checked="false"] .menu-tick { visibility: hidden; }
.menu-sep { height: var(--bw); margin: var(--s-4) var(--s-3); background: var(--c-border); border: 0; }
.menu-head {
  padding: var(--s-4) var(--s-5) var(--s-3);
  color: var(--c-text-3);
  font-size: var(--t-2xs);
  font-weight: var(--t-semibold);
  letter-spacing: var(--t-track-caps);
  text-transform: uppercase;
}

/* The look picker shows each look as a swatch of its own four key surfaces
   rather than as a name, because "precision" and "editorial" mean nothing until
   you have seen them, and a two-word description does not survive translation
   into a colour. The swatches are painted from inline custom properties the JS
   reads back out of tokens.css, so this list can never drift from the looks
   that actually exist. */
.look-row { display: flex; align-items: center; gap: var(--s-5); }
.look-swatch {
  flex: none;
  display: flex;
  width: 44px;
  height: 22px;
  border-radius: var(--r-xs);
  border: var(--bw) solid var(--c-border-strong);
  overflow: hidden;
}
.look-swatch i { flex: 1 1 0; min-width: 0; }

/* ============================================================================
   EMPTY AND LOADING STATES

   Both are designed, not left over. An empty dashboard is the FIRST thing a new
   organisation sees, so it has to look like a screen that is ready rather than
   a screen that is broken, and it has to say what will fill it.
   ========================================================================= */

.empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--s-4);
  padding: var(--s-11) var(--s-7);
  text-align: center;
  color: var(--c-text-2);
  font-size: var(--t-sm);
  line-height: var(--t-loose);
}
.empty-mark {
  width: 40px;
  height: 40px;
  margin-bottom: var(--s-2);
  color: var(--c-text-3);
  opacity: 0.7;
}
.empty-title {
  color: var(--c-text);
  font-family: var(--t-display);
  font-size: var(--t-md);
  font-weight: var(--t-semibold);
  letter-spacing: var(--t-track-display);
}
.empty-body { max-width: 42ch; }
.empty-actions { margin-top: var(--s-4); display: flex; gap: var(--s-4); flex-wrap: wrap; justify-content: center; }
.empty-inline { padding: var(--s-8) var(--s-6); }

/* A skeleton is shaped like the thing it is standing in for. A generic grey
   block tells somebody the page is loading; a block the size and position of
   the score card tells them what is about to arrive, and the swap is invisible
   instead of being a jump. */
.skeleton {
  background: var(--c-skeleton);
  background-size: 200% 100%;
  animation: shimmer 1.3s linear infinite;
  border-radius: var(--r-sm);
  color: transparent !important;
  user-select: none;
}
.sk-line { height: 11px; margin-bottom: var(--s-4); }
.sk-line:last-child { margin-bottom: 0; }
.sk-w-40 { width: 40%; }
.sk-w-60 { width: 60%; }
.sk-w-80 { width: 80%; }
.sk-title { height: 18px; width: 38%; margin-bottom: var(--s-6); }
.sk-block { height: 84px; border-radius: var(--r-lg); }
@keyframes shimmer { from { background-position: 200% 0; } to { background-position: -200% 0; } }

.spinner {
  width: 15px; height: 15px;
  flex: none;
  border: 2px solid color-mix(in srgb, currentColor 30%, transparent);
  border-top-color: currentColor;
  border-radius: var(--r-full);
  animation: spin 0.6s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* ============================================================================
   AS-OF STAMP

   Every ext_* row in the schema carries an as_of, and the contract is that
   every screen rendering one shows it. That is not politeness: a task list
   projected from another system is a photograph, not a mirror, and somebody
   deciding whether to chase a person needs to know whether they are looking at
   two minutes ago or two days ago. The component exists so that showing it is
   the path of least resistance.
   ========================================================================= */

.asof {
  display: inline-flex;
  align-items: center;
  gap: var(--s-3);
  color: var(--c-text-3);
  font-size: var(--t-2xs);
  letter-spacing: var(--t-track-normal);
  white-space: nowrap;
}
.asof svg { width: 12px; height: 12px; flex: none; }
.asof[data-stale="1"] { color: var(--c-warn); }

/* ============================================================================
   NOTE / CALLOUT
   ========================================================================= */

.note {
  display: flex;
  gap: var(--s-5);
  padding: var(--s-5) var(--s-6);
  border: var(--bw) solid var(--c-border);
  border-left: 3px solid var(--c-border-strong);
  border-radius: var(--r-md);
  background: var(--c-surface);
  color: var(--c-text-2);
  font-size: var(--t-sm);
  line-height: var(--t-body);
}
.note[data-tone="accent"] { border-left-color: var(--c-accent); background: var(--c-accent-quiet); }
.note[data-tone="warn"]   { border-left-color: var(--c-warn); background: var(--c-warn-quiet); }
.note[data-tone="bad"]    { border-left-color: var(--c-danger); background: var(--c-danger-quiet); }
.note svg { flex: none; width: 16px; height: 16px; margin-top: 2px; }
.note strong { color: var(--c-text); }

/* ============================================================================
   TABS
   ========================================================================= */

.tabs {
  display: flex;
  gap: var(--s-2);
  border-bottom: var(--bw) solid var(--c-border);
  overflow-x: auto;
  overflow-y: hidden;
  scrollbar-width: none;
  min-width: 0;
}
.tabs::-webkit-scrollbar { display: none; }
.tab {
  position: relative;
  min-height: 36px;
  padding: var(--s-4) var(--s-6);
  border: 0;
  border-radius: 0;
  background: transparent;
  color: var(--c-text-2);
  font-size: var(--t-base);
  font-weight: var(--t-medium);
  white-space: nowrap;
  box-shadow: none;
  flex: none;
}
.tab:hover { background: var(--c-hover); color: var(--c-text); }
.tab[aria-selected="true"] { color: var(--c-text); font-weight: var(--t-semibold); }
.tab[aria-selected="true"]::after {
  content: "";
  position: absolute;
  left: var(--s-5);
  right: var(--s-5);
  bottom: -1px;
  height: 2px;
  border-radius: var(--r-full);
  background: var(--c-accent);
}
