/* ==========================================================================
   Instant Feedback - the shared stylesheet.

   Sections 1-15 of docs/BRAND.md were the AI Superboard design system; the colours and
   headings now follow the SmartVMS family (teal, bold sans) so the products read as one suite. §16 of that file is the only Instant Feedback-specific
   part, and it fills exactly one slot: the subject's semantic colours.

   Light is the default and is NOT bound to prefers-color-scheme. A memory
   record is a document, and a document should not change appearance because
   somebody's operating system switched at sunset. Dark is a deliberate
   choice, stored per browser, applied before first paint.

   No webfont, no CDN, no build step. An enterprise product should not make a
   third-party request on every page load - and an air-gapped install (SPEC
   §7.4) could not resolve one if it tried.
   ========================================================================== */

/* ── 1. Tokens ─────────────────────────────────────────────────────────── */

:root {
  color-scheme: light;

  /* Shared with AI Superboard, never varied. Two products with different
     purples look like two companies; two products with the same purple and
     different marks look like a suite. */
 --brand-600: #0b5266;   /* SmartVMS deep teal */
 --brand-500: #14708a;
 --brand-100: #e1eff3;
 --brand-ink: #0b5266;   /* brand colour used AS TEXT; --brand-600 stays for fills */

  /* The SmartVMS data palette, shared by every chart so a dashboard here reads like one there. */
 --chart-1: #064a5c;   /* deep teal: the primary series */
 --chart-2: #1f9d73;   /* green */
 --chart-3: #f2a81d;   /* amber */
 --chart-4: #8b73c2;   /* purple */
 --chart-5: #a9b3bc;   /* grey: "other" */
 --chart-6: #2b6cb0;   /* blue */
 --chart-7: #c25b56;   /* brick red */
 --chart-8: #5aa9a5;   /* light teal */
 --chart-9: #f08a2c;   /* orange */
 --good: #1a8f55;
 --bad: #d03b3b;

 --bg: #f3f6f8;
 --surface: #ffffff;
 --surface-2: #f8fafb;
 --border: #dde5ea;
 --border-strong: #c9d4db;

 --text: #12202b;
 --text-muted: #4f5f6b;
 --text-faint: #5a6a76;   /* was #878d9e (3.1:1): raised to pass WCAG AA - see docs/DESIGN.md */

  /* ── The Instant Feedback semantic range (BRAND.md §16.2) ───────────────────────
     Four categorical dimensions and one ordered one, kept apart on purpose:
     reusing a hue across two dimensions teaches a reader that green means
     "done" on one screen and "active" on the next. */

  /* Modality - the hear/say/do axis (SPEC §2.2). The most important
     distinction in the product, so the most separable hues. */
 --mode-hear: #0891b2;
 --mode-say:  #d97706;
 --mode-do:   #16a34a;

  /* Assertion strength. Ordered, six steps, NEVER read by colour alone -
     the whole point of the axis is that proposed and decided are different
     commitments, so the word is always printed too. */
 --strength-inferred: #9ca3af;
 --strength-observed: #78909c;
 --strength-stated:   #0891b2;
 --strength-proposed: #d97706;
 --strength-decided:  #5b4be3;
 --strength-done:     #16a34a;

  /* Memory state (SPEC §2.4). A lifecycle, so a fading ramp rather than five
     unrelated colours. */
 --state-active:       #16a34a;
 --state-dormant:      #9ca3af;
 --state-archived:     #878d9e;
 --state-superseded:   #7c6fd4;
 --state-contradicted: #dc2626;
 --state-tombstoned:   #5b6172;

  /* Gap kinds (SPEC §3.6). Sole provenance gets the red: it is the single
     point of failure and the commercial story. */
 --gap-unfulfilled:     #d97706;
 --gap-unacted:         #94a3b8;
 --gap-no-rationale:    #0891b2;
 --gap-silent-period:   #a855f7;
 --gap-sole-provenance: #dc2626;

  /* Memory layers L0-L4. ORDERED, so a sequential single-hue ramp. Five
     categorical hues for an ordered dimension is a chart error as much as a
     brand one. */
 --layer-0: #cbd5e1;
 --layer-1: #93b4d8;
 --layer-2: #5b8ac4;
 --layer-3: #2f5f9e;
 --layer-4: #1b3a66;

 --risk-high: #dc2626;
 --risk-medium: #d97706;
 --risk-low: #16a34a;

 --radius: 10px;
 --radius-lg: 12px;
 --shadow-sm: 0 1px 2px rgba(20, 22, 29, 0.06);
 --shadow: 0 1px 3px rgba(20, 22, 29, 0.08), 0 1px 2px rgba(20, 22, 29, 0.04);

 --font: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto,
    "Helvetica Neue", Arial, sans-serif;
 /* Headings are bold sans, as in SmartVMS. The variable keeps its old name so no component changes. */
 --font-serif: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto,
    "Helvetica Neue", Arial, sans-serif;
 --font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;

 --text-xs: 12px;
 --text-sm: 13.5px;
 --text-base: 16px;
 --text-lg: 18px;
 --text-xl: 21px;
 --text-2xl: 26px;
 --text-3xl: 34px;
}

