/* fraction-ds — dist/css/ds.css
   The component layer. Plain CSS, no preprocessor, one class per
   component, BEM-ish modifiers with `--`. Every size and colour is a
   token from tokens.css (loaded first); this file writes no colour
   literal of its own except the ONE marked `ds-lint: allow` line below.

   Carried over from Design Canvas' public/css/ds.css, class for class and
   value for value, with three changes:
     · the reset block moved up into base.css (it is not a component),
     · the three inline colour literals Canvas kept (menu shadow, modal
       scrim, kbd-on-primary) now point at the tokens named for them,
     · the two component <style> blocks that lived inline in Canvas'
       partials are absorbed here as real classes: .help__* (the ?
       shortcuts overlay) and .bar__more / .bar__menu (the … overflow).

   New markup reaches for these classes instead of utility soup. Anything
   this layer does not cover is either in shell.css (app frame) or
   prose.css (reading surface) — or it does not belong in the system yet. */

/* ── button ───────────────────────────────────────────────────────
   Similarity law: everything that acts like a button looks like this,
   and nothing that looks like this fails to act. An action with a key
   carries its .kbd inside the button, so the label and the key can never
   drift apart. */
.btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: var(--h-control);
  padding: 0 10px;
  border-radius: var(--r-1);
  font-size: var(--fs-ui);
  font-weight: 500;
  border: 1px solid var(--line-2);
  background: var(--bg);
  color: var(--ink);
  text-decoration: none;    /* an <a class="btn"> is still a button, not a link */
  cursor: pointer;
  transition: background-color 120ms, border-color 120ms, color 120ms;
}
.btn:hover { background: var(--bg-2); }
.btn:active { background: var(--ink-4); }
.btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.btn:disabled { opacity: .45; cursor: not-allowed; }

.btn--primary { background: var(--accent); color: var(--accent-ink); border-color: transparent; }
.btn--primary:hover { background: var(--accent); filter: brightness(0.86); }
.btn--ghost { border-color: transparent; background: transparent; }
.btn--ghost:hover { background: var(--bg-2); }
.btn--danger { color: var(--danger); border-color: var(--danger); }
.btn--danger:hover { background: var(--danger-soft); }
.btn--icon { width: 32px; height: 32px; padding: 0; justify-content: center; }
.btn--on { background: var(--accent-soft); color: var(--accent); border-color: var(--accent); }
.btn--sm { height: 26px; font-size: var(--fs-meta); padding: 0 8px; }
/* Small icon button: --sm comes after --icon, so without this its 8px side
   padding squeezed the 16px glyph inside a 32px box down to a sliver. */
.btn--icon.btn--sm { width: 26px; padding: 0; }

/* ── kbd ──────────────────────────────────────────────────────── */
.kbd {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 18px;
  height: 18px;
  padding: 0 5px;
  border-radius: 4px;
  font-family: var(--mono);
  font-size: var(--fs-meta);
  color: var(--ink-2);
  background: var(--bg-2);
  border: 1px solid var(--line-2);
  border-bottom-width: 2px;
}
.kbds { display: inline-flex; gap: 2px; }
.btn > .kbd, .btn > .kbds { margin-left: auto; }
/* A trailing key sits as far from the button's right edge as from its top
   and bottom: (height - 2px border - 18px key) / 2, so 6px, and 3px on
   --sm. Without this the side gap (10 / 8px) read as lopsided padding. */
.btn:has(> .kbd:last-child, > .kbds:last-child) { padding-right: 6px; }
.btn--sm:has(> .kbd:last-child, > .kbds:last-child) { padding-right: 3px; }
.btn--primary .kbd { background: var(--on-accent-soft); color: var(--accent-ink); border-color: transparent; }

