/*
 * The house palette. The accent is CodigoSH orange and it is the same in every
 * project — it is not mirrored or rotated per product.
 *
 * An operator can override --accent for their own instance; this is the
 * default, not a hardcoded brand. Everything tinted with it is written as
 * rgba(var(--accent-rgb), …) so that an instance colour reaches the tints too:
 * a token holding a resolved colour would keep the default orange forever.
 */
:root {
  --accent: #e8520e;
  --accent-rgb: 232, 82, 14;
  /* #e8520e on white is 3.6:1 — enough for large text, icons, borders and
     surfaces, but NOT for body text, which needs 4.5. Use --accent-alt there. */
  --accent-alt: #c2410c;

  /* The accent, at the three strengths the interface actually uses: a wash
     behind something chosen, a line around it, and a glow under something
     raised. Named once so that "selected" looks the same everywhere. */
  --accent-wash: rgba(var(--accent-rgb), 0.09);
  --accent-line: rgba(var(--accent-rgb), 0.38);
  --accent-glow: 0 12px 26px -14px rgba(var(--accent-rgb), 0.85);

  --canvas: #eef0f4;
  /* The bar floats over the page and blurs what passes under it, so it needs a
     translucent version of the page colour. color-mix would compute it, but a
     bar that turns transparent on a browser without it is not a trade worth
     making for one value. */
  --canvas-glass: rgba(238, 240, 244, 0.82);
  --panel: #ffffff;
  /* The sheet the panel's cards and menu are made of: white here, #242424 in the dark. */
  --sheet: #ffffff;
  /* What the sheet lies on: the canvas, a step down from the sheet so a card is a
     raised thing and not a veil over its own background. */
  --ground: var(--canvas);
  /* A row pointed at, in the accent: 6% here, 10% in the dark. */
  --surface-hover: rgba(var(--accent-rgb), 0.06);
  /* Something switched off but alive: 12% here, 20% in the dark. The track of
     a switch sits on it. */
  --surface-active: rgba(var(--accent-rgb), 0.12);
  --surface-dim: #eef0f4;
  /* The one card that asks for attention — an update is waiting. On light,
     a frosted white with a long shadow. On dark, no shadow at all,
     like every other card there: a dark shadow on a dark ground only draws
     rings. It stands out by a neutral lift, an accent border and the glow in
     its corner — no brown wash, which is what made it look off. */
  --feature-card-bg: rgba(255, 255, 255, 0.72);
  --feature-card-border: var(--border);
  --float-shadow: 0 20px 60px rgba(0, 0, 0, 0.4);
  --text-main: #0f172a;
  --text-dim: #64748b;
  --border: rgba(0, 0, 0, 0.05);
  --border-strong: #cdd4dd;
  --card-border: rgba(0, 0, 0, 0.07);
  --card-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.7), 0 2px 10px rgba(0, 0, 0, 0.06);

  /* State, as hues rather than as words. A board where everything is grey is a
     board nobody can scan; these are the four meanings a status can carry. */
  --hue-open: #2563eb;
  /* The one red in the product: a refusal, a deletion, and leaving. Named here
     so the three of them are the same red and so the dark theme can lighten it
     — #dc2626 on a dark surface is under 4.5:1 and this is text people read. */
  --danger-color: #dc2626;
  --danger-rgb: 220, 38, 38;
  --success-color: #16a34a;
  --success-rgb: 22, 163, 74;
  --warning-color: #ea580c;
  --warning-rgb: 234, 88, 12;
  --info-color: #0284c7;
  --info-rgb: 2, 132, 199;
  --hue-work: var(--accent);
  --hue-done: #15803d;
  --hue-hold: #b45309;
  --hue-off: #6b7280;

  --radius-sm: 8px;
  --radius: 12px;
  --radius-lg: 16px;
  --radius-xl: 22px;

  /* Three depths, and no more. Everything that sits on the page uses the first,
     everything that lifts on hover the second, and only what floats over the
     page — a dialog, a toast — the third. */
  --shadow-sm: 0 1px 2px rgba(16, 20, 28, 0.05), 0 1px 3px rgba(16, 20, 28, 0.04);
  --shadow-md: 0 6px 16px -8px rgba(16, 20, 28, 0.18), 0 2px 6px -3px rgba(16, 20, 28, 0.08);
  --shadow-lg: 0 26px 60px -28px rgba(16, 20, 28, 0.45);
  /* The lit top edge that separates a raised surface from a flat one. It is
     white in daylight and a whisper of white in the dark, which is why it is a
     token and not a literal. */
  --hairline: inset 0 1px 0 rgba(255, 255, 255, 0.7);
  --ring: 0 0 0 3px rgba(var(--accent-rgb), 0.3);

  --font: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --font-mono: ui-monospace, "SF Mono", Menlo, Consolas, monospace;

  /* One measure for a page of things and one for a page of prose. A board read
     at the width of a paragraph wastes half the window; a paragraph read at the
     width of a board is unreadable. */
  --page: 70rem;
  --prose: 46rem;

  --ease: cubic-bezier(0.2, 0.7, 0.3, 1);
  /* The second curve, for what should overshoot slightly on the way in: a
     card lifting under the pointer, a dialog arriving. */
  --ease-bounce: cubic-bezier(0.34, 1.56, 0.64, 1);

  /* Physical transforms do not follow reading direction. Anything that slides
     horizontally multiplies its offset by this instead of hardcoding a sign. */
  --rtl-flip: 1;

  color-scheme: light dark;
}

:root[dir="rtl"] {
  --rtl-flip: -1;
}

/*
 * Dark, twice, and the guard on the first one is the whole point.
 *
 * The reader's choice is stamped on <html data-theme>. With no choice stamped
 * the system decides, which is the media query below; with "light" stamped it
 * must not, which is what :not() holds. And the explicit dark rule repeats the
 * values so that choosing dark on a light system works too — a theme toggle
 * that only ever turns things darker is one that cannot be turned back.
 */
/*
 * The dark palette, written once.
 *
 * It is switched on from two places — the system preference and an explicit
 * choice — and the values used to be copied into both. One copy got a change
 * and the other did not, and because the screenshots emulate the system
 * preference the edit appeared to do nothing at all. Now the values live in
 * --dark-* and the two selectors below only point at them; an architecture rule
 * fails if either stops covering the same set or grows a literal of its own.
 *
 * The greys are the reference's: neutral rather than blue-black, so the warm
 * light in the corner has something to warm.
 */
:root {
  --dark-canvas: #121212;
  --dark-canvas-glass: rgba(18, 18, 18, 0.78);
  --dark-panel: #1a1a1a;
  --dark-surface-dim: #242424;
  --dark-feature-card-bg: rgba(255, 255, 255, 0.03);
  --dark-feature-card-border: rgba(var(--accent-rgb), 0.3);
  --dark-float-shadow: 0 0 #0000;
  --dark-sheet: #242424;
  --dark-ground: var(--dark-canvas);
  --dark-surface-hover: rgba(var(--accent-rgb), 0.1);
  --dark-surface-active: rgba(var(--accent-rgb), 0.2);
  --dark-text-main: #ffffff;
  --dark-text-dim: #a0a0a0;
  --dark-border: #333333;
  --dark-border-strong: #444444;
  /* A card's edge, and the light line along its top that stops it reading as a
     hole cut in the page. */
  --dark-card-border: rgba(255, 255, 255, 0.09);
  --dark-card-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.07), 0 2px 12px rgba(0, 0, 0, 0.35);
  --dark-hue-open: #60a5fa;
  --dark-danger-color: #f87171;
  --dark-danger-rgb: 248, 113, 113;
  --dark-hue-done: #4ade80;
  --dark-hue-hold: #fbbf24;
  --dark-hue-off: #9aa3b2;
  --dark-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.5);
  --dark-shadow-md: 0 8px 22px -10px rgba(0, 0, 0, 0.75);
  --dark-shadow-lg: 0 30px 70px -30px rgba(0, 0, 0, 0.9);
  --dark-hairline: inset 0 1px 0 rgba(255, 255, 255, 0.05);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --canvas: var(--dark-canvas);
    --canvas-glass: var(--dark-canvas-glass);
    --panel: var(--dark-panel);
    --sheet: var(--dark-sheet);
    --ground: var(--dark-ground);
    --surface-hover: var(--dark-surface-hover);
    --surface-active: var(--dark-surface-active);
    --surface-dim: var(--dark-surface-dim);
    --feature-card-bg: var(--dark-feature-card-bg);
    --feature-card-border: var(--dark-feature-card-border);
    --float-shadow: var(--dark-float-shadow);
    --text-main: var(--dark-text-main);
    --text-dim: var(--dark-text-dim);
    --border: var(--dark-border);
    --border-strong: var(--dark-border-strong);
    --card-border: var(--dark-card-border);
    --card-shadow: var(--dark-card-shadow);
    --hue-open: var(--dark-hue-open);
    --danger-color: var(--dark-danger-color);
    --danger-rgb: var(--dark-danger-rgb);
    --hue-done: var(--dark-hue-done);
    --hue-hold: var(--dark-hue-hold);
    --hue-off: var(--dark-hue-off);
    --shadow-sm: var(--dark-shadow-sm);
    --shadow-md: var(--dark-shadow-md);
    --shadow-lg: var(--dark-shadow-lg);
    --hairline: var(--dark-hairline);
  }
}

:root[data-theme="dark"] {
  --canvas: var(--dark-canvas);
  --canvas-glass: var(--dark-canvas-glass);
  --panel: var(--dark-panel);
  --sheet: var(--dark-sheet);
  --ground: var(--dark-ground);
  --surface-hover: var(--dark-surface-hover);
  --surface-active: var(--dark-surface-active);
  --surface-dim: var(--dark-surface-dim);
  --feature-card-bg: var(--dark-feature-card-bg);
  --feature-card-border: var(--dark-feature-card-border);
  --float-shadow: var(--dark-float-shadow);
  --text-main: var(--dark-text-main);
  --text-dim: var(--dark-text-dim);
  --border: var(--dark-border);
  --border-strong: var(--dark-border-strong);
  --card-border: var(--dark-card-border);
  --card-shadow: var(--dark-card-shadow);
  --hue-open: var(--dark-hue-open);
  --danger-color: var(--dark-danger-color);
  --danger-rgb: var(--dark-danger-rgb);
  --hue-done: var(--dark-hue-done);
  --hue-hold: var(--dark-hue-hold);
  --hue-off: var(--dark-hue-off);
  --shadow-sm: var(--dark-shadow-sm);
  --shadow-md: var(--dark-shadow-md);
  --shadow-lg: var(--dark-shadow-lg);
  --hairline: var(--dark-hairline);
}

* {
  box-sizing: border-box;
}

/*
 * hidden has to win.
 *
 * The attribute applies display:none at the lowest specificity there is, so any
 * class rule with a display of its own silently beats it — and the element sits
 * there in the layout wearing a correct `hidden` attribute. Every automated
 * check passes, because the attribute really is set; it is only visible by
 * looking at the page.
 *
 * This house has had that exact bug before, with a nav that had display:flex.
 */
[hidden] {
  display: none !important;
}

/*
 * Links need a rule of their own. Without one they fall back to the browser's
 * blue and, once visited, its purple — which on a dark surface reads as a
 * different, broken colour and has nothing to do with this palette. A class
 * with no base rule is invisible when reading the stylesheet: it only shows up
 * when somebody looks at the page.
 */
a {
  color: var(--accent-alt);
  text-decoration-color: rgba(var(--accent-rgb), 0.4);
  text-underline-offset: 0.15em;
}

a:hover {
  text-decoration-color: var(--accent);
}

/* The page colour goes on <html> so that the fixed light below can sit behind
   the content and still be painted: with the background on <body> it would
   propagate to the canvas and cover anything at a negative depth. */
html {
  background: var(--canvas);
}

body {
  margin: 0;
  background: transparent;
  color: var(--text-main);
  font-family: var(--font);
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

/*
 * The light behind everything: one warm pool at the top of the window and a
 * cool one under it, fixed, so the page moves and the light does not.
 *
 * A fixed pseudo-element rather than a background-attachment: fixed on <body>,
 * which tiles visibly in Firefox as soon as anything above it blurs — and the
 * bar above it does.
 */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  /* The house's ambient light, copied from the reference rather than guessed at:
     a warm pool up and to the left and a cool one down and to the right. Ours
     had both of them at the top, tighter, which lit the header instead of the
     room. */
  background:
    radial-gradient(circle at 20% 30%, rgba(var(--accent-rgb), 0.15) 0%, transparent 50%),
    radial-gradient(circle at 80% 80%, rgba(100, 100, 255, 0.08) 0%, transparent 50%);
}

::selection {
  background: rgba(var(--accent-rgb), 0.25);
}

::placeholder {
  /* Firefox dims placeholders on its own; without this the two engines disagree
     about how grey a hint is. */
  color: var(--text-dim);
  opacity: 0.75;
}

/* Scrollbars, everywhere.
 *
 * Thin and the colour of a border, on every element rather than through a
 * class, so a new scrolling box inherits them without anybody deciding to.
 * The standard properties are the ones that take effect in current engines;
 * the webkit ones stay underneath for those that only know them.
 *
 * And none on the public pages: they scroll with the bar hidden, because the page is the product and a grey
 * gutter down its edge is the operating system's, not the page's. Scrolling
 * still works by wheel, touch and keyboard. The panel keeps its bar: that is
 * a tool, and there the bar says how much more of the list there is. */
* {
  scrollbar-width: thin;
  scrollbar-color: var(--border) transparent;
}

*::-webkit-scrollbar { width: 6px; height: 6px; }
*::-webkit-scrollbar-track { background: transparent; }
*::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }
*::-webkit-scrollbar-corner { background: transparent; }

html:has(> body:not(.admin-view)) { scrollbar-width: none; }
html:has(> body:not(.admin-view))::-webkit-scrollbar { display: none; }

/*
 * Entrance, once, in the normal flow.
 *
 * Micro-animations attached to actions read as no animation at all: the house
 * asks for something visible in the ordinary path, which is a page arriving.
 * It rises from above — a positive translateY inside a scrolling container
 * pushes a scrollbar out during the animation, and we have had that bug.
 */
@keyframes rise {
  from {
    opacity: 0;
    transform: translateY(-8px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

@keyframes fade {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

/* Nothing enters for somebody who asked the system for stillness. Scoped to
   what this stylesheet animates rather than to everything: the toast has a
   reduced-motion rule of its own and a timer that must keep running. */
@media (prefers-reduced-motion: reduce) {
  .page > *,
.cards > li,
.items > li,
.roadmap .column {
    animation: none !important;
  }
}
/* --- layout ---------------------------------------------------------------
 *
 * Logical properties throughout: inset-inline, margin-inline, text-align:start.
 * Three of the twenty languages read right to left, and a stylesheet written in
 * left and right is a stylesheet that has to be written twice.
 */

/* The page's own bar, and only that one.
 *
 * A bare `nav` selector reaches every nav anywhere, including the one inside
 * the administration sidebar — which then laid its links out in a row and grew
 * a horizontal scrollbar. Same for `main`: the sidebar's content column is a
 * <main> too, and inherited a 46rem cap meant for the reading width of the
 * public pages. Both are scoped to the direct children of body, which is what
 * they always meant. */
body > nav {
  position: sticky;
  inset-block-start: 0;
  z-index: 30;
  background: var(--canvas-glass);
  backdrop-filter: blur(14px) saturate(140%);
  border-block-end: 1px solid var(--border);
}

/*
 * The bar uses the window: the mark at one edge
 * and the controls at the other, 20px from each. It used to share the measure
 * of the page under it, which left both of them adrift in the middle of a wide
 * screen with the space they should be using on the outside of them.
 */
.nav-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  inline-size: 100%;
  block-size: 48px;
  padding: 0 20px;
  box-sizing: border-box;
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: 0.55rem;
  font-weight: 650;
  font-size: 1rem;
  color: var(--text-main);
  text-decoration: none;
  letter-spacing: -0.01em;
}

.nav-right {
  display: flex;
  align-items: center;
  /* The toggles sit 4px apart. */
  gap: 4px;
}

body > main {
  max-inline-size: var(--page);
  margin-inline: auto;
  padding: clamp(1.5rem, 4vw, 2.5rem) 1.25rem 5rem;
}

/*
 * One page, one rhythm.
 *
 * Every public screen is a .page: a header block, then its content, at the same
 * distances on all of them. Before this each screen invented its own spacing,
 * which is why the board, the roadmap and the front page looked like three
 * products photographed in the same window.
 */
.page {
  display: grid;
  gap: 1.5rem;
  align-content: start;
}

.page.narrow {
  max-inline-size: var(--prose);
  margin-inline: auto;
  inline-size: 100%;
}

.page > * {
  animation: rise 0.4s var(--ease) both;
}

.page > *:nth-child(2) { animation-delay: 0.06s; }
.page > *:nth-child(3) { animation-delay: 0.12s; }
.page > *:nth-child(n + 4) { animation-delay: 0.18s; }

/* The header of a page: where you are, what it is called, and what you can do
   here — the third of those on the end the eye leaves from. */
.page-head {
  display: flex;
  flex-wrap: wrap;
  align-items: end;
  justify-content: space-between;
  gap: 1rem 1.5rem;
}

.page-head .head-text {
  display: grid;
  gap: 0.15rem;
  min-inline-size: 0;
}

/* A page title with its mark in front: the mark at the height of the crumb
   and the title together. */
.head-lead {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  min-inline-size: 0;
}
.card-mark.head-mark { inline-size: 3.25rem; block-size: 3.25rem; }

.head-actions {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
}

h1 {
  font-size: clamp(1.5rem, 1.1rem + 1.4vw, 2rem);
  line-height: 1.15;
  margin-block: 0 0.15rem;
  letter-spacing: -0.025em;
}

h2 {
  font-size: 1.1rem;
  margin-block: 0 0.5rem;
  letter-spacing: -0.015em;
}

.lead {
  color: var(--text-dim);
  margin-block: 0;
  max-inline-size: var(--prose);
}

.hint { color: var(--text-dim); font-size: 0.875rem; }

/* Where you are, above what you are looking at. Small, quiet, and a link. */
.crumb {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  margin: 0;
  font-size: 0.8rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--text-dim);
}

.crumb a { color: var(--text-dim); text-decoration: none; }
.crumb a:hover { color: var(--accent-alt); }
.brand:hover { text-decoration: none; }

/*
 * Nothing here — said as a place rather than as a missing thing.
 *
 * A grey sentence in the middle of a blank column reads as a page that failed
 * to load. A bordered field with a mark in it reads as a place with nothing in
 * it yet, which is what it is.
 */
.empty {
  display: grid;
  justify-items: center;
  gap: 0.5rem;
  color: var(--text-dim);
  padding: clamp(1.75rem, 4vw, 2.75rem) 1.25rem;
  text-align: center;
  border: 1px dashed var(--border-strong);
  border-radius: var(--radius-lg);
  background: color-mix(in srgb, var(--panel) 55%, transparent);
}

.empty .icon { color: var(--border-strong); }

/* Under the middle of the panel, like everything else in it: .button plants
   itself at the start of the line by default, which left the way onward hanging
   off to one side of a centred box. */
.empty .button { margin-block-start: 0.35rem; justify-self: center; }

ul { list-style: none; margin: 0; padding: 0; }

/* --------------------------------------------------------------- surfaces */

/*
 * The card, which is most of this product.
 *
 * Raised rather than recessed: a surface lighter than the page, a hairline on
 * its top edge and a shadow under it. The old card was a grey rectangle a
 * shade off the page it sat on, which is why a page holding one of them looked
 * like a page holding nothing.
 */
.cards {
  display: grid;
  gap: 0.75rem;
  /* auto-fit, not auto-fill: an organisation with one board had a card a
     quarter of the window wide sitting in three columns of nothing, which reads
     as a page that failed to load the rest. Empty tracks collapse and the one
     card takes the row. */
  grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr));
}

.card {
  position: relative;
  display: flex;
  align-items: center;
  gap: 0.85rem;
  padding: 1rem 1.1rem;
  border: 1px solid var(--card-border);
  border-radius: var(--radius-lg);
  background: var(--panel);
  box-shadow: var(--card-shadow);
  text-decoration: none;
  color: inherit;
  transition: transform 0.18s var(--ease), box-shadow 0.18s var(--ease),
    border-color 0.18s var(--ease);
}

.card:hover {
  transform: translateY(-2px);
  border-color: var(--accent-line);
  box-shadow: var(--shadow-md), var(--hairline);
}

.card-body { display: grid; gap: 0.1rem; min-inline-size: 0; }
.card-title { font-weight: 600; letter-spacing: -0.01em; }
.card-sub { color: var(--text-dim); font-size: 0.85rem; }

/* The monogram in front of a name. Two letters of the thing itself, which is
   how somebody finds their own organisation in a list of eight. */
.card-mark {
  display: grid;
  place-items: center;
  flex: none;
  inline-size: 2.5rem;
  block-size: 2.5rem;
  border-radius: var(--radius);
  background: var(--accent-wash);
  color: var(--accent-alt);
  border: 1px solid var(--accent-line);
  font-weight: 700;
  font-size: 0.9rem;
  letter-spacing: 0.02em;
  text-transform: uppercase;
}

/* The arrow that says a card goes somewhere. It leans towards the end of the
   line on hover, and the line runs the other way in Arabic — so the chevron is
   flipped by the same variable everything else physical uses. */
.card-go {
  margin-inline-start: auto;
  color: var(--text-dim);
  display: inline-flex;
  transition: transform 0.18s var(--ease), color 0.18s var(--ease);
  transform: scaleX(var(--rtl-flip));
}

.card:hover .card-go {
  color: var(--accent);
  transform: scaleX(var(--rtl-flip)) translateX(3px);
}

/*
 * A small word about a thing: what a board accepts.
 *
 * Not .tag, which this stylesheet already had: that one is the monospaced role
 * label in the panel, it is declared further down, and at equal specificity the
 * later rule wins — so a public chip written as .tag came out in a code font
 * with its icon jammed against the word. Two different things had one class
 * name, which is the same trap .choices sprang last month.
 */
.chip {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  padding: 0.1rem 0.55rem;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--surface-dim);
  color: var(--text-dim);
  font-size: 0.75rem;
  font-weight: 600;
  white-space: nowrap;
}

.chip .icon { color: var(--text-dim); }

/* A kind wears its colour wherever it appears as a pill, the same two as the
   marks on the front page: the accent for a bug, blue for an idea. */
.chip.kind-bug .icon { color: var(--accent); }
.chip.kind-idea .icon { color: var(--hue-open); }
.chips { display: flex; flex-wrap: wrap; gap: 0.35rem; align-items: center; }

/* ------------------------------------------------------------------ board */

.board-head { margin-block-end: 0; }

/*
 * The toolbar: what is shown, in what order, and a search across the row.
 *
 * One bar rather than three loose groups. The filters sit in a track so that
 * the chosen one is a raised tile inside it — the old pills gave the same
 * weight to the choice and to the alternatives, so no row of them said which
 * board you were looking at.
 */
.controls {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
  align-items: center;
}

.tabs,
.sorts {
  display: inline-flex;
  gap: 0.15rem;
  padding: 0.2rem;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--surface-dim);
}

.tab, .sort, .choice {
  padding: 0.32rem 0.8rem;
  border: 0;
  border-radius: 999px;
  background: transparent;
  color: var(--text-dim);
  cursor: pointer;
  font: inherit;
  font-size: 0.875rem;
  font-weight: 600;
  white-space: nowrap;
  transition: color 0.15s var(--ease), background 0.15s var(--ease);
}

.tab:hover, .sort:hover, .choice:hover { color: var(--text-main); }

.tab.on, .sort.on, .choice.on {
  background: var(--panel);
  color: var(--accent-alt);
  box-shadow: var(--shadow-sm);
}

/* The search of a board's page, in the toolbar beside the tabs and the sorts: the
   box of the house, which it shares with the reports screen. */
.board-search { flex: 1 1 14rem; min-inline-size: 10rem; min-block-size: 40px; padding-inline: 14px; }
.board-search input { font-size: 13px; }

/* ------------------------------------------------------------------- rows */

/*
 * A section of a board: the same device as a roadmap column, written once here
 * so the two cannot drift. A bordered box whose name is a pill on the border —
 * a legend, interrupting the line rather than sitting under it.
 *
 * `overflow: visible` and the top margin are what keep the pill from being
 * clipped: it is lifted half its own height above the edge.
 */
.section {
  position: relative;
  margin-block-start: 0.55rem;
  padding: 1.1rem 0.9rem 0.9rem;
  border: 1px solid var(--card-border);
  border-radius: var(--radius-lg);
  background: color-mix(in srgb, var(--panel) 70%, transparent);
  box-shadow: var(--card-shadow);
  overflow: visible;
}

/*
 * Written as a child selector on purpose, and this is not decoration.
 *
 * `.section-legend` alone scores (0,1,0) and `.admin-main h2` scores (0,1,1),
 * so inside the panel the pill quietly took the panel's heading size: 18.4px
 * instead of 10.4, which made it 35px tall instead of 25 and dropped it off its
 * own border and on top of the first row. The three places this component was
 * used before were all public pages, so it had never met that rule and looked
 * perfect everywhere it had been tried.
 *
 * `.section > .section-legend` scores (0,2,0) and wins wherever it is put.
 */
.section > .section-legend,
.section-legend {
  margin: 0;
  font-size: 0.65rem;
  position: absolute;
  inset-block-start: 0;
  inset-inline-start: 0.85rem;
  transform: translateY(-50%);
  z-index: 2;
  display: flex;
  align-items: center;
  gap: 0.4rem;
  margin: 0;
  padding: 0.15rem 0.5rem;
  border: 1px solid var(--card-border);
  border-radius: 12px;
  background: var(--panel);
  box-shadow: var(--shadow-sm);
  font-family: var(--font-mono);
  font-size: 0.65rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-dim);
}

.section-legend .count-chip {
  padding: 0 0.3rem;
  min-inline-size: 0;
  font-size: 0.65rem;
}

.items { display: grid; gap: 0.5rem; }

/* Under the list: how much of it is shown, and the way to the rest. */
.board-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  flex-wrap: wrap;
  margin-block-start: 0.75rem;
}
.board-shown { color: var(--text-dim); font-size: 0.85rem; }

.items > li { animation: fade 0.35s var(--ease) both; }
.items > li:nth-child(2) { animation-delay: 0.04s; }
.items > li:nth-child(3) { animation-delay: 0.08s; }
.items > li:nth-child(4) { animation-delay: 0.12s; }
.items > li:nth-child(n + 5) { animation-delay: 0.16s; }