/* Dark applies only on an explicit choice - never from a system preference.
   Only the tokens are redefined; no component carries a dark variant. If a
   component needs one, it is using a raw colour where it should use a token. */
:root[data-theme='dark'] {
  color-scheme: dark;
 --bg: #0e1015;
 --surface: #161922;
 --surface-2: #1c202b;
 --border: #272b36;
 --border-strong: #363b49;
 --text: #eceef3;
 --text-muted: #a2a9ba;
 --text-faint: #9097ab;   /* was #7b8296 (4.2:1 on inset surfaces) */
 --brand-600: #178098;   /* lighter teal for fills: #0b5266 sinks into dark surfaces */
 --brand-100: #0f3340;
 --brand-ink: #62c3d8;   /* the deep teal is too dark to read as text on dark surfaces */
 --chart-1: #3aa6c0;   /* the deep teal is too dark on dark surfaces */
 --good: #3ec182;
 --bad: #ef7b73;

  /* The layer ramp is the one thing that must be rebuilt for dark rather than
     inherited. It runs light-to-dark on a light page, which on a dark page
     puts the deepest layer closest to the background and makes L4 vanish. */
 --layer-0: #3c4a5c;
 --layer-1: #4a6a90;
 --layer-2: #6b96c8;
 --layer-3: #8fb4dc;
 --layer-4: #b9d2ee;
}

* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; min-height: 100%; }

/* The `hidden` attribute must actually hide things.
 *
 * The browser's own stylesheet says `[hidden] { display: none }`, but that is a
 * user-agent rule and ANY author rule setting `display` beats it. `.drawer` sets
 * `display: flex`, so the header menus were never hidden at all: they sat open
 * on top of each other permanently, and no amount of toggling the attribute
 * changed anything.
 *
 * This is the standard fix and it belongs in the reset, not next to one
 * component, because the next component to set `display` would hit it too. */
[hidden] { display: none !important; }

