/* ── The front door's page ──────────────────────────────────────────────────
   `~/repos/volo-grid/volo-sys-doc/design-system/volo-design-system.md`: §2 colour (every value
   here is `var(--token)`, and `tokens.css` is the only file that writes one),
   §3 type (10-14px, two weights, mono for data and sans for chrome), §4 spacing
   in multiples of 4 and radius at most 8 except a pill, §5 the shell, §8 what
   animates and what is instant, §9 the state system, §11's do's and don'ts.

   Two things this page needed that the system does not define are **proposed
   there** rather than invented here — §15, registered as DEC-018 in
   `volo-sys-doc/conductor/decisions.md`:

     1. a 44x44px minimum hit area on a coarse pointer, because Kit opens this
        from a tablet and §5 fixes the topbar at 38px, which cannot hold one.
        Reached by padding; the 10-14px type scale is untouched.
     2. an interactive card rests on `--bg-subtle` and fills `--bg-hover` on
        hover. §6 gives a card `--bg-hover` at rest, which leaves an interactive
        one with no hover state at all.
   ────────────────────────────────────────────────────────────────────────── */

* { box-sizing: border-box; }

html, body { height: 100%; }

body {
  margin: 0;
  background: var(--bg-base);
  color: var(--text-primary);
  font-family: var(--font-sans);
  font-size: 12px;
}

/* `hidden` has to beat the display rules below, which is why it is stated once
   here rather than guarded at every element that can be hidden. */
[hidden] { display: none !important; }

/* ── The shell (§5, §14.1) ───────────────────────────────────────────────────
   Viewport height, a column, and the error bar is the last child of it: the
   content area scrolls, the page does not, and the bar is always in view. */

.shell {
  display: flex;
  flex-direction: column;
  height: 100dvh;
}

.topbar {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-shrink: 0;
  height: 38px;
  padding: 0 12px;
  background: var(--bg-subtle);
  border-bottom: 1px solid var(--border);
}

.topbar-title {
  font-size: 13px;
  font-weight: 600;
  color: var(--text-primary);
}

.topbar-spacer { flex: 1; }

/* The signed-in account's display name, beside Sign out (0.26.0). */
.topbar-account {
  max-width: 16rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 12px;
  color: var(--text-secondary);
}

/* The tab-mode strip (app-hint.js): one quiet row under the topbar. Hidden by the
   attribute, so `display: flex` must not defeat it. */
.app-hint {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px 12px;
  flex-shrink: 0;
  padding: 6px 12px;
  background: var(--bg-active);
  border-bottom: 1px solid var(--border);
  font-size: 12px;
  color: var(--text-secondary);
}

.app-hint[hidden] { display: none; }

.app-hint-title {
  font-weight: 600;
  color: var(--text-primary);
}

.app-hint-text { flex: 1; min-width: 12rem; }

.content {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: 12px 16px 16px;
}

/* ── The fatal statement (§14.8) ─────────────────────────────────────────────
   A fatal condition is stated where the operator is looking, in place, with the
   action beside it — and recorded in the bar as well, never in the bar alone. */

.fatal {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 0 0 12px;
  padding: 12px;
  background: var(--status-error-bg);
  border: 1px solid var(--status-error);
  border-radius: 6px;
  color: var(--status-error-text);
}

.fatal-text {
  margin: 0;
  font-size: 12px;
}

/* ── The starting window (0.13.0) ────────────────────────────────────────────
   `/_starting/<id>`: what is in the window a tool's tile opens, while the front
   door starts the tool. One block of text, centred in the content, because the
   window has one job and nothing else is in it yet. Every value below is a token
   or a number already on the design system's scales — there is no second visual
   language here, this is the same application in a smaller window. */

/* The busy cursor, over the whole window for the whole wait (0.15.0). `starting.html`
   puts `waiting` on <html> so it holds from the first paint, and `starting.js`
   takes it off only when it stops waiting — a failure said in place. On success the
   window navigates away, so the class goes with the document. Every descendant is
   named because links and text carry a cursor of their own. */
.waiting,
.waiting * { cursor: progress; }

.starting {
  display: flex;
  flex-direction: column;
  gap: 8px;
  align-items: flex-start;
  max-width: 420px;
}

.starting-text {
  margin: 0;
  font-size: 12px;
}

/* The component's own words, never dressed as the page's prose: attributed above
   and mono below, the same shape the error bar gives the same field (DEC-010). */
.starting-said-by {
  margin: 0;
  font-size: 10px;
  color: var(--text-tertiary);
}

.starting-said {
  margin: 0;
  padding: 8px;
  background: var(--bg-subtle);
  border: 1px solid var(--border);
  border-radius: 5px;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--text-secondary);
  overflow-wrap: anywhere;
}

.starting-back {
  margin: 0;
  font-size: 12px;
}

.starting-back a { color: var(--accent-active); }

/* ── The grid ────────────────────────────────────────────────────────────────
   A list, so the page is navigable without a pointer, and an auto-filling grid,
   so it holds from a phone's width to a desktop's without a breakpoint. */

.grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  /* Every row as tall as the tallest tile in the grid, so all tiles are one height. */
  grid-auto-rows: 1fr;
  gap: 12px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.empty {
  margin: 0;
  font-size: 12px;
  color: var(--text-tertiary);
}

/* The card is the list item, and the region that opens the component is inside
   it. It was one element until 0.7.0; a `<button>` inside an `<a>` is invalid and
   is not operable from a keyboard, so the anchor shrank to the part of the tile
   it actually opens and the controls became a sibling beneath it. */
.tile {
  display: flex;
  flex-direction: column;
  gap: 6px;
  height: 100%;
  padding: 12px;
  background: var(--bg-subtle);
  border: 1px solid var(--border);
  border-radius: 6px;
  color: var(--text-primary);
}

/* The head, the sentence and the path: a row in §6's sense, which is why it fills
   on hover at the 5px radius §4 gives a row rather than repainting the card. */
.tile-open {
  display: flex;
  flex-direction: column;
  gap: 6px;
  flex: 1;
  min-height: 0;
  margin: -4px;
  padding: 4px;
  border-radius: 5px;
  color: inherit;
  text-decoration: none;
}

/* A tile is a link only when the front door has an address behind its path. A
   tile that is not a link must not look like one: no pointer, no hover fill, no
   accent on the name, no mark, and no path — the path is shown exactly where it
   resolves. Nothing below applies to a plain `.tile-open`. */
.tile-open-link { cursor: pointer; }

/* §8: row hover is instant, and there is no transition on it. */
.tile-open-link:hover { background: var(--bg-hover); }

.tile-open-link:focus-visible {
  outline: 2px solid var(--accent-focus);
  outline-offset: -2px;
}

/* A tool's tile with nothing behind its path yet is a <button>, because pressing
   it starts the tool (Kit, 2026-09-27 14:52) and a div with a click handler is
   not operable from a keyboard. A button carries a UA font, border, background
   and centred text that a div does not, so those are taken back off: what is
   wanted is the card, behaving like the link beside it. */