/* ── form controls ────────────────────────────────────────────── */
.input, .select, .textarea {
  border-radius: var(--r-1);
  border: 1px solid var(--line-2);
  background: var(--bg);
  color: var(--ink);
  font-size: var(--fs-ui);
  font-family: var(--sans);
  width: 100%;
  transition: border-color 120ms, box-shadow 120ms;
}
.input:hover, .select:hover, .textarea:hover { border-color: var(--line-3); }
.input, .select { height: var(--h-control); padding: 0 10px; }
.textarea { min-height: 80px; padding: 8px 10px; }
.input::placeholder, .textarea::placeholder { color: var(--ink-3); }
.input:focus, .select:focus, .textarea:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}
.select {
  appearance: none;
  padding-right: 28px;
  /* the chevron is a whole url() token per theme (tokens.css --chevron) */
  background-image: var(--chevron);
  background-repeat: no-repeat;
  background-position: right 8px center;
}

/* Dropdown: a custom select for when a native <select> can't show what the
   choice needs (a key, an icon, a separator). It is a .menu on a <details>,
   the same contract as the template's menu controller, which closes it on
   an outside click, on Esc and after a pick — the product sets the summary's
   text and moves .menu__item--on. The trigger IS a .select, so the two are
   indistinguishable until opened.

     <details class="dropdown" data-controller="menu">
       <summary class="select">Object mode</summary>
       <div class="menu">
         <button class="menu__item menu__item--on" data-action="menu#close">Object mode<svg class="icon">…#check</svg></button>
         <button class="menu__item" data-action="menu#close">Edit mode</button>
       </div>
     </details> */
.dropdown { position: relative; display: block; width: 100%; }
.dropdown > summary {
  display: block;
  list-style: none;
  line-height: calc(var(--h-control) - 2px);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  cursor: pointer;
  user-select: none;
}
.dropdown > summary::-webkit-details-marker { display: none; }
.dropdown[open] > summary { border-color: var(--accent); }
.dropdown > .menu {
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  z-index: 120;
  min-width: 100%;
}
.dropdown__empty { color: var(--ink-3); }   /* summary text before anything is chosen */

.field { display: flex; flex-direction: column; gap: 6px; }
.field__label { font-size: var(--fs-meta); color: var(--ink-2); font-weight: 500; }
.field__hint { font-size: var(--fs-meta); color: var(--ink-3); }

/* ── check / switch ───────────────────────────────────────────── */
.check {
  appearance: none;
  width: 16px;
  height: 16px;
  flex: none;
  border-radius: 4px;
  border: 1px solid var(--line-2);
  background: var(--bg);
  cursor: pointer;
  display: inline-block;
  position: relative;
}
.check:checked {
  background: var(--accent);
  border-color: var(--accent);
  box-shadow: inset 0 0 0 1px var(--accent);
}
.check:checked::after {
  content: '';
  position: absolute;
  left: 4px;
  top: 1px;
  width: 5px;
  height: 9px;
  border: solid var(--accent-ink);
  border-width: 0 2px 2px 0;
  transform: rotate(45deg);
}
.check:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

.switch {
  appearance: none;
  width: 34px;
  height: 20px;
  border-radius: 999px;
  background: var(--ink-4);
  border: none;
  position: relative;
  cursor: pointer;
  flex: none;
  transition: background-color 120ms;
}
.switch::after {
  content: '';
  position: absolute;
  top: 2px;
  left: 2px;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--bg);
  transition: transform 120ms;
}
.switch:checked { background: var(--accent); }
.switch:checked::after { transform: translateX(14px); }
.switch:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/* ── chip ─────────────────────────────────────────────────────────
   A status chip carries its status as a 6px dot, never as a filled
   background: the surface stays neutral so a list of twenty chips does
   not turn into a rainbow. --warn joins --ok and --danger on the same
   terms. */
.chip {
  display: inline-flex;
  align-items: center;
  height: 22px;
  padding: 0 8px;
  border-radius: 999px;
  font-size: var(--fs-meta);
  background: var(--bg-2);
  color: var(--ink-2);
  border: 1px solid var(--line);
  gap: 6px;
}
.chip--on { background: var(--accent-soft); color: var(--accent); border-color: var(--accent); }
.chip--neutral::before, .chip--ok::before, .chip--warn::before,
.chip--danger::before, .chip--accent::before {
  content: '';
  width: 6px;
  height: 6px;
  border-radius: 50%;
  flex: none;
}
.chip--neutral::before { background: var(--ink-3); }
.chip--ok::before { background: var(--ok); }
.chip--warn::before { background: var(--warn); }
.chip--danger::before { background: var(--danger); }
.chip--accent::before { background: var(--accent); }
/* A chip whose tone is data — a user-chosen select option, stored as one of
   the five token names — carries a real .dot child instead of a --tone
   modifier, so script can swap the tone by changing one class. Same 6px as
   the ::before dot. An icon inside a chip (a link, a lock) drops to 12px so
   it sits on the 11px label instead of towering over it. */