body {
  background: var(--bg);
  color: var(--text);
  font-family: var(--font);
  font-size: var(--text-base);
  line-height: 1.6;
 -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

/* ── The footer ─────────────────────────────────────────────────────────────
   Ownership and the running version, on every page. Quiet deliberately: this
   is a governance product, and a notice that shouts is one people stop
   reading. It must never compete with the memory somebody is reading. */
.sitefoot {
  margin-top: 3rem; padding: 1.25rem 0 2rem;
  border-top: 1px solid var(--border);
  color: var(--text-faint); font-size: var(--text-xs); line-height: 1.55;
}
.sitefoot__inner {
  /* Matches the page gutter so it lines up with the content above it rather
     than sitting in its own margin. */
  max-width: 1220px; margin: 0 auto; padding: 0 1.5rem;
  display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: baseline;
}
/* The separator goes when the lines stack, because a dot alone at the end of
   a line reads as a typo. */
.sitefoot__sep { color: var(--border-strong); }
.sitefoot__version {
  font-family: var(--font-mono); margin-left: auto; white-space: nowrap;
}
@media (max-width: 720px) {
  .sitefoot__sep { display: none; }
  .sitefoot__version { margin-left: 0; }
}

/* ── 2. Type ───────────────────────────────────────────────────────────── */

/* Titles are serif by default so a component never has to remember to ask.
   The serif is the strongest thing in this system: it says "this is a
   document, not a dashboard", which is exactly right for a memory record. */
h1, h2, h3 {
  font-family: var(--font-serif);
  font-weight: 700;
  letter-spacing: -0.015em;
  line-height: 1.25;
}

/* clamp rather than a breakpoint, so the size tracks the viewport
   continuously - a title that jumps between two fixed sizes looks broken at
   the boundary. The 30px floor matters: below it a serif title on a narrow
   phone crowds the content it introduces. */
h1 { font-size: clamp(26px, 3vw, 36px); line-height: 1.15; letter-spacing: -0.02em;
     margin: 0 0 0.5rem; }
h2 { font-size: clamp(20px, 2vw, 26px); margin: 0 0 0.75rem; }
h3 { font-size: var(--text-lg); margin: 0 0 0.5rem; }

/* An eyebrow is a signpost, not a title; setting it in the serif would
   compete with the heading it introduces. */
h1[data-sans], h2[data-sans], h3[data-sans] {
  font-family: var(--font); letter-spacing: 0.12em;
}

.eyebrow {
  font-size: var(--text-xs); letter-spacing: 0.12em; text-transform: uppercase;
  font-weight: 700; color: var(--text-faint); margin: 0 0 0.5rem;
}

a { color: var(--brand-ink); }

/* A full-width page is right. A full-width LINE is not. */
.prose { max-width: 72ch; }
.lede  { max-width: 72ch; font-size: 16.5px; color: var(--text-muted);
         margin: 0; }

.muted { color: var(--text-muted); }
.faint { color: var(--text-faint); }
.mono  { font-family: var(--font-mono); font-size: var(--text-sm); }
.small { font-size: var(--text-sm); }
.tiny  { font-size: var(--text-xs); }

/* A visible focus ring on everything interactive. Keyboard navigation is not
   optional for a product sold where WCAG 2.2 AA is a procurement question. */
:where(a, button, input, select, textarea, [tabindex]):focus-visible {
  outline: 2px solid var(--brand-500);
  outline-offset: 2px;
  border-radius: 4px;
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}

/* ── 3. Layout ─────────────────────────────────────────────────────────── */
/* Inline styles cannot carry media queries, and an inline style beats a
   stylesheet rule even inside one. Anything that changes shape at a
   breakpoint lives here as a class. */

/* One vertical rhythm for the whole page.
 *
 * Pages append a mixture of things straight to the page element: a title, the
 * guide, cards, forms, split layouts. Previously each carried its own bottom
 * margin, so anything that carried none sat flush against its neighbour. The
 * Sources page showed it plainly: the table and the form beneath it touched,
 * with no gap at all.
 *
 * A flex column with a gap means nothing has to remember its own spacing, and
 * a new section added later is spaced correctly without anybody thinking about
 * it. The elements that used to carry a bottom margin have had it removed, so
 * the two do not add up. */
.page {
  max-width: 1600px; margin: 0 auto; padding: 2rem 2.5rem 4rem;
  display: flex; flex-direction: column; gap: 1.25rem;
  /* Without this a wide table inside a flex column can force the whole page
     wider than the window rather than scrolling inside its own card. */
  min-width: 0;
}
.page--narrow  { max-width: 1100px; }
.page--reading { max-width: 820px; }

@media (max-width: 1100px) { .page { padding: 1.75rem 1.5rem 3rem; } }
@media (max-width: 700px)  { .page { padding: 1.25rem 1rem 2.5rem; } }

.split {
  display: grid; gap: 1.5rem;
  grid-template-columns: minmax(0, 1fr) 320px;
  align-items: start;
}
@media (max-width: 1100px) { .split { grid-template-columns: 1fr; gap: 1.75rem; } }

/* A rail that follows the reading position.
 *
 * On the Recall page the rail is where "where this came from" appears, and it
 * appears when somebody clicks Why on a line of the answer - which they do
 * most often part-way down a long brief. Unpinned, the panel was rendered
 * above the viewport and the only way to read it was to scroll back to the
 * top, then scroll down again to find the next line worth checking. The
 * provenance chain is the thing that makes a memory defensible in a meeting,
 * and it was the hardest part of the page to look at.
 *
 * `align-items: start` on .split is what makes this work: the rail's grid
 * area spans the whole row, which is as tall as the answer, so a sticky item
 * inside it has the length of the answer to travel.
 */
.rail {
  position: sticky;
  /* Clear of the top edge rather than flush against it. The header is not
     sticky - it scrolls away - so there is nothing else to clear. */
  top: 1rem;
  /* Its own scrollbar, and only when the rail is taller than the space it
     has. A provenance chain with nine sources is longer than a viewport, and
     the alternative to scrolling it is clipping it. */
  max-height: calc(100dvh - 2rem);
  overflow-y: auto;
  /* So the cards do not shift sideways by a scrollbar's width the moment the
     panel grows past the viewport. */
  scrollbar-gutter: stable;
}

/* One column: the rail is below the answer rather than beside it, and pinning
   something to the viewport on a phone covers the thing being read. */
@media (max-width: 1100px) {
  .rail { position: static; max-height: none; overflow-y: visible; }
}

.pair { display: grid; gap: 1rem; grid-template-columns: 1fr 1fr; }
@media (max-width: 700px) { .pair { grid-template-columns: 1fr; } }

.grid {
  display: grid; gap: 1rem;
  grid-template-columns: repeat(auto-fill, minmax(290px, 1fr));
}

/* The action cell at the end of a table row. Right-aligned so the buttons sit
   against the edge of the table rather than drifting with the width of the
   column, and wrapping as a block so a third button lands under the first
   instead of hanging off the end. `align-items: stretch` keeps two wrapped
   rows the same width as each other. */
/* A single row of buttons. It used to be a plain block inside a 1%-wide cell, so the cell
   shrank to its widest button and every other one wrapped underneath it. The cell now
   refuses to wrap; a table with many actions scrolls sideways inside its card instead. */
.rowactions {
  display: flex; flex-wrap: nowrap; justify-content: flex-end; align-items: center;
  gap: 0.4rem;
}
.rowactions .btn { white-space: nowrap; }
td:has(> .rowactions) { text-align: right; width: 1%; white-space: nowrap; }

.row { display: flex; align-items: center; gap: 0.75rem; flex-wrap: wrap; }
.row--tight { gap: 0.4rem; }
.spacer { flex: 1 1 auto; }
.stack { display: flex; flex-direction: column; gap: 1rem; }
.stack--tight { gap: 0.5rem; }

.scroll-x { overflow-x: auto; max-width: 100%; }

@media (max-width: 900px) { .hide-md { display: none !important; } }
@media (max-width: 700px) { .hide-sm { display: none !important; } }

/* ── 4. Header ─────────────────────────────────────────────────────────── */

.header {
  border-bottom: 1px solid var(--border);
  background: var(--surface);
  padding: 0.85rem 2.5rem;
  display: flex; align-items: center; gap: 1rem; flex-wrap: wrap;
}

.header__id    { display: flex; align-items: center; gap: 1rem; min-width: 0; }
.header__nav   { display: flex; gap: 0.15rem; margin-left: 0.5rem; margin-right: auto; }
.header__action{ display: flex; align-items: center; gap: 0.5rem; }
.header__tools { display: flex; align-items: center; gap: 0.75rem; }
.header__id > a { flex-shrink: 0; }

/* The wordmark. Type, not artwork, so it costs nothing and never renders
   blurry. Only the product-name span differs from AI Superboard's - the "AI"
   and the slash are the constant, and that constant is the entire brand
   relationship between the products. */
.wordmark {
  font-size: 13.5px; letter-spacing: 0.14em; text-transform: uppercase;
  font-weight: 700; text-decoration: none; white-space: nowrap;
}
.wordmark__ai    { color: var(--brand-ink); }
.wordmark__slash { color: var(--text-faint); margin: 0 0.28em; }
.wordmark__name  { color: var(--text); }

/* The tenancy name is the one thing that must never be hidden. On a system
   holding several organisations' memory, not knowing which one you are
   looking at is how a private fact is read in the wrong room. */
.header__tenant {
  font-weight: 600; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; min-width: 0; max-width: 32ch;
}

.navlink {
  padding: 0.35rem 0.7rem; border-radius: var(--radius);
  color: var(--text-muted); text-decoration: none;
  font-size: 14.5px; font-weight: 500; white-space: nowrap;
}
.navlink:hover { background: var(--surface-2); color: var(--text); }
.navlink[aria-current='page'] {
  background: var(--brand-100); color: var(--brand-ink); font-weight: 600;
}

/* Scoped to .header on purpose, and the scoping is the whole point. The burger
   is also an .iconbtn, which sets display: inline-flex several hundred lines
   below this - equal specificity, later in the file, so a bare
   .header__burger rule loses and the burger shows at every width beside a
   perfectly good nav bar. Two menus on a desktop, one of them redundant.
   Keep both of these at .header scope so neither depends on source order. */
.header .header__burger { display: none; }

@media (max-width: 1100px) { .header { padding: 0.75rem 1.5rem; } }

/* Two deliberate rows rather than three fallen into: identity and account on
   top, navigation and the one action beneath. */
@media (max-width: 900px) {
  .header {
    display: grid; grid-template-columns: minmax(0, 1fr) auto;
    column-gap: 0.6rem; row-gap: 0.6rem; align-items: center;
    padding: 0.7rem 1rem;
  }
  .header__id     { grid-column: 1; gap: 0.55rem; }
  .header__tenant { max-width: none; }
  .header__tools  { grid-column: 2; gap: 0.5rem; }
  /* Full width rather than adrift against the right edge: on its own row a
     right-aligned button reads as a leftover. */
  .header__action      { grid-column: 1 / -1; justify-self: stretch; }
  .header__action > *  { flex: 1 1 0; min-width: 0; }
  .header__action button, .header__action > a { width: 100%; text-align: center; }
  .header__nav    { display: none; }
  /* inline-flex, not block: .iconbtn centres its icon with flex, and block
     would leave it sitting top-left in the 30px square. */
  .header .header__burger { display: inline-flex; }
  .wide-only      { display: none; }
}

/* The header sits above the page and owns its own stacking context, so a
   panel hanging off it is never covered by page content. Without this, the
   Explorer's canvas and any card with a shadow can paint over an open menu. */
.header { position: relative; z-index: 100; }

/* Panels that hang off the header: the user menu and the navigation drawer.
   Only ONE is ever open - see popover() in kfeedback.js. They used to be two
   independent toggles, and the result was both open at once, stacked against
   the same right edge, with the lower one half-covered and unclickable. */
.drawer {
  position: absolute; top: calc(100% + 4px); right: 1rem; z-index: 200;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius-lg); box-shadow: var(--shadow);
  padding: 0.4rem; min-width: 220px; display: flex; flex-direction: column;

  /* Ten navigation items on a short phone is taller than the viewport. Cap it
     and let it scroll rather than letting the last items fall off the bottom
     with no way to reach them. */
  max-height: calc(100dvh - 5rem);
  overflow-y: auto;
  overscroll-behavior: contain;
}