.tile-open-start {
  width: 100%;
  border: 0;
  background: none;
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.tile-open-start:hover { background: var(--bg-hover); }

.tile-open-start:focus-visible {
  outline: 2px solid var(--accent-focus);
  outline-offset: -2px;
}

/* A tile being started stops being pressable, and says so in the cursor — the
   same `aria-busy` the controls set, which is the one place it is written. */
.tile[aria-busy='true'] .tile-open-start { cursor: progress; }

/* Said, not drawn: the new-tab fact reaches a reader who cannot see the mark.
   Off-screen rather than `display: none`, which would take it out of the
   accessibility tree along with the pixels. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.tile-head {
  display: flex;
  align-items: center;
  gap: 12px;
}

.tile-glyph {
  display: inline-flex;
  flex-shrink: 0;
  color: var(--text-secondary);
}

/* The tile's mark: Kit's artwork used as a mask over the tile's own text colour,
   so it follows the tile as the old stroked SVG did and carries no colour of its
   own. The files keep their shape in the alpha channel — white ink, transparent
   elsewhere — and no mask mode is declared, so the default reads it. Luminance
   masking was tried first and dropped: the browser this front door is used in
   does not honour it, falls back to alpha, and paints a solid square from a file
   that has none. `icons.js` names the light and dark files; which shows is the
   theme attribute's to say, here, and no script is involved. */
.tile-icon {
  display: inline-block;
  width: 48px;
  height: 48px;
  background-color: currentColor;
  -webkit-mask-image: var(--icon-light);
  mask-image: var(--icon-light);
  -webkit-mask-size: contain;
  mask-size: contain;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-position: center;
  mask-position: center;
}

[data-theme="dark"] .tile-icon {
  -webkit-mask-image: var(--icon-dark);
  mask-image: var(--icon-dark);
}

/* The mark for an id with no artwork, at the size of the artwork so a tile keeps
   one header whichever it has. */
.tile-icon-fallback {
  width: 48px;
  height: 48px;
}

.tile-name {
  flex: 1;
  color: var(--tile-title);
  font-size: 14px;
  font-weight: 600;
  min-width: 0;
  overflow-wrap: anywhere;
}

/* The state, as a light (0.15.0): a small round lamp that replaced the badge. Lit
   in the running/success green when the component is running; otherwise a neutral
   grey disc with a visible edge, which has to read as an unlit lamp and not as a
   hole in both themes. A tile with no page and nothing to run has none. Its words
   are its accessible name and the first line of the bubble. */
.tile-light {
  flex-shrink: 0;
  width: 12px;
  height: 12px;
  border: 1px solid var(--text-tertiary);
  border-radius: 50%;
  background: var(--status-idle);
}

[data-theme="dark"] .tile-light-off { background: var(--bg-base); }

.tile-light-on {
  border-color: var(--status-success-text);
  background: var(--status-success);
}

/* The bubble (0.15.0): what the card no longer prints, shown on hover and on
   keyboard focus of its tile. `tile-bubble.js` places it, fixed, so the scrolling
   panel cannot clip it. Surface, edge and text are the theme's own tokens; no
   transition, because §8 keeps hover instant; and it takes no pointer, so it never
   covers a neighbour's hover. */
.tile-bubble {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 10;
  width: max-content;
  max-width: min(320px, calc(100vw - 16px));
  max-height: calc(100dvh - 16px);
  overflow-y: auto;
  padding: 8px 10px;
  background: var(--bg-base);
  border: 1px solid var(--text-tertiary);
  border-radius: 6px;
  pointer-events: none;
}

.tile-bubble p { margin: 0; }
.tile-bubble p + p { margin-top: 4px; }

.tile-bubble-state {
  font-size: 11px;
  font-weight: 600;
  color: var(--text-primary);
}

.tile-bubble-line {
  font-size: 12px;
  color: var(--text-secondary);
}

.tile-open:focus-visible {
  outline: 2px solid var(--accent-focus);
  outline-offset: -2px;
}

.tile-foot {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: auto;
  padding-top: 4px;
}

/* A tile the signed-in account may not open (0.26.0): the front door's sentence, quiet. */
.tile-unavailable {
  font-size: 11px;
  color: var(--text-muted);
}

/* The mark saying this link opens a new tab. It replaced a chevron in 0.7.0: a
   chevron says "onward from here", and that is no longer what pressing one does. */
.tile-out {
  display: inline-flex;
  margin-left: auto;
  color: var(--text-tertiary);
}

.tile-open-link:hover .tile-out { color: var(--accent-active); }

/* ── The controls (Kit, 2026-09-26 11:53) ────────────────────────────────────
   A row of its own beneath the open region, ruled off from it, because pressing
   one of these does something to the machine and opening the component does not.
   One button in the ordinary case: the route offers Start only when the component
   is not running and Stop only when it is.

   Both are secondary buttons. §6 specifies a primary and a secondary button and
   no third, and `--action-danger` has no component specified for it — styling a
   Stop with it would be inventing a component rather than proposing one (§15,
   `rules/design-adherence.md`). If a stop should look weightier than an open,
   that is a `volo-sys-doc` proposal and not a colour chosen here. */
.tile-controls {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  margin-top: auto;
  padding-top: 8px;
  border-top: 1px solid var(--border-subtle);
}

/* ── Buttons (§6, §9) ────────────────────────────────────────────────────── */

.button {
  flex-shrink: 0;
  padding: 5px 10px;
  border: 1px solid var(--border);
  border-radius: 5px;
  background: transparent;
  font-family: var(--font-sans);
  font-size: 12px;
  color: var(--text-secondary);
  cursor: pointer;
}

.button:hover { background: var(--bg-hover); }

.button:focus-visible {
  outline: none;
  border-color: var(--accent-focus);
  box-shadow: 0 0 0 2px var(--accent-focus-ring);
}

.button:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

.button-primary {
  border-color: transparent;
  background: var(--action-primary);
  color: var(--text-inverse);
}

.button-primary:hover { background: var(--action-primary-hover); }

/* A ghost icon button (volo-dark-theme.md §4.2): no label, tooltip only, so its
   accessible name is the aria-label the document carries. */
.icon-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  padding: 4px 6px;
  border: none;
  border-radius: 5px;
  background: transparent;
  color: var(--text-tertiary);
  cursor: pointer;
}

.icon-button:hover {
  background: var(--bg-hover);
  color: var(--text-secondary);
}

.icon-button:focus-visible {
  outline: 2px solid var(--accent-focus);
  outline-offset: -2px;
}

/* ── Scrollbars (volo-dark-theme.md §3.2) ────────────────────────────────── */

* { scrollbar-color: var(--scrollbar-thumb) transparent; }

::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--scrollbar-thumb); border-radius: 5px; }
::-webkit-scrollbar-thumb:hover { background: var(--scrollbar-thumb-hover); }

/* ── Reach from a tablet (proposed §15) ──────────────────────────────────────
   Kit opens this from a tablet as well as a desktop. On a coarse pointer every
   control grows to a 44x44px hit area and the topbar grows to hold one; the
   type scale and the palette are untouched, and a desktop sees none of this. */

@media (pointer: coarse) {
  .topbar { height: 48px; }

  .button {
    min-height: 44px;
    padding: 0 14px;
  }

  .icon-button {
    min-height: 44px;
    min-width: 44px;
    padding: 0;
  }

  .grid { gap: 16px; }

  .tile { padding: 16px; }
}

/* ── Track 31: Restart's reminder -- a slow amber breath while a newer version is installed ── */
@keyframes restart-due-glow {
  0%, 100% { box-shadow: 0 0 0 0 transparent; border-color: var(--status-warning); }
  50% { box-shadow: 0 0 0 4px color-mix(in srgb, var(--status-warning) 45%, transparent);
        border-color: var(--status-warning); }
}

.button.button-due {
  border-color: var(--status-warning);
  animation: restart-due-glow 2.5s ease-in-out infinite;
}

@media (prefers-reduced-motion: reduce) {
  .button.button-due {
    animation: none;
    box-shadow: 0 0 0 2px var(--status-warning);
  }
}