/*
 * One report in the list.
 *
 * The score leads, because on an ideas board it is what the row is sorted by
 * and what somebody scans down the column for. The title is the row; the kind,
 * the age and the state are the quiet line under it.
 */
.item {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  gap: 0.9rem;
  align-items: center;
  padding: 0.85rem 1rem;
  border: 1px solid var(--card-border);
  border-radius: var(--radius-lg);
  text-decoration: none;
  color: inherit;
  background: var(--panel);
  box-shadow: var(--card-shadow);
  transition: transform 0.18s var(--ease), box-shadow 0.18s var(--ease),
    border-color 0.18s var(--ease);
}

.item:hover {
  transform: translateY(-1px);
  border-color: var(--accent-line);
  box-shadow: var(--shadow-md), var(--hairline);
}

.item-main { display: grid; gap: 0.25rem; min-inline-size: 0; }

.item-title {
  font-weight: 600;
  letter-spacing: -0.01em;
  overflow-wrap: anywhere;
}

.item-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.8rem;
  color: var(--text-dim);
}

/* The count of votes, as a tile the eye lands on first. The word stays under
   the number: a bare 3 beside a title is a number nobody can name, which is
   why every counter in this house is written label-then-value. */
.score {
  display: grid;
  justify-items: center;
  align-content: center;
  gap: 0.05rem;
  min-inline-size: 3.4rem;
  padding: 0.4rem 0.5rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface-dim);
}

.score .count {
  font-size: 1.15rem;
  font-weight: 700;
  line-height: 1.1;
  font-variant-numeric: tabular-nums;
}

.score .label {
  font-size: 0.65rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-dim);
}

.score.voted {
  border-color: var(--accent-line);
  background: var(--accent-wash);
}

.score.voted .count, .score.voted .label { color: var(--accent-alt); }

/* The kind, as a mark and a word. Twenty rows repeating "Bug" in full weight
   is twenty words nobody reads; the shape is what carries at a glance. */
.kind {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--text-dim);
  white-space: nowrap;
}

.kind .icon { color: var(--hue-open); }
.kind-bug .icon { color: var(--accent); }

.status, .when, .votes {
  font-size: 0.8rem;
  color: var(--text-dim);
  white-space: nowrap;
}

.votes.voted { color: var(--accent-alt); font-weight: 600; }

/*
 * The state of a report, in the colour of its meaning.
 *
 * Four meanings, not nine colours: open, being worked on, finished, and put
 * aside. A board where every state is the same grey word is a board that has to
 * be read rather than scanned, and the state is the one thing a reporter came
 * back to find out.
 */
.status {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0.15rem 0.6rem;
  border-radius: 999px;
  border: 1px solid transparent;
  font-weight: 600;
  --hue: var(--hue-off);
  color: var(--hue);
  border-color: color-mix(in srgb, var(--hue) 35%, transparent);
  background: color-mix(in srgb, var(--hue) 12%, transparent);
}

.status::before {
  content: "";
  inline-size: 0.4rem;
  block-size: 0.4rem;
  border-radius: 50%;
  background: currentColor;
}

.status-new, .status-confirmed { --hue: var(--hue-open); }
.status-planned { --hue: var(--hue-hold); }
.status-in_progress, .status-in_forge { --hue: var(--hue-work); }
.status-done, .status-closed { --hue: var(--hue-done); }
.status-declined, .status-not_reproducible { --hue: var(--hue-off); }

/* ------------------------------------------------------------------ forms */

form { display: grid; gap: 0.5rem; margin-block-start: 1rem; }
label { font-size: 0.875rem; font-weight: 600; }

input, textarea, select {
  padding: 0.55rem 0.7rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--panel);
  color: inherit;
  font: inherit;
  /* start, not left: the caret belongs at the beginning of the line in every
     reading direction. */
  text-align: start;
  transition: border-color 0.15s var(--ease), box-shadow 0.15s var(--ease);
}

input:hover, textarea:hover, select:hover { border-color: var(--border-strong); }

/* The house dropdown (web/src/services/select.ts puts it over every
 * <select>).
 *
 * The trigger takes the field's own padding, border and background, so it
 * sits at the height of the inputs beside it; then the inset shade, the
 * chevron that turns and lights up, the accent ring while open. Custom
 * properties give it a compact size. */
.sel {
  position: relative;
  display: block;
  inline-size: 100%;
  min-inline-size: 0;
}

/* Out of sight and out of the way, and still in the form: it is what a form
   submits and what the code reads. Not display:none — a hidden control that
   is required cannot be focused, and the browser then refuses the form with
   no field to point at. */
.sel > select.sel-native {
  position: absolute;
  inline-size: 1px;
  block-size: 1px;
  padding: 0;
  margin: -1px;
  border: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  opacity: 0;
  pointer-events: none;
}

.sel-trigger {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  inline-size: 100%;
  box-sizing: border-box;
  padding: var(--sel-padding, 0.55rem 0.7rem);
  border: 1px solid var(--border);
  border-radius: var(--sel-radius, var(--radius));
  background: var(--panel);
  color: var(--text-main);
  font: inherit;
  font-size: var(--sel-font-size, inherit);
  line-height: inherit;
  text-align: start;
  cursor: pointer;
  box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.1);
  transition:
    border-color 0.2s var(--ease),
    background-color 0.2s var(--ease),
    box-shadow 0.2s var(--ease);
}

.sel-trigger:hover { border-color: var(--text-dim); }
.sel-trigger:disabled { opacity: 0.55; cursor: default; }

.sel-trigger:focus-visible {
  outline: none;
  border-color: var(--accent);
  border-radius: var(--sel-radius, var(--radius));
  box-shadow: var(--ring);
}

.sel.open .sel-trigger {
  border-color: var(--accent);
  background: rgba(var(--accent-rgb), 0.05);
  box-shadow: 0 0 0 3px rgba(var(--accent-rgb), 0.15);
}

/* Block, not flex: an ellipsis never applies to a flex container. */
.sel-value {
  display: block;
  min-inline-size: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.sel-icon {
  inline-size: 16px;
  block-size: 16px;
  flex-shrink: 0;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
  opacity: 0.5;
  transition: transform 0.2s var(--ease), opacity 0.2s var(--ease);
}

.sel.open .sel-icon { transform: rotate(180deg); opacity: 1; color: var(--accent); }

/* Measures where a fixed box lands, so the list can be put where it is
   meant to be even under a dialog that is still arriving. */
.sel-probe {
  position: fixed;
  top: 0;
  left: 0;
  inline-size: 0;
  block-size: 0;
  pointer-events: none;
}

/* The list. Fixed, so no scrolling box it sits in can cut it off; its place
   is written as a rule when it opens. Physical top and left on purpose: they
   are screen coordinates measured by the script, and those do not mirror. */
.sel-list {
  position: fixed;
  z-index: 1000;
  box-sizing: border-box;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 12px;
  box-shadow: 0 10px 40px rgba(0, 0, 0, 0.4);
  max-block-size: 0;
  opacity: 0;
  overflow: hidden;
  outline: none;
  transform: translateY(-8px);
  transition:
    max-block-size 0.2s var(--ease),
    opacity 0.2s var(--ease),
    transform 0.2s var(--ease);
}

.sel-list.open {
  max-block-size: 300px;
  opacity: 1;
  transform: translateY(0);
  overflow-y: auto;
}

.sel-option {
  padding: 10px 16px;
  font-size: 14px;
  color: var(--text-main);
  cursor: pointer;
  transition: background-color 0.1s;
}

.sel-option:hover,
.sel-option.active { background: rgba(127, 127, 127, 0.12); }

.sel-option.selected {
  color: var(--accent);
  background: rgba(var(--accent-rgb), 0.1);
  font-weight: 500;
}

.sel-option.selected:hover,
.sel-option.selected.active { background: rgba(var(--accent-rgb), 0.15); }

@media (prefers-reduced-motion: reduce) {
  .sel-list,
  .sel-icon,
  .sel-trigger { transition: none; }
}

textarea { resize: vertical; }

/* One focus ring for the whole product, and it is drawn with a shadow so that
   it follows the rounded corner instead of boxing it. The outline stays for
   anything this rule does not reach. */
input:focus-visible,
textarea:focus-visible,
select:focus-visible {
  outline: none;
  border-color: var(--accent);
  box-shadow: var(--ring);
}

a:focus-visible,
button:focus-visible,
summary:focus-visible,
[tabindex]:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

.choices { display: flex; gap: 0.5rem; margin-block-end: 0.5rem; }

/* ---------------------------------------------------------------- buttons */

.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.45rem;
  padding: 0.55rem 1rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--panel);
  box-shadow: var(--shadow-sm), var(--hairline);
  color: inherit;
  font: inherit;
  font-weight: 600;
  cursor: pointer;
  text-decoration: none;
  justify-self: start;
  white-space: nowrap;
  transition: transform 0.15s var(--ease), box-shadow 0.15s var(--ease),
    border-color 0.15s var(--ease), background 0.15s var(--ease);
}

.button:hover { border-color: var(--border-strong); transform: translateY(-1px); }
.button:active { transform: translateY(0); }

/* The one action a screen is for. A flat fill reads as a coloured rectangle;
   the light down its face and the glow beneath it are what make it a button. */
.button.primary {
  background: linear-gradient(180deg,
    rgba(255, 255, 255, 0.16), rgba(255, 255, 255, 0) 60%), var(--accent);
  border-color: rgba(var(--accent-rgb), 0.9);
  color: #fff;
  box-shadow: var(--accent-glow);
}

.button.primary:hover {
  border-color: var(--accent);
  box-shadow: var(--accent-glow), 0 0 0 4px rgba(var(--accent-rgb), 0.14);
}

/* Merged from two: the later one made it a solid red button, which meant the
   earlier one's text colour never applied. */
.button.danger { background: #b91c1c; color: #fff; }
.button.danger:hover { border-color: rgba(var(--danger-rgb), 0.5); }
.button .count { font-variant-numeric: tabular-nums; font-weight: 700; }
.button.small { padding: 0.35rem 0.75rem; font-size: 0.875rem; }

.link {
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  cursor: pointer;
  padding: 0;
  text-decoration: underline;
  text-underline-offset: 0.15em;
}

.form-error {
  color: var(--danger-color);
  font-size: 0.875rem;
  font-weight: 600;
}

/* One rule, merged from two that were fifteen hundred lines apart. What the
   browser actually applied was this: the first's border, bar and shadow, with
   the second's spacing and background on top. Reading either half on its own
   told you something that was not on the screen.
 *
 * The tint is written against --accent-rgb. It was a literal rgba(232, 82, 14),
 * so an operator who chose their own accent got every other tinted surface in
 * their colour and their notices in ours. */
.notice {
  display: flex;
  align-items: start;
  gap: 0.5rem;
  padding: 0.6rem 0.75rem;
  border: 1px solid var(--border);
  border-inline-start: 3px solid var(--accent);
  border-radius: var(--radius);
  background: rgba(var(--accent-rgb), 0.1);
  box-shadow: var(--shadow-sm);
  color: var(--text-main);
  font-size: 0.9rem;
}

.notice .icon { color: var(--accent); flex: none; }

.similar {
  border: 1px dashed var(--border-strong);
  border-radius: var(--radius);
  padding: 0.75rem 0.9rem;
  background: var(--surface-dim);
}

.similar-head { font-weight: 600; margin-block: 0 0.35rem; }

.detail .meta { display: flex; gap: 0.6rem; align-items: center; }

.follow-link { display: flex; gap: 0.5rem; margin-block-start: 0.5rem; }
.follow-link input { flex: 1; font-family: var(--font-mono); font-size: 0.8rem; }
.keep { font-weight: 600; }

.comments { display: grid; gap: 0.6rem; margin-block: 0.5rem 1rem; }

.comment {
  padding: 0.85rem 1rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--panel);
  box-shadow: var(--shadow-sm), var(--hairline);
}

.comment-team { border-inline-start: 3px solid var(--accent); }
/* Merged from two: the later one set the layout and won the margin, the earlier
   one still supplied the size and the colour. Neither half described the line
   that is on the screen. */
.comment .who {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin: 0 0 0.4rem;
  font-size: 0.8rem;
  color: var(--text-dim);
}
.comment .what { margin: 0; white-space: pre-wrap; }
/* The pencil and the bin at the end of the line, after the date. */
.comment-tools { display: inline-flex; align-items: center; gap: 2px; }
.comment-edit { display: grid; gap: 8px; }
.comment-edit textarea { inline-size: 100%; }
.comment-edit .settings-actions { gap: 8px; }

.queue { display: grid; gap: 0.75rem; margin-block-start: 1rem; }
.queue li {
  padding: 1rem 1.1rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--panel);
  box-shadow: var(--shadow-sm), var(--hairline);
}
.queue h3 { margin: 0.25rem 0; }
.queue .body { white-space: pre-wrap; color: var(--text-dim); }
.actions { display: flex; gap: 0.5rem; margin-block-start: 0.6rem; flex-wrap: wrap; }

/* The honeypot: out of the layout entirely rather than display:none, because
 * some automated fillers skip anything that is not rendered. Nobody who can see
 * or hear the page is ever offered it. */
.trap {
  position: absolute;
  inset-inline-start: -9999px;
  inline-size: 1px;
  block-size: 1px;
  overflow: hidden;
}

/* A button that cannot be pressed says so. Without this a disabled Publish
   looked exactly like one waiting to be pressed. Last in this section so it wins
   over every variant above with the same weight. */
.button:disabled,
.button:disabled:hover {
  opacity: 0.45;
  cursor: default;
  transform: none;
  box-shadow: none;
}

/* ---------------------------------------------------------------- roadmap */

/* Three columns of the same height, whatever is in them. Left to their
   contents they stepped down like a bar chart, which made a quiet board look
   like a broken one. */
.roadmap {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  gap: 1rem;
  align-items: stretch;
}

/*
 * A roadmap column is a section of the board, and the house draws a section as
 * a bordered box whose name is a pill sitting ON the border — a fieldset
 * legend, interrupting the line rather than sitting under it inside a rule of
 * its own. Taken from the reference's grid, where it is what makes a group of
 * tiles read as a group.
 *
 * `overflow: visible` and the position are not decoration: the pill is lifted
 * half its height above the top edge and would be clipped otherwise.
 */
.roadmap .column {
  position: relative;
  display: grid;
  align-content: start;
  gap: 0.6rem;
  margin-block-start: 0.55rem;
  border: 1px solid var(--card-border);
  border-radius: var(--radius-lg);
  padding: 1.1rem 0.9rem 0.9rem;
  background: color-mix(in srgb, var(--panel) 70%, transparent);
  box-shadow: var(--card-shadow);
  overflow: visible;
  animation: rise 0.4s var(--ease) both;
}

.roadmap .column:nth-child(2) { animation-delay: 0.07s; }
.roadmap .column:nth-child(3) { animation-delay: 0.14s; }

/* The legend, on the border. Monospaced and spaced out, which is what tells the
   eye it labels the box rather than starting its contents — and opaque, so the
   border passes behind it and stops. Each one carries the colour of the state
   it collects: three identical grey boxes are three boxes you have to read. */
.column-head {
  position: absolute;
  inset-block-start: 0;
  inset-inline-start: 0.85rem;
  transform: translateY(-50%);
  z-index: 2;
  display: flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.15rem 0.5rem;
  border: 1px solid var(--card-border);
  border-radius: 12px;
  background: var(--panel);
  box-shadow: var(--shadow-sm);
  --hue: var(--hue-off);
}

.column-head h2 {
  margin: 0;
  font-family: var(--font-mono);
  font-size: 0.65rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-dim);
}

.column-head .dot {
  inline-size: 0.45rem;
  block-size: 0.45rem;
  border-radius: 50%;
  background: var(--hue);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--hue) 20%, transparent);
}

/* Inside the pill now, right after the name: pushed to the far end it would
   stretch the legend the width of the column. */
.column-head .count-chip {
  padding: 0 0.3rem;
  min-inline-size: 0;
  font-size: 0.65rem;
}

.column-planned .column-head { --hue: var(--hue-hold); }
.column-in_progress .column-head { --hue: var(--hue-work); }
.column-done .column-head { --hue: var(--hue-done); }

.roadmap ul { display: grid; gap: 0.5rem; }

.roadmap li a {
  display: grid;
  /* start, not stretch: a grid child fills its column by default, so the kind
     badge — a pill sized to two words — was being stretched the full width of
     the card. Only visible by looking at the page. */
  justify-items: start;
  gap: 0.3rem;
  padding: 0.7rem 0.8rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--panel);
  box-shadow: var(--shadow-sm), var(--hairline);
  text-decoration: none;
  color: inherit;
  transition: transform 0.18s var(--ease), border-color 0.18s var(--ease),
    box-shadow 0.18s var(--ease);
}

.roadmap li a:hover {
  transform: translateY(-1px);
  border-color: var(--accent-line);
  box-shadow: var(--shadow-md), var(--hairline);
}

/* A column with nothing in it is quiet, not broken: a short dashed line with a
   short sentence, rather than a hand-sized grey box saying so. */
.roadmap .column-empty {
  padding: 0.9rem 0.5rem;
  font-size: 0.8rem;
  border-style: dashed;
  border-color: var(--border);
  background: transparent;
  color: var(--text-dim);
}

/* ----------------------------------------------------------- panel pieces */

.new-org, .new-repo { max-inline-size: 28rem; }
.forge-fields { display: grid; gap: 0.35rem; }

.notes {
  white-space: pre-wrap;
  font-family: inherit;
  font-size: 0.875rem;
  color: var(--text-dim);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 0.6rem 0.8rem;
  max-block-size: 14rem;
  overflow-y: auto;
}
.update-note { font-size: 0.875rem; color: var(--text-dim); }

.credentials, .channel { max-inline-size: 28rem; }
.channel-fields, .channel-mail { display: grid; gap: 0.35rem; }
.webhook-path {
  font-family: var(--font-mono);
  font-size: 0.8rem;
  color: var(--text-dim);
  word-break: break-all;
}

.exports { font-size: 0.875rem; margin-block: 0.5rem 0; }

/* ------------------------------------------------------------ shared marks */

/* A large mark in a lit tile, for a screen that has one thing to say — a page
   that could not be shown. */
.hero-mark {
  display: grid;
  place-items: center;
  inline-size: 4.5rem;
  block-size: 4.5rem;
  border-radius: var(--radius-xl);
  background: var(--panel);
  border: 1px solid var(--border);
  box-shadow: var(--shadow-md), var(--hairline), 0 0 0 8px rgba(var(--accent-rgb), 0.05);
}

.point-icon {
  display: grid;
  place-items: center;
  flex: none;
  inline-size: 2.1rem;
  block-size: 2.1rem;
  border-radius: var(--radius-sm);
  background: var(--accent-wash);
  color: var(--accent-alt);
}

/* ------------------------------------------------------------ the front page
 *
 * A stack of bands. Each band is a stretch of the window with its own
 * background and one row of columns inside it; each column holds modules, one
 * under the other. That is the whole model — the one every website builder
 * settles on, and the reason this one needs no coordinates: a band says what
 * goes beside what, and reading order says the rest, including on a phone.
 *
 * The page is not a column. Every other public screen asks for one thing and a
 * measure keeps it readable; this one shows several, so the bands run to the
 * window's edges and their contents keep the page's measure inside them.
 */
/* The first run is the setup and nothing else: no bar above
   it — there is nowhere yet for it to lead — and the card in the middle of the
   window. Only the setup: its wizard is the one that hangs straight from the
   page; the panel's wizards live inside the panel. */
body:has(> main > .wizard) > nav { display: none; }
body > main:has(> .wizard) {
  display: grid;
  place-items: center;
  min-block-size: 100dvh;
  box-sizing: border-box;
  padding-block: 2rem;
}
/* Centred by its own grid, not shrunk to its contents: the form's width is a
   share of this one, and a phone has to have one to share. */
body > main > .wizard { inline-size: 100%; }

body > main:has(> .lp) {
  max-inline-size: none;
  padding: 0;
}

/* A column, so a short page still ends with its foot at the bottom. */
.lp {
  display: flex;
  flex-direction: column;
  min-block-size: calc(100vh - 3.5rem);
  /* The columns answer to the page's own width, not the window's, so the
     editor can show the phone layout in a phone-wide frame. */
  container: lp / inline-size;
}

.lp-band { position: relative; }

/* The band with the search open sits above the rest: every module animates in,
   and an animated transform makes a stacking context of its own, so without
   this the results would open underneath the next band's cards. */
.lp-band:has(.lp-results:not([hidden])) { z-index: 5; }

.lp-band.bg-soft {
  background: color-mix(in srgb, var(--panel) 55%, transparent);
  border-block: 1px solid var(--border);
}

.lp-band.bg-accent {
  background: var(--accent-wash);
  border-block: 1px solid rgba(var(--accent-rgb), 0.18);
}

/* Two coloured bands in a row share one line between them, not two. */
.lp-band:is(.bg-soft, .bg-accent) + .lp-band:is(.bg-soft, .bg-accent) { border-block-start: 0; }

.lp-inner {
  max-inline-size: var(--page);
  margin-inline: auto;
  padding-inline: 1.25rem;
}

.lp-band.full > .lp-inner {
  max-inline-size: none;
  padding-inline: clamp(1rem, 2.5vw, 2rem);
}

.lp-band.space-compact > .lp-inner { padding-block: 1.25rem; }
.lp-band.space-normal > .lp-inner { padding-block: clamp(1.75rem, 4cqi, 2.75rem); }
.lp-band.space-airy > .lp-inner { padding-block: clamp(2.75rem, 8cqi, 5rem); }

/* A band's name, in the house pill: the same letters, border and chip as the
   pill a section wears, set in the flow above the band rather than on a border
   the band does not have. */
.lp-pill {
  display: flex;
  inline-size: fit-content;
  margin: 0 0 1.1rem;
  padding: 0.2rem 0.6rem;
  border: 1px solid var(--card-border);
  border-radius: 12px;
  background: var(--panel);
  box-shadow: var(--shadow-sm);
  font-family: var(--font-mono);
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-dim);
}

.lp-cols {
  display: grid;
  gap: 1.25rem;
  /* Every column as tall as the tallest: two lists side by side end on one
     line rather than one short and one long. */
  align-items: stretch;
}

.lp-cols.cols-1 { grid-template-columns: minmax(0, 1fr); }
.lp-cols.cols-1-1 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.lp-cols.cols-1-1-1 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.lp-cols.cols-2-1 { grid-template-columns: minmax(0, 2fr) minmax(0, 1fr); }
.lp-cols.cols-1-2 { grid-template-columns: minmax(0, 1fr) minmax(0, 2fr); }
.lp-cols.cols-1-1-1-1 { grid-template-columns: repeat(4, minmax(0, 1fr)); }

.lp-col {
  display: flex;
  flex-direction: column;
  gap: 1.25rem;
  min-inline-size: 0;
}

/* The last module of a column takes what is left, which is what lines the
   bottoms up across the band. */
.lp-col > .lp-mod:last-child { flex: 1 0 auto; }