.drawer a, .drawer button {
  padding: 0.5rem 0.7rem; border-radius: var(--radius); text-align: left;
  background: none; border: none; font: inherit; color: var(--text);
  text-decoration: none; cursor: pointer; white-space: nowrap;
}
.drawer a:hover, .drawer button:hover { background: var(--surface-2); }

/* The page you are on, marked in the drawer as it is in the nav bar. Folding
   the navigation into a burger should not lose the one piece of orientation
   the bar was giving you. */
.drawer a[aria-current='page'] {
  background: var(--brand-100); color: var(--brand-ink); font-weight: 600;
}

/* Explorer search results, hanging under the box. Above the canvas, which owns
   its own stacking context, so z-index alone is not enough - the wrapper has
   to be positioned for the list to escape the row it sits in. */
.search-box { position: relative; }
.search-hits {
  position: absolute; top: calc(100% + 4px); left: 0; right: 0; z-index: 50;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); box-shadow: var(--shadow);
  padding: 0.3rem; max-height: 17rem; overflow-y: auto;
}
.search-hit {
  display: flex; align-items: center; gap: 0.5rem; width: 100%;
  padding: 0.4rem 0.55rem; border-radius: var(--radius);
  background: none; border: none; font: inherit; color: var(--text);
  text-align: left; cursor: pointer;
}
.search-hit:hover { background: var(--surface-2); }
.search-hit .tiny { margin-left: auto; }
.search-hits > .tiny { padding: 0.45rem 0.55rem; line-height: 1.45; }