.chip > .dot { width: 6px; height: 6px; flex: none; }
.chip > .icon { width: 12px; height: 12px; }

/* ── avatar ───────────────────────────────────────────────────────
   A person, as initials — there are no photos in the system. Neutral like
   a chip: --bg-2 disc, a --line-2 hairline, mono 11 initials (initials are
   derived, not written). 24px, or 20px (--sm) inside a chip or a card
   face. .avatars stacks several with a --bg outline ring (not a shadow)
   so the overlap reads. */
.avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background: var(--bg-2);
  border: 1px solid var(--line-2);
  color: var(--ink);
  font-family: var(--mono);
  font-size: var(--fs-meta);
  line-height: 1;
  letter-spacing: 0;
}
.avatar--sm { width: 20px; height: 20px; }
.chip > .avatar { margin-left: -5px; }
.avatars { display: inline-flex; align-items: center; }
.avatars > .avatar { outline: 2px solid var(--bg); }
.avatars > .avatar + .avatar { margin-left: -6px; }

/* ── dropzone ─────────────────────────────────────────────────────
   Where files can be dropped. A dashed --line-2 hairline at rest — the one
   dashed line in the system, so it only ever means "drop here" — the
   accent edge and wash while a file is over it. It is a <button> too:
   clicking it opens the file picker. */
.dropzone {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  width: 100%;
  min-height: 72px;
  padding: 12px;
  border: 1px dashed var(--line-2);
  border-radius: var(--r-2);
  background: none;
  color: var(--ink-3);
  font: inherit;
  font-size: var(--fs-ui);
  cursor: pointer;
  transition: border-color 120ms, color 120ms, background-color 120ms;
}
.dropzone:hover { border-color: var(--ink-3); color: var(--ink-2); }
.dropzone:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.dropzone--slim { min-height: 36px; }
.is-over > .dropzone, .dropzone.is-over { border-color: var(--accent); color: var(--accent); background: var(--accent-soft); }

/* ── icon ─────────────────────────────────────────────────────────
   Lucide, and only Lucide, referenced out of one sprite. Never an icon
   font, never a one-off inline path. */