/* Four across is four narrow columns: two by two until there is room. */
@container lp (max-width: 64rem) {
  .lp-cols.cols-1-1-1-1 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* On a phone every band is one column, in reading order. */
@container lp (max-width: 45rem) {
  .lp-cols[class*="cols-"] { grid-template-columns: minmax(0, 1fr); }
}

/* ---- a module ---- */

.lp-mod {
  display: flex;
  flex-direction: column;
  min-inline-size: 0;
  animation: rise 0.45s var(--ease) both;
}

.lp-col > .lp-mod:nth-child(2) { animation-delay: 0.06s; }
.lp-col:nth-child(2) > .lp-mod { animation-delay: 0.08s; }
.lp-col:nth-child(3) > .lp-mod { animation-delay: 0.16s; }
.lp-col:nth-child(4) > .lp-mod { animation-delay: 0.24s; }

/* The three dresses. A card is the house's card; soft is the same box with no
   border to hold it, for a band that already has a background; plain is the
   content alone. */
.lp-mod.style-card {
  padding: 1.1rem 1.2rem;
  border: 1px solid var(--card-border);
  border-radius: var(--radius-lg);
  background: color-mix(in srgb, var(--panel) 80%, transparent);
  box-shadow: var(--card-shadow);
}

.lp-mod.style-soft {
  padding: 1.1rem 1.2rem;
  border-radius: var(--radius-lg);
  background: color-mix(in srgb, var(--surface-dim) 60%, transparent);
}

.lp-mod.style-plain { padding: 0; }

/* A module's name: its mark and its name above what it shows, with a line
   under them — the recipe a widget's header uses. */
.lp-head {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding-block-end: 0.65rem;
  margin-block-end: 0.75rem;
  border-block-end: 1px solid var(--border);
}

.lp-head-icon { display: flex; color: var(--accent); }

.lp-head h3 {
  flex: 1;
  min-inline-size: 0;
  margin: 0;
  font-size: 0.8rem;
  font-weight: 600;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--text-main);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.lp-mod .empty { flex: 1; align-content: center; }

/* ---- the heading ---- */

.lp-heading {
  display: grid;
  justify-items: center;
  gap: 0.75rem;
  text-align: center;
}

.lp-heading.align-start {
  justify-items: start;
  text-align: start;
}

.lp-heading > * { animation: rise 0.5s var(--ease) both; }
.lp-heading > :nth-child(2) { animation-delay: 0.07s; }
.lp-heading > :nth-child(3) { animation-delay: 0.14s; }
.lp-heading > :nth-child(4) { animation-delay: 0.21s; }

/* The installation's mark, bare — the About screen draws it the same way, and
   a tile around it was one frame more than anything else on the page wears.
   Three sizes, which the heading module chooses; the default is About's. */
.lp-logo {
  display: block;
  inline-size: 80px;
  block-size: 80px;
  margin-block-end: 0.25rem;
  border-radius: 18px;
  object-fit: contain;
}
.lp-logo.logo-small {
  inline-size: 56px;
  block-size: 56px;
  border-radius: 13px;
}
.lp-logo.logo-large {
  inline-size: 120px;
  block-size: 120px;
  border-radius: 26px;
}

.lp-heading h1 {
  margin: 0;
  font-size: clamp(2.1rem, 1.2rem + 3.4cqi, 3.4rem);
  line-height: 1.05;
  letter-spacing: -0.035em;
  overflow-wrap: anywhere;
}

.lp-subtitle {
  margin: 0;
  max-inline-size: 40rem;
  font-size: clamp(1.05rem, 0.95rem + 0.5cqi, 1.25rem);
  color: var(--text-dim);
  text-wrap: balance;
}

.lp-heading .lp-search {
  margin-block-start: 0.75rem;
  inline-size: min(100%, 36rem);
}

/* ---- the search ---- */

.lp-search {
  position: relative;
  inline-size: 100%;
  text-align: start;
}

.lp-search-icon {
  position: absolute;
  inset-block-start: 0;
  inset-inline-start: 0.95rem;
  block-size: 3rem;
  display: flex;
  align-items: center;
  color: var(--text-dim);
  pointer-events: none;
}

.lp-search input {
  inline-size: 100%;
  block-size: 3rem;
  padding-inline: 2.75rem 1rem;
  border-radius: var(--radius-lg);
  font-size: 1rem;
  background: var(--panel);
  box-shadow: var(--shadow-md);
}

.lp-results {
  position: absolute;
  z-index: 20;
  inset-inline: 0;
  inset-block-start: calc(100% + 0.4rem);
  display: grid;
  padding: 0.35rem;
  border: 1px solid var(--card-border);
  border-radius: var(--radius-lg);
  background: var(--panel);
  box-shadow: var(--shadow-lg);
  animation: rise 0.18s var(--ease) both;
}

.lp-results[hidden] { display: none; }

.lp-hit {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.5rem 0.6rem;
  border-radius: var(--radius);
  color: inherit;
  text-decoration: none;
}

.lp-hit:hover,
.lp-hit:focus-visible { background: var(--surface-hover); }

.lp-hit-title {
  flex: 1;
  min-inline-size: 0;
  font-weight: 500;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The way to write a new one, set apart from what was found. */
.lp-hit-new { color: var(--accent-alt); }
.lp-hit + .lp-hit-new:not(.lp-hit-new + .lp-hit-new),
.lp-hit-none + .lp-hit-new {
  margin-block-start: 0.3rem;
  border-block-start: 1px solid var(--border);
  border-start-start-radius: 0;
  border-start-end-radius: 0;
  padding-block-start: 0.7rem;
}

.lp-hit-plus {
  display: grid;
  place-items: center;
  inline-size: 1.9rem;
  block-size: 1.9rem;
  border-radius: var(--radius-sm);
  background: var(--accent-wash);
}

.lp-hit-none {
  margin: 0;
  padding: 0.55rem 0.6rem;
  color: var(--text-dim);
  font-size: 0.9rem;
}

/* ---- words ---- */

.lp-mod .words { block-size: auto; padding: 0; flex: 1; }

.words.tone-note .words-box,
.words.tone-notice .words-box {
  padding: 0.85rem 1rem;
  border-radius: var(--radius);
  border-inline-start: 3px solid var(--hue-open);
  background: rgba(var(--info-rgb), 0.07);
}

.words.tone-notice .words-box {
  border-inline-start-color: var(--accent);
  background: var(--accent-wash);
}

/* A point of the default page: its mark, a short title, one line. */
.words.point {
  display: flex;
  align-items: start;
  gap: 0.8rem;
}

.words.point > .words-box { flex: 1; min-inline-size: 0; }

/* The title is as tall as the mark beside it, so the two sit on one line.
   Weighted past the words box's own h3 and p rules, which come later in this
   sheet and would otherwise win. */
.words.point .words-box h3 { margin: 0 0 0.2rem; font-size: 1rem; line-height: 2.1rem; }
.words.point .words-box p { margin: 0; color: var(--text-dim); font-size: 0.925rem; }

/* ---- boards ---- */

.lp-cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  gap: 0.85rem;
}

.lp-card {
  display: flex;
  align-items: center;
  gap: 0.8rem;
  block-size: 100%;
  padding: 0.95rem 1rem;
  border: 1px solid var(--card-border);
  border-radius: var(--radius-lg);
  background: var(--panel);
  box-shadow: var(--card-shadow);
  color: inherit;
  text-decoration: none;
  transition: border-color 0.15s var(--ease), transform 0.2s var(--ease), box-shadow 0.2s var(--ease);
}

.lp-card:hover {
  border-color: var(--accent-line);
  transform: translateY(-2px);
  box-shadow: var(--shadow-md);
}

.lp-card-body {
  display: grid;
  gap: 0.3rem;
  flex: 1;
  min-inline-size: 0;
}

.lp-card-title {
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.lp-card-go {
  display: flex;
  color: var(--text-dim);
  transform: scaleX(var(--rtl-flip));
  transition: color 0.15s var(--ease);
}

.lp-card:hover .lp-card-go { color: var(--accent); }

.lp-board-mark {
  display: grid;
  place-items: center;
  flex: none;
  inline-size: 2.4rem;
  block-size: 2.4rem;
  border-radius: var(--radius);
  border: 1px solid var(--accent-line);
  background: var(--accent-wash);
  color: var(--accent-alt);
  font-weight: 700;
  font-size: 0.85rem;
}

.lp-rows .lp-board-mark {
  inline-size: 1.9rem;
  block-size: 1.9rem;
  border-radius: var(--radius-sm);
  font-size: 0.75rem;
}

.lp-accepts { display: flex; gap: 0.6rem; }

.lp-org + .lp-org { margin-block-start: 1.1rem; }

.lp-org-name {
  margin: 0 0 0.55rem;
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-dim);
}

/* ---- lists of reports ---- */

/* minmax(0, 1fr), not the default: a grid item is as wide as its content by
   default, and a long title would push the row — and on a phone the page —
   past the edge instead of ending in an ellipsis. */
.lp-rows { display: grid; grid-template-columns: minmax(0, 1fr); gap: 0.1rem; }
.lp-rows .brief { padding-inline: 0.4rem; }
.lp-rows .when,
.lp-rows .card-sub { flex: none; font-size: 0.8rem; color: var(--text-dim); }

.lp-roadmap {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
  gap: 1rem;
}

.lp-roadmap-col .column-name {
  margin: 0 0 0.4rem;
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-dim);
}

/* ---- statistics ---- */

.lp-stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(6rem, 1fr));
  gap: 1rem;
  flex: 1;
  align-content: center;
}

.lp-stat { display: grid; gap: 0.15rem; }

.lp-stat-number {
  font-size: clamp(1.6rem, 1.2rem + 1.2cqi, 2.2rem);
  font-weight: 700;
  line-height: 1.1;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
}

.lp-stat-label {
  font-size: 0.72rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-dim);
}

/* ---- links ---- */

.lp-buttons {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
}

.lp-buttons + .lp-links { margin-block-start: 0.8rem; }

.lp-links { display: grid; gap: 0.35rem; }

.lp-link {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  color: var(--accent-alt);
  font-weight: 500;
  text-decoration: none;
}

.lp-link:hover { text-decoration: underline; }
.lp-out { display: flex; opacity: 0.7; }

/* ---- a picture ---- */

.lp-figure { margin: 0; display: grid; gap: 0.5rem; }

.lp-figure img {
  display: block;
  inline-size: 100%;
  block-size: auto;
  border-radius: var(--radius);
}

.lp-figure figcaption {
  font-size: 0.85rem;
  color: var(--text-dim);
  text-align: center;
}

/* ---- the foot ---- */

.lp-footer {
  margin-block-start: auto;
  border-block-start: 1px solid var(--border);
}

.lp-footer > .lp-inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.75rem 1.5rem;
  padding-block: 1.5rem;
  font-size: 0.875rem;
  color: var(--text-dim);
}

.lp-footer-name {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-weight: 600;
  color: var(--text-main);
}

.lp-footer-links {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem 1rem;
}

.lp-footer-links .lp-link { color: var(--text-dim); font-weight: 400; }
.lp-footer-links .lp-link:hover { color: var(--text-main); }
.lp-made { margin-inline-start: auto; font-size: 0.8rem; }
.lp-made a { color: inherit; text-decoration: none; font-weight: 600; }
.lp-made a:hover,
.lp-made a:focus-visible { color: var(--accent-alt); text-decoration: underline; text-underline-offset: 2px; }

/* -------------------------------------------------------- the page editor
 *
 * The page, with a bar above it and a panel beside it. The panel is a
 * drawer: fixed under the top bar, a border on the side it opens from, and
 * the page keeps the rest of the window.
 */
body > main:has(> .lpe) {
  max-inline-size: none;
  padding: 0;
}

.lpe {
  --lpe-top: 49px;
  display: grid;
  grid-template-columns: minmax(0, 1fr) 22rem;
  grid-template-rows: auto 1fr;
  min-block-size: calc(100vh - var(--lpe-top));
}

.lpe-bar-slot {
  grid-column: 1;
  position: sticky;
  inset-block-start: var(--lpe-top);
  z-index: 20;
}

.lpe-stage { grid-column: 1; min-inline-size: 0; }

.lpe-panel {
  grid-column: 2;
  grid-row: 1 / span 2;
  position: sticky;
  inset-block-start: var(--lpe-top);
  block-size: calc(100vh - var(--lpe-top));
  overflow-y: auto;
  border-inline-start: 1px solid var(--border);
  background: var(--panel);
}

/* The editor on a narrow window: the panel becomes a sheet at the bottom. */
@media (max-width: 60rem) {
  .lpe { grid-template-columns: minmax(0, 1fr); }
  .lpe-panel {
    grid-column: 1;
    grid-row: auto;
    position: fixed;
    inset: auto 0 0;
    z-index: 25;
    block-size: auto;
    max-block-size: 45vh;
    border-inline-start: 0;
    border-block-start: 1px solid var(--border);
    box-shadow: var(--shadow-lg);
  }
  .lpe-panel:has(.lpe-panel-empty) { display: none; }
}

/* ---- the bar ---- */

.lpe-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 1rem;
  padding: 0.5rem 1rem;
  background: var(--canvas-glass);
  backdrop-filter: blur(14px) saturate(140%);
  border-block-end: 1px solid var(--border);
}

.lpe-bar-group { display: flex; align-items: center; gap: 0.35rem; }
.lpe-bar-group.end { margin-inline-start: auto; gap: 0.5rem; }
.lpe-bar .icon-button[aria-pressed="true"] { color: var(--accent); border-color: var(--accent-line); background: var(--accent-wash); }

.lpe-state {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  font-size: 0.8rem;
  color: var(--text-dim);
}

.lpe-state i {
  inline-size: 8px;
  block-size: 8px;
  border-radius: 50%;
  background: var(--success-color);
}

.lpe-state.draft i { background: var(--accent); box-shadow: 0 0 0 3px var(--accent-wash); }
.lpe-state.problem { color: var(--warning-color); font-weight: 500; }

/* ---- the page, as it is being edited ---- */

/* Previewed at a phone's width: the page's own rules answer to its container,
   so this is the phone layout and not a narrowed desktop one. */
.lpe.phone .lpe-canvas {
  max-inline-size: 390px;
  margin: 1.5rem auto 3rem;
  border: 1px solid var(--border-strong);
  border-radius: 28px;
  overflow: hidden;
  box-shadow: var(--shadow-lg);
}

.lp-editing .lp-band {
  outline: 1px dashed transparent;
  outline-offset: -1px;
  transition: outline-color 0.15s var(--ease);
}

.lp-editing .lp-band:hover,
.lp-editing .lp-footer:hover { outline-color: var(--border-strong); }
.lp-editing .lp-band.is-selected,
.lp-editing .lp-footer.is-selected { outline: 2px solid var(--accent); outline-offset: -2px; }

/* The footer is chosen like a section, with its name on the same kind of tag. */
.lp-editing .lp-footer {
  position: relative;
  outline: 1px dashed transparent;
  outline-offset: -1px;
  cursor: pointer;
}

/* Hung on the footer's top edge: the footer is one line tall, and inside it
   the tag covered the name and mark it sits beside. */
.lp-footer > .lpe-band-tools { inset-block-start: -15px; }

.lp-footer:hover > .lpe-band-tools,
.lp-footer.is-selected > .lpe-band-tools,
.lp-footer:focus-within > .lpe-band-tools { display: flex; }

/* An empty line says what goes in it, from the stylesheet: the words are an
   attribute, and content drawn by CSS is not a style the policy governs. */
.lp-footer-text:empty::before {
  content: attr(data-placeholder);
  color: var(--text-dim);
  opacity: 0.6;
}

.lp-editing .lp-footer-text { min-inline-size: 8rem; }

.lpe-tools {
  position: absolute;
  z-index: 6;
  display: none;
  align-items: center;
  gap: 2px;
  padding: 3px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--panel);
  box-shadow: var(--shadow-md);
}

.lpe-band-tools { inset-block-start: 8px; inset-inline-start: 12px; }
.lpe-mod-tools { inset-block-start: -15px; inset-inline-end: 12px; }

.lp-band:hover > .lpe-band-tools,
.lp-band.is-selected > .lpe-band-tools,
.lp-band:focus-within > .lpe-band-tools,
.lp-mod:hover > .lpe-mod-tools,
.lp-mod.is-selected > .lpe-mod-tools,
.lp-mod:focus-within > .lpe-mod-tools { display: flex; }

/* A module's tools win over its band's while the pointer is on the module. */
.lp-band:has(.lp-mod:hover) > .lpe-band-tools:not(.lp-band.is-selected > *) { display: none; }

.lpe-tool,
.lpe-grip {
  display: grid;
  place-items: center;
  inline-size: 26px;
  block-size: 26px;
  padding: 0;
  border: 0;
  border-radius: 7px;
  background: transparent;
  color: var(--text-dim);
  cursor: pointer;
}

.lpe-tool:hover { background: var(--surface-hover); color: var(--text-main); }
.lpe-tool[data-danger]:hover { color: var(--danger-color); background: rgba(var(--danger-rgb), 0.08); }
.lpe-tool:disabled { opacity: 0.35; cursor: default; }
/* Up and down are the chevron turned a quarter. Fixed on purpose: up and down
   are the same in every reading direction. */
.lpe-tool[data-turn="up"] .icon { transform: rotate(-90deg); }
.lpe-tool[data-turn="down"] .icon { transform: rotate(90deg); }
.lpe-grip { cursor: grab; }
.lpe-grip:active { cursor: grabbing; }

.lpe-tag {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  block-size: 26px;
  padding: 0 0.5rem;
  border: 0;
  border-radius: 7px;
  background: var(--accent-wash);
  color: var(--accent-alt);
  font-family: var(--font-mono);
  font-size: 0.65rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  cursor: pointer;
}

.lp-editing .lp-mod {
  position: relative;
  cursor: pointer;
  outline: 1px dashed transparent;
  outline-offset: 5px;
  border-radius: var(--radius-lg);
}

.lp-editing .lp-mod:hover { outline-color: var(--border-strong); }
.lp-editing .lp-mod.is-selected,
.lp-editing .lp-mod:focus-visible { outline: 2px solid var(--accent); outline-offset: 5px; }
.lp-editing .lp-mod.dragging { opacity: 0.35; }

/* Nothing animates in while it is being edited: every change redraws. */
.lp-editing .lp-mod,
.lp-editing .lp-heading > * { animation: none; }

.lp-editing [contenteditable="true"] {
  cursor: text;
  border-radius: 6px;
  outline: 1px dashed transparent;
  outline-offset: 4px;
}

.lp-editing [contenteditable="true"]:hover { outline-color: var(--border-strong); }
.lp-editing [contenteditable="true"]:focus { outline: 2px solid var(--accent-line); }

.lpe-drop {
  block-size: 3px;
  border-radius: 2px;
  background: var(--accent);
  box-shadow: 0 0 12px rgba(var(--accent-rgb), 0.7);
}

.lpe-add {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.4rem;
  min-block-size: 2.25rem;
  padding: 0.4rem;
  border: 1px dashed var(--accent-line);
  border-radius: var(--radius);
  background: transparent;
  color: var(--accent-alt);
  font: inherit;
  font-size: 0.85rem;
  cursor: pointer;
  opacity: 0;
  transition: opacity 0.15s var(--ease), background 0.15s var(--ease);
}

.lp-editing .lp-mod:has(.words-bar:not([hidden])) { z-index: 8; }

.lpe-add.empty { flex: 1; min-block-size: 6rem; opacity: 1; }
.lp-col:hover > .lpe-add,
.lpe-add:focus-visible { opacity: 1; }
.lpe-add:hover { background: var(--accent-wash); }

/* Between two sections: the way to put one there. */
.lpe-insert {
  position: relative;
  z-index: 7;
  block-size: 0;
}

.lpe-insert > button {
  position: absolute;
  inset-inline-start: 50%;
  inset-block-start: 0;
  transform: translate(calc(-50% * var(--rtl-flip)), -50%);
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  padding: 0.2rem 0.75rem;
  border: 1px solid var(--accent-line);
  border-radius: 999px;
  background: var(--panel);
  color: var(--accent-alt);
  font: inherit;
  font-size: 0.75rem;
  white-space: nowrap;
  cursor: pointer;
  transition: color 0.15s var(--ease), border-color 0.15s var(--ease), background 0.15s var(--ease);
}

/* Quiet at rest, and still solid: the pill sits on the line between two bands
   and on the frames of the modules, so it dims its ink, never itself — an
   opacity on the button let every line behind it show through. The wash on
   hover is laid over the panel for the same reason. */
.lpe-insert > button > * { opacity: 0.6; transition: opacity 0.15s var(--ease); }
.lpe-insert:first-child > button { transform: translate(calc(-50% * var(--rtl-flip)), 25%); }
.lpe-insert > button:hover,
.lpe-insert > button:focus-visible {
  border-color: var(--accent);
  background: linear-gradient(var(--accent-wash), var(--accent-wash)), var(--panel);
}
.lpe-insert > button:hover > *,
.lpe-insert > button:focus-visible > * { opacity: 1; }

/* ---- the panel ---- */

.lpe-panel-empty {
  display: grid;
  justify-items: center;
  gap: 0.75rem;
  padding: 3rem 1.5rem;
  color: var(--text-dim);
  text-align: center;
}

.lpe-panel-head {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.9rem 1rem;
  border-block-end: 1px solid var(--border);
}

.lpe-panel-icon { display: flex; color: var(--accent); }

.lpe-panel-head h2 {
  flex: 1;
  margin: 0;
  font-size: 0.8rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.lpe-close {
  display: grid;
  place-items: center;
  inline-size: 30px;
  block-size: 30px;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: transparent;
  color: var(--text-dim);
  cursor: pointer;
}

.lpe-close:hover { color: var(--accent); border-color: var(--accent-line); }

.lpe-tabs {
  display: flex;
  border-block-end: 1px solid var(--border);
  padding-inline: 0.5rem;
}

.lpe-tab {
  padding: 0.65rem 0.75rem;
  border: 0;
  border-block-end: 2px solid transparent;
  background: transparent;
  color: var(--text-dim);
  font: inherit;
  font-size: 0.85rem;
  font-weight: 500;
  cursor: pointer;
}

.lpe-tab.on { color: var(--accent-alt); border-block-end-color: var(--accent); }

.lpe-panel-body { padding: 1rem; }
.lpe-panel-body .field { margin-block-end: 1rem; }
/* `.field:last-of-type` zeroes the last div, and in a panel the last div can
   be followed by a checkbox or a hint — the logo's size sat against "Show
   search". Only what is really last loses its margin here. */
.lpe-panel-body .field:not(:last-child) { margin-block-end: 1rem; }
.lpe-panel-body .field > .label { display: block; font-weight: 600; margin-block-end: 0.35rem; }
.lpe-panel-body .hint { font-size: 0.8rem; }
.lpe-seg { margin-block-start: 0; }
.lpe-seg .segment { padding: 6px; }

/* Weighted past `.field label`, which makes a label a block of bold text: a
   checkbox inside a field lost its words to a line of its own. */
.lpe-panel-body .lpe-check {
  display: flex;
  font-weight: 400;
  align-items: center;
  gap: 0.5rem;
  margin-block-end: 0.75rem;
  font-size: 0.9rem;
  cursor: pointer;
}

/* A box, not a field: inside a field every input is as wide as the column. */
.lpe-panel-body .lpe-check input { inline-size: auto; flex: none; margin: 0; }

.lpe-layouts {
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  gap: 6px;
}

.lpe-layout {
  display: grid;
  place-items: center;
  block-size: 38px;
  padding: 0 7px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: transparent;
  color: var(--text-dim);
  cursor: pointer;
}

.lpe-layout:hover { color: var(--text-main); }
.lpe-layout.on { color: var(--accent); border-color: var(--accent); background: var(--accent-wash); }

.lpe-diagram {
  display: flex;
  gap: 3px;
  inline-size: 100%;
  block-size: 16px;
}

.lpe-diagram i { flex: 1; border-radius: 2px; background: currentColor; opacity: 0.55; }
.lpe-diagram i.w2 { flex: 2; }
.picker-icon .lpe-diagram { inline-size: 34px; block-size: 22px; }

.lpe-links { display: grid; grid-template-columns: minmax(0, 1fr); gap: 0.6rem; margin-block-end: 0.75rem; }

.lpe-link {
  display: grid;
  /* minmax(0, 1fr): a grid item is as wide as its content by default, and a
     select with a long board name pushed the box out of the panel. */
  grid-template-columns: minmax(0, 1fr);
  gap: 0.45rem;
  padding: 0.6rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

.lpe-link-head { display: flex; align-items: center; gap: 4px; }
.lpe-link-head input { flex: 1; min-inline-size: 0; }
.lpe-link .lpe-check { margin: 0; }

.lpe-image { display: grid; gap: 0.6rem; margin-block-end: 1rem; }

.lpe-image img {
  max-inline-size: 100%;
  max-block-size: 10rem;
  justify-self: center;
  border-radius: var(--radius-sm);
}

.lpe-image-none {
  display: grid;
  place-items: center;
  block-size: 6rem;
  border: 1px dashed var(--border-strong);
  border-radius: var(--radius);
  color: var(--text-dim);
}

.lpe-image-actions { display: flex; gap: 0.5rem; flex-wrap: wrap; }

/* Restoring the default is the one change here that replaces everything, so
   it stands apart at the foot of the page settings. */
.lpe-restore {
  display: grid;
  gap: 0.5rem;
  justify-items: start;
  margin-block-start: 1.5rem;
  padding-block-start: 1rem;
  border-block-start: 1px solid var(--border);
}

/* ---- the pickers ---- */

.lpe-pick-group + .lpe-pick-group { margin-block-start: 1.5rem; }

.lpe-group-name {
  margin: 0 0 0.75rem;
  font-family: var(--font-mono);
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-dim);
}

.lpe-picker .picker-grid { gap: 14px; }
.lpe-picker .picker-card { padding: 16px; }
.lpe-picker .picker-icon { inline-size: 52px; block-size: 52px; margin-block-end: 12px; }

/* ---------------------------------------------------------------- wizard */

/*
 * The wizard, in the shape the rest of the house uses: one narrow card holding
 * the mark, where you are, the fields and the way on — rather than a mark
 * floating above a card with a heading loose between them, which is three
 * groups pretending to be one.
 */
.wizard {
  display: grid;
  justify-items: center;
  /* No room of its own: the page around it already gives 40px above and 80px
     below, and adding more pushed the tallest step — the account, with its
     picture and three fields — off the bottom of the window. */
  padding-block: 0;
}

.wizard form,
.wizard-done {
  inline-size: min(26rem, 100%);
}

/* The end of the first run: what comes next, said once, centred. */
.wizard-next {
  margin: 0;
  text-align: center;
  color: var(--text-dim);
  font-size: 0.9rem;
  line-height: 1.55;
}

.wizard-card {
  display: grid;
  gap: 1.25rem;
  justify-items: stretch;
  padding: clamp(1.5rem, 4vw, 2rem);
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-md);
}

.wizard-mark {
  justify-self: center;
}

.wizard-head {
  display: grid;
  justify-items: center;
  gap: 0.75rem;
  text-align: center;
}

/* Where you are, in the house's caption voice: small, spaced, quiet. It says
   the name of the step, so the row of dots below it does not have to. */
.wizard-where {
  margin: 0;
  font-family: inherit;
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-dim);
}

.wizard-head .lead {
  margin: 0;
  font-size: 0.95rem;
  color: var(--text-dim);
}

/* The dots: a line with a circle on each step and a ring around the one you are
   on. The names live in the markup for a screen reader and are not drawn — the
   caption above already said which step this is, and three names on one line is
   what stopped the card being narrow. */
.steps {
  display: flex;
  align-items: center;
  gap: 0;
  margin: 0;
  padding: 0;
  list-style: none;
  inline-size: min(15rem, 100%);
}

.steps li {
  display: flex;
  align-items: center;
  flex: 1;
}

.steps li:last-child {
  flex: 0;
}

.steps li::after {
  content: "";
  flex: 1;
  block-size: 1px;
  background: var(--border-strong);
}

.steps li:last-child::after {
  display: none;
}

.steps .dot {
  inline-size: 9px;
  block-size: 9px;
  border-radius: 50%;
  background: var(--border-strong);
}

.steps .done .dot {
  background: var(--accent);
}

.steps .done::after {
  background: var(--accent);
}

.steps .now .dot {
  background: var(--accent);
  box-shadow: 0 0 0 4px rgba(var(--accent-rgb), 0.25);
}

/* In the markup, out of the picture: a screen reader gets "step 2 of 3" and the
   name of each one without being able to see a row of circles. */
.visually-hidden {
  position: absolute;
  inline-size: 1px;
  block-size: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  white-space: nowrap;
  clip-path: inset(50%);
  border: 0;
}

/* One spacing between fields, 10px. The grid's gap used to add to
   the margin every .field carries elsewhere, and the setup grew so tall it
   was cut off on a 27-inch screen. */
.wizard-fields {
  display: grid;
  gap: 10px;
}
.wizard-fields > .field { margin-block-end: 0; }

/* The field labels in the card speak the same caption voice as the step name.
   Scoped to the card on purpose: the forms a visitor fills in are prose with a
   question above them, and shouting at somebody reporting a bug is not the
   house's voice. */
.wizard-card .field > label {
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-dim);
}

/* The way on, full width, with the way back under it as a plain link. A primary
   action tucked in a corner of a narrow card reads like a footnote. */
.wizard-actions {
  display: grid;
  gap: 0.5rem;
  justify-items: stretch;
}

.wizard-actions .button.primary {
  inline-size: 100%;
  justify-content: center;
  letter-spacing: 0.04em;
  box-shadow: var(--accent-glow);
}

.wizard-actions .button.ghost {
  justify-self: center;
  justify-content: center;
  background: none;
  border: 0;
}