/* Which memory you are looking at: your own, the organisation's, or both.
   In the header because the answer changes what every page shows, so a page
   that could be filtered must always say whether it is. */

/* A readout that can be changed, rather than a button that happens to have a
   word on it. The standing "Showing" caption is what does that: without it the
   control answers "what does this do", and the question a person actually has
   is "what am I looking at" - which every page on the other side depends on. */
.scope-switch {
  display: inline-flex; align-items: center; gap: 0.4rem;
  padding: 0.3rem 0.55rem 0.3rem 0.6rem; border-radius: var(--radius);
  background: var(--surface-2); border: 1px solid var(--border-strong);
  color: var(--text); font: inherit; font-size: var(--text-xs);
  cursor: pointer; white-space: nowrap;
}
.scope-switch:hover { border-color: var(--brand-600); }

.scope-switch__caption {
  text-transform: uppercase; letter-spacing: 0.05em; font-size: 10.5px;
  color: var(--text-muted); font-weight: 600;
}
.scope-switch__label { font-weight: 600; }

/* The chevron is a text glyph, and a glyph sits on its own baseline rather
   than in the middle of its box - so flex centring puts the BOX in the middle
   and leaves the mark low. Sizing it explicitly and centring it as its own
   flex box puts the mark where the eye expects, level with the label. */