.icon {
  width: 16px;
  height: 16px;
  flex: none;
  stroke: currentColor;
  fill: none;
  stroke-width: 1.75;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.icon--lg { width: 20px; height: 20px; }

/* ── panel ────────────────────────────────────────────────────────
   Common Region: grouped by a 1px hairline, never by a tint or a card
   shadow. Panels are square — radius belongs to controls, not regions. */
.panel {
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: 0;
  display: flex;
  flex-direction: column;
  min-height: 0;
}
.panel__head {
  height: 40px;
  flex: none;
  padding: 0 12px;
  font-size: var(--fs-ui);
  font-weight: 600;
  border-bottom: 1px solid var(--line);
  display: flex;
  align-items: center;
  gap: 8px;
}
.panel__body {
  padding: 12px;
  overflow: auto;
  flex: 1;
  min-height: 0;
}

/* ── row ──────────────────────────────────────────────────────── */
.row {
  min-height: 32px;
  padding: 0 8px;
  border-radius: var(--r-1);
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: var(--fs-ui);
  color: inherit;
  text-decoration: none;    /* rows are very often links */
  cursor: pointer;
}
.row:hover { background: var(--bg-2); }
.row--on { background: var(--accent-soft); color: var(--ink); }
.row__meta { font-family: var(--mono); font-size: var(--fs-meta); color: var(--ink-3); margin-left: auto; }

/* .row--stack: a list item that needs a second line (a "why", a sub-title)
   still reads as one row in a list, separated by a hairline from the next
   — never a card. align-items goes to flex-start because the leading dot
   or icon should sit at the cap-height of the title, not centred across
   two lines. */
.row--stack {
  min-height: 48px;
  height: auto;
  align-items: flex-start;
  padding: 10px 8px;
}
.row--stack > .dot, .row--stack > .icon { margin-top: 4px; flex: none; }
.row__body { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.row__title { font-size: var(--fs-body); color: var(--ink); }
.row__sub { font-size: var(--fs-ui); color: var(--ink-2); }
.row--stack .row__meta { margin-left: 0; flex: none; align-self: center; }

/* ── menu ─────────────────────────────────────────────────────── */
.menu {
  background: var(--bg);
  border: 1px solid var(--line-2);
  border-radius: var(--r-2);
  padding: 4px;
  min-width: 220px;
  /* the one allowed shadow — floating overlays only */
  box-shadow: var(--shadow-menu);
}
.menu__item {
  min-height: 32px;
  padding: 0 8px;
  border-radius: var(--r-1);
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: var(--fs-ui);
  cursor: pointer;
  width: 100%;
  text-align: left;
  background: none;
  border: none;
}
.menu__item:hover { background: var(--bg-2); }
.menu__item .kbd, .menu__item .kbds { margin-left: auto; }
/* The chosen row of a .dropdown: its trailing check is the one accent. */
.menu__item--on > .icon:last-child { margin-left: auto; color: var(--accent); }
/* A destructive row (Delete, Remove…): --danger text, the soft danger fill
   on hover — the same pair .btn--danger uses. Last in its menu, after a
   .menu__sep. */
.menu__item--danger { color: var(--danger); }
.menu__item--danger:hover { background: var(--danger-soft); }
.menu__sep { height: 1px; background: var(--line); margin: 4px 0; }
.menu__label { font-size: var(--fs-meta); color: var(--ink-3); padding: 6px 8px; }

/* ── bar (bottom action bar) ──────────────────────────────────────
   Fitts: 48px tall and glued to the bottom edge, so it cannot be
   overshot. Miller: at most seven items per zone; the rest fold behind
   the … overflow (.bar__more) rather than crowding the row. */
.bar {
  height: var(--h-bar);
  flex: none;
  background: var(--bg);
  border-top: 1px solid var(--line);
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  padding: 0 12px;
  gap: 8px;
}
.bar__status { display: flex; align-items: center; gap: 12px; font-size: var(--fs-meta); color: var(--ink-2); }
.bar__tools { display: flex; gap: 4px; justify-content: center; }
.bar__view { display: flex; gap: 4px; justify-content: flex-end; align-items: center; }
.bar__sep { width: 1px; height: 20px; background: var(--line-2); }

/* The … menu floats ABOVE the bar — it is the last row on screen, so a
   menu that opened downwards would open off it. */
.bar__more { position: relative; }
.bar__menu { position: absolute; bottom: calc(100% + 8px); right: 0; z-index: 200; }
.bar__tools, .bar__view { min-width: 0; }
/* Measured, not guessed. The bar is a three-track grid (1fr auto 1fr) and
   the middle track takes what it needs, so the right-hand group is the one
   that gets squeezed. Two rules keep them apart, in the order that loses
   the least:
     · the view group is icons + keys, always. Panel toggles are recognised
       by their icon and pressed by their number; the tooltip carries the name.
     · under 1500 the tool labels go too, because seven labelled tools plus
       the view group do not fit next to each other below that.
   The key never goes. It is the part that teaches. */
.bar__view .btn__label { display: none; }
@media (max-width: 1500px) { .bar .btn__label { display: none; } }
/* The bar is the last row on screen, so the shared tooltip's downward
   position would put it under the edge of the window. Flip it. */
.bar [data-tip]:hover::after { top: auto; bottom: 100%; margin: 0 0 6px; }

/* ── hdr (top header + tabs) ──────────────────────────────────── */
.hdr {
  height: var(--h-bar);
  flex: none;
  background: var(--bg);
  border-bottom: 1px solid var(--line);
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  padding: 0 12px;
}
.hdr__left { display: flex; align-items: center; gap: 8px; position: relative; min-width: 0; }
.hdr__doc { position: relative; margin-left: 20px; min-width: 0; }
.hdr__doc > .btn { max-width: 100%; white-space: nowrap; }
.hdr__doc > .btn > span:not(.dot) { overflow: hidden; text-overflow: ellipsis; }
.hdr__right { display: flex; justify-content: flex-end; align-items: center; gap: 4px; }

/* Brand lockup: the mark, the wordmark in DM Sans, then the product name in
   the UI sans. Fonts are vendored in ../fonts and declared in base.css. */
.brand { display: flex; align-items: center; gap: 10px; color: var(--ink); text-decoration: none; flex: none; }
.brand__logo { width: 28px; height: 28px; object-fit: contain; border-radius: 8px; }
.brand__name {
  font-family: var(--display);
  font-size: 19px; font-weight: 700; line-height: 1; letter-spacing: -.02em;
  white-space: nowrap;
}
.brand__product { font-size: 15px; font-weight: 500; color: var(--ink-2); white-space: nowrap; }

/* Segmented: the tabs sit in one rounded container, the current one raised
   inside it. No underline — the fill is the state. */
.tabs {
  display: flex;
  align-items: center;
  align-self: center;
  gap: 2px;
  padding: 3px;
  background: var(--bg-2);
  border: 1px solid var(--line);
  border-radius: var(--r-2);
}
.tab {
  font: inherit;               /* a .tab can be a <button>: no native font */
  background: transparent;     /* …or native fill; .tab--on sets the real one */
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 6px;
  height: 26px;
  padding: 0 14px;
  border-radius: var(--r-1);
  font-size: var(--fs-ui);
  font-weight: 500;
  color: var(--ink-2);
  text-decoration: none;
  border: 1px solid transparent;
  transition: background-color 120ms, color 120ms;
}
.tab:hover { color: var(--ink); }
/* The current tab is a --board fill with no hairline: lifted off pure black,
   still darker than the --bg-2 container, so the fill alone is the state. */
.tab--on { color: var(--ink); background: var(--board); box-shadow: inset 0 0 0 1px var(--tab-on-edge); }
/* Small: the compact segment for a rail or a header, an icon beside the
   label (a segmented control that sets state is <button class="tab">). */
.tab .icon { width: 14px; height: 14px; }
.tabs--sm { padding: 2px; }
.tabs--sm .tab { height: 22px; padding: 0 8px; gap: 5px; font-size: var(--fs-meta); }

/* Theme switch: Dark / Light / Auto (fraction-ds 0.15.0, js/theme.js).
   Which segment is on is read straight off <html data-theme> — no pin is
   Auto — so it is right on first paint, before any script runs. Same fill
   as .tab--on.
   Icons only (0.16.0): moon / sun / monitor are clear enough, and three
   words made the switch wider than anything else in a header. Each button
   carries its name as aria-label and data-tip, so it is still named for a
   screen reader and on hover. */
.theme-switch .tab { width: 22px; padding: 0; justify-content: center; }
:root[data-theme="dark"] .theme-switch .tab[data-fr-theme="dark"],
:root[data-theme="light"] .theme-switch .tab[data-fr-theme="light"],
:root:not([data-theme]) .theme-switch .tab[data-fr-theme="auto"] { color: var(--ink); background: var(--board); box-shadow: inset 0 0 0 1px var(--tab-on-edge); }

/* ── modal / toast ────────────────────────────────────────────── */
.modal-scrim {
  position: fixed;
  inset: 0;
  background: var(--scrim);
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: center;
}
.modal {
  background: var(--bg);
  border-radius: var(--r-3);
  border: 1px solid var(--line-2);
  padding: 20px;
  width: min(480px, 90vw);
  max-height: 85vh;
  overflow: auto;
}
.modal__title { font-size: var(--fs-body); font-weight: 600; margin: 0 0 8px; }
.modal__actions { display: flex; gap: 8px; justify-content: flex-end; margin-top: 16px; }

/* ── danger panel ─────────────────────────────────────────────── an
   inline "are you sure" that opens beside the control that triggers it,
   not a .modal over the whole window — for a delete that stays in
   context (a row in a list, a field in a drawer). --danger border for
   the live confirm ("this is the point of no return"); --danger-panel--
   static is the quieter twin beside it that only explains and opens the
   confirm, same shape, a hairline instead of the danger colour. */
.danger-panel {
  background: var(--bg);
  border: 1px solid var(--danger);
  border-radius: var(--r-2);
  padding: 20px;
  max-width: 460px;
}
.danger-panel--static { border-color: var(--line-2); }
.danger-panel__label { color: var(--danger); }
.danger-panel--static .danger-panel__label { color: var(--ink-2); }
.danger-panel p { margin: 8px 0 0; font-size: var(--fs-ui); color: var(--ink-2); }

.toast {
  position: fixed;
  bottom: 60px;
  left: 50%;
  transform: translateX(-50%);
  background: var(--ink);
  color: var(--bg);
  font-size: var(--fs-ui);
  padding: 8px 12px;
  border-radius: var(--r-2);
  z-index: 110;
}

/* ── help overlay (the ? shortcut sheet) ──────────────────────────
   Absorbed from Canvas' _help_overlay.html.erb inline <style>. Sits
   inside a .modal-scrim > .modal, so it inherits the overlay chrome and
   only lays its own two columns out. */
.help__cols { columns: 2; column-gap: 28px; }
.help__group { break-inside: avoid; margin: 0 0 16px; }
.help__group-name {
  font-size: var(--fs-meta); font-weight: 600; text-transform: uppercase;
  letter-spacing: .08em; color: var(--ink-3); margin: 0 0 6px;
}
.help__row {
  display: flex; align-items: flex-start; gap: 12px;
  min-height: 26px; font-size: var(--fs-ui); padding: 3px 0;
}
.help__row .kbds { margin-left: auto; flex: none; padding-top: 1px; }
.help__row-text { min-width: 0; }
.help__row-desc { font-size: var(--fs-meta); color: var(--ink-3); margin-top: 1px; }
.help__head { display: flex; align-items: center; gap: 12px; margin: 0 0 16px; }
.help__foot {
  display: flex; align-items: center; margin: 4px 0 0;
  padding-top: 12px; border-top: 1px solid var(--line);
}
.help__link { font-size: var(--fs-meta); color: var(--ink-2); text-decoration: none; }
.help__link:hover { color: var(--accent); }

/* ── empty state ──────────────────────────────────────────────── */
.empty { text-align: center; color: var(--ink-3); font-size: var(--fs-ui); padding: 32px 16px; }
.empty__kbd { margin-top: 8px; font-size: var(--fs-meta); }

/* ── tooltip ──────────────────────────────────────────────────── */
.tip, [data-tip] { position: relative; }
[data-tip]:hover::after {
  content: attr(data-tip);
  position: absolute;
  top: 100%;
  left: 50%;
  transform: translateX(-50%);
  margin-top: 6px;
  background: var(--ink);
  color: var(--bg);
  font-size: var(--fs-meta);
  border-radius: 4px;
  padding: 4px 8px;
  /* Short tooltips (most of them) stay one line; a "label — explanation" one
     wraps instead of running off the edge of the window or the bar. */
  white-space: pre-wrap;
  max-width: 240px;
  width: max-content;
  text-align: center;
  z-index: 120;
  opacity: 0;
  animation: ds-tip-in 120ms 300ms forwards;
  pointer-events: none;
}
@keyframes ds-tip-in { to { opacity: 1; } }

/* ── dot ──────────────────────────────────────────────────────── */
.dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; background: var(--ink-3); }
.dot--ok { background: var(--ok); }
.dot--warn { background: var(--warn); }
.dot--danger { background: var(--danger); }
.dot--accent { background: var(--accent); }
/* Hollow: "should have a status and has none" (a task in progress with
   nobody on it). A 1.5px --line-2 ring, not a fill, so it reads as absence
   rather than as a fourth status colour. */
.dot--hollow { background: transparent; border: 1.5px solid var(--line-2); }

/* ── utilities ────────────────────────────────────────────────── */
.mono { font-family: var(--mono); }
.meta { font-size: var(--fs-meta); color: var(--ink-2); }
.hairline { border-color: var(--line); }
.hide-scrollbar::-webkit-scrollbar { display: none; }
.hide-scrollbar { scrollbar-width: none; }
.visually-hidden {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