/* ---------------------------------------------------------------- fields */

.field {
  margin-bottom: 1.25rem;
}

.field:last-of-type {
  margin-bottom: 0;
}

.field label {
  display: block;
  font-weight: 600;
  margin-bottom: 0.35rem;
}

.field .hint {
  margin: 0.4rem 0 0;
  font-size: 0.875rem;
  color: var(--text-dim);
}

/* An example is worth more than a paragraph of prose about the format, and it
   is the thing the operator asked for: a field that shows what goes in it. */
.field .example {
  font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  font-size: 0.9em;
  color: var(--text-main);
  background: rgba(127, 127, 127, 0.14);
  border-radius: 5px;
  padding: 0.1rem 0.35rem;
  /* A URL or an account name is latin inside an Arabic sentence: without the
     isolate the punctuation around it is placed by the paragraph. */
  unicode-bidi: isolate;
}

.field input,
.field textarea {
  inline-size: 100%;
  margin: 0;
}

/* ------------------------------------------------------------- admin shell
 *
 * The public side is a page. The administration is an application, and an
 * application shows what it can do down one side rather than making somebody
 * remember the URLs. Until now the panel was a page with a list and a button,
 * so the answer to "what can I do here?" was "whatever this screen happens to
 * be showing".
 *
 * The public nav is hidden here instead of stacked on top: two navigations, one
 * horizontal and one vertical, is how a screen stops having an obvious start.
 */
/* The flag on <body> is deliberately not the same word as the layout class on
 * the wrapper. It was, once, and `.admin { display: grid }` matched the body as
 * well — so the body became the grid, #app landed in its first column, and the
 * content rendered 254px wide inside a 1225px page. Nothing about the markup
 * looked wrong; only measuring the boxes showed it. */
/*
 * The bar stays. It used to disappear inside the panel, which left the one
 * control that opens and closes the panel reachable only from outside it — so
 * the way back was the browser's button. The bar is on every screen, and
 * the panel toggle is what opens and closes it.
 */
body.admin-view > nav {
  display: block;
}

body.admin-view > main {
  max-inline-size: none;
  margin: 0;
  padding: 0;
}

/* Laid on its own ground: one solid colour rather than the public page's dark
   ground with its glow — the panel is a tool, and reads as one surface you work on.
   The menu and the cards are the sheet, a step above it, so nothing is the colour
   of what it sits on. */
.admin {
  display: grid;
  grid-template-columns: 16rem 1fr;
  min-block-size: calc(100vh - 48px);
  background: var(--ground);
}

/* Merged from the two declared at the top level. The sidebar is the one surface
   that does not float: it is the wall the application hangs on, so it takes the
   recessed colour and lets the content beside it be the thing with depth. The
   third `.side` further down is inside a media query — the narrow strip — and
   is a different rule, not a copy of this one. */
.side {
  border-inline-end: 1px solid var(--border);
  /* The menu column: the sheet. */
  background: var(--sheet);
  padding: 1.25rem 0.9rem;
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  /* Held under the bar, and only as tall as what is left of the window. It
     was 100vh tall from the top of the window but hung below the bar, so its
     foot sat 49px past the bottom edge — the bar's 48 and its border — and
     "Sign out" was below the fold on every screen, a 27-inch one included.
     dvh rather than vh where it exists, so a phone's browser bar is counted
     too. */
  position: sticky;
  top: 49px;
  block-size: calc(100vh - 49px);
  block-size: calc(100dvh - 49px);
  overflow-y: auto;
}

/* The screen scrolls inside its own column, and not the document. The public pages hide the document's scrollbar and the
   panel shows it, so a panel that scrolled the document narrowed the window
   by the bar's width the moment it opened, and every icon in the bar above
   slid 10px sideways under the pointer. Wide screens only: on a phone the
   bar overlays the page and takes no width. */
@media (min-width: 52.0625rem) {
  .admin {
    block-size: calc(100vh - 49px);
    block-size: calc(100dvh - 49px);
    min-block-size: 0;
  }
  .admin-main { overflow-y: auto; }
  /* The menu has grown by a screen at a time, and its foot — sign out — has to
     stay on the window. Eight pixels above and below each row, on the sidebar
     only: the strip a phone gets keeps the taller rows a thumb needs. */
  .side .side-item { padding-block: 8px; }
  /* And the document never scrolls under it. Measured: with nothing to
     scroll, a fraction of a pixel of the bar's border was enough for the
     browser to draw a scrollbar and take its 10px anyway. */
  html:has(> body.admin-view) { overflow: hidden; }
}

/* Who is acting: a 64px picture, the name at 16px and the role in a small
   monospaced badge, centred over a rule that runs out to the sidebar's
   edges. */
.side-head {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  margin: -1.25rem -0.9rem 0.75rem;
  padding: 32px 16px 24px;
  border-block-end: 1px solid var(--border);
  text-align: center;
}

.side-head .avatar {
  inline-size: 64px;
  block-size: 64px;
  font-size: 24px;
  margin-block-end: 6px;
}

.side-head .who {
  font-size: 16px;
  font-weight: 600;
  line-height: 1.2;
  color: var(--text-main);
  overflow-wrap: anywhere;
}

.side-role {
  padding: 2px 10px;
  border: 1px solid rgba(var(--accent-rgb), 0.2);
  border-radius: 2px;
  background: rgba(var(--accent-rgb), 0.1);
  color: var(--accent);
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

/* A section label: monospaced, ten pixels, quiet. */
.side-label {
  margin: 1rem 0 0.35rem;
  padding-inline-start: 16px;
  font-family: var(--font-mono);
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-dim);
}

/* A menu item: 13px, dimmed until it is pointed at or chosen. */
.side-item {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 16px;
  border-radius: 6px;
  color: var(--text-dim);
  text-decoration: none;
  font-size: 13px;
  line-height: 1.3;
  transition: background-color 0.15s ease, color 0.15s ease;
}

/* Pointed at: the accent, faintly, and the words brought up. It was a
   neutral grey, which read as a different product the moment the pointer
   moved. */
.side-item:hover {
  background: var(--surface-hover);
  color: var(--text-main);
}

/*
 * What is waiting, beside the place it is waiting in.
 *
 * The shape is .count-chip's and the colour is the one this product already
 * uses for "waiting" — neither is new. A badge in the accent would compete with
 * the current-page marker, which is the accent, and the two would be shouting
 * the same colour at each other on the one item that is usually both.
 *
 * margin-inline-start rather than margin-left: this sits at the far end of the
 * item, which is the left one in Arabic.
 */
.side-count {
  margin-inline-start: auto;
  padding: 0.05rem 0.45rem;
  border-radius: 999px;
  border: 1px solid color-mix(in srgb, var(--hue-hold) 40%, transparent);
  background: color-mix(in srgb, var(--hue-hold) 12%, transparent);
  color: var(--hue-hold);
  font-size: 0.75rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  line-height: 1.5;
}

/*
 * The current page, wearing what the house wears: a wash of the accent, the
 * mark in accent, and a bar of it on the edge that faces the content — not the
 * outer one. The row also loses its rounding and its margin on that side, so it
 * runs into the panel it opened rather than floating beside it.
 *
 * The side matters and is easy to get backwards: the bar is a join between the
 * chosen row and what it is showing, so it belongs where they meet.
 *
 * It used to be accent all through, which made the chosen item the loudest
 * thing on the screen and every other one look switched off.
 *
 * aria-current is on it in the markup as well: a colour and a bar are not
 * something a screen reader can hear.
 */
.side-item[aria-current="page"] {
  /* The house's strength for a chosen row: a little more of it in the dark,
     where a light wash disappears into the surface. */
  background: rgba(var(--accent-rgb), 0.12);
  color: var(--text-main);
  border-inline-end: 2px solid var(--accent);
  /* Out to the sidebar's own edge: the column is padded by 0.9rem. Logical
     radii rather than the four-value shorthand, which is physical and would
     square the wrong corners where the line runs the other way. */
  margin-inline-end: -0.9rem;
  border-start-end-radius: 0;
  border-end-end-radius: 0;
}

.side-item.sub {
  padding-inline-start: 1.6rem;
  font-size: 0.9rem;
  color: var(--text-dim);
}

.side-item.sub[aria-current="page"] {
  color: var(--text-main);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .side-item[aria-current="page"] {
    background: rgba(var(--accent-rgb), 0.2);
  }
}

:root[data-theme="dark"] .side-item[aria-current="page"] {
  background: rgba(var(--accent-rgb), 0.2);
}

.side-foot {
  margin-top: auto;
  padding-top: 1rem;
  border-top: 1px solid var(--border);
  display: grid;
  gap: 0.5rem;
}

/* The panel uses the window it is given.
 *
 * It was clamped to 52rem, so on any screen wider than about 900px everything
 * sat in a column on the left with the rest of the page empty — cards half the
 * size they could be, three to a row where nine would fit. A measure that long
 * is right for a paragraph and wrong for a dashboard.
 *
 * The cap that remains is for very wide screens, where a grid stretched across
 * two metres of monitor stops being a layout and becomes a spreadsheet. */
/* The column runs to the window's edge: it is what scrolls, and a scroller
   with a maximum width puts its bar in the middle of a wide screen — 344px
   short of the edge at 2200px, measured. The measure is its content's,
   written with no weight so any width a screen sets for itself still wins. */
.admin-main {
  padding: clamp(1.25rem, 3vw, 2.25rem);
}
:where(.admin-main > *) { max-inline-size: 100rem; }

/* Prose still gets a measure. A settings screen that is one form does not want
   the width of a dashboard: a line of help two thousand pixels long is a line
   nobody reads to the end of. */
.admin-main > .lead,
.admin-main > .hint,
.admin-main > p {
  max-inline-size: 46rem;
}

/* A form on its own screen keeps a sane width rather than stretching. */
.admin-main form:not(.dashboard-form) > .wizard-card {
  max-inline-size: 46rem;
}

/* Narrow: the sidebar becomes a strip across the top that scrolls sideways.
   A drawer would need a button, a state and a trap; a strip needs none of it
   and never hides where you are. */
@media (max-width: 52rem) {
  /* Two rows: the strip as tall as its links, the screen the rest. Left to
     auto, a grid with a minimum height shares what is left over between its
     rows, so on a short screen the strip grew to 300px with its links
     floating in the middle of it. */
  .admin {
    grid-template-columns: 1fr;
    grid-template-rows: auto 1fr;
  }

  .side {
    position: static;
    block-size: auto;
    flex-direction: row;
    align-items: center;
    gap: 0.4rem;
    overflow-x: auto;
    border-inline-end: 0;
    border-block-end: 1px solid var(--border);
    padding: 0.6rem 0.75rem;
  }

  /* In a strip there is no room for the header: the name is on the Profile
     screen anyway, and a band only has space for the places to go. */
  .side-head,
  .side-label {
    display: none;
  }

  .side-item {
    white-space: nowrap;
  }

  /* In a strip the content is below, so that is where the bar goes, and the
     row is rounded again on every corner. */
  .side-item[aria-current="page"] {
    border-inline-end: 0;
    border-block-end: 2px solid var(--accent);
    margin-inline-end: 0;
    border-start-end-radius: 8px;
    border-end-end-radius: 8px;
  }

  /* The links live inside a <nav> and it stayed a block, so the strip laid
     out three things in a row — the nav, the public link and the foot — and
     the nav stacked its eight links down the middle of it. */
  .side nav {
    display: flex;
    align-items: center;
    gap: 0.4rem;
  }

  /* In a strip, what separates the way out from the menu is a rule beside it,
     not above it. Written through .side because the rule for the sidebar
     comes later in the file and would otherwise keep its top border and its
     margin here. */
  .side .side-public {
    margin: 0;
    margin-inline-start: 0.4rem;
    padding-block: 0.5rem;
    padding-inline-start: 0.9rem;
    border-block-start: 0;
    border-inline-start: 1px solid var(--border);
    border-radius: 0;
    white-space: nowrap;
  }

  /* To the end of the strip, in the reading direction: a physical left margin
     would push it the wrong way in Arabic. */
  .side-foot {
    margin: 0;
    margin-inline-start: auto;
    padding: 0;
    border: 0;
    grid-auto-flow: column;
    align-items: center;
  }

  .side-foot .link { white-space: nowrap; }
}

/* The theme choice: three tiles with a drawing on each, the chosen one outlined
   in the accent. It was a segmented bar of three words — a control somebody
   reads instead of one they recognise, and the house answers this question by
   pointing. */
.theme-choice {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 0.6rem;
}

.theme-choice button {
  display: grid;
  justify-items: center;
  align-content: center;
  gap: 0.5rem;
  min-block-size: 5.25rem;
  padding: 0.8rem 0.4rem;
  font: inherit;
  font-size: 0.85rem;
  color: var(--text-dim);
  background: var(--surface-dim);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  cursor: pointer;
  transition: color 0.15s var(--ease), border-color 0.15s var(--ease);
}

.theme-choice button:hover {
  color: var(--text-main);
  border-color: var(--border-strong);
}

.theme-choice button .icon {
  inline-size: 20px;
  block-size: 20px;
}

.theme-choice button[aria-pressed="true"] {
  color: var(--text-main);
  border-color: var(--accent);
  box-shadow: 0 0 0 1px var(--accent), var(--accent-glow);
}

.theme-choice button[aria-pressed="true"] .icon {
  color: var(--accent);
}

/* --------------------------------------------------------- panel sections */

.people {
  list-style: none;
  padding: 0;
  margin: 1rem 0 2rem;
  display: grid;
  gap: 0.4rem;
}

.person {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.65rem 0.9rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--panel);
}

.person-name {
  font-weight: 600;
}

.person-role {
  font-size: 0.85rem;
  color: var(--text-dim);
}

.facts {
  list-style: none;
  padding: 0;
  margin: 1rem 0;
  display: grid;
  gap: 0.4rem;
  max-inline-size: 34rem;
}

.facts li {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.55rem 0.9rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

.facts code {
  font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  font-size: 0.875rem;
  color: var(--text-dim);
  unicode-bidi: isolate;
}

.admin-main h2 {
  margin-block-start: 2.25rem;
  font-size: 1.15rem;
}

/* The wizard card is reused for the settings forms, so it needs to stop being
   as wide as the reading column when it holds two fields. */
.admin-main .wizard-card {
  max-inline-size: 34rem;
}

.admin-main .wizard-actions {
  max-inline-size: 34rem;
}

/* ------------------------------------------------------------------- icons */

.icon {
  flex: none;
  vertical-align: -0.15em;
}

.button.with-icon {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
}

/* A button that is only an icon still needs a hit area a thumb can find. */
.icon-button {
  display: inline-grid;
  place-items: center;
  inline-size: 2rem;
  block-size: 2rem;
  padding: 0;
  border: 1px solid transparent;
  border-radius: 8px;
  background: transparent;
  color: var(--text-dim);
  cursor: pointer;
}

.icon-button:hover {
  background: rgba(127, 127, 127, 0.14);
  color: var(--text-main);
}

.icon-button.danger:hover {
  color: var(--danger-color);
  background: rgba(var(--danger-rgb), 0.12);
}

.side-item .icon {
  color: var(--text-dim);
}

.side-item[aria-current="page"] .icon {
  color: var(--accent);
}

/* ------------------------------------------------------------------ modals */

/* The shadow was declared again on its own further down and that is the one
   that applied; the hand-written value here had been dead for a long time. The
   radius still comes from the shared rule further down, where .modal is the
   last selector of a list that also dresses the cards — which is why that rule
   is left exactly where it is. */
.modal {
  border: 1px solid var(--border);
  border-radius: calc(var(--radius) + 6px);
  background: var(--canvas);
  color: var(--text-main);
  padding: 0;
  inline-size: min(34rem, calc(100vw - 2rem));
  box-shadow: var(--shadow-lg);
}

.modal::backdrop {
  background: rgba(0, 0, 0, 0.55);
  backdrop-filter: blur(2px);
}

.modal form {
  display: block;
  margin: 0;
  padding: 1.25rem;
}

.modal-head {
  display: flex;
  align-items: start;
  justify-content: space-between;
  gap: 1rem;
}

.modal-head h2 {
  margin: 0;
  font-size: 1.2rem;
}

.modal .lead {
  color: var(--text-dim);
  margin: 0.35rem 0 0;
}

.modal-body {
  margin-top: 1.1rem;
}

.modal-foot {
  display: flex;
  justify-content: flex-end;
  gap: 0.6rem;
  margin-top: 1.4rem;
}

/* ------------------------------------------------------------------- cards */

/* The house's card grid. Named for what it is rather than for its columns, so
   a screen with three cards and one with nine use the same word. */
.bento {
  display: grid;
  /* auto-fit, so the only card on a screen takes the row instead of sitting a
     quarter of the way across it with three columns of nothing beside it. */
  grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr));
  gap: 0.9rem;
  margin: 1.25rem 0 2rem;
  padding: 0;
  list-style: none;
}

.bento-card {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  padding: 1.1rem;
  border: 1px solid var(--border);
  border-radius: calc(var(--radius) + 2px);
  background: var(--panel);
  color: inherit;
  text-decoration: none;
  transition: border-color 0.15s ease, transform 0.15s ease;
}

a.bento-card:hover {
  border-color: rgba(var(--accent-rgb), 0.55);
  transform: translateY(-1px);
}

.bento-card .card-head {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  color: var(--accent);
}

.bento-card h3 {
  margin: 0;
  font-size: 1.05rem;
  color: var(--text-main);
}

.bento-card .card-sub {
  color: var(--text-dim);
  font-size: 0.875rem;
}

/* The row of controls on a card, which appears on hover and on focus — on
   focus because a keyboard never hovers, and a control that only a mouse can
   reach is one half the people cannot use. */
.card-actions {
  position: absolute;
  inset-block-start: 0.6rem;
  inset-inline-end: 0.6rem;
  display: flex;
  gap: 0.15rem;
  opacity: 0;
  transition: opacity 0.15s ease;
}

.bento-card:hover .card-actions,
.card-actions:focus-within {
  opacity: 1;
}

@media (hover: none) {
  .card-actions {
    opacity: 1;
  }
}

/* ------------------------------------------------------------------- stats */

.stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
  gap: 0.75rem;
  margin: 1.25rem 0 2rem;
  padding: 0;
  list-style: none;
}

.stat {
  padding: 0.9rem 1rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--panel);
}

/* A stat that is a link is still a card. Without this the anchor stays inline,
   the list item keeps no border, and the number floats over the one below. */
a.stat {
  display: block;
  color: inherit;
  text-decoration: none;
}

a.stat:hover {
  border-color: rgba(var(--accent-rgb), 0.55);
}

.stats > li {
  display: contents;
}

.stat .value {
  display: block;
  font-size: 1.9rem;
  font-weight: 700;
  line-height: 1.1;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
}

.stat .label {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  margin-top: 0.2rem;
  font-size: 0.85rem;
  color: var(--text-dim);
}

/* A bar per status, drawn from the numbers rather than from a chart library:
   one dependency for eight rectangles is one dependency too many. */
.bars {
  display: grid;
  gap: 0.55rem;
  margin: 1rem 0 2rem;
  max-inline-size: 40rem;
}

.bar-row {
  display: grid;
  grid-template-columns: 9rem 1fr 3rem;
  align-items: center;
  gap: 0.75rem;
  font-size: 0.9rem;
}

/*
 * The track and the fill are spans, so they have to be told to be boxes.
 *
 * They were not, and an inline box ignores both width and height: the fill was
 * measured at zero pixels wide on every card. The panel drew a row of empty
 * grey tracks for a year and looked, at a glance, like a chart with nothing in
 * it — which is exactly what it was. Found by measuring the element rather than
 * by looking at the screenshot, where an empty track and a full one at low
 * contrast are the same picture.
 */
.bar-track {
  display: block;
  block-size: 0.55rem;
  border-radius: 999px;
  background: rgba(127, 127, 127, 0.16);
  overflow: hidden;
}

/* Every width a bar can be, in twentieths. It was an inline percentage; a
   relative bar reads the same at this granularity and needs no style
   attribute to say it. */
.bar-fill[data-fill="0"] { inline-size: 0; }
.bar-fill[data-fill="1"] { inline-size: 5%; }
.bar-fill[data-fill="2"] { inline-size: 10%; }
.bar-fill[data-fill="3"] { inline-size: 15%; }
.bar-fill[data-fill="4"] { inline-size: 20%; }
.bar-fill[data-fill="5"] { inline-size: 25%; }
.bar-fill[data-fill="6"] { inline-size: 30%; }
.bar-fill[data-fill="7"] { inline-size: 35%; }
.bar-fill[data-fill="8"] { inline-size: 40%; }
.bar-fill[data-fill="9"] { inline-size: 45%; }
.bar-fill[data-fill="10"] { inline-size: 50%; }
.bar-fill[data-fill="11"] { inline-size: 55%; }
.bar-fill[data-fill="12"] { inline-size: 60%; }
.bar-fill[data-fill="13"] { inline-size: 65%; }
.bar-fill[data-fill="14"] { inline-size: 70%; }
.bar-fill[data-fill="15"] { inline-size: 75%; }
.bar-fill[data-fill="16"] { inline-size: 80%; }
.bar-fill[data-fill="17"] { inline-size: 85%; }
.bar-fill[data-fill="18"] { inline-size: 90%; }
.bar-fill[data-fill="19"] { inline-size: 95%; }
.bar-fill[data-fill="20"] { inline-size: 100%; }

.bar-fill {
  display: block;
  block-size: 100%;
  /* Enough to be seen at one out of a hundred: a bar that rounds to nothing
     says "none" where the number beside it says one. */
  min-inline-size: 0.55rem;
  border-radius: 999px;
  background: linear-gradient(90deg, rgba(var(--accent-rgb), 0.75), var(--accent));
}

.bar-row .count {
  text-align: end;
  color: var(--text-dim);
  font-variant-numeric: tabular-nums;
}

/* ------------------------------------------------------------------ avatar */

.avatar {
  display: grid;
  place-items: center;
  inline-size: 3rem;
  block-size: 3rem;
  border-radius: 50%;
  background: rgba(var(--accent-rgb), 0.16);
  color: var(--accent-alt);
  font-weight: 700;
  letter-spacing: 0.02em;
  overflow: hidden;
  flex: none;
}

.avatar img {
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
}

.avatar.big {
  inline-size: 5rem;
  block-size: 5rem;
  font-size: 1.75rem;
}

.identity {
  display: flex;
  align-items: center;
  gap: 1rem;
  margin: 1rem 0 1.75rem;
}

.identity .name {
  font-size: 1.15rem;
  font-weight: 600;
}

/* A short, monospaced label for a role or a state. */
.tag {
  display: inline-block;
  padding: 0.1rem 0.45rem;
  border-radius: 6px;
  background: rgba(127, 127, 127, 0.14);
  color: var(--text-dim);
  font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  font-size: 0.75rem;
}

.tag.accent {
  background: rgba(var(--accent-rgb), 0.16);
  color: var(--accent-alt);
}

/* --------------------------------------------------- the rest of the panel */

.screen-head {
  display: flex;
  align-items: center;
  gap: 1rem;
  flex-wrap: wrap;
}

.screen-head h1 {
  margin: 0;
  flex: 1;
}

/* Sized by the caller through a variable rather than by the class, which used
   to win over the width attribute and shrank the sign-in mark to the size of a
   favicon. */
/*
 * The sizes a mark comes in, as classes.
 *
 * They were a --mark-size in a style attribute, which the policy this is served
 * under does not allow — and there are only a few in the whole product, so a
 * class each says the same thing with nothing dynamic about it.
 */
.mark-24 { --mark-size: 24px; }
.mark-26 { --mark-size: 26px; }
.mark-44 { --mark-size: 44px; }

.brand-logo {
  inline-size: var(--mark-size, 1.5rem);
  block-size: var(--mark-size, 1.5rem);
  border-radius: 6px;
  object-fit: contain;
}

.logo-preview {
  inline-size: 3rem;
  block-size: 3rem;
  border-radius: 8px;
  object-fit: cover;
}

/* A card whose whole face is a link, with its controls floating over the
   corner. The link is the <a>, not the <li>, so the buttons are not inside it —
   a button inside a link is a browser deciding which one you meant. */
.card-link {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  color: inherit;
  text-decoration: none;
}

.bento-card:has(.card-link):hover {
  border-color: rgba(var(--accent-rgb), 0.55);
}

.bento.wide {
  grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr));
}

.person-head {
  display: flex;
  align-items: center;
  gap: 0.7rem;
}

.person-head .name {
  font-weight: 600;
}

.person-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem;
  margin-top: 0.6rem;
}

.fact-value {
  font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  font-size: 0.9rem;
  color: var(--text-dim);
  overflow-wrap: anywhere;
  unicode-bidi: isolate;
}

.restore-note {
  margin: 0.75rem 0 0;
  font-size: 0.9rem;
  color: var(--text-dim);
}

.spacer {
  flex: 1;
}

/* A switch, which is a checkbox that says what it turns on. */
.switch {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  margin-top: 0.75rem;
  cursor: pointer;
}

.switch input {
  inline-size: 1.05rem;
  block-size: 1.05rem;
  margin: 0;
  accent-color: var(--accent);
  cursor: pointer;
}

/* The roles somebody holds, one per row with the way to take it back. */
.grants {
  list-style: none;
  padding: 0;
  margin: 0 0 1rem;
  display: grid;
  gap: 0.4rem;
}