/* An SVG, so no baseline and no optical correction: the flex centring on
   .scope-switch puts it where it belongs, exactly as it does the layers icon
   at the other end of the row. */
.scope-switch__chev {
  display: inline-flex; color: var(--text-muted); margin-left: 0.05rem;
}

/* Narrowed views are marked. A filtered screen that looks exactly like an
   unfiltered one is how somebody comes to report that their data has gone.
   The two narrowed scopes are marked differently from each other, because
   "only mine" and "only the organisation" are opposite mistakes to make. */
.scope-switch--narrowed {
  border-color: var(--brand-600); background: var(--brand-100);
}
.scope-switch--narrowed .scope-switch__caption,
.scope-switch--narrowed .scope-switch__label,
.scope-switch--narrowed .scope-switch__chev { color: var(--brand-ink); }

.scope-switch--mine { border-style: dashed; }

/* Below this the caption goes first and the VALUE stays: which memory you are
   in is the part that must survive, and the icon plus the highlight still say
   a filter is on. */
@media (max-width: 640px) {
  .scope-switch__caption { display: none; }
}

/* The notification panel. A `.drawer` like the other two, so it inherits the
   positioning, the stacking and the height cap rather than being a third
   thing with its own quirks. */
/* max-width, and a min-width that yields on a narrow phone rather than
   forcing the panel wider than the screen. */
.notif {
  width: 360px; max-width: calc(100vw - 2rem); min-width: 0; padding: 0;
}
.notif__head {
  display: flex; align-items: center; gap: 0.5rem;
  padding: 0.55rem 0.5rem 0.55rem 0.75rem;
  border-bottom: 1px solid var(--border); font-weight: 600;
}
.notif__body { padding: 0.25rem; }
/* `.notif .notif__item`, not `.notif__item`, and the extra class is the whole
   point. `.drawer a, .drawer button` sets `white-space: nowrap` - a class plus
   an element, specificity (0,1,1) - so a bare `.notif__item` at (0,1,0) loses
   to it wherever it sits in the file. The first attempt at this fix set
   `white-space: normal` and changed nothing at all.

   Same trap as the burger menu two commits ago, one property along. */
.notif .notif__item {
  display: block; padding: 0.55rem 0.7rem; border-radius: var(--radius);
  color: var(--text); text-decoration: none;
  /* nowrap is right for a menu of one-word items and wrong for a sentence.
     `min-width: 0` because a flex child will not shrink below its content
     otherwise, and overflow-wrap handles the single long token - a url, an
     id - that wrapping alone cannot break. */
  white-space: normal; min-width: 0; overflow-wrap: anywhere;
  /* Unread carries a rail rather than a dot, so a long list reads as a shape
     at a glance instead of needing every row read. The transparent border on
     the base rule keeps read and unread rows on the same left edge - without
     it the list shifts sideways the moment anything is cleared. */
  border-left: 2px solid transparent;
}
.notif .notif__item + .notif__item { margin-top: 0.15rem; }
.notif a.notif__item:hover { background: var(--surface-2); }
.notif .notif__title { font-size: 14px; font-weight: 500; }
.notif .notif--unread { border-left-color: var(--brand-600); background: var(--brand-100); }
.notif .notif--unread .notif__title { font-weight: 650; }

.scope-menu { min-width: 264px; padding: 0.35rem; }
.scope-menu__head {
  font-size: var(--text-xs); font-weight: 700; letter-spacing: 0.04em;
  text-transform: uppercase; color: var(--text-faint);
  padding: 0.45rem 0.7rem 0.3rem;
}
.scope-menu button { display: flex; flex-direction: column; gap: 0.12rem; }
.scope-menu__name { font-weight: 600; }
.scope-menu__note {
  font-size: var(--text-xs); color: var(--text-muted);
  white-space: normal; line-height: 1.4;
}
.scope-menu .is-current { background: var(--brand-100); }
.scope-menu .is-current .scope-menu__name { color: var(--brand-ink); }

/* The file drop target on the import page. A dashed outline rather than a
   card, because it has to read as "put something here" and not as "here is
   some content" - which is the whole difference between an affordance and a
   panel. */