.grants li {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.4rem 0.55rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

.grants li span:nth-child(2) {
  flex: 1;
}

.grants-title {
  margin: 1.25rem 0 0.5rem;
  font-size: 0.95rem;
}

/* The kind, as a shape plus a word: the icon is what the eye finds in a list of
   twenty, the word is what makes it nameable. */
.votes {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  font-variant-numeric: tabular-nums;
}

/* Status carries a colour of its own, so a closed report and one in progress
   are not the same grey. Not colour alone — the word is always there. */
.status-done,
.status-closed {
  color: #16a34a;
}

.status-in_progress,
.status-planned,
.status-in_forge {
  color: var(--accent-alt);
}

.status-declined,
.status-not_reproducible {
  color: var(--text-dim);
}

/* And not by colour alone: a closed "no" has a dashed edge and a hollow dot, where
   a delivered one is solid. */
.status-declined,
.status-not_reproducible { border-style: dashed; }
.status-declined::before,
.status-not_reproducible::before { background: transparent; box-shadow: inset 0 0 0 1.5px currentColor; }

body > nav .brand {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
}

/* ------------------------------------------------------------- the dashboard */

/* A fixed number of columns per width, not a track size.
 *
 * With auto-fit the column width came from the widest card, so a tile showing
 * "0" and a chart spanning three of them never lined up and every row started
 * somewhere different. A dashboard is read down as well as across: the columns
 * have to be the same on every row, which is what a fixed count gives.
 *
 * dense, so a one-column tile fills the gap a three-column chart leaves at the
 * end of a row instead of leaving a hole and starting a new one. */
.dashboard {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  grid-auto-flow: row dense;
  gap: 0.85rem;
  margin: 1.25rem 0 2rem;
  padding: 0;
  list-style: none;
  align-items: stretch;
}

@media (min-width: 48rem) {
  .dashboard { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}

@media (min-width: 80rem) {
  .dashboard { grid-template-columns: repeat(6, minmax(0, 1fr)); }
}

/* A team member's overview has five numbers, not six — the server's people
   are not theirs — so the wide row is ten columns: two each, and the wide
   cards half of it, and the first row fills as the administrator's does. */
@media (min-width: 80rem) {
  .dashboard.team { grid-template-columns: repeat(10, minmax(0, 1fr)); }
  .dashboard.team .dash-card:not(.wide):not(.full) { grid-column: span 2; }
  .dashboard.team .dash-card.wide { grid-column: span 5; }
}

.dash-card {
  position: relative;
  border: 1px solid var(--card-border);
  border-radius: calc(var(--radius) + 2px);
  background: var(--panel);
  box-shadow: var(--card-shadow);
  padding: 0;
  overflow: hidden;
}

.dash-card.wide {
  grid-column: span 2;
}

@media (min-width: 80rem) {
  .dash-card.wide {
    grid-column: span 3;
  }
}

/* Merged from two: the later one laid it out and won the margin, so the
   quarter-rem in the first was a number nobody was reading. */
.dash-card > h3 {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin: 1rem 1.1rem 0.5rem;
  font-size: 1rem;
}

.dash-card .bars,
.dash-card .hint {
  margin: 0.5rem 1.1rem 1.1rem;
}

.dash-card .lead {
  margin: 0 1.1rem 1.1rem;
}

/* The stat fills its card rather than drawing a second border inside it. */
.dash-card .stat {
  display: block;
  border: 0;
  border-radius: 0;
  background: transparent;
  block-size: 100%;
}

/* -------------------------------------------------------- one report, public */

/* The page that travels: reached from a link somebody was sent, by a reader
   who has not seen any other screen. The vote sits beside the report rather
   than under it, because on an ideas board it is what the page is for. */
.detail {
  display: grid;
  /* minmax(0,…) rather than 1fr: a long unbroken word in a title would
     otherwise set the column's minimum and push the vote off the card. */
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 1.25rem;
  align-items: start;
  padding: clamp(1.1rem, 3vw, 1.75rem);
  border: 1px solid var(--border);
  border-radius: var(--radius-xl);
  background: var(--panel);
  margin-block-start: 0;
  box-shadow: var(--shadow-sm), var(--hairline);
}

.detail h1 { margin: 0.55rem 0 0.9rem; }
.detail .meta { flex-wrap: wrap; margin: 0; }
.detail .body { white-space: normal; }
.detail .body p { margin: 0 0 0.7rem; }
.detail .body p:last-child { margin-block-end: 0; }

/* A vote is a number and a verb stacked, not a word with a number after it:
   the count is what somebody scans for and the verb is what they press. */
/* Merged from two declarations. What applied was the first's grid and width
   with the second's padding, radius and recessed background on top; each half
   on its own described a block that is not on the screen. */
.vote-block {
  display: grid;
  justify-items: center;
  gap: 0.1rem;
  min-inline-size: 4.75rem;
  padding: 0.85rem 1rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface-dim);
  box-shadow: var(--hairline);
  color: inherit;
  font: inherit;
  cursor: pointer;
  transition: transform 0.15s var(--ease), border-color 0.15s var(--ease),
    background 0.15s var(--ease);
}

/* Both halves said this; the later one added the lift. */
.vote-block:hover { transform: translateY(-1px); border-color: var(--accent); }

.vote-block .count {
  font-size: 1.5rem;
  font-weight: 700;
  line-height: 1.1;
  font-variant-numeric: tabular-nums;
}

.vote-block .label { font-size: 0.8rem; color: var(--text-dim); }

/* An answered idea: the number, with nothing to press. */
.vote-block.closed { cursor: default; }
.vote-block.closed:hover { transform: none; border-color: var(--border); }

.vote-block.voted {
  border-color: var(--accent);
  background: rgba(var(--accent-rgb), 0.12);
}

.vote-block.voted .count,
.vote-block.voted .label { color: var(--accent-alt); }

/* The forge issue, when there is one. It is a link off this site, so it says
   so with the same arrow every other outbound link here uses. */
.tracked {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  font-size: 0.8rem;
  color: var(--accent-alt);
  text-decoration: none;
}

.tracked:hover { text-decoration: underline; }

/* The reporter's own copy of the link. Only they ever see this card. */
.yours { margin-block-start: 1rem; }

.thread { margin-block-start: 1.75rem; }

.thread h2 {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

/* The number beside a heading, which is the heading's own word made countable.
   It is never alone: the label is the h2 it sits in. */
.count-chip {
  padding: 0.05rem 0.5rem;
  border-radius: 999px;
  background: var(--panel);
  border: 1px solid var(--border);
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--text-dim);
  font-variant-numeric: tabular-nums;
}

.comment .avatar {
  inline-size: 1.75rem;
  block-size: 1.75rem;
  font-size: 0.7rem;
}

.comment .who-name { color: var(--text-main); font-weight: 600; }

/* Who is answering matters more here than anywhere else in the product: a
   reply from the team is the reason somebody opened the page again. */
.who-badge {
  padding: 0.05rem 0.45rem;
  border-radius: 999px;
  background: rgba(var(--accent-rgb), 0.14);
  color: var(--accent-alt);
  font-size: 0.7rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.comment .when { margin-inline-start: auto; }

.comment-form { margin-block-start: 1rem; gap: 0.5rem; }
.comment-form .hint { margin: 0; }

/* ------------------------------------------------------------------ pictures */

/* The tray a report or a message is given its pictures in: one row, the
   previews beside a dashed tile, so a form that takes none looks as it did. */
.tray { display: grid; gap: 0.5rem; position: relative; }
.tray .hint { margin: 0; }

.tray-thumbs,
.pictures {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  list-style: none;
  margin: 0;
  padding: 0;
}

.tray-thumb,
.picture { position: relative; }

.tray-thumb img {
  display: block;
  inline-size: 4rem;
  block-size: 4rem;
  object-fit: cover;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface-dim);
}

.tray-add {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.35rem 0.6rem;
  padding: 0.6rem 0.8rem;
  border: 1px dashed var(--border-strong);
  border-radius: var(--radius-sm);
  color: var(--text-dim);
  cursor: pointer;
  transition: border-color 0.15s var(--ease), color 0.15s var(--ease), background 0.15s var(--ease);
}

.tray-add:hover,
.tray.dropping .tray-add {
  border-color: var(--accent);
  color: var(--accent);
  background: var(--accent-wash);
}

.tray:focus-within .tray-add { outline: 2px solid var(--ring); outline-offset: 2px; }
.tray-add-label { font-weight: 600; color: var(--text-main); font-size: 0.875rem; }
.tray-add-hint { font-size: 0.8rem; }

/* Reached by its label, which is what is drawn; the control itself is kept
   in the page so the keyboard and assistive technology still find it. */
.tray-input {
  position: absolute;
  inline-size: 1px;
  block-size: 1px;
  opacity: 0;
  pointer-events: none;
}

/* The pictures on a report or a message: small squares, each opening the
   picture at full size. Cut to the square so a row of them reads as a row,
   whatever shape each one is. */
.pictures { margin-block-start: 0.75rem; }

.picture-open {
  display: block;
  inline-size: 5rem;
  block-size: 5rem;
  overflow: hidden;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface-dim);
  transition: border-color 0.15s var(--ease);
}

.picture-open:hover,
.picture-open:focus-visible { border-color: var(--accent); }

.picture-open img {
  display: block;
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
}

/* Taking one out: a small round button on the corner where a reading
   direction ends. */
.tray-remove,
.picture-remove {
  position: absolute;
  inset-block-start: -0.4rem;
  inset-inline-end: -0.4rem;
  display: grid;
  place-items: center;
  inline-size: 1.35rem;
  block-size: 1.35rem;
  padding: 0;
  border: 1px solid var(--border-strong);
  border-radius: 50%;
  background: var(--panel);
  color: var(--text-main);
  font-size: 0.9rem;
  line-height: 1;
  cursor: pointer;
}

.tray-remove:hover,
.picture-remove:hover { border-color: rgba(var(--danger-rgb), 0.6); color: var(--danger-color); }

/* The viewer: the picture as large as the screen allows, on a dark ground
   whichever theme the page is in — a picture is judged against black, and a
   white sheet around a screenshot of a white page loses its edges. */
.viewer {
  inline-size: 100vw;
  block-size: 100dvh;
  max-inline-size: none;
  max-block-size: none;
  margin: 0;
  padding: 0;
  border: 0;
  background: transparent;
  color: #fff;
}

.viewer::backdrop {
  background: rgba(8, 8, 10, 0.92);
}

.viewer-figure {
  display: grid;
  place-items: center;
  align-content: center;
  gap: 0.75rem;
  inline-size: 100%;
  block-size: 100%;
  margin: 0;
  padding: 3.5rem 4.5rem;
  box-sizing: border-box;
}

.viewer-picture {
  display: block;
  max-inline-size: 100%;
  max-block-size: calc(100dvh - 9rem);
  object-fit: contain;
  border-radius: var(--radius-sm);
  box-shadow: 0 12px 48px rgba(0, 0, 0, 0.5);
}

.viewer-count {
  font-size: 0.8rem;
  color: rgba(255, 255, 255, 0.7);
  font-variant-numeric: tabular-nums;
}

/* The way back: the square bordered close every dialog of the product has,
   in the corner where reading starts its line ends. */
.viewer-close,
.viewer-step {
  position: fixed;
  display: grid;
  place-items: center;
  padding: 0;
  border: 1px solid rgba(255, 255, 255, 0.22);
  background: rgba(255, 255, 255, 0.08);
  color: #fff;
  cursor: pointer;
  transition: background 0.15s var(--ease), border-color 0.15s var(--ease);
}

.viewer-close {
  inset-block-start: 1rem;
  inset-inline-end: 1rem;
  inline-size: 2.25rem;
  block-size: 2.25rem;
  border-radius: 12px;
}

.viewer-step {
  inset-block-start: 50%;
  inline-size: 2.75rem;
  block-size: 2.75rem;
  margin-block-start: -1.375rem;
  border-radius: 50%;
}

.viewer-prev { inset-inline-start: 1rem; }
.viewer-next { inset-inline-end: 1rem; }

/* The chevron points to the end of a line, so the one going back is turned
   round — and both follow the reading direction. */
.viewer-prev svg { transform: scaleX(calc(-1 * var(--rtl-flip))); }
.viewer-next svg { transform: scaleX(var(--rtl-flip)); }

.viewer-close:hover,
.viewer-step:hover { background: rgba(255, 255, 255, 0.16); border-color: rgba(255, 255, 255, 0.4); }

.viewer-close:focus-visible,
.viewer-step:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

@media (max-width: 640px) {
  .viewer-figure { padding: 3.5rem 0.5rem 4.5rem; }
  /* On a phone the steps go under the picture, where a thumb reaches them. */
  .viewer-step { inset-block-start: auto; inset-block-end: 1rem; margin-block-start: 0; }
}

.comment-actions {
  display: flex;
  align-items: center;
  gap: 0.75rem;
}

.comment-sent { margin: 0; font-size: 0.875rem; color: var(--text-dim); }

/* Narrow: the vote goes under the report rather than squeezing the title into
   a column two words wide. */
@media (max-width: 34rem) {
  .detail { grid-template-columns: 1fr; }
  .vote-block { justify-self: start; }
}

/* The comment queue sits under the reports on the same screen, so it needs a
   heading of its own — two lists with one title reads as one list. */
.queue-head { margin: 2rem 0 0; }

.queue .on {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  align-self: start;
  font-size: 0.85rem;
  color: var(--text-dim);
  text-decoration: none;
}

.queue .on:hover { color: var(--accent-alt); }

/* ------------------------------------------------------- reporting something */

/* The kind, as two cards rather than two words in a row. It decides which
   questions come next, and it is the first thing anybody touches on the page —
   a pair of pills gave it the weight of a filter tab. */
.kind-choices {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
  gap: 0.75rem;
  margin-block: 1rem;
}

/* Merged from two declarations: the later one set the radius, the padding, the
   shadow and the transition, so half of what the first said never applied. */
.choice-card {
  display: grid;
  justify-items: start;
  gap: 0.2rem;
  padding: 1rem 1.1rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm), var(--hairline);
  background: var(--panel);
  color: inherit;
  font: inherit;
  text-align: start;
  cursor: pointer;
  transition: transform 0.18s var(--ease), border-color 0.18s var(--ease),
    box-shadow 0.18s var(--ease), background 0.18s var(--ease);
}

.choice-card:hover { border-color: rgba(var(--accent-rgb), 0.55); }
/* The mark of the kind, in a tile rather than loose above the words: the same
   shape as every other icon tile in the product. Merged from two — the earlier
   one's margin never applied. */
.choice-card .icon {
  box-sizing: content-box;
  padding: 0.5rem;
  border-radius: var(--radius-sm);
  background: var(--surface-dim);
  color: var(--text-dim);
  margin-block-end: 0.35rem;
}
.choice-card .choice-title { font-weight: 600; }
.choice-card .choice-sub { color: var(--text-dim); font-size: 0.875rem; }

.choice-card.on {
  border-color: var(--accent);
  background: rgba(var(--accent-rgb), 0.1);
}

.choice-card.on .icon,
.choice-card.on .choice-title { color: var(--accent-alt); }

/* The questions that belong to one kind. Separated by a rule rather than by a
   heading: they are part of the same form, not a second one. */
.kind-fields {
  display: grid;
  gap: 0.75rem;
  padding-block-start: 0.75rem;
  border-block-start: 1px dashed var(--border);
}

/* The answers, on the report and in the queue. A definition list, because that
   is what it is: a question and what somebody answered. */
.answers {
  margin: 0.9rem 0 0;
  padding-block-start: 0.75rem;
  border-block-start: 1px dashed var(--border);
}

.answers-head { margin: 0 0 0.5rem; font-size: 0.8rem; font-weight: 600; color: var(--text-dim); }
.answers dl { display: grid; gap: 0.5rem; margin: 0; }
.answers dl > div { display: grid; gap: 0.1rem; }
.answers dt { font-size: 0.8rem; font-weight: 600; color: var(--text-dim); }
.answers dd { margin: 0; white-space: pre-wrap; }

/* Checkboxes in a settings form. They were a row of pills reading "Bug" and
   "Idea", which is the vocabulary of a filter tab rather than of a setting —
   and they broke outright when the public form's cards took the class they
   shared. Two things sharing one class name is one rename away from this. */
.checks {
  display: grid;
  gap: 0.4rem;
  margin-block: 0.35rem;
}

.check {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  padding: 0.5rem 0.7rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--canvas);
  font-weight: 400;
  cursor: pointer;
}

.check:hover { border-color: rgba(var(--accent-rgb), 0.55); }

.check input {
  inline-size: 1.05rem;
  block-size: 1.05rem;
  margin: 0;
  accent-color: var(--accent);
  cursor: pointer;
}

.check:has(input:checked) {
  border-color: rgba(var(--accent-rgb), 0.55);
  background: rgba(var(--accent-rgb), 0.07);
}

/* About, with updates inside it.
 *
 * The composition, in order: the product as a statement, centred and only
 * as tall as what it says, with the update underneath it inside the same card;
 * the release channel as a setting on a row of its own; the server's health;
 * and the links out at the foot, over a rule: a 72px mark, 18px name, 12px
 * version, 18px down to the update zone. */
.admin-main .about-hero {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  /* The about card: centred, only as tall as what it says. */
  padding: 28px 20px 24px;
}

/* The light behind the mark: the product is what this card is about, so it
   is lit from behind that. */

.about-logo {
  inline-size: 72px;
  block-size: 72px;
  border-radius: 16px;
  margin-block-end: 14px;
}

/* Through .about, because the panel's own heading rule (.admin-main h2, 0,1,1)
   gives every h2 36px above it and a size of its own — which pushed the name
   half a card away from its mark. */
.about .about-name { margin: 0 0 2px; font-size: 18px; font-weight: 600; letter-spacing: 0; }
.about-version { margin: 0; font-size: 12px; color: var(--text-dim); font-variant-numeric: tabular-nums; }

.about-update {
  inline-size: 100%;
  max-inline-size: 480px;
  display: flex;
  flex-direction: column;
  align-items: center;
  margin-block-start: 18px;
}

.about-state {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin: 0;
  font-size: 14px;
  font-weight: 500;
  color: var(--text-dim);
}

.about-state.ok { color: var(--success-color); }
.about-state.warn { color: var(--warning-color); }

.about-spin {
  inline-size: 14px;
  block-size: 14px;
  border: 2px solid currentColor;
  border-block-start-color: transparent;
  border-radius: 50%;
  animation: about-spin 0.8s linear infinite;
}

@keyframes about-spin { to { transform: rotate(360deg); } }

/* A ghost button: outlined, in capitals, quiet until it is pointed at. */
.about-check { margin-block-start: 16px; }

.about-check:disabled { opacity: 0.55; cursor: default; }

/* Something newer: the update card — a card within the card, lit from
   one corner, the badge whose dot pulses, the version under it in a gradient,
   and one button the width of the card. */
.about-new {
  position: relative;
  overflow: hidden;
  inline-size: 100%;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  gap: 20px;
  padding: 24px;
  border: 1px solid var(--feature-card-border);
  border-radius: 24px;
  background: var(--feature-card-bg);
  backdrop-filter: blur(40px);
  -webkit-backdrop-filter: blur(40px);
  box-shadow: var(--float-shadow), inset 0 1px 0 rgba(255, 255, 255, 0.1);
  text-align: start;
}

.about-new-glow {
  position: absolute;
  inset-block-start: -50%;
  inset-inline-end: -50%;
  inline-size: 100%;
  block-size: 100%;
  /* closest-side: the glow fades to nothing at the edges of its own box. Sized
     to the farthest corner, it was still visible where the box ends, halfway
     down the card, and drew a straight line across it. */
  background: radial-gradient(circle closest-side, var(--accent) 0%, transparent 100%);
  opacity: 0.1;
  pointer-events: none;
}

.about-new > :not(.about-new-glow) { position: relative; }
.about-new .button { inline-size: 100%; justify-self: stretch; }

.about-new-head { display: flex; justify-content: center; align-items: center; gap: 12px; }

.about-badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 10px;
  border: 1px solid rgba(var(--accent-rgb), 0.2);
  border-radius: 100px;
  background: rgba(var(--accent-rgb), 0.1);
  color: var(--accent);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  box-shadow: 0 4px 12px rgba(var(--accent-rgb), 0.1);
}

.about-badge::before {
  content: "";
  inline-size: 6px;
  block-size: 6px;
  border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 8px var(--accent);
  animation: about-pulse 2s infinite;
}

@keyframes about-pulse {
  0%, 100% { transform: scale(0.95); opacity: 0.7; }
  50% { transform: scale(1.1); opacity: 1; }
}

.about-new-version {
  font-size: 16px;
  font-weight: 600;
  letter-spacing: 0.02em;
  background: linear-gradient(135deg, var(--text-main) 0%, var(--text-dim) 100%);
  background-clip: text;
  -webkit-background-clip: text;
  color: transparent;
}

/* A setting on a row of its own: what it is and what it means on
   one side, the switch on the other. */

/* A switch, 44 by 24. The checkbox is still there, over the whole of it,
   so a click, a key and a screen reader all reach a real control. */
.toggle {
  position: relative;
  display: inline-block;
  inline-size: 44px;
  block-size: 24px;
  flex-shrink: 0;
}

.toggle input {
  position: absolute;
  inset: 0;
  inline-size: 100%;
  block-size: 100%;
  margin: 0;
  padding: 0;
  opacity: 0;
  cursor: pointer;
  z-index: 1;
}

.toggle input:disabled { cursor: default; }

.toggle-track {
  position: absolute;
  inset: 0;
  border: 1px solid var(--border);
  border-radius: 24px;
  background: var(--surface-active);
  transition: background-color 0.3s, border-color 0.3s, box-shadow 0.2s;
}

.toggle-track::before {
  content: "";
  position: absolute;
  inset-inline-start: 2px;
  inset-block-end: 2px;
  inline-size: 18px;
  block-size: 18px;
  border-radius: 50%;
  background: var(--text-dim);
  transition: transform 0.3s, background-color 0.3s;
}

.toggle input:checked + .toggle-track { background: var(--accent); border-color: var(--accent); }
.toggle input:checked + .toggle-track::before {
  transform: translateX(calc(var(--rtl-flip, 1) * 20px));
  background: #fff;
}
.toggle input:focus-visible + .toggle-track { box-shadow: var(--ring); }
.toggle input:disabled + .toggle-track { opacity: 0.5; }

/* The name of a group of cards: the pill of a section head, with the rule
   running on from it. */

/* One fact about the machine: what it is, in a tile the accent marks, and the
   number — which is the point — at the size of a number. */

/* How full the disk is. A <meter>, so its length is an attribute and not a
   style; accent while there is room, amber past three quarters, red past
   nine tenths — the element's own low, high and optimum. */
.about-meter {
  appearance: none;
  inline-size: 100%;
  block-size: 6px;
  margin: 0.15rem 0 0;
  border: 0;
  border-radius: 3px;
  background: var(--surface-dim);
}

.about-meter::-webkit-meter-bar { block-size: 6px; border: 0; border-radius: 3px; background: var(--surface-dim); }
.about-meter::-webkit-meter-optimum-value { border-radius: 3px; background: var(--accent); }
.about-meter::-webkit-meter-suboptimum-value { border-radius: 3px; background: var(--warning-color); }
.about-meter::-webkit-meter-even-less-good-value { border-radius: 3px; background: var(--danger-color); }
.about-meter::-moz-meter-bar { border-radius: 3px; background: var(--accent); }
.about-meter:-moz-meter-sub-optimum::-moz-meter-bar { background: var(--warning-color); }
.about-meter:-moz-meter-sub-sub-optimum::-moz-meter-bar { background: var(--danger-color); }

.about-links {
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
  gap: 24px;
  margin-block-start: 8px;
  padding-block-start: 16px;
  border-block-start: 1px solid var(--border);
}

/* The server's facts: a card each, two to a row, one on a phone. */
.about-stats {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 16px;
}

.about-stat-value {
  display: block;
  margin-block-start: 6px;
  font-size: 24px;
  font-weight: 600;
  line-height: 1.2;
  font-variant-numeric: tabular-nums;
}

.about-stat-sub { margin: 4px 0 0; font-size: 12px; color: var(--text-dim); }
.about-stat .about-meter { margin-block-start: 10px; }

@media (max-width: 40rem) {
  .about-stats { grid-template-columns: minmax(0, 1fr); }
}

.about-links a {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  color: var(--text-dim);
  font-size: 13px;
  text-decoration: none;
  transition: color 0.2s;
}

.about-links a:hover { color: var(--accent); }

@media (prefers-reduced-motion: reduce) {
  .about-badge::before,
  .about-spin { animation: none; }
}

/* ---------------------------------------------------------------- toasts */

/* Bottom centre, three at a time, a bar that drains and pauses under the
   pointer. Somebody who uses two CodigoSH products should not have to learn a
   second set of manners. */
.toasts {
  position: fixed;
  inset-block-end: 24px;
  inset-inline-start: 50%;
  transform: translateX(-50%);
  z-index: 1000;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.6rem;
  inline-size: max-content;
  max-inline-size: min(92vw, 420px);
  /* The empty container must not swallow clicks on the page below; each toast
     turns pointer events back on for itself. */
  pointer-events: none;
}

.toast {
  pointer-events: auto;
  position: relative;
  display: flex;
  align-items: center;
  gap: 0.75rem;
  inline-size: 100%;
  box-sizing: border-box;
  padding: 0.75rem 0.75rem 0.75rem 1rem;
  border: 1px solid var(--border);
  border-inline-start: 4px solid var(--accent);
  border-radius: var(--radius);
  background: var(--panel);
  color: var(--text-main);
  box-shadow: 0 18px 40px rgba(0, 0, 0, 0.28);
  font-size: 0.9rem;
  overflow: hidden;
  opacity: 0;
  transform: translateY(16px);
  transition: opacity 240ms ease, transform 240ms ease;
}

.toast-visible { opacity: 1; transform: translateY(0); }
.toast-leaving { opacity: 0; transform: translateY(16px); }

.toast-success { border-inline-start-color: #16a34a; }
.toast-error { border-inline-start-color: #b91c1c; }
.toast-warning { border-inline-start-color: #d97706; }
.toast-info { border-inline-start-color: var(--accent); }

.toast-message { flex: 1 1 auto; line-height: 1.35; overflow-wrap: anywhere; }

.toast-action {
  flex: none;
  border: 0;
  background: none;
  color: var(--accent-alt);
  font: inherit;
  font-weight: 600;
  padding: 0.25rem 0.5rem;
  border-radius: calc(var(--radius) - 4px);
  cursor: pointer;
  white-space: nowrap;
}

.toast-action:hover { background: rgba(var(--accent-rgb), 0.12); }

.toast-close {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 0;
  background: none;
  color: var(--text-dim);
  padding: 0.2rem;
  border-radius: 4px;
  line-height: 0;
  cursor: pointer;
}

.toast-close:hover { color: var(--text-main); background: rgba(128, 128, 128, 0.15); }

.toast-bar {
  position: absolute;
  inset-block-end: 0;
  inset-inline-start: 0;
  block-size: 3px;
  inline-size: 100%;
  background: var(--accent);
  opacity: 0.7;
  /* transform-origin has no logical keyword, so the mirrored case is stated
     below: the bar drains from the side the text starts on. */
  transform-origin: left center;
  animation: toast-drain 3000ms linear forwards;
}

.toast-bar:dir(rtl) { transform-origin: right center; }
.toast-success .toast-bar { background: #16a34a; }
.toast-error .toast-bar { background: #b91c1c; }
.toast-warning .toast-bar { background: #d97706; }

/* Paused with the pointer over it, in step with the timer in the script. */
.toast:hover .toast-bar { animation-play-state: paused; }

@keyframes toast-drain {
  from { transform: scaleX(1); }
  to { transform: scaleX(0); }
}

@media (prefers-reduced-motion: reduce) {
  /* The countdown bar is functional and stays; the slide is decoration. */
  .toast, .toast-visible, .toast-leaving { transform: none; }
  .toast { transition: opacity 120ms linear; }
}

/* The picture, as one control: the circle is the button.
 *
 * A label, a separate upload button and a sentence saying a picture is optional is three pieces of furniture for
 * something somebody either clicks or ignores. */
.picture-field { display: flex; justify-content: center; }

.avatar-pick {
  position: relative;
  border: 0;
  background: none;
  padding: 0;
  cursor: pointer;
  border-radius: 50%;
  line-height: 0;
}

.avatar-pick .avatar {
  transition: box-shadow 0.15s ease;
}

.avatar-pick:hover .avatar,
.avatar-pick:focus-visible .avatar {
  box-shadow: 0 0 0 2px var(--accent);
}

.avatar-pick-badge {
  position: absolute;
  inset-block-end: -2px;
  inset-inline-end: -2px;
  padding: 0.05rem 0.3rem;
  border: 1px solid var(--border);
  border-radius: 3px;
  background: var(--canvas);
  color: var(--text-dim);
  font-family: ui-monospace, monospace;
  font-size: 0.6rem;
  line-height: 1.5;
  text-transform: uppercase;
  letter-spacing: 0.03em;
}

.avatar-pick:hover .avatar-pick-badge { color: var(--text-main); }

/* A repository's mark with an icon in it: the picture fills the tile to its
   rounded corners, on a neutral ground so a transparent icon does not sit on
   the accent's tint. Without one it stays the initials, as it always was. */
.card-mark.has-icon,
.lp-board-mark.has-icon,
.repo-si-mark.has-icon {
  overflow: hidden;
  background: var(--panel);
  border-color: var(--border);
}
.card-mark > img,
.lp-board-mark > img,
.repo-si-mark > img {
  display: block;
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
}
.repo-si-mark { border: 1px solid var(--border); border-radius: var(--radius-sm); }

/* The same control as the picture and the logo, square like the mark it
   fills, at the size it is chosen at. */
.icon-pick { border-radius: 14px; }
.icon-pick .card-mark {
  inline-size: 64px;
  block-size: 64px;
  border-radius: 14px;
  font-size: 1.3rem;
  transition: box-shadow 0.15s ease;
}
.icon-pick:hover .card-mark,
.icon-pick:focus-visible .card-mark { box-shadow: 0 0 0 2px var(--accent); }

/* The picker, and the cross that clears it.
 *
 * A cross on the corner rather than a "Remove" button beside it: the button was
 * a second control the width of a sentence for something that only makes sense
 * while there is an image, and it appeared and disappeared next to the one that
 * chooses. The cross sits on the thing it removes and is drawn only when there
 * is something to remove. */
.picker {
  position: relative;
  display: inline-block;
  line-height: 0;
}

.picker-clear {
  position: absolute;
  inset-block-start: -4px;
  inset-inline-end: -4px;
  display: grid;
  place-items: center;
  inline-size: 1.35rem;
  block-size: 1.35rem;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: 50%;
  background: var(--canvas);
  color: var(--text-dim);
  cursor: pointer;
  z-index: 1;
}

.picker-clear:hover {
  color: #fff;
  background: #b91c1c;
  border-color: #b91c1c;
}

/* The way out to the public side, kept apart from the sections of the panel:
   it is not one of them, it leaves. */
.side-public {
  margin-block-start: 1rem;
  border-block-start: 1px solid var(--border);
  padding-block-start: 0.9rem;
  color: var(--text-dim);
}

.side-public:hover { color: var(--text-main); }

/* ------------------------------------------------------------------ signing in */

/* The card on a plain field, centred in the window rather than at the top of a
   column. This is the first screen of the day for everybody on the team, and it
   was a heading, two boxes and a button with the same word on all three. */
/* Merged from two: the later width is the one that applied, so the 24rem here
   was a number nobody was reading. */
.signin {
  display: grid;
  justify-items: center;
  gap: 1.25rem;
  max-inline-size: 26rem;
  margin-inline: auto;
  padding-block-start: clamp(1rem, 8vh, 5rem);
}

.signin-head {
  display: grid;
  justify-items: center;
  text-align: center;
}

/* Merged from two: the size came from the later one, the margin from the
   earlier, and neither half described the heading on the screen. */
.signin-head h1 {
  margin: 0.4rem 0 0;
  font-size: clamp(1.4rem, 1.1rem + 1vw, 1.9rem);
  letter-spacing: -0.03em;
}
.signin-head .lead { margin: 0; color: var(--text-dim); }

.signin form { inline-size: 100%; margin: 0; }
.signin .wizard-card { inline-size: 100%; box-sizing: border-box; }

/* Full width: there is one thing to do on this page. */
/* The way in, placed by the same rule as the wizard's: full width inside the
   card rather than a button of its own hanging under it. */
.signin-go { inline-size: 100%; }

/* The mark above the wizard and the entrance: bare, as the front page and the
   About screen draw it — 64px with 12px under it. */
.wizard-mark {
  display: block;
  inline-size: 64px;
  block-size: 64px;
  /* The card's own gap puts the room under it; a margin on top of that was
     the same spacing twice. */
  margin: 0 auto;
  border-radius: 14px;
}

/* The eye on a password field. Put there by a service that walks every screen,
   so the five password fields in this product all have one. */
.pw-wrap {
  position: relative;
  display: flex;
  align-items: center;
}

.pw-wrap input {
  inline-size: 100%;
  padding-inline-end: 2.5rem;
}

.pw-toggle {
  position: absolute;
  inset-inline-end: 0.5rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 0;
  background: none;
  padding: 0.25rem;
  border-radius: 4px;
  color: var(--text-dim);
  cursor: pointer;
}

.pw-toggle:hover { color: var(--text-main); }

/* ------------------------------------------------- the same light, inside
 *
 * The public pages were rebuilt on raised surfaces, a hairline and three
 * depths. Everything the panel and the wizard already had is put on the same
 * footing here rather than being edited in nine places: one product, one
 * material. Later in the file than the rules it refines, which is how it wins
 * at equal specificity.
 */

.wizard-card,
.bento-card,
.person,
.facts li,
.modal {
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm), var(--hairline);
}

a.bento-card:hover,
.person:hover {
  border-color: var(--accent-line);
  box-shadow: var(--shadow-md), var(--hairline);
}

.admin-main > h1 { margin-block-end: 0.35rem; }

/* The dialog title bar and the card heads pick up the section-title weight
   used on the public side, so a heading is a heading everywhere. */
.bento-card h3 { letter-spacing: -0.015em; }

/* ---------------------------------------------------- one report, refined */

/* The report itself reads at the measure of a paragraph even though the card
   is as wide as the page: a line of a hundred and forty characters is a line
   the eye loses its place in. */
.detail .body,
.detail .body p { max-inline-size: var(--prose); }

.detail .body p { line-height: 1.65; }

/* Whatever a stranger typed has to stay inside the box it is drawn in. A line of
   text breaks at spaces, and a URL, a path or a log pasted whole has none: it
   ran out of the card, widened the page to tens of thousands of pixels and was
   cut off at the edge on a phone. `anywhere` rather than `break-word`, because
   it also lowers the width the text asks for, which is what lets a grid or flex
   parent shrink the column instead of growing it. Words with spaces still wrap
   where they always did; only a run too long for its line is broken. */
.detail h1,
.detail .body p,
.answers dd,
.comment .what,
.similar a { overflow-wrap: anywhere; }

/* The merge form shows the title of the report chosen, which a stranger wrote.
   Every form is a one-column grid whose column is as wide as its widest child,
   and a title with no spaces is a child that is as wide as itself even though
   the dropdown then cuts it with an ellipsis: the column, the card and the page
   grew to fit it. A column that may shrink lets the ellipsis do its job. */
form.merge { grid-template-columns: minmax(0, 1fr); }

.answers dl > div {
  padding: 0.6rem 0.75rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface-dim);
}

/* --------------------------------------------------- reporting, refined */

.choice-card:hover { transform: translateY(-2px); box-shadow: var(--shadow-md), var(--hairline); }

.choice-card.on { box-shadow: var(--shadow-sm), var(--hairline), 0 0 0 1px var(--accent); }
.choice-card.on .icon { background: var(--accent-wash); color: var(--accent-alt); }

/* ------------------------------------------------------ signing in, refined */

.signin .wizard-card { box-shadow: var(--shadow-md), var(--hairline); }


/* --------------------------------------------------- the last few details */

/* The feed is an action like the two beside it, so it wears the same material
   rather than floating over the row as a bare mark. */
.head-actions .icon-button {
  inline-size: 2.4rem;
  block-size: 2.4rem;
  border-color: var(--border);
  border-radius: var(--radius);
  background: var(--panel);
  box-shadow: var(--shadow-sm), var(--hairline);
}

.head-actions .icon-button:hover {
  border-color: var(--border-strong);
  color: var(--text-main);
  background: var(--panel);
}

/* The bar's own controls. An underlined link beside a button reads as the older
   half of a page that was styled twice. */
.nav-right .link {
  color: var(--text-dim);
  font-weight: 600;
  text-decoration: none;
}

.nav-right .link:hover { color: var(--text-main); text-decoration: underline; }
.nav-right a:not(.button) { color: var(--text-dim); font-weight: 600; text-decoration: none; }
.nav-right a:not(.button):hover { color: var(--accent-alt); }

/* Signing out of the panel: the same quiet control as the bar's, at the foot of
   the sidebar rather than an underlined sentence in the middle of it. */
.side-foot .link {
  display: inline-flex;
  align-items: center;
  gap: 0.55rem;
  justify-self: start;
  padding: 0.35rem 0.6rem;
  border-radius: var(--radius-sm);
  color: var(--text-dim);
  font-size: 0.9rem;
  text-decoration: none;
}

.side-foot .link:hover { background: rgba(127, 127, 127, 0.12); color: var(--text-main); }

/* The way back carries the chevron pointing back — the same drawing mirrored,
   and mirrored once more in Arabic, where back is the other way. */
.button.back .icon {
  transform: scaleX(calc(-1 * var(--rtl-flip)));
}

/* Inside a .page the row of actions is a child like any other, and the page
   already sets the distance between children: its own top margin on top of
   that is a gap and a half. */
.page > .wizard-actions {
  margin-block-start: 0;
}

/* ------------------------------------------------------------ the overview
 *
 * A number on its own answers "how many" and nothing else. What follows is for
 * the three things it cannot say — whether it is getting busier, what people
 * are actually asking for, and which board the work is in — and for the fact
 * that every tile is now a way into the listing it counts.
 */

/* A card that takes the whole row, whatever the row is. The chart is read
   across: at a quarter of the window it is a smear. */
.dash-card.full {
  grid-column: 1 / -1;
}

.stat-go {
  position: absolute;
  inset-block-start: 0.9rem;
  inset-inline-end: 0.9rem;
  color: var(--border-strong);
  transform: scaleX(var(--rtl-flip));
  transition: color 0.15s var(--ease), transform 0.15s var(--ease);
}

a.stat:hover .stat-go {
  color: var(--accent);
  transform: scaleX(var(--rtl-flip)) translateX(2px);
}

.dash-card:has(a.stat):hover {
  border-color: var(--accent-line);
  box-shadow: var(--shadow-md), var(--hairline);
}

/* --------------------------------------------------------------- the chart */

.chart-wrap {
  margin: 0 1.1rem 1rem;
}

/* One rule. The height was set twice and only the later value ever applied, so
   the first was a number nobody was reading. */
.chart {
  display: block;
  inline-size: 100%;
  block-size: clamp(4.5rem, 9vw, 6.5rem);
  overflow: visible;
}

/* The gradient stops are coloured here rather than in the markup, which is what
   lets an operator's own accent reach the chart. */
.chart-stop-top { stop-color: rgba(var(--accent-rgb), 0.45); }
.chart-stop-bottom { stop-color: rgba(var(--accent-rgb), 0); }

.chart-line {
  stroke: var(--accent);
  stroke-width: 2;
  stroke-linejoin: round;
  stroke-linecap: round;
}

.chart-last {
  stroke: var(--accent);
  stroke-width: 2;
  stroke-linecap: round;
  opacity: 0.55;
}

.chart-axis {
  display: flex;
  justify-content: space-between;
  margin: 0.35rem 0 0;
  font-size: 0.75rem;
  color: var(--text-dim);
}

/* ---------------------------------------------------------------- the ring */

.donut-row {
  display: flex;
  align-items: center;
  gap: 1.25rem;
  margin: 0 1.1rem 1.1rem;
  flex-wrap: wrap;
}

.donut {
  position: relative;
  inline-size: 7rem;
  flex: none;
}

.donut svg { display: block; inline-size: 100%; }

.donut-middle {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  pointer-events: none;
}

.donut-value {
  font-size: 1.4rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

.arc-track { stroke: var(--surface-dim); }
.arc-idea { stroke: var(--accent); }
.arc-bug { stroke: var(--hue-open); }

.legend {
  display: grid;
  gap: 0.4rem;
  font-size: 0.9rem;
  min-inline-size: 8rem;
}

.legend li {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.legend .count {
  margin-inline-start: auto;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

.swatch {
  inline-size: 0.7rem;
  block-size: 0.7rem;
  border-radius: 3px;
  flex: none;
}

.swatch-idea { background: var(--accent); }
.swatch-bug { background: var(--hue-open); }

/*
 * The hue of a state, in one place for every shape that carries it.
 *
 * The chip sets it and so does a bar and so does a dot, so the mapping is
 * written once against both class families rather than copied into each.
 */
.status-new, .status-confirmed, .hue-new, .hue-confirmed { --hue: var(--hue-open); }
.status-planned, .hue-planned, .hue-pending { --hue: var(--hue-hold); }
.status-in_progress, .status-in_forge, .hue-in_progress, .hue-in_forge { --hue: var(--hue-work); }
.status-done, .status-closed, .hue-done, .hue-closed, .hue-published { --hue: var(--hue-done); }
/* What the team closed without doing it is not the colour of what it did: green is
   an answer that was delivered, grey an answer that was no. */
.status-declined, .status-not_reproducible,
.hue-declined, .hue-not_reproducible, .hue-rejected, .hue-spam { --hue: var(--hue-off); }
/* A report that has not been published is not open. Its chip says what it is
   waiting for, in the colour waiting wears everywhere else. */
.status-pending { --hue: var(--hue-hold); }
.status-rejected, .status-spam { --hue: var(--hue-off); }
.hue-bug { --hue: var(--accent); }
.hue-idea { --hue: var(--hue-open); }

/* A bar takes the colour of what it counts. A chart in one grey is a chart you
   have to read the labels of. */
.bar-row .bar-fill {
  background: linear-gradient(90deg,
    color-mix(in srgb, var(--hue, var(--accent)) 70%, transparent), var(--hue, var(--accent)));
}

/* ------------------------------------------------------------- brief lists */

.briefs, .board-rows {
  display: grid;
  /* One column that is as wide as the card and no wider. Without it the grid's
     implicit column took the width of its longest row, so a long title never
     reached its ellipsis and the state beside it was pushed out of a card
     that clips — "En attente de vali…" on the overview, in French. */
  grid-template-columns: minmax(0, 1fr);
  gap: 0.35rem;
  margin: 0 1.1rem 1.1rem;
}

.brief, .board-rows a {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.5rem 0.6rem;
  border-radius: var(--radius);
  text-decoration: none;
  color: inherit;
  border: 1px solid transparent;
  transition: background 0.15s var(--ease), border-color 0.15s var(--ease);
}

.brief:hover, .board-rows a:hover {
  background: var(--surface-dim);
  border-color: var(--border);
}

/* The state is what the row is for; the title gives way before it does. */
.brief > .status { flex-shrink: 0; }

.brief-title {
  flex: 1;
  min-inline-size: 0;
  font-weight: 500;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The mark of a kind, in the tile shape the rest of the product uses. */
.kind-mark {
  display: grid;
  place-items: center;
  flex: none;
  inline-size: 1.9rem;
  block-size: 1.9rem;
  border-radius: var(--radius-sm);
  background: var(--surface-dim);
  color: var(--hue-open);
}

.kind-mark.kind-bug { color: var(--accent); }

.score.small {
  min-inline-size: 2.4rem;
  padding: 0.25rem 0.4rem;
}

.score.small .count { font-size: 0.95rem; }

/* The lead tile of a row that carries a mark rather than a number. */
.score.mark {
  display: grid;
  place-items: center;
  color: var(--accent);
}

.board-name { display: grid; min-inline-size: 0; }
.board-name .card-sub { font-size: 0.75rem; }

.board-counts {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  margin-inline-start: auto;
}

.board-counts .count { font-variant-numeric: tabular-nums; font-weight: 700; }

/* --------------------------------------------------------- the listing --- */

.reports { margin-block-start: 1rem; }

/*
 * A row is the card, and the decision is inside it.
 *
 * The buttons used to be a sibling of the row rather than part of it, so they
 * floated in the gap between two cards — nearer the row below than the one they
 * belonged to. Now the card is the <li>, the link inside it drops its own
 * border, and a dashed rule separates the decision from what it is about.
 */
/* One line: the link over most of it, then the decision, then the state it
   would change. A queue of ten waiting reports was ten double-height rows. */
.queue-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.6rem;
  padding-inline-end: 1rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--panel);
  box-shadow: var(--shadow-sm), var(--hairline);
  transition: transform 0.18s var(--ease), box-shadow 0.18s var(--ease),
    border-color 0.18s var(--ease);
}

.queue-row:hover {
  transform: translateY(-1px);
  border-color: var(--accent-line);
  box-shadow: var(--shadow-md), var(--hairline);
}

.queue-row .item,
.queue-row .item:hover {
  border-color: transparent;
  background: transparent;
  box-shadow: none;
  transform: none;
}

.queue-row .item {
  flex: 1 1 18rem;
  min-inline-size: 0;
}

/*
 * The three of them read as one group, and the group is not the state.
 *
 * Tight inside — a third of the space they keep from the chip — so the eye
 * takes them as one decision with three answers rather than as four things
 * heaped against the right-hand edge.
 */
.row-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem;
  margin-inline-start: auto;
  margin-inline-end: 0.6rem;
}

.row-actions .button {
  padding: 0.3rem 0.6rem;
  font-size: 0.8rem;
  gap: 0.3rem;
}

/* Narrow: the row gives up and stacks, and the decision keeps the indent of the
   title rather than starting under the score tile. */
@media (max-width: 60rem) {
  .row-actions {
    margin-inline: 0;
    padding-block-end: 0.75rem;
    padding-inline-start: 4.3rem;
  }
}

.chip.waiting {
  border-color: color-mix(in srgb, var(--hue-hold) 40%, transparent);
  background: color-mix(in srgb, var(--hue-hold) 12%, transparent);
  color: var(--hue-hold);
}

.chip.waiting .icon { color: inherit; }

.tabs .count-chip {
  margin-inline-start: 0.4rem;
  padding: 0 0.35rem;
  background: transparent;
  border-color: transparent;
  font-size: 0.75rem;
}

.tab.on .count-chip {
  background: var(--accent-wash);
  border-color: var(--accent-line);
  color: var(--accent-alt);
}

/* The chart reads across, not down: a tall box of mostly empty air says the
   month was empty when it was only quiet. */
.chart-grid {
  stroke: var(--border);
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
}

/* The ring and its legend sit in the middle of the card rather than at the top
   of a box sized by whatever is beside it. */
.dash-card:has(.donut-row) { display: grid; align-content: center; }

/* Inside a .page the thread is a child like any other and the page already
   sets the distance between children: its own top margin on top of that left a
   hand's width of nothing between the team's tools and the first comment. */
.page > .thread { margin-block-start: 0; }

/* Leaving, in the bar. The mark points at the door and the door is at the end
   of the line, which is the other end in Arabic. */
.nav-right .sign-out .icon { transform: scaleX(var(--rtl-flip)); }

/*
 * The bar's icons: 32 by 32 with no border, the icon in the
 * text colour at 20px and a 1.5 stroke, and a hover that dims to 0.7 over a
 * faint grey square of radius 4. They had been the generic icon button of the
 * panel — dim grey, smaller, rounder, brightening on hover — which is the same
 * control in a different accent, and it read as a different product.
 */
.nav-right .icon-button,
.nav-right a.review-pill {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  min-inline-size: 32px;
  block-size: 32px;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: transparent;
  color: var(--text-main);
  transition: opacity 0.2s;
}

.nav-right .icon-button svg,
.nav-right a.review-pill svg {
  inline-size: 20px;
  block-size: 20px;
  stroke-width: 1.5px;
}

.nav-right .icon-button:hover,
.nav-right a.review-pill:hover {
  opacity: 0.7;
  background: rgba(128, 128, 128, 0.1);
  border-radius: 4px;
  color: var(--text-main);
}

/* A page that could not be shown. Centred, because there is one thing on it. */
.failure {
  justify-items: center;
  text-align: center;
  padding-block-start: clamp(2rem, 12vh, 6rem);
}

.failure .hero-mark {
  inline-size: 3.5rem;
  block-size: 3.5rem;
  color: var(--text-dim);
}

.failure h1 { margin: 0; }

/* A chip in the house accent: a role, a connection, anything named rather than
   counted. The monospaced .tag it replaced is for identifiers only — a
   username, an address, a webhook path — which is what a code font is for. */
.chip.accent {
  border-color: var(--accent-line);
  background: var(--accent-wash);
  color: var(--accent-alt);
}

/* The one thing on a page that could not be shown sits under the middle of it,
   button included: .button plants itself at the start of the line by default,
   which left it hanging off to one side of a centred page. */
.failure .button { justify-self: center; }

/* Cards in a row take the height of what is in them. Stretched to match the
   tallest, a short one puts its action in the middle of a field of nothing. */
.bento { align-items: start; }

/*
 * Leaving, in red.
 *
 * It is the only control in the sidebar and in the bar that ends what you are
 * doing, and the operator asked for it to look like it. Red at rest rather than
 * on hover: a colour that only appears once the pointer is already on it is a
 * warning nobody reads before pressing.
 */
.side-foot .link,
.nav-right .sign-out {
  color: var(--danger-color);
}

.side-foot .link:hover,
.nav-right .sign-out:hover {
  color: var(--danger-color);
  background: rgba(var(--danger-rgb), 0.12);
}

/* ---------------------------------------------------- the chart, asked

 * The chart is thirty quiet days and one spike until somebody can ask it which
 * day was which. The pointer gets a guide, the point it landed on, and a label
 * with the day and the number.
 */
.chart-wrap { position: relative; }

.chart-dot {
  fill: var(--accent);
  opacity: 0;
  transition: opacity 0.12s var(--ease);
}

.chart-dot.on {
  opacity: 1;
  stroke: var(--panel);
  stroke-width: 2;
}

.chart-cursor {
  stroke: var(--border-strong);
  stroke-width: 1;
  stroke-dasharray: 3 3;
}

.chart-tip {
  position: absolute;
  z-index: 2;
  /* Where it landed, written into these two by the pointer as a rule in the
     document's adopted sheet — never as a style attribute, which is what the
     content policy refuses. */
  inset-block-start: var(--tip-y, 0);
  left: var(--tip-x, 0);
  transform: translate(-50%, calc(-100% - 0.6rem));
  /* Measured before it is placed, so it has to be laid out even while it is
     off: visibility rather than display, and hidden from the reader either
     way by [hidden] elsewhere doing the same. */
  padding: 0.3rem 0.6rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--panel);
  box-shadow: var(--shadow-md), var(--hairline);
  font-size: 0.8rem;
  font-weight: 600;
  white-space: nowrap;
  pointer-events: none;
}

/* Under the point instead, when the point is against the top of the card. */
.chart-tip.below { transform: translate(-50%, 0.6rem); }

/* The axis is pinned the way the drawing runs — oldest on the left — rather
   than following the paragraph, which in Arabic put "30 days ago" on the right
   of a line that starts on the left. */
.chart-axis { direction: ltr; }

/* What a day was: the date, what arrived, and what kind it was. */
.chart-tip {
  display: grid;
  gap: 0.15rem;
  min-inline-size: 8rem;
  text-align: start;
}

.chart-tip .tip-day {
  color: var(--text-dim);
  font-size: 0.75rem;
  font-weight: 600;
}

.chart-tip .tip-total,
.chart-tip .tip-row {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  font-weight: 500;
}

.chart-tip .tip-total { font-weight: 700; }
.chart-tip .count { margin-inline-start: auto; font-variant-numeric: tabular-nums; }
.chart-tip .tip-row { color: var(--text-dim); font-size: 0.8rem; }

/* Held still so it can start again: an animation that is already running does
   not replay, and removing this class after a reflow is what restarts it. */
.toast-bar.no-drain { animation: none !important; }

/* While the page is being arranged, the control that says so wears the
   accent. The bar's own rule, above, carries it there. */
.icon-button.composing {
  color: var(--accent);
}

/*
 * Picking what to add: a grid of cards, each with its mark, its name and a line
 * saying what it draws. Cards that fit themselves to the width rather than a fixed number of
 * columns, so the same dialog works on a laptop and on a phone.
 */
/*
 * Called chooser and not picker: this stylesheet already has a `.picker`, the
 * control that chooses an image, and it carries `line-height: 0`. Naming this
 * one the same inherited that and left the title and the line under it both
 * nought pixels tall, printed on top of each other — a name collision no test
 * for duplicate rules can see, which is a fault this project has met before.
 */
.modal.chooser {
  /* 720 wide. */
  inline-size: min(720px, 95vw);
  max-inline-size: none;
  padding: 32px;
  /* The cap: the dialog never outgrows the window, and what does not fit
     scrolls inside it rather than pushing the close button off the screen. */
  max-block-size: min(88vh, 900px);
  overflow: auto;
}

.picker-head {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  margin-block-end: 28px;
}

.picker-head h2 {
  margin: 0;
  font-size: 26px;
  font-weight: 800;
  letter-spacing: -0.02em;
  /* The title fades from the main text colour into the dim one. It is the
     first thing the eye lands on in the dialog and the reason the header
     reads as a heading rather than as a label. */
  background: linear-gradient(135deg, var(--text-main) 0%, var(--text-dim) 100%);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
}

.picker-head .title-group p {
  margin: 4px 0 0;
  color: var(--text-dim);
  font-size: 0.9rem;
}

.picker-close {
  background: transparent;
  border: 1px solid var(--border);
  color: var(--text-dim);
  padding: 8px;
  border-radius: 12px;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: all 0.2s;
}

/* A dialog opened with showModal() puts the focus on this button, so the
   focus-ring rule applies the moment it appears — and that rule sets a radius
   of its own, which out-specifies the one above and rounds the close to 8
   where it is 12. The shape is restated here rather than dropped from the
   focus rule, which exists to shape the ring on everything else. */
.picker-close:focus,
.picker-close:focus-visible { outline: none; border-radius: 12px; }

/* The way into the review screen, and what is waiting, in one control.
 *
 * A pill rather than a dot: a dot answers "is there anything?", and here the
 * question is "how much work is it?", which is what decides whether somebody
 * opens it now or after lunch. The bar's other icons have no quantity to
 * report; a queue does. The shape is this product's own count-chip, not a new
 * element invented for the bar.
 *
 * Accent while the screen is open, marking the page you are on — and it
 * stays clickable there, because it is also the way out.
 */
.review-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 8px;
  border: 1px solid transparent;
  border-radius: 999px;
  color: var(--text-dim);
  text-decoration: none;
  font-size: 0.8rem;
  font-weight: 600;
  line-height: 1;
  transition:
    color 0.18s var(--ease),
    background-color 0.18s var(--ease),
    border-color 0.18s var(--ease);
}

.review-pill b { font-variant-numeric: tabular-nums; }
/* Written as `.nav-right a.review-pill…` on purpose: the bar's own rule for its
   links, `.nav-right a:not(.button)`, is 0,2,1 and paints every link grey. At
   0,2,0 these lost to it, so "something is waiting" was never coloured
   anywhere but on the review screen itself. */


/* Something is waiting: the colour this product already uses for "waiting",
   not the accent — which is what marks the page you are on, and the two would
   be shouting the same colour at each other on the one control that is often
   both. */
.nav-right a.review-pill.has,
.nav-right a.review-pill.has:hover { color: var(--hue-hold); padding-inline: 4px; }

/* The mode that is on wears the accent and nothing else: no fill, no
   frame. */
.nav-right a.review-pill.on,
.nav-right a.review-pill.on.has,
.nav-right .icon-button.composing {
  color: var(--accent);
}

/* Reviewing what is waiting.
 *
 * The list is 545px capped at 52% with a border on its trailing edge, the
 * detail takes the rest, and the whole thing starts BELOW the 48px top bar.
 * The bar stays because the control that opens this is also the way out.
 *
 * The rows are NOT cards. They are the menu's recipe — the panel's own
 * .side-item: radius 8, an accent wash at 12% and a 2px line on the trailing
 * edge running out to the column's own edge. A card's border-color would only
 * tint the divider under a flush row, which reads as a separator and not as a
 * selection.
 */
.review {
  position: fixed;
  inset-block-start: 48px;
  inset-inline: 0;
  inset-block-end: 0;
  display: flex;
  flex-direction: column;
  background: var(--canvas);
  z-index: 3;
}

.rev-head {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 14px 20px;
  border-block-end: 1px solid var(--border);
  background: var(--panel);
  flex-shrink: 0;
}

.rev-where {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 15px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-main);
  white-space: nowrap;
}

.rev-modes {
  display: flex;
  gap: 4px;
  padding-inline-start: 14px;
  border-inline-start: 1px solid var(--border);
}

.rev-spacer { flex: 1; }

.rev-body { display: flex; flex: 1; min-block-size: 0; }

/* The column: the archive's controls stay put at its head while the rows
   scroll under them. */
.rev-side {
  inline-size: 545px;
  max-inline-size: 52%;
  flex-shrink: 0;
  border-inline-end: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  min-block-size: 0;
}

.rev-list {
  flex: 1;
  min-block-size: 0;
  overflow: auto;
  padding-inline: 12px;
  padding-block-end: 12px;
}

/* The archive's controls. The search box: input colours, the house radius,
   12px text and no glass. */
.rev-filter {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 14px 14px 10px;
  border-block-end: 1px solid var(--border);
}

.search-box {
  display: flex;
  align-items: center;
  gap: 6px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 0 9px;
}

.search-box svg { color: var(--text-dim); flex: none; }

.search-box input {
  border: 0;
  background: none;
  color: var(--text-main);
  font: inherit;
  font-size: 12px;
  padding: 6px 2px;
  inline-size: 100%;
  min-inline-size: 0;
  outline: none;
  box-shadow: none;
}

.search-box:focus-within { border-color: var(--accent); box-shadow: var(--ring); }

.rev-narrow { display: flex; flex-wrap: wrap; gap: 6px; }

/* Two to a row, at every width. Three fitted in English and not in Greek or
   Japanese — "Οποιαδήποτε κατάσταση" is 141px where a third of the column
   leaves 130 — and a label that does not fit is cut short. Two also make the
   four of them a square rather than three and a stray.
 *
 * The house dropdown at its compact size: the same control on the same line as a search
 * box, at the search box's height. */
.rev-narrow {
  --sel-padding: 5px 10px;
  --sel-font-size: 12px;
  --sel-radius: var(--radius-sm);
}

.rev-narrow .sel { flex: 1 1 calc(50% - 3px); inline-size: auto; }
.rev-narrow .sel-trigger { color: var(--text-dim); }

/* A narrowing that is on says so: the same accent a chosen row carries. */
.rev-narrow .sel.set .sel-trigger {
  color: var(--text-main);
  border-color: var(--accent-line);
  background: var(--accent-wash);
}

.rev-more,
.rev-unfilter { display: flex; justify-content: center; margin: 12px 0 4px; }

.rev-detail {
  flex: 1 1 auto;
  min-inline-size: 0;
  overflow: auto;
  padding: 28px 32px;
  position: relative;
}

/* The drawer's own parts, which only exist on a phone. */
.rev-veil,
.rev-shut { display: none; }

.rev-group {
  margin: 0;
  padding: 14px 6px 6px;
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-dim);
  display: flex;
  align-items: center;
  gap: 8px;
}

.rev-group .count-chip { margin-inline-start: auto; }

/* The menu's row, to the letter. */
.rev-row {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  inline-size: 100%;
  box-sizing: border-box;
  padding: 0.5rem 0.6rem;
  border: none;
  border-radius: 8px;
  background: transparent;
  color: var(--text-main);
  font: inherit;
  text-align: start;
  cursor: pointer;
}

.rev-row:hover { background: rgba(127, 127, 127, 0.12); }

.rev-row.on {
  background: rgba(var(--accent-rgb), 0.12);
  border-inline-end: 2px solid var(--accent);
  /* Out to the column's own edge, which is padded by 12px. Logical radii: the
     four-value shorthand is physical and would square the wrong corners where
     the line runs the other way. */
  margin-inline-end: -12px;
  border-start-end-radius: 0;
  border-end-end-radius: 0;
}

.rev-row.child { margin-inline-start: 26px; inline-size: calc(100% - 26px); }

/* Turned away, in the archive: still findable, visibly not part of the
   product's public face. */
.rev-row.out .rev-t,
.rev-row.out .rev-p { opacity: 0.55; }
.rev-row.out .rev-t { text-decoration: line-through; text-decoration-color: var(--text-dim); }

.rev-dot {
  inline-size: 8px;
  block-size: 8px;
  border-radius: 50%;
  margin-block-start: 6px;
  flex-shrink: 0;
}

.rev-dot.bug { background: var(--danger-color); }
.rev-dot.idea { background: var(--hue-open); }
.rev-dot.msg { background: var(--text-dim); }
.rev-row.on .rev-dot { box-shadow: 0 0 0 3px rgba(var(--accent-rgb), 0.15); }

.rev-main { min-inline-size: 0; flex: 1; }

.rev-t {
  display: block;
  font-weight: 600;
  font-size: 0.92rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Two lines of what it says, which is what makes a row decidable without
   opening it. The screen this replaces showed none. */
.rev-p {
  margin: 2px 0 0;
  font-size: 0.82rem;
  color: var(--text-dim);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.rev-w { font-size: 0.72rem; color: var(--text-dim); white-space: nowrap; flex-shrink: 0; }

/* The detail is a column with one gap, not a stack of margins. Each part used
   to carry its own space underneath, and the two that carried none — the
   answers and the state of a decided report — sat touching what came next:
   the three decisions glued to the bottom of "What was asked". A part added
   tomorrow gets the same space without having to know it needs one. */
.rev-detail [data-detail] { display: flex; flex-direction: column; gap: 18px; }
.rev-detail [data-detail] > * { margin-block: 0; }
.rev-detail .answers { margin: 0; }
.rev-detail [data-detail] > .status { align-self: flex-start; }

.rev-meta { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.rev-when { color: var(--text-dim); font-size: 0.82rem; }
.rev-title { font-size: 1.5rem; line-height: 1.25; }
.rev-full { color: var(--text-main); line-height: 1.65; }
.rev-actions { display: flex; gap: 10px; flex-wrap: wrap; }
.rev-nothing { color: var(--text-dim); }
/* What a message is about, above the decision and not below it: read blind is
   decided blind. */
.rev-onwhat { color: var(--text-dim); font-size: .9rem; }
.rev-empty { padding-block-start: 2rem; }

.rev-foot {
  padding: 8px 16px;
  border-block-start: 1px solid var(--border);
  background: var(--panel);
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--text-dim);
  display: flex;
  gap: 14px;
  flex-wrap: wrap;
  flex-shrink: 0;
}

.rev-foot kbd {
  font-family: var(--font-mono);
  background: var(--surface-dim);
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 1px 5px;
  font-size: 10px;
  color: var(--text-main);
}

/* The fold, at 640px: the list takes the screen, and a tapped row opens the
   detail as a drawer from the trailing edge — 420px or the whole width, a veil
   at 45%, a 0.28s slide mirrored by --rtl-flip, and the square close that
   turns. */
@media (max-width: 640px) {
  .rev-side { inline-size: 100%; max-inline-size: 100%; border-inline-end: none; }
  .rev-foot { display: none; }
  .rev-modes { padding-inline-start: 8px; }

  /* Both below the top bar, like the rest of this screen: the bar stays on
     top of everything here, so a drawer drawn from the window's own top edge
     had its close button hidden underneath it. */
  .rev-veil {
    display: block;
    position: fixed;
    inset: 48px 0 0;
    background: rgba(0, 0, 0, 0.45);
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.25s ease;
    z-index: 4;
  }

  .rev-detail {
    position: fixed;
    inset-block: 48px 0;
    inset-inline-end: 0;
    inline-size: min(420px, 100%);
    background: var(--panel);
    border-inline-start: 1px solid var(--border);
    box-shadow: calc(var(--rtl-flip, 1) * -10px) 0 40px rgba(0, 0, 0, 0.35);
    padding: 64px 20px 24px;
    box-sizing: border-box;
    transform: translateX(calc(var(--rtl-flip, 1) * 100%));
    /* Hidden once it has slid out, so what is off screen cannot be reached
       with the tab key; shown at once when it slides in. */
    transition: transform 0.28s var(--ease), visibility 0s linear 0.28s;
    visibility: hidden;
    z-index: 5;
  }

  .review.reading .rev-veil { opacity: 1; pointer-events: auto; }
  .review.reading .rev-detail {
    transform: translateX(0);
    visibility: visible;
    transition: transform 0.28s var(--ease), visibility 0s;
  }

  .rev-shut {
    display: flex;
    align-items: center;
    justify-content: center;
    position: absolute;
    inset-block-start: 15px;
    inset-inline-end: 16px;
    inline-size: 36px;
    block-size: 36px;
    padding: 8px;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: transparent;
    color: var(--text-dim);
    cursor: pointer;
    transition: all 0.2s;
  }

  .rev-shut:hover,
  .rev-shut:focus-visible {
    background: rgba(var(--accent-rgb), 0.1);
    transform: rotate(90deg);
    color: var(--accent);
    border-color: rgba(var(--accent-rgb), 0.3);
    border-radius: var(--radius);
  }
}

@media (max-width: 640px) and (prefers-reduced-motion: reduce) {
  .rev-veil,
  .rev-detail { transition: none; }
}

/* A block of words. The frame is the module's, so the words carry none of
   their own: a second border inside the first reads as a box in a box. */

.words {
  inline-size: 100%;
  box-sizing: border-box;
  /* The writing bar is placed inside this, so this is what it is placed
     against. */
  position: relative;
}

.words-box {
  box-sizing: border-box;
  color: var(--text-main);
  line-height: 1.6;
}

/* The things somebody can write, dressed once. Margins collapse to nothing at
   the ends so the first line sits where the padding puts it. */
.words-box > :first-child { margin-block-start: 0; }
.words-box > :last-child { margin-block-end: 0; }
.words-box p { margin: 0 0 0.6rem; }
.words-box h1 { font-size: 1.9rem; margin: 0 0 0.5rem; line-height: 1.2; }
.words-box h2 { font-size: 1.35rem; margin: 0.9rem 0 0.4rem; }
.words-box h3 { font-size: 1.1rem; margin: 0.8rem 0 0.3rem; }
.words-box ul,
.words-box ol { margin: 0 0 0.6rem; padding-inline-start: 1.4rem; }
.words-box blockquote {
  margin: 0 0 0.6rem;
  padding-inline-start: 0.9rem;
  border-inline-start: 3px solid var(--accent-line);
  color: var(--text-dim);
}
.words-box code {
  font-family: var(--font-mono);
  font-size: 0.9em;
  background: var(--surface-dim);
  padding: 0.1em 0.35em;
  border-radius: 4px;
}
.words-box a { color: var(--accent-alt); }
.words-box .lead { color: var(--text-dim); font-size: 1.05rem; }

/* Alignment and colour, as classes rather than as inline styles.
 *
 * Not a style choice: this page is served with style-src 'self' and no
 * unsafe-inline, so a style attribute written by an editing command is dropped
 * by the browser without a word. These are the same names the server accepts,
 * and the set is closed on both sides. */
.words-box .align-center { text-align: center; }
.words-box .align-end { text-align: end; }
.words-box .align-justify { text-align: justify; }

.words-box .ink-plain { color: var(--text-main); }
.words-box .ink-dim { color: var(--text-dim); }
.words-box .ink-accent { color: var(--accent-alt); }
.words-box .ink-danger { color: var(--danger-color); }
.words-box .ink-success { color: var(--success-color); }
.words-box .ink-warning { color: var(--warning-color); }
.words-box .ink-info { color: var(--info-color); }

/* A picture inside a block of words.
 *
 * The width comes from the attribute the handle writes, so it is not set here:
 * what is set is the ceiling, because a module is narrower on a phone than the
 * width somebody chose on a desktop and a picture that keeps that width would
 * push the page sideways. */
.words-box img {
  max-inline-size: 100%;
  block-size: auto;
  border-radius: var(--radius);
  vertical-align: middle;
}

/* Chosen, while the block is being written: the corner to drag is drawn on the
   picture rather than inserted beside it, so there is nothing in the markup to
   remember to take out again before it is stored. */
.words-box[contenteditable="true"] img {
  cursor: pointer;
}

.words-box[contenteditable="true"] img.chosen {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  /* The triangle in the bottom-right corner, drawn the way the grid draws the
     one that resizes a module — same corner, same idea, so the gesture is
     already known. Physical, not logical: it is where the pointer is. */
  box-shadow:
    inset -14px -14px 0 -7px var(--accent),
    inset -18px -18px 0 -8px var(--panel);
  cursor: nwse-resize;
}

/* What an untouched block says while the page is being arranged. */
.words-prompt { color: var(--text-dim); font-style: italic; }

/* The bar that follows the cursor. It hangs under the block being written,
   not over it — over it, it covered the words it formats — and it is placed
   against the block rather than the window, so nothing has to be recalculated
   as the page scrolls. */
.words-bar {
  position: absolute;
  inset-inline-start: 0;
  inset-block-start: calc(100% + 10px);
  inline-size: max-content;
  max-inline-size: min(36rem, 90vw);
  z-index: 8;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px;
  padding: 5px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 10px;
  box-shadow: var(--shadow-lg);
}

.words-bar[hidden] { display: none; }

.words-group {
  display: flex;
  align-items: center;
  gap: 2px;
}

.words-group + .words-group {
  padding-inline-start: 5px;
  border-inline-start: 1px solid var(--border);
}

.words-tool {
  display: flex;
  align-items: center;
  justify-content: center;
  inline-size: 26px;
  block-size: 26px;
  padding: 0;
  background: transparent;
  border: none;
  border-radius: 6px;
  color: var(--text-dim);
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 600;
  cursor: pointer;
  transition: all 0.15s var(--ease);
}

.words-tool.wide { inline-size: 30px; }
.words-tool:hover { background: var(--accent-wash); color: var(--accent); }

/* The palette. A swatch shows its own colour, which is the only label a colour
   needs; the name is in the tooltip for anybody who cannot tell them apart. */
.words-ink {
  display: flex;
  align-items: center;
  justify-content: center;
  inline-size: 22px;
  block-size: 22px;
  padding: 0;
  background: transparent;
  border: none;
  border-radius: 6px;
  cursor: pointer;
}

.words-ink span {
  inline-size: 13px;
  block-size: 13px;
  border-radius: 50%;
  border: 1px solid var(--card-border);
}

.words-ink:hover span { transform: scale(1.15); }
.words-ink.ink-plain span { background: var(--text-main); }
.words-ink.ink-dim span { background: var(--text-dim); }
.words-ink.ink-accent span { background: var(--accent); }
.words-ink.ink-danger span { background: var(--danger-color); }
.words-ink.ink-success span { background: var(--success-color); }
.words-ink.ink-warning span { background: var(--warning-color); }
.words-ink.ink-info span { background: var(--info-color); }

.picker-grid {
  display: grid;
  /* 190, not 200: there are eleven cards, the dialog is capped at the window
     height, and the scrollbar takes the width back — measured at 639px of
     usable space against the 640 that three 200px columns need. One pixel
     short reads as a two-column dialog, so the floor drops by ten. */
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: 20px;
}

.picker-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  padding: 24px;
  border: 1px solid var(--border);
  border-radius: 20px;
  background: var(--surface-dim);
  color: inherit;
  cursor: pointer;
  font: inherit;
  /* The card is not a flat tile that darkens: it lifts eight pixels, grows a
     little, and lights a wash of accent behind itself, on the overshooting
     curve. */
  position: relative;
  overflow: hidden;
  box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05);
  transition: all 0.4s var(--ease-bounce);
}

.picker-card::before {
  content: "";
  position: absolute;
  inset: 0;
  background: radial-gradient(circle at top right, var(--accent), transparent 70%);
  opacity: 0;
  transition: opacity 0.5s;
  z-index: 0;
}

.picker-card:hover {
  background: rgba(var(--accent-rgb), 0.08);
  border-color: var(--accent);
  transform: translateY(-8px) scale(1.02);
  box-shadow:
    0 20px 40px -15px rgba(0, 0, 0, 0.2),
    0 0 20px rgba(var(--accent-rgb), 0.1);
}

.picker-card:hover::before { opacity: 0.15; }

/* The mark sits in a tile of its own, which is most of why the card reads as
   a card and not as a button with a picture on it. */
.picker-icon {
  inline-size: 64px;
  block-size: 64px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 18px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--accent);
  margin-block-end: 20px;
  box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.08);
  transition: all 0.3s;
  z-index: 1;
}