.dropzone {
  display: flex; flex-direction: column; align-items: center; gap: 0.35rem;
  padding: 2.2rem 1.2rem; text-align: center; cursor: pointer;
  border: 2px dashed var(--border-strong); border-radius: var(--radius-lg);
  background: var(--surface); color: var(--text-muted);
  transition: border-color 0.12s ease, background 0.12s ease;
}
.dropzone:hover { border-color: var(--brand-600); background: var(--surface-2); }
.dropzone > svg { color: var(--brand-600); }
.dropzone__title { font-weight: 600; color: var(--text); font-size: 15px; }

/* While something is being dragged over it. Strong enough to be unmistakable
   at the moment somebody is deciding whether to let go. */
.dropzone--over {
  border-color: var(--brand-600); background: var(--brand-100);
  color: var(--brand-ink);
}

@media (prefers-reduced-motion: reduce) { .dropzone { transition: none; } }

/* ── 5. Components ─────────────────────────────────────────────────────── */

.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  padding: 1.1rem 1.2rem;
}
.card--flush { padding: 0; }
.card__head {
  display: flex; align-items: baseline; gap: 0.6rem;
  padding: 0.9rem 1.2rem; border-bottom: 1px solid var(--border);
}

.btn {
  padding: 0.65rem 1rem; border: none; border-radius: var(--radius);
  background: var(--brand-600); color: #fff;
  font: inherit; font-size: 15.5px; font-weight: 600; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  gap: 0.55rem; text-decoration: none;
}
.btn:hover { background: var(--brand-500); }
.btn[disabled], .btn[aria-busy='true'] { cursor: progress; opacity: 0.7; }

.btn--quiet {
  background: var(--surface-2); color: var(--text);
  border: 1px solid var(--border-strong);
}
.btn--quiet:hover { background: var(--surface); border-color: var(--brand-600); }

.btn--small { padding: 0.35rem 0.7rem; font-size: var(--text-sm); }

/* Destructive stays quiet until reached for. Colouring it red at rest gives a
   page a warning it has not earned; colouring it on hover says what will
   happen at the moment it could. */
.btn--danger:hover, .btn--danger:focus-visible {
  border-color: var(--state-contradicted);
  color: var(--state-contradicted);
  background: color-mix(in srgb, var(--state-contradicted) 10%, transparent);
}

.iconbtn {
  width: 30px; height: 30px; padding: 0; flex: 0 0 auto;
  border: 1px solid var(--border-strong); border-radius: var(--radius);
  background: var(--surface); color: var(--text-muted);
  display: inline-flex; align-items: center; justify-content: center;
  cursor: pointer; position: relative;
}
.iconbtn:hover { color: var(--text); border-color: var(--brand-600); }

.avatar {
  width: 30px; height: 30px; border-radius: 50%;
  background: var(--brand-100); color: var(--brand-ink);
  display: inline-flex; align-items: center; justify-content: center;
  font-size: var(--text-xs); font-weight: 700; letter-spacing: 0.02em;
  border: 1px solid var(--border); cursor: pointer; flex: 0 0 auto;
}

/* An unread count nobody can see is not a notification, so this stays at
   every width. */
.dot {
  position: absolute; top: -3px; right: -3px;
  min-width: 15px; height: 15px; padding: 0 3px;
  border-radius: 8px; background: var(--brand-600); color: #fff;
  font-size: 10px; font-weight: 700; line-height: 15px; text-align: center;
}

.field {
  width: 100%; box-sizing: border-box; padding: 0.55rem 0.65rem;
  border: 1px solid var(--border-strong); border-radius: var(--radius);
  background: var(--surface); color: var(--text);
  font-size: 14.5px; font-family: inherit;
}
.field:focus { border-color: var(--brand-600); }
textarea.field { min-height: 7rem; resize: vertical; line-height: 1.55; }

.label {
  display: block; font-size: 12.5px; font-weight: 600;
  color: var(--text-muted); margin-bottom: 0.25rem;
}
.hint { font-size: 12.5px; color: var(--text-muted); margin-top: 0.3rem; }

/* Filter pills, as on the board page: the active one filled, the rest
   outlined. Always a real <button>, so they are reachable by keyboard. */