.picker-icon .icon { inline-size: 32px; block-size: 32px; }

/* The tile turns to the accent and tips five degrees under the pointer. It is
   a small thing and it is exactly the sort of small thing that makes the two
   products feel like one. */
.picker-card:hover .picker-icon {
  transform: translateY(-4px) rotate(-5deg);
  background: var(--accent);
  border-color: #fff;
  color: #fff;
  box-shadow: 0 10px 20px rgba(var(--accent-rgb), 0.4);
}

.picker-name {
  font-weight: 700;
  font-size: 18px;
  margin-block-end: 8px;
  z-index: 1;
  color: var(--text-main);
}

.picker-about {
  font-size: 13px;
  color: var(--text-dim);
  line-height: 1.5;
  z-index: 1;
}

/* The close does not just change colour: it turns a quarter turn into the
   accent, with a wash behind it. */
.picker-close:hover {
  background: var(--accent-wash);
  transform: rotate(90deg);
  color: var(--accent);
  border-color: var(--accent-line);
}

@media (prefers-reduced-motion: reduce) {
  .picker-close:hover { transform: none; }
}

/* And the action: the whole width, outlined, in the accent, in small caps. */
.modal-foot { padding: 16px 24px 24px; }

/* Asking before removing: a composition of its own rather than the settings
   dialog's furniture — its own padding, a header over a rule, an 18px title, a
   14px message, and one button at the end of the line rather than stretched
   across the width. */
.modal.confirm {
  padding: 28px;
  inline-size: min(30rem, calc(100vw - 2rem));
}

.confirm-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  margin-block-end: 20px;
  padding-block-end: 12px;
  border-block-end: 1px solid var(--border);
}

.confirm-title {
  margin: 0;
  font-size: 18px;
  font-weight: 600;
  color: var(--text-main);
}

.modal.confirm .picker-close { inline-size: 36px; block-size: 36px; flex: none; }

.confirm-message {
  margin: 0 0 28px;
  font-size: 14px;
  line-height: 1.6;
  color: var(--text-dim);
}

.confirm-actions {
  display: flex;
  gap: 12px;
  justify-content: flex-end;
}

/* The menu the plus opens, anchored under it. */
.add-wrap { position: relative; display: inline-flex; }
.add-wrap .add-menu { inset-block-start: calc(100% + 6px); inset-inline-end: 0; }

@keyframes tab-underline {
  from { transform: scaleX(0); opacity: 0; }
  to { transform: scaleX(1); opacity: 1; }
}

/* Three widths, as a segmented control. */
.segmented {
  display: flex;
  background: color-mix(in srgb, var(--panel) 60%, transparent);
  padding: 4px;
  border-radius: 8px;
  margin-block-start: 8px;
  gap: 4px;
  border: 1px solid var(--border);
}

.segment {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 8px;
  background: transparent;
  border: 1px solid transparent;
  border-radius: 6px;
  color: var(--text-dim);
  font: inherit;
  cursor: pointer;
  transition: all 0.2s ease;
}

.segment span:last-child { font-size: 11px; font-weight: 500; }

.segment:hover { color: var(--text-main); }

.segment.on {
  background: color-mix(in srgb, var(--accent) 10%, transparent);
  color: var(--accent);
  border-color: var(--accent);
  box-shadow: 0 0 10px rgba(var(--accent-rgb), 0.1);
}

/*
 * The panel comes in from the side, as a drawer does.
 *
 * translateX is physical and does not follow the reading direction, so it is
 * multiplied by --rtl-flip: in Arabic the panel has to arrive from the left or
 * it slides in from the wrong edge of the screen.
 */
@keyframes panel-arrives {
  from {
    transform: translateX(calc(var(--rtl-flip, 1) * 3%));
    opacity: 0;
  }
  to {
    transform: translateX(0);
    opacity: 1;
  }
}

.admin.arriving {
  animation: panel-arrives 0.4s var(--ease) both;
}

/* And out the way it came in, when the same control closes it. */
@keyframes panel-leaves {
  from { transform: translateX(0); opacity: 1; }
  to { transform: translateX(calc(var(--rtl-flip, 1) * 3%)); opacity: 0; }
}

.admin.leaving {
  animation: panel-leaves 0.25s var(--ease) both;
}

/*
 * A control that cannot be used while another mode is on: dimmed, greyed and deaf to the pointer, rather than missing — a bar whose
 * controls come and go is a bar you have to read again every time.
 */
/* The side column of the panel icon, which fills while the panel is open — so
   the icon says which state you are in, not what clicking will do. */
.panel-indicator { fill: currentColor; opacity: 0; transition: opacity 0.2s; }
.panel-indicator.on { opacity: 1; }

.icon-button.cannot,
.nav-right a.review-pill.cannot {
  opacity: 0.3;
  pointer-events: none;
  filter: grayscale(1);
  cursor: default;
}

@media (prefers-reduced-motion: reduce) {
  .admin.arriving,
  .admin.leaving { animation: none; }
}

/* The add menu, not the kebab's.
 *
 * It borrowed .kebab-menu, which is a different control: wider, rounder, in
 * body text. This is a small monospaced list — 4px radius, 4px of padding,
 * 11px mono, 14px icons at 0.7 opacity. */
.add-menu {
  position: absolute;
  inline-size: max-content;
  inset-block-start: 36px;
  inset-inline-end: 4px;
  z-index: 5;
  min-inline-size: 140px;
  padding: 4px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 4px;
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.28);
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.add-menu[hidden] { display: none; }

.add-menu-item {
  display: flex;
  align-items: center;
  /* One line, whatever the language. The menu is sized by the words in it, and
     an item that wraps turns a two-entry menu into a four-line block — which is
     what "Add a module" did in the languages that carry an article. */
  white-space: nowrap;
  inline-size: 100%;
  box-sizing: border-box;
  padding: 8px 12px;
  background: transparent;
  border: none;
  border-radius: 3px;
  color: var(--text-dim);
  font-family: var(--font-mono);
  font-size: 11px;
  text-align: start;
  cursor: pointer;
  transition: all 0.2s var(--ease);
}

.add-menu-item:hover {
  background: var(--accent-wash);
  color: var(--accent);
}

.add-menu-item .icon {
  inline-size: 14px;
  block-size: 14px;
  margin-inline-end: 8px;
  opacity: 0.7;
  flex-shrink: 0;
}

.add-menu-item:hover .icon { opacity: 1; }

/*
 * The settings panel of the house.
 *
 * A person who uses more than one CodigoSH product should not be able to tell
 * which one they are in. The recipe:
 *   - cards with no icon: a whisper of fill, a border, radius 10, 16 by 20 of
 *     padding, one under another, 16 apart;
 *   - the card's name at 11px, semibold, in dim capitals spaced 0.08em;
 *   - labels at 12px, dim; fields 36px tall, 13px, radius 8, a slight inset
 *     shade, no wider than 420px;
 *   - buttons outlined — 2px of accent for the one that commits, 1px of
 *     border for the rest — 36px, 13px, capitals spaced 0.05em;
 *   - rows with what a thing is on one side and its control at the end of
 *     the line, a rule between them.
 *
 * Scoped to the panel's content, and never the new-organisation wizard,
 * which belongs with sign-up and sign-in: the screens that ask for one thing
 * wear a filled button.
 */