.pill {
  padding: 0.35rem 0.85rem; border-radius: 999px;
  border: 1px solid var(--border-strong); background: var(--surface);
  color: var(--text-muted); font: inherit; font-size: var(--text-sm);
  cursor: pointer; white-space: nowrap;
}
.pill:hover { border-color: var(--brand-600); color: var(--text); }
.pill[aria-pressed='true'] {
  background: var(--brand-600); border-color: var(--brand-600);
  color: #fff; font-weight: 600;
}

/* A small uppercase marker, as CORE / OURS are on the board cards. */
.tag {
  display: inline-block; padding: 0.1rem 0.4rem; border-radius: 4px;
  border: 1px solid var(--border-strong); background: var(--surface-2);
  font-size: 10.5px; font-weight: 700; letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--text-muted); vertical-align: 2px;
}

/* ── 8. Tables ─────────────────────────────────────────────────────────── */

table.data { width: 100%; border-collapse: collapse; font-size: 14px; }
table.data th {
  text-align: left; font-size: var(--text-xs); font-weight: 700;
  letter-spacing: 0.06em; text-transform: uppercase; color: var(--text-faint);
  padding: 0.5rem 0.7rem; border-bottom: 1px solid var(--border);
  white-space: nowrap;
}
table.data td {
  padding: 0.55rem 0.7rem; border-bottom: 1px solid var(--border);
  vertical-align: top;
}
table.data tr:last-child td { border-bottom: none; }
table.data tr:hover td { background: var(--surface-2); }

/* ── 9. Motion ─────────────────────────────────────────────────────────── */
/* Sparing, and every animation means something specific. Each has a still
   fallback that STILL CONVEYS THE STATE - removing the animation and leaving
   the resting state is not an accessible fallback, it is a lie. */

@keyframes sweep { 0% { transform: translateX(-100%); } 100% { transform: translateX(400%); } }
@keyframes spin  { to { transform: rotate(360deg); } }
@keyframes pulse { 0%,100% { transform: scale(1); opacity: 1; }
                   50% { transform: scale(1.55); opacity: 0.45; } }

/* A pressed button that is still working. Never leave one looking unpressed. */
.spinner {
  display: inline-block; width: 1em; height: 1em;
  border: 2px solid currentColor; border-right-color: transparent;
  border-radius: 50%; animation: spin 0.7s linear infinite;
  vertical-align: -0.15em; flex-shrink: 0;
}
@media (prefers-reduced-motion: reduce) {
  .spinner { animation: none; border-right-color: currentColor;
             border-style: dashed; opacity: 0.7; }
}

/* Indeterminate work in another service. Deliberately indeterminate: a bar
   that filled steadily would be inventing progress it does not have. */
.working { position: relative; overflow: hidden; height: 3px;
           background: color-mix(in srgb, var(--brand-600) 16%, transparent); }
.working::after {
  content: ''; position: absolute; inset: 0; width: 30%;
  background: var(--brand-600); animation: sweep 1.9s ease-in-out infinite;
}
@media (prefers-reduced-motion: reduce) {
  .working::after { animation: none; width: 100%; opacity: 0.5; }
}

/* The Night Shift while it is running. The one element that must never look
   finished while it is not. */
.live { animation: pulse 1.5s ease-in-out infinite;
        display: inline-block; width: 7px; height: 7px; border-radius: 50%;
        background: var(--brand-600); }
@media (prefers-reduced-motion: reduce) {
  .live { animation: none;
          box-shadow: 0 0 0 3px color-mix(in srgb, var(--brand-600) 30%, transparent); }
}

/* ── 10. Messages ──────────────────────────────────────────────────────── */

.notice {
  border: 1px solid var(--border-strong); border-radius: var(--radius);
  background: var(--surface-2); padding: 0.7rem 0.9rem;
  font-size: 14px; color: var(--text-muted);
}
.notice--warn {
  border-color: color-mix(in srgb, var(--gap-unfulfilled) 45%, transparent);
  background: color-mix(in srgb, var(--gap-unfulfilled) 8%, transparent);
  color: var(--text);
}
.notice--bad {
  border-color: color-mix(in srgb, var(--state-contradicted) 45%, transparent);
  background: color-mix(in srgb, var(--state-contradicted) 8%, transparent);
  color: var(--text);
}

/* An empty state says what to do next, and says whether empty is a problem. */
.empty {
  text-align: center; padding: 3rem 1.5rem; color: var(--text-muted);
  border: 1px dashed var(--border-strong); border-radius: var(--radius-lg);
}
.empty h3 { font-family: var(--font); font-size: var(--text-lg); color: var(--text); }