.admin-main {
  --field: 420px;
  --control-h: 36px;
  --radius-control: 8px;
  --sel-padding: 0 12px;
  --sel-font-size: 13px;
  --sel-radius: var(--radius-control);
}

/* The page's own name is the item chosen in the menu. The heading stays for whoever reads the page out loud. */
.admin-main > h1:not(.visually-hidden) { font-size: 1.35rem; }

.settings {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 16px;
  max-inline-size: 760px;
  margin: 0 0 16px;
  padding: 0;
  list-style: none;
}

.admin-main .bento-card {
  gap: 0;
  padding: 16px 20px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--sheet);
  box-shadow: none;
}

.admin-main .bento-card .card-head { gap: 0; color: inherit; margin: 0 0 4px; }
.admin-main .bento-card .card-head > .icon { display: none; }

/* 13px. */
.admin-main .bento-card .card-head h3,
.admin-main .card-top h3 {
  margin: 0;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-dim);
}

.card-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-block-end: 4px;
}

.admin-main .bento-card .card-sub {
  display: block;
  margin: 0 0 12px;
  font-size: 13px;
  line-height: 1.5;
  color: var(--text-dim);
}

/* Fields: the form group, label and control. */
.admin-main :not(.wizard) .field { margin-block: 0 16px; max-inline-size: var(--field); }
.admin-main :not(.wizard) .field > label {
  display: block;
  margin-block-end: 6px;
  font-size: 12px;
  font-weight: 500;
  color: var(--text-dim);
}

.admin-main :not(.wizard) .field input:not([type="checkbox"]):not([type="radio"]):not([type="color"]):not([type="file"]),
.admin-main :not(.wizard) .field textarea,
.admin-main .input-row input,
.admin-main .logo-row input {
  box-sizing: border-box;
  inline-size: 100%;
  max-inline-size: var(--field);
  min-block-size: var(--control-h);
  padding: 0 12px;
  border: 1px solid var(--border);
  border-radius: var(--radius-control);
  background: rgba(255, 255, 255, 0.05);
  box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.1);
  font-size: 13px;
}

.admin-main :not(.wizard) .field textarea { padding-block: 8px; }
.admin-main :not(.wizard) .field .hint { margin: 4px 0 0; font-size: 11px; color: var(--text-dim); }
.admin-main :not(.wizard) .field .sel { max-inline-size: var(--field); }
.admin-main .sel-trigger { min-block-size: var(--control-h); }

/* A field and the button that commits it, on one line. */
.input-row { display: flex; gap: 8px; align-items: center; }
.input-row input { flex: 1; }
.input-row .button { flex: none; }

/* What closes a form: aligned with the fields, as wide as its label. */
.settings-actions { display: flex; flex-wrap: wrap; gap: 12px; margin-block-start: 24px; }
.settings-actions .button { min-inline-size: 160px; }

/* Buttons. */
.admin-main .button:not(.wizard .button),
.update-dialog .button {
  block-size: var(--control-h);
  padding: 0 16px;
  border: 1px solid var(--border);
  border-radius: var(--radius-control);
  background: transparent;
  box-shadow: none;
  color: var(--text-dim);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  transform: none;
}

.admin-main .button:not(.wizard .button):hover,
.update-dialog .button:hover {
  border-color: var(--border-strong);
  background: var(--panel);
  color: var(--text-main);
  box-shadow: none;
  transform: none;
}

.admin-main .button:not(.wizard .button):active,
.update-dialog .button:active { transform: translateY(1px); }

.admin-main .button.primary:not(.wizard .button),
.update-dialog .button.primary {
  border: 2px solid var(--accent);
  background: transparent;
  color: var(--accent);
}

.admin-main .button.primary:not(.wizard .button):hover,
.update-dialog .button.primary:hover {
  border-color: var(--accent);
  background: rgba(var(--accent-rgb), 0.1);
  color: var(--accent);
}

.admin-main .button.danger:not(.wizard .button) {
  border: 2px solid var(--danger-color);
  background: transparent;
  color: var(--danger-color);
}

.admin-main .button.danger:not(.wizard .button):hover {
  background: rgba(var(--danger-rgb), 0.1);
  color: var(--danger-color);
}

.admin-main .button .icon { inline-size: 14px; block-size: 14px; }

/* A dialog's two buttons share its width. */
.admin-main .modal-foot .button { flex: 1; justify-content: center; }

/* The row: what a thing is, and the one thing you do to it at the end. */
.settings-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  column-gap: 32px;
  align-items: center;
  min-block-size: 56px;
  padding: 14px 0;
  border-block-end: 1px solid var(--border);
}

.settings-row:last-of-type { border-block-end: 0; padding-block-end: 0; }
/* A row after a form in the same card starts a new thing: the rule the rows
   draw between themselves, drawn above it. */
.settings-card form + .settings-row,
.settings-card .settings-list + .settings-row { margin-block-start: 20px; border-block-start: 1px solid var(--border); }
.settings-info h4 { margin: 0 0 2px; font-size: 13px; font-weight: 600; }
.settings-info p { margin: 0; font-size: 12px; line-height: 1.5; color: var(--text-dim); max-inline-size: 56ch; }
.settings-info .settings-danger { color: var(--danger-color); font-weight: 600; }

/* A list of users: a row per thing, a surface and a border, the accent
   when pointed at, the actions at the far end. */
.settings-list { display: grid; gap: 10px; margin: 8px 0 0; padding: 0; list-style: none; }
.settings-list + .card-sub { margin-block-start: 12px; }

.settings-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 10px 14px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--sheet);
  transition: border-color 0.2s var(--ease), background-color 0.2s var(--ease);
}

/* The word list: an add form on one line, the hint under it, and the rows. */
.word-add { display: flex; align-items: flex-end; gap: 12px; margin-block-start: 12px; }
.settings-card .word-add .field { flex: 1; min-inline-size: 0; margin: 0; }
.word-add .button { flex: none; }
.word-help { margin: 8px 0 4px; }
.settings-card .word-help + .settings-list { margin-block-start: 16px; }
.word-edit { display: flex; align-items: center; gap: 6px; flex: 1; min-inline-size: 0; }
.word-edit input { flex: 1; min-inline-size: 0; }
/* The decision row wraps: in the reports pane the buttons do not fit beside the
   sentence, and the sentence was squeezed into a column of a few letters. */
.settings-row.decide { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 10px 32px; }
.settings-row.decide .settings-info { flex: 1 1 14rem; }
.settings-card .settings-row + .add-word { margin-block-start: 16px; padding-block-start: 16px; border-block-start: 1px solid var(--border); }
/* Narrow and at the end of the line: it narrows a list, it is not the card's main act. */
.word-search { flex: 0 0 auto; align-self: flex-end; inline-size: min(100%, 18rem); margin-block-start: 16px; }
.word-results { margin-block-start: 4px; }
.pager { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-block-start: 12px; }
.pager-range { font-family: var(--font-mono); font-size: 11px; color: var(--text-dim); font-variant-numeric: tabular-nums; }
.pager-prev svg { transform: scaleX(calc(-1 * var(--rtl-flip))); }
.pager-next svg { transform: scaleX(var(--rtl-flip)); }
.pager .icon-button[disabled] { opacity: 0.35; cursor: default; pointer-events: none; }
@media (max-width: 480px) { .word-add { flex-direction: column; align-items: stretch; } }

.settings-item:hover { border-color: rgba(var(--accent-rgb), 0.4); background: rgba(var(--accent-rgb), 0.04); }

.si-main {
  display: flex;
  align-items: center;
  gap: 12px;
  flex: 1;
  min-inline-size: 0;
  color: inherit;
  text-decoration: none;
}

.si-main .avatar { inline-size: 36px; block-size: 36px; font-size: 13px; flex: none; }
.si-mark { display: grid; place-items: center; inline-size: 36px; block-size: 36px; flex: none; color: var(--text-dim); }
.si-text { display: grid; gap: 2px; min-inline-size: 0; }
.si-name { font-size: 14px; font-weight: 600; color: var(--text-main); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.si-sub { font-family: var(--font-mono); font-size: 11px; color: var(--text-dim); }
.si-tags { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-inline-start: auto; }
.si-actions { display: flex; gap: 4px; flex: none; }

/* A small monospaced tag: a role, a slug, a state. */
.mono-tag {
  display: inline-block;
  padding: 2px 8px;
  border: 1px solid var(--border);
  border-radius: 4px;
  background: rgba(128, 128, 128, 0.06);
  color: var(--text-dim);
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.05em;
  text-transform: uppercase;
}

.mono-tag.accent {
  border-color: rgba(var(--accent-rgb), 0.2);
  background: rgba(var(--accent-rgb), 0.1);
  color: var(--accent);
}

/* A state as a mark: what waits, as the review icon and its count; a forge
   not connected, as the warning. In the hue the review pill wears when it
   has work, which is what both of them mean. */
.state-mark {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-family: var(--font-mono);
  font-size: 12px;
  font-variant-numeric: tabular-nums;
}
.state-mark.hold { color: var(--hue-hold); }
.state-mark b { font-weight: 600; }

/* The profile header: the picture, and the name with its tags beside it. */
.profile-head { display: flex; align-items: center; gap: 20px; margin: 8px 0 32px; }
.profile-who { display: flex; flex-direction: column; align-items: flex-start; gap: 2px; }
.profile-name { font-size: 17.5px; font-weight: 600; }
.profile-role { font-size: 16px; color: var(--text-main); }

.logo-row { display: flex; align-items: center; gap: 12px; }
.logo-row input { flex: 1; }
.org-tags { display: flex; align-items: center; gap: 6px; margin: 0 0 8px; }

/* A segmented control, for the theme: small, one line, the chosen segment
   raised. */
.admin-main .theme-choice {
  display: flex;
  gap: 2px;
  max-inline-size: 290px;
  padding: 2px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface-dim);
}

.admin-main .theme-choice button {
  display: flex;
  flex: 1;
  flex-direction: row;
  align-items: center;
  justify-content: center;
  gap: 5px;
  min-inline-size: 0;
  min-block-size: 0;
  padding: 8px 6px;
  border: 1px solid transparent;
  border-radius: 6px;
  background: transparent;
  box-shadow: none;
  color: var(--text-dim);
  font-size: 11px;
}

.admin-main .theme-choice button .icon { inline-size: 14px; block-size: 14px; color: inherit; }
.admin-main .theme-choice button:hover { background: var(--border); color: var(--text-main); }

.admin-main .theme-choice button[aria-pressed="true"] {
  border-color: var(--border);
  background: var(--panel);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
  color: var(--text-main);
  font-weight: 500;
}

.admin-main .theme-choice button[aria-pressed="true"] .icon { color: inherit; }

/* Overview wears the same card: no icons, the quiet capital name, the number
   under it. */
.admin-main .dash-card {
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--sheet);
  box-shadow: none;
}

.admin-main .dash-card > h3 {
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-dim);
}

.admin-main .dash-card .stat .label {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.admin-main .dash-card .stat .label .icon { display: none; }

@media (max-width: 40rem) {
  .settings-row { grid-template-columns: minmax(0, 1fr); row-gap: 10px; }
  /* A switch is small enough to stay at the end of its line. */
  .settings-row:has(> .toggle) { grid-template-columns: minmax(0, 1fr) auto; }
  .profile-head { flex-direction: column; align-items: flex-start; }
  .si-tags { display: none; }
}

/* The logo is a square tile with a border beside the name, not a round
   portrait: a mark is not a face. */
.logo-row .avatar,
.logo-row .logo-preview {
  inline-size: 48px;
  block-size: 48px;
  border: 1px solid var(--border);
  border-radius: 10px;
  object-fit: contain;
}

/* The accent swatches: 32px squares with a 4px
   radius and a faint border, growing a little under the pointer; the chosen
   one ringed in white with a glow and its tick shown. The eighth is a
   rainbow until a colour of its own is chosen, and the device's own colour
   picker sits invisibly over it. */
.color-grid { display: flex; flex-wrap: wrap; gap: 12px; }

/* Through the grid: the eighth is a <label>, and ".field label" (0,1,1) makes
   every label in a field a block, which dropped its palette into the corner. */
.color-grid > .accent-swatch {
  position: relative;
  display: flex;
  margin: 0;
  align-items: center;
  justify-content: center;
  inline-size: 32px;
  block-size: 32px;
  padding: 0;
  border: 2px solid rgba(255, 255, 255, 0.05);
  border-radius: 4px;
  background-clip: padding-box;
  cursor: pointer;
  transition: transform 0.15s var(--ease), border-color 0.15s var(--ease), box-shadow 0.15s var(--ease);
}

.accent-swatch:hover { transform: scale(1.1); border-color: rgba(255, 255, 255, 0.2); z-index: 2; }

.accent-swatch.on {
  border-color: #fff;
  box-shadow: 0 0 15px rgba(255, 255, 255, 0.2);
  transform: scale(1.05);
}

.accent-swatch svg { inline-size: 18px; block-size: 18px; fill: #fff; filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.5)); }
.accent-swatch:not(.acc-custom) svg { display: none; }
.accent-swatch.on:not(.acc-custom) svg { display: block; }

.acc-blue { background: #228be6; }
.acc-indigo { background: #4c6ef5; }
.acc-cyan { background: #15aabf; }
.acc-teal { background: #12b886; }
.acc-orange { background: #e8520e; }
.acc-red { background: #fa5252; }
.acc-grape { background: #be4bdb; }

.acc-custom {
  overflow: hidden;
  border: 1px solid rgba(255, 255, 255, 0.1);
  background: conic-gradient(from 0deg, red, yellow, lime, aqua, blue, magenta, red);
}

.acc-custom .acc-palette { pointer-events: none; }

.acc-custom input[type="color"] {
  position: absolute;
  inset: 0;
  inline-size: 100%;
  block-size: 100%;
  padding: 0;
  border: 0;
  opacity: 0;
  cursor: pointer;
}

@media (prefers-reduced-motion: reduce) {
  .accent-swatch { transition: none; }
}

/* The team's report: the report card, then one card per job. The pencil and
   the bin sit at the end of the report's meta line, where the report begins. */
.item-team { max-inline-size: 760px; }
.item-team .detail { margin: 0; }
.detail .meta .detail-tools { margin-inline-start: auto; display: inline-flex; gap: 4px; }

/* Who and with which role, side by side: two short choices that belong to
   one sentence. On a phone they stack again. */
.member-add-fields {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px;
}
.member-add-fields > .field { margin: 0; }
@media (max-width: 34rem) {
  .member-add-fields { grid-template-columns: minmax(0, 1fr); }
}
.detail-words .rewrite { display: grid; gap: 12px; }
.detail-words .rewrite .settings-actions { gap: 8px; margin-block-start: 4px; }
.decide-actions { display: flex; flex-wrap: wrap; gap: 8px; }
/* A field and its button on one line, as the attach row asks for one short
   value and acts on it. */
.inline-field { display: flex; gap: 8px; align-items: center; }
.inline-field input { inline-size: 200px; }
.item-team .comments { margin-block: 8px 16px; }
.item-team .team-reply textarea { inline-size: 100%; max-inline-size: none; }
.item-team .settings-row .sel { min-inline-size: 180px; }


/* ------------------------------------------------------------ the updater */

/* The updater: the red dot on the bar's panel icon, "New update!" beside Info
   in the menu, the Docker notice, and the dialog with its timeline. */

.panel-toggle { position: relative; }

.notification-dot {
  position: absolute;
  inset-block-start: 4px;
  inset-inline-end: 4px;
  inline-size: 8px;
  block-size: 8px;
  /* Content-box: 8px of red inside a 2px ring, 12px in all.
     This product sets border-box on everything, and that alone had shrunk
     the dot to 8px with 4px of red — the same recipe, visibly smaller. */
  box-sizing: content-box;
  background-color: var(--danger-color);
  border-radius: 50%;
  border: 2px solid var(--panel);
  animation: notification-blink 2s infinite;
  will-change: opacity, transform;
}

@keyframes notification-blink {
  0%, 100% { opacity: 1; transform: scale(1); }
  50% { opacity: 0.5; transform: scale(0.8); }
}

.side-update {
  margin-inline-start: auto;
  font-size: 10px;
  font-weight: 600;
  color: var(--danger-color);
  background: rgba(var(--danger-rgb), 0.1);
  padding: 2px 6px;
  border-radius: 4px;
  border: 1px solid rgba(var(--danger-rgb), 0.2);
  white-space: nowrap;
  animation: side-update-blink 2s infinite;
}

@keyframes side-update-blink {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.6; }
}

.about-docker {
  display: flex;
  gap: 12px;
  align-items: flex-start;
  inline-size: 100%;
  box-sizing: border-box;
  padding: 16px;
  border: 1px solid rgba(var(--info-rgb), 0.3);
  border-radius: var(--radius);
  background: rgba(var(--info-rgb), 0.1);
  text-align: start;
}

.about-docker > .icon { flex-shrink: 0; color: var(--accent); }
.about-docker h4 { margin: 0 0 4px; font-size: 14px; color: var(--text-main); }
.about-docker p { margin: 0; font-size: 13px; color: var(--text-dim); }
.about-docker strong { color: var(--accent); }

.update-dialog {
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--canvas);
  color: var(--text-main);
  padding: 28px;
  inline-size: clamp(320px, 90vw, 460px);
  box-sizing: border-box;
  box-shadow: var(--shadow-lg);
}

.update-dialog::backdrop {
  background: rgba(0, 0, 0, 0.55);
  backdrop-filter: blur(2px);
}

.update-dialog[open] { animation: um-in 0.3s var(--ease-bounce); }

@keyframes um-in {
  from { opacity: 0; transform: scale(0.9) translateY(20px); }
  to { opacity: 1; transform: scale(1) translateY(0); }
}

.um-header {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 12px;
  margin-block-end: 16px;
}

.um-heading { display: flex; flex-direction: column; gap: 8px; min-inline-size: 0; }

.um-badge {
  align-self: flex-start;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--accent);
  background: rgba(var(--accent-rgb), 0.12);
  border: 1px solid rgba(var(--accent-rgb), 0.3);
  border-radius: 999px;
  padding: 3px 10px;
}

.um-badge.is-error {
  color: var(--danger-color);
  background: rgba(var(--danger-rgb), 0.12);
  border-color: rgba(var(--danger-rgb), 0.3);
}

.um-title { margin: 0; font-size: 18px; font-weight: 600; color: var(--text-main); }

.um-close {
  display: flex;
  align-items: center;
  justify-content: center;
  inline-size: 36px;
  block-size: 36px;
  flex-shrink: 0;
  padding: 8px;
  background: transparent;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  color: var(--text-dim);
  cursor: pointer;
  transition: all 0.2s;
}

.um-close:hover,
.um-close:focus-visible {
  background: rgba(var(--accent-rgb), 0.1);
  transform: rotate(90deg);
  color: var(--accent);
  border-color: rgba(var(--accent-rgb), 0.3);
  border-radius: var(--radius);
}

.um-versions {
  display: flex;
  align-items: center;
  gap: 10px;
  font-family: var(--font-mono);
  font-size: 13px;
  margin-block-end: 16px;
}

.um-from { color: var(--text-dim); }
.um-arrow { color: var(--text-dim); opacity: 0.6; }
.um-to { color: var(--accent); font-weight: 700; }

/* Two lines' worth, whatever the sentence: the swap between them would
   otherwise resize a dialog the browser centres, and move everything above. */
.um-message {
  margin: 0 0 20px;
  font-size: 13px;
  color: var(--text-dim);
  line-height: 1.6;
  min-block-size: calc(2 * 1.6 * 13px);
}

.um-notice {
  background: rgba(var(--info-rgb), 0.1);
  border: 1px solid rgba(var(--info-rgb), 0.3);
  border-radius: var(--radius);
  padding: 14px 16px;
  margin-block-end: 20px;
}

.um-notice-title { margin: 0 0 4px; font-size: 14px; color: var(--text-main); }
.um-notice-text { margin: 0; font-size: 13px; color: var(--text-dim); line-height: 1.5; }

.um-timeline { list-style: none; margin: 0 0 4px; padding: 0; }

.tl-step {
  position: relative;
  display: flex;
  align-items: flex-start;
  gap: 14px;
  padding-block-end: 18px;
}

/* The line to the next dot; green grows down the list as steps complete. */
.tl-step::before {
  content: "";
  position: absolute;
  inset-inline-start: 10px;
  inset-block-start: 22px;
  inset-block-end: 0;
  inline-size: 2px;
  background: var(--border);
  transition: background 0.3s ease;
}

.tl-step:last-child { padding-block-end: 0; }
.tl-step:last-child::before { display: none; }

.tl-step.is-ok::before,
.tl-step.is-skipped::before { background: rgba(var(--success-rgb), 0.5); }

.tl-dot {
  position: relative;
  z-index: 1;
  flex-shrink: 0;
  inline-size: 22px;
  block-size: 22px;
  box-sizing: border-box;
  border-radius: 50%;
  border: 2px solid var(--border);
  background: var(--panel);
  color: transparent;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: border-color 0.3s ease, background 0.3s ease, color 0.3s ease;
}

.tl-dot svg { inline-size: 12px; block-size: 12px; }

/* The note beside the label, not under it: under it, a note arriving mid-run
   made the row taller and moved every step above. */
.tl-body {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 2px 8px;
  min-inline-size: 0;
  padding-block-start: 1px;
}

.tl-label { font-size: 13px; font-weight: 500; color: var(--text-dim); transition: color 0.3s ease; }
.tl-note { font-size: 11px; color: var(--text-dim); opacity: 0.75; }
.tl-note:empty { display: none; }

.tl-step.is-running .tl-dot {
  border-color: var(--accent);
  box-shadow: 0 0 0 4px rgba(var(--accent-rgb), 0.15);
  animation: tl-pulse 1.4s ease-in-out infinite;
}

.tl-step.is-running .tl-label { color: var(--text-main); font-weight: 600; }

.tl-step.is-ok .tl-dot {
  border-color: var(--success-color);
  background: var(--success-color);
  color: #06240f;
  animation: tl-pop 0.35s var(--ease-bounce);
}

.tl-step.is-ok .tl-label { color: var(--text-main); }

.tl-step.is-skipped .tl-dot { border-color: var(--border); background: var(--border); color: var(--text-dim); }

.tl-step.is-error .tl-dot {
  border-color: var(--danger-color);
  background: var(--danger-color);
  color: #2a0a0a;
  animation: tl-pop 0.35s var(--ease-bounce);
}

.tl-step.is-error .tl-label { color: var(--danger-color); font-weight: 600; }

/* Steps after a failure stay visibly untouched: they were never attempted. */
.tl-step.is-pending .tl-label { opacity: 0.55; }

@keyframes tl-pulse {
  0%, 100% { box-shadow: 0 0 0 4px rgba(var(--accent-rgb), 0.15); }
  50% { box-shadow: 0 0 0 7px rgba(var(--accent-rgb), 0.05); }
}

@keyframes tl-pop {
  from { transform: scale(0.4); }
  60% { transform: scale(1.15); }
  to { transform: scale(1); }
}

.um-failure {
  margin-block-start: 16px;
  background: rgba(var(--danger-rgb), 0.1);
  border: 1px solid rgba(var(--danger-rgb), 0.3);
  border-radius: var(--radius);
  padding: 12px 14px;
}

.um-failure[hidden] { display: none; }
.um-failure-text { margin: 0; font-size: 13px; color: var(--danger-color); line-height: 1.5; }

.um-actions { display: flex; gap: 12px; justify-content: flex-end; margin-block-start: 24px; }

@media (prefers-reduced-motion: reduce) {
  .update-dialog[open],
  .tl-step.is-running .tl-dot,
  .tl-step.is-ok .tl-dot,
  .tl-step.is-error .tl-dot,
  .notification-dot,
  .side-update { animation: none; }
}

/* Open and closed on the archive. A row of equal segments the width of the search
   box and the dropdowns under it, in their corners and their 12px, so the column
   reads as one set of controls; the segment that is on takes the colour a chosen
   dropdown takes. The count is a number beside the name, not a second control. */
.rev-sides {
  display: flex;
  gap: 2px;
  padding: 3px;
  inline-size: 100%;
  box-sizing: border-box;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface-dim);
}

.rev-sides .tab {
  flex: 1 1 0;
  min-inline-size: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 4px 6px;
  border: 1px solid transparent;
  border-radius: calc(var(--radius-sm) - 2px);
  font-size: 12px;
  font-weight: 600;
  line-height: 1.5;
}

.rev-sides .tab .icon { flex: none; opacity: 0.8; }

.rev-side-name { min-inline-size: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.rev-sides .tab .count-chip {
  margin: 0;
  padding: 0;
  min-inline-size: 0;
  border: 0;
  background: transparent;
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 500;
  color: var(--text-dim);
}

.rev-sides .tab.on {
  background: var(--accent-wash);
  border-color: var(--accent-line);
  color: var(--accent-alt);
  box-shadow: none;
}

.rev-sides .tab.on .icon { opacity: 1; }
.rev-sides .tab.on .count-chip { background: transparent; border: 0; color: inherit; }

/* A repository's heading in the archive is the control that folds its reports
   away: the same heading, as a button, with a mark that turns down when open. */
button.rev-group {
  inline-size: 100%;
  border: 0;
  background: transparent;
  cursor: pointer;
  text-align: start;
}
button.rev-group:hover { color: var(--text-main); }
button.rev-group .icon { flex: none; transform: rotate(90deg); transition: transform 0.15s var(--ease); }
button.rev-group.shut .icon { transform: rotate(0deg); }
:root[dir="rtl"] button.rev-group.shut .icon { transform: rotate(180deg); }
.rev-group-name { min-inline-size: 0; overflow-wrap: anywhere; }

/* The default picture: one figure, worn two ways. Everybody but the team gets it in the
   page's light grey, the figure darker than its circle. The team gets the same figure with
   the colours turned round and a stronger grey — the circle in the text grey, the figure in
   the page's own — so a reply from the team reads as one without a colour of its own. Drawn,
   not a font or a file; both follow the theme. */
.avatar .avatar-fig {
  inline-size: 68%;
  block-size: 68%;
  fill: currentColor;
}

.avatar.is-guest {
  background: var(--surface-dim);
  color: var(--text-dim);
  box-shadow: inset 0 0 0 1px var(--border);
}

.avatar.is-team {
  background: var(--text-dim);
  color: var(--panel);
}
