/* ==========================================================================
   ContactMatch base stylesheet.

   Hand-written CSS, no build step, no framework. Every value here is a token
   from tokens.css: a raw colour, spacing or duration in this file would be a
   review failure (DESIGN-tokens.md, the contract towards Frontend).

   Three rules run through the whole file and are worth stating once:

   1. DARK-MODE RESILIENCE (DESIGN-riktning 9.2). Every surface paints BOTH a
      background and a text colour, always as a pair. An element that inherits
      "no background" inherits the user's browser setting, and light text on an
      inherited light surface is invisible. There is no transparent panel over
      an uncoloured body anywhere below.
   2. THE FOCUS RING IS ALWAYS THE SAME (DESIGN-komponenter.md 5.1): 2 px
      --focus, offset 2 px, everywhere, and it is never removed.
   3. HIT TARGETS are at least --hit-min on touch, also where the rhythm is
      compact (DESIGN-riktning 5, 9.1).
   ========================================================================== */

/* --- reset ------------------------------------------------------------- */

*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  -webkit-text-size-adjust: 100%;
  /* No horizontal scroll on the document itself; wide content scrolls inside
     its own container (DESIGN-riktning 9.1). */
  overflow-x: hidden;
}

body {
  margin: 0;
  background: var(--bg-app);
  color: var(--text-primary);
  font-family: var(--font-sans);
  font-size: var(--text-body-size);
  line-height: var(--text-body-line);
  /* Columns line up: tabular figures across the whole product surface, plus
     the slashed zero and the disambiguation set, because we show email
     addresses and URLs (DESIGN-tokens.md 6). */
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1, "zero" 1, "ss02" 1;
}

h1,
h2,
h3,
p,
ul,
ol,
figure {
  margin: 0;
}

ul,
ol {
  padding: 0;
  list-style: none;
}

img,
svg {
  max-width: 100%;
}

a {
  color: var(--action);
}

:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

/* --- typography scale --------------------------------------------------- */

.h1 {
  font-size: var(--text-h1-size);
  line-height: var(--text-h1-line);
  font-weight: var(--text-h1-weight);
  letter-spacing: -0.01em;
}

.h2 {
  font-size: var(--text-h2-size);
  line-height: var(--text-h2-line);
  font-weight: var(--text-h2-weight);
}

.dense {
  font-size: var(--text-dense-size);
  line-height: var(--text-dense-line);
}

/* An ETIQUETTE at the smallest size: a column heading, a field label above a
   value, the word over a counter. The uppercase step is reserved for exactly
   that by DESIGN-tokens.md 6 — it is a label treatment, and it is what makes a
   label read as a label rather than as something to read. */
.micro {
  font-size: var(--text-micro-size);
  line-height: var(--text-micro-line);
  letter-spacing: var(--text-micro-tracking);
  text-transform: uppercase;
  color: var(--text-muted);
}

/* A SENTENCE at the smallest size. Same size and same quiet colour as .micro,
   without the uppercase and without the tracking: a whole sentence set in caps
   is slower to read, wraps to three shouting lines on a phone, and is exactly
   the "everything shouts" that the toning-down in BESLUT #55 was about (D4's
   review, finding 4). Labels keep .micro; sentences take this. */
.note {
  font-size: var(--text-micro-size);
  line-height: var(--text-micro-line);
  color: var(--text-muted);
}

.muted {
  color: var(--text-secondary);
}

.mono {
  font-family: var(--font-mono);
}

/* --- skip link ---------------------------------------------------------- */

.skip-link {
  position: absolute;
  left: var(--space-2);
  top: calc(var(--space-2) * -10);
  z-index: 20;
  padding: var(--space-2) var(--space-3);
  background: var(--bg-surface);
  color: var(--action);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-md);
  font-size: var(--text-dense-size);
  transition: top var(--motion-fast) var(--motion-ease);
}

.skip-link:focus {
  top: var(--space-2);
}

/* --- the signed-in shell: compact by default (BESLUT #53) --------------- */

.shell {
  min-height: 100vh;
  background: var(--bg-app);
  color: var(--text-primary);
  display: flex;
  flex-direction: column;
}

.topbar {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  padding: 0 var(--space-3);
  min-height: var(--hit-min);
  background: var(--bg-surface);
  color: var(--text-primary);
  border-bottom: 1px solid var(--border);
}

.wordmark {
  font-weight: 620;
  font-size: 14px;
  letter-spacing: -0.01em;
  color: var(--text-primary);
  text-decoration: none;
}

.wordmark span {
  color: var(--action);
}

.topbar-nav {
  display: flex;
  gap: 2px;
  flex-wrap: wrap;
}

.topbar-nav a {
  display: inline-flex;
  align-items: center;
  min-height: 28px;
  padding: 0 var(--space-2);
  border-radius: var(--radius-md);
  font-size: var(--text-dense-size);
  text-decoration: none;
  background: var(--bg-surface);
  color: var(--text-secondary);
}

.topbar-nav a[aria-current] {
  background: var(--action-surface);
  color: var(--on-action-surface);
  font-weight: 550;
}

.topbar .spacer {
  flex: 1;
}

.whoami {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--text-micro-size);
  color: var(--text-secondary);
}

.whoami b {
  color: var(--text-primary);
  font-weight: 560;
}

/* The credit balance in the top bar (B3), emphasised the same way the
   organisation name is — but NOT with a <b>.

   `.whoami b` is a markup contract, not only a style: the E2E suite reads it as
   "the organisation whose data is on screen" and checks the tenant boundary
   with it on every signed-in page. Two bold elements in there make that read
   ambiguous, so the balance takes the appearance without taking the element. */
.whoami .credit-count {
  color: var(--text-primary);
  font-weight: 560;
}

.main {
  flex: 1;
  width: 100%;
  max-width: 1180px;
  margin: 0 auto;
  padding: var(--space-4) var(--space-3) var(--space-7);
}

/* Notices that belong to the ACCOUNT rather than to the page under them: the
   email confirmation prompt, and what pressing its button did. Aligned with
   the page's own column so the band does not read as a second, wider layout,
   and with no bottom padding of its own -- the notice carries its own margin,
   and an empty container must take up no room at all. */
.chrome-notices {
  width: 100%;
  max-width: 1180px;
  margin: 0 auto;
  padding: var(--space-4) var(--space-3) 0;
}

.chrome-notices .notice {
  margin-bottom: 0;
}

/* The control in zone three. `.ways` is a flex row built for links, and a form
   inside it would otherwise stretch to the band's full width on a phone. */
.chrome-notices .ways form {
  display: inline-flex;
}

.page-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-2) var(--space-4);
  margin-bottom: var(--space-4);
}

/* The standfirst sits UNDER the heading, never beside it (D4's review, finding
   12). The head is a flex row because a page action and a status chip belong on
   the heading's line; a sentence does not. Left to the row it landed to the
   RIGHT of the title on a wide screen and read as a caption pinned next to it
   rather than as the line that introduces the page.

   `flex-basis: 100%` gives it its own row, and `order` puts that row after the
   heading even where the markup writes the sentence before a trailing button
   (campaigns/index.html) -- so no template has to reorder itself to get the
   right picture.

   The measure is a PADDING and not a `max-width`, which is not a stylistic
   choice: a flex item's hypothetical main size is clamped by its max-width
   BEFORE lines are broken, so `max-width: 76ch` next to `flex-basis: 100%`
   silently defeats it -- the item measures 76ch, fits beside the heading, and
   never wraps. That is exactly what the first attempt at this fix did, and it
   looked identical to no fix at all. Padding leaves the basis at 100 %, so the
   box takes the whole row and the TEXT still stops at a readable width. */
.page-head p {
  order: 1;
  flex: 1 0 100%;
  padding-right: max(0px, calc(100% - 76ch));
  margin-top: calc(var(--space-1) * -1);
  color: var(--text-secondary);
  font-size: var(--text-dense-size);
}

/* Pushes what follows to the right edge. The same word as in the top bar, so
   the mechanism is learned once. */
.page-head .spacer,
.panel-head .spacer {
  flex: 1;
}

.panel {
  background: var(--bg-surface);
  color: var(--text-primary);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  margin-bottom: var(--space-4);
}

.panel-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-3);
  padding: var(--space-3);
  border-bottom: 1px solid var(--border);
  background: var(--bg-surface);
  color: var(--text-primary);
}

.panel-body {
  padding: var(--space-3);
  background: var(--bg-surface);
  color: var(--text-primary);
}

.stack {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

/* --- the logged-out entry surface: roomier (BESLUT #53) -----------------
   Not a second design language — the same tokens, a different SELECTION from
   the same spacing scale (steps 5-8 instead of 1-4). */

.entry {
  min-height: 100vh;
  background: var(--bg-app);
  color: var(--text-primary);
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: var(--space-6) var(--space-4) var(--space-8);
}

.entry-head {
  width: 100%;
  max-width: 460px;
  margin-bottom: var(--space-6);
}

.entry-head .wordmark {
  font-size: 16px;
}

.entry-card {
  width: 100%;
  max-width: 460px;
  background: var(--bg-surface);
  color: var(--text-primary);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: var(--space-6);
}

.entry-card h1 {
  font-size: var(--text-h1-size);
  line-height: var(--text-h1-line);
  font-weight: var(--text-h1-weight);
  margin-bottom: var(--space-2);
}

.entry-card > p {
  color: var(--text-secondary);
  font-size: var(--text-dense-size);
  margin-bottom: var(--space-5);
}

.entry-foot {
  width: 100%;
  max-width: 460px;
  margin-top: var(--space-5);
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-5);
  font-size: var(--text-dense-size);
  color: var(--text-secondary);
}

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

form {
  margin: 0;
}

.field {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  margin-bottom: var(--space-4);
}

.field > label {
  font-size: var(--text-dense-size);
  font-weight: 560;
  color: var(--text-primary);
}

.field .hint {
  font-size: var(--text-micro-size);
  line-height: var(--text-micro-line);
  color: var(--text-secondary);
}

.input,
select.input {
  width: 100%;
  min-height: var(--hit-min);
  padding: var(--space-2) var(--space-3);
  font: inherit;
  font-size: var(--text-body-size);
  /* The pair, always together: an input that only sets a border hands its
     background to the browser's dark mode. */
  background: var(--bg-surface);
  color: var(--text-primary);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-md);
}

.input::placeholder {
  color: var(--text-muted);
}

.input[aria-invalid="true"] {
  border-color: var(--attention);
}

.field-error {
  font-size: var(--text-micro-size);
  line-height: var(--text-micro-line);
  color: var(--attention);
  font-weight: 560;
}

.form-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  margin-top: var(--space-5);
}

.textarea {
  min-height: 0;
  font-family: var(--font-mono);
  font-size: var(--text-dense-size);
  line-height: var(--text-dense-line);
  resize: vertical;
}

/* --- the step indicator (F2's wizard) -----------------------------------
   An ordered list drawn as a row. The current step is marked by WEIGHT and by
   a filled number, never by colour alone (DESIGN-riktning 3.1 rule 4); the
   accessible current marker is aria-current in the markup. */

.steps {
  margin-bottom: var(--space-4);
}

.steps ol {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1) var(--space-4);
  align-items: center;
}

.step {
  font-size: var(--text-dense-size);
  color: var(--text-muted);
}

.step a,
.step > span {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-height: 28px;
  text-decoration: none;
  color: inherit;
}

.step a {
  color: var(--text-secondary);
}

.step-n {
  display: grid;
  place-items: center;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  border: 1px solid var(--border-strong);
  background: var(--bg-surface);
  color: var(--text-muted);
  font-size: var(--text-micro-size);
  font-weight: 560;
}

.step-now {
  color: var(--text-primary);
  font-weight: 560;
}

.step-now .step-n {
  background: var(--action);
  border-color: var(--action);
  color: var(--on-action);
}

/* --- choice rows: the whole row is the target --------------------------- */

.choice {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-3);
  min-height: var(--hit-min);
  background: var(--bg-surface);
  color: var(--text-primary);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  cursor: pointer;
}

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

.choice input {
  margin-top: 3px;
  width: 16px;
  height: 16px;
  accent-color: var(--action);
  flex: none;
}

.choice-body {
  display: flex;
  flex-direction: column;
  gap: 1px;
}

.choice-label {
  font-size: var(--text-dense-size);
  font-weight: 560;
}

.choice-note {
  display: block;
  font-size: var(--text-micro-size);
  line-height: var(--text-micro-line);
  color: var(--text-secondary);
  font-weight: 400;
}

/* A checkbox inside a data row keeps its touch target through the cell's own
   padding rather than by growing into the rhythm. */
.grid input[type="checkbox"] {
  width: 16px;
  height: 16px;
  accent-color: var(--action);
}

.grid .micro,
.grid .note {
  display: block;
}

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

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-1);
  min-height: var(--hit-min);
  padding: 0 var(--space-4);
  border-radius: var(--radius-md);
  border: 1px solid transparent;
  font: inherit;
  font-size: var(--text-dense-size);
  font-weight: 560;
  text-decoration: none;
  cursor: pointer;
  transition: background var(--motion-fast) var(--motion-ease);
}

.btn-primary {
  background: var(--action);
  color: var(--on-action);
}

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

.btn-secondary {
  background: var(--bg-surface);
  color: var(--text-primary);
  border-color: var(--border-strong);
}

.btn-secondary:hover {
  background: var(--bg-sunken);
}

/* THE CHROME'S BUTTON, and nothing else's (D5's review, finding 8).
   No border and no background, which is right for the top bar and the footer
   and wrong everywhere else: inside a panel it renders as a caption, and the
   reader cannot tell it from the explanation above it.

   The review found it on five controls that CHANGE DATA -- "Delete the list",
   "Stop filling from …", "Remove a tag", "Take off a list", and worst of all
   "Keep everything showing here", which decides about up to 200 people in one
   click and was drawn as an indented line of text.

   D4's finding 7 was the same defect on the password reveal, and it was fixed
   on that one control instead of as a rule -- so the pattern came back on every
   surface F4 built. It is a rule now, and tests/app/test_quiet_buttons.py fails
   a new one: a control that changes data looks like a control
   (DESIGN-komponenter 5, DESIGN-riktning 5). */
.btn-quiet {
  background: var(--bg-surface);
  color: var(--text-secondary);
  padding: 0 var(--space-2);
}

.btn[disabled],
.btn[aria-disabled="true"] {
  opacity: .6;
  cursor: default;
}

/* A button that IS a row: the choice on the left, its qualifier on the right.
   Full width so the whole row is the target, not a word inside it. */
.btn-row {
  width: 100%;
  justify-content: space-between;
  text-align: left;
}

/* Compact buttons inside a dense row keep the touch target through padding on
   a wrapping cell rather than by shrinking below --hit-min on touch.

   The modifier belongs to the CHROME — a switcher in the top bar, a shortcut in
   a panel head — and never to a control on the path through a form. That
   distinction is what Q1-1 found the hard way: the password reveal wore it and
   rendered 30 px tall on a phone, under the 44 px rule the entry surface is held
   to (DESIGN-riktning 9.1). It does not wear it any more. */
@media (pointer: fine) {
  .btn-compact {
    min-height: 30px;
  }
}

/* The reveal keeps its full height on every pointer and does not stretch to the
   width of the field above it.

   Q1-1 fixed the HEIGHT; this is the appearance (D4's review, finding 7). A
   quiet button inside a column-flex .field rendered full width with a centred
   label, no border and no background — which reads as a caption under the
   password box rather than as something you press. It is a real control now:
   the secondary button's border and surface, sized to its own label. */
.reveal {
  align-self: flex-start;
  margin-top: var(--space-1);
  padding: 0 var(--space-3);
}

/* --- the notice / error component: THREE ZONES -------------------------
   what happened - why - a clickable way forward (DESIGN-komponenter.md 2).
   An empty third zone is a layout defect, not only a copy defect. */

.notice {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 2px var(--space-2);
  padding: var(--space-3);
  margin-bottom: var(--space-4);
  background: var(--attention-surface);
  color: var(--attention);
  border: 1px solid var(--border);
  border-left: 4px solid var(--attention);
  border-radius: var(--radius-md);
  font-size: var(--text-dense-size);
  line-height: var(--text-dense-line);
}

.notice .glyph {
  grid-row: 1 / span 3;
  width: 20px;
  height: 20px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: var(--attention);
  color: var(--on-attention);
  font-size: var(--text-dense-size);
  font-weight: 700;
  margin-top: 1px;
}

.notice .what {
  font-weight: 600;
}

/* Zone one AS the page heading, on the surfaces where the notice is the whole
   page (errors/not_found.html, errors/denied.html). It stays the size of zone
   one on purpose: the notice is already a strong shape, and a headline-sized
   sentence inside it would make a 404 louder than anything the product says
   when something actually went wrong. The change is the ELEMENT, so a screen
   reader gets a heading to land on and the sentence is written once. */
h1.what {
  font-size: inherit;
  line-height: inherit;
  letter-spacing: normal;
  margin: 0;
}

.notice .ways {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1) var(--space-4);
  margin-top: var(--space-1);
}

.notice .ways a {
  color: var(--attention);
  font-weight: 560;
}

/* The calm confirmation. Same three zones, the action colour instead of the
   attention colour: a finished thing is not a problem. */
.notice-done {
  background: var(--action-surface);
  color: var(--on-action-surface);
  border-left-color: var(--action);
}

.notice-done .glyph {
  background: var(--action);
  color: var(--on-action);
}

.notice-done .ways a {
  color: var(--on-action-surface);
}

/* --- empty state: what the surface is, why it is empty, one step -------- */

.empty {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  align-items: flex-start;
  padding: var(--space-6) var(--space-4);
  background: var(--bg-surface);
  color: var(--text-primary);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
}

.empty h2 {
  font-size: var(--text-h2-size);
  line-height: var(--text-h2-line);
  font-weight: var(--text-h2-weight);
}

.empty p {
  color: var(--text-secondary);
  font-size: var(--text-dense-size);
  max-width: 62ch;
}

.empty .btn {
  margin-top: var(--space-2);
}

/* --- status chips: the lightness ladder, glyph AND word ---------------- */

.chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: 1px 7px 1px 5px;
  border-radius: var(--radius-sm);
  font-size: 12px;
  font-weight: 550;
  white-space: nowrap;
}

.chip .g {
  font-size: 11px;
  line-height: 1;
}

/* A chip that is also a filter link. The underline is dropped because the shape
   already says "clickable thing" and an underline inside a 20 px pill reads as
   a mistake; the hover underline puts the affordance back where a pointer can
   ask for it. */
a.chip {
  text-decoration: none;
}

a.chip:hover {
  text-decoration: underline;
}

.chip-new {
  background: var(--chip-new-bg);
  color: var(--chip-new-ink);
}

.chip-updated {
  background: var(--chip-updated-bg);
  color: var(--chip-updated-ink);
}

.chip-review {
  background: var(--chip-review-bg);
  color: var(--chip-review-ink);
}

/* A site we read part of the way (BESLUT #74). Between "finished" and
   "running" on the ladder, and on the warm axis so the two are told apart by
   hue as well as by lightness. The word and the ↻ glyph carry the meaning; this
   only decides where the eye lands first. */
.chip-partial {
  background: var(--chip-partial-bg);
  color: var(--chip-partial-ink);
}

/* So light it would have no shape against the surface: the border is both the
   form and a non-colour-borne difference (DESIGN-tokens.md 4). */
.chip-blocked {
  background: var(--chip-blocked-bg);
  color: var(--chip-blocked-ink);
  border: 1px solid var(--border-strong);
}

/* --- the evidence markers (DESIGN-komponenter 3, DESIGN-riktning 2.4) ---
   D5's review, finding 1: a derived value and a stated one rendered
   identically, so the one thing that separates this product from a bought
   address list was invisible on both surfaces it is argued on.

   ONE layer, not four decorations: the whole family shares the micro size and
   the muted colour, sits last in its row, and wraps rather than widening a
   table column. The row is a flex line so a marker that does not apply simply
   is not there, and the gaps stay even without an empty slot holding space. */

.evidence {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-1) var(--space-2);
  margin-top: 2px;
  font-size: var(--text-micro-size);
  line-height: var(--text-micro-line);
  color: var(--text-muted);
}

/* A value and its evidence as ONE thing in a cell.
   Not a change to `.grid.stacks td` itself, and that is deliberate rather than
   shy: the stacked cell is a two-item flex row shared by six tables, and making
   it wrap so the markers could take their own line pushed every long TITLE onto
   a line of its own too, misaligned under its label. Wrapping the pair instead
   leaves the cell a two-item row on every surface, and the pair stacks inside
   the 60 % the value already had. `min-width: 0` is what lets it actually
   narrow — a flex item's automatic minimum size is its content, so without this
   the address pushes the cell wider than the phone. */
.cell {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
  min-width: 0;
}

.evidence-label {
  text-transform: uppercase;
  letter-spacing: var(--text-micro-tracking);
}

/* The way back to the page that said so. Monospace is right HERE — a URL is
   exactly what DESIGN-riktning 4 keeps the face for — and the elision happens
   in app/web/evidence.py, so the cell never widens on a long query string. */
.evidence-source {
  font-family: var(--font-mono);
  color: var(--text-muted);
  overflow-wrap: anywhere;
}

.evidence-age {
  white-space: nowrap;
}

/* The only marker in the family that carries colour, and DESIGN-komponenter 3
   says why: stated versus inferred is the one distinction we promise never to
   blur, so it gets a second channel. The WORD is the first channel and it
   stands on its own — "from an address pattern" is legible to a reader who
   sees no colour at all, which is the requirement (DESIGN-riktning 3.1). */
.evidence-derived {
  color: var(--attention);
  font-weight: 560;
}

/* Content that must be allowed to break rather than be cut off: an email
   address in a 375 px stacked row block (D5's review, finding 9). The old
   monospace refused to break where a proportional face does, and the block
   showed "johan.kviberg.3f0f18@www" and then nothing — against 9.1's promise
   that the stacked block carries the SAME data as the row.

   `break-word` rather than `anywhere`, and the difference is visible rather
   than pedantic: `anywhere` also shrinks the element's MIN-CONTENT width, so
   the register's email column collapsed to the narrowest it could be and broke
   every address mid-word on a 1280 px desktop where there was room for all of
   them. `break-word` leaves the intrinsic width alone and breaks only when the
   word genuinely does not fit — which on 375 px it does not. Measured in the
   browser at both widths, not reasoned about. */
.breakable {
  overflow-wrap: break-word;
}

/* The same finding one layer further in (F7's before/after table).
   `.grid.stacks td` is a flex row on a phone, and its VALUE is an anonymous
   text node -- which cannot take `min-width: 0`, so a long address pushed the
   row past the viewport and the tablewrap scrolled sideways instead of the word
   breaking. Wrapping it in `.cell` moved the problem rather than fixing it:
   `.cell` is itself a flex container, so the text became an anonymous item
   again inside it and `break-word` had nothing to break in.

   This is the element the value needs: a plain box that IS a flex item, so it
   can narrow, and whose own content wraps. Measured at 375 px
   (scratch/probe_replay_cell.py) rather than reasoned about -- the span was
   168 px holding 186 px of text before this. */
.cellvalue {
  min-width: 0;
  overflow-wrap: break-word;
}

/* --- the problem panel (FR-D2) -----------------------------------------
   First-class content, never a badge on a tab. A blocking line is separated
   from a warning by border WEIGHT and by the word in its chip, not by hue:
   the two accents differ in lightness as well, but lightness is the channel
   that survives a colour-vision deficiency and it is the one relied on
   (DESIGN-riktning 3.1, DESIGN-tokens.md 4). */

.problem {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  background: var(--bg-surface);
  color: var(--text-primary);
  border: 1px solid var(--border);
  border-left: 4px solid var(--border-strong);
  border-radius: var(--radius-md);
}

.problem-blocking {
  background: var(--attention-surface);
  color: var(--attention);
  border-left-color: var(--attention);
}

.problem .glyph {
  width: 20px;
  height: 20px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: var(--bg-sunken);
  color: var(--text-secondary);
  font-size: var(--text-dense-size);
  font-weight: 700;
  margin-top: 1px;
}

.problem-blocking .glyph {
  background: var(--attention);
  color: var(--on-attention);
}

.problem .what {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  font-weight: 600;
  font-size: var(--text-dense-size);
}

.problem p {
  font-size: var(--text-dense-size);
  line-height: var(--text-dense-line);
  max-width: 74ch;
}

.problem .ways {
  margin-top: var(--space-1);
}

.problem-blocking .ways a {
  color: var(--attention);
  font-weight: 560;
}

/* --- the meter: a SETTLED ratio, never live progress -------------------
   Used by the coverage view (FR-D8) and nothing else. The run view had one and
   lost it (D5's review, finding 5): a bar over sites stands still for minutes
   at a time on a real run, and DESIGN-riktning 2.3 forbids a meter that can,
   because a still meter reads as broken whatever is written beside it.

   Coverage is the opposite case and keeps it: the fraction is finished
   arithmetic over a list that is not moving while you look at it, so the bar
   cannot stand still — there is nothing for it to be waiting for. */

.meter {
  height: 8px;
  width: 100%;
  background: var(--bg-sunken);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  overflow: hidden;
}

.meter-fill {
  display: block;
  height: 100%;
  background: var(--action);
  transition: width var(--motion-base) var(--motion-ease);
}

.counters {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3) var(--space-6);
}

.counter {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 88px;
}

.counter-n {
  font-size: 21px;
  line-height: 1.15;
  font-weight: 620;
  color: var(--text-primary);
}

.counter-note {
  font-size: var(--text-micro-size);
  color: var(--text-secondary);
}

/* --- the activity feed --------------------------------------------------
   A list of things that HAVE happened, newest first. Bounded by the read
   behind it, so it stays a screenful however big the run is. */

.feed {
  display: flex;
  flex-direction: column;
  margin-top: var(--space-2);
}

.feed-row {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-2) var(--space-3);
  padding: var(--space-2) 0;
  border-bottom: 1px solid var(--border);
  font-size: var(--text-dense-size);
}

.feed-row:last-child {
  border-bottom: 0;
}

.feed-when {
  font-family: var(--font-mono);
  font-size: var(--text-micro-size);
  color: var(--text-muted);
  flex: 0 0 44px;
}

.feed-what {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
}

.feed-num {
  margin-left: auto;
}

/* --- tables ------------------------------------------------------------- */

.tablewrap {
  overflow-x: auto;
}

table.grid {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--text-dense-size);
  background: var(--bg-surface);
  color: var(--text-primary);
}

.grid thead th {
  text-align: left;
  font-weight: 560;
  font-size: var(--text-micro-size);
  letter-spacing: var(--text-micro-tracking);
  text-transform: uppercase;
  color: var(--text-muted);
  background: var(--bg-surface);
  padding: var(--space-2) var(--space-3);
  border-bottom: 1px solid var(--border-strong);
  white-space: nowrap;
}

.grid td {
  padding: var(--space-2) var(--space-3);
  border-bottom: 1px solid var(--border);
  vertical-align: top;
}

.grid tbody tr:last-child td {
  border-bottom: 0;
}

/* --- language switch ---------------------------------------------------- */

.langswitch {
  display: flex;
  align-items: center;
  gap: var(--space-1);
}

.langswitch button {
  min-height: 28px;
  padding: 0 var(--space-2);
  font: inherit;
  font-size: var(--text-micro-size);
  background: var(--bg-surface);
  color: var(--text-secondary);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  cursor: pointer;
}

.langswitch button[aria-current] {
  background: var(--action-surface);
  color: var(--on-action-surface);
  border-color: var(--action-surface);
  font-weight: 560;
}

/* --- the contact workspace (F4) -----------------------------------------
   Three shapes the register needs and the earlier surfaces did not: a filter
   bar, a facts block, and a timeline. All three are ordinary flow layouts that
   wrap — nothing here is positioned, so a long Swedish label lengthens a row
   instead of overlapping the next one. */

.filters {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--space-3);
}

/* The fields size themselves to their content but never below a legible
   width, and the actions take the rest of the row rather than being pushed
   onto a line of their own on a wide screen. */
.filters .field {
  flex: 1 1 200px;
  min-width: 0;
}

.filters .form-actions {
  flex: 0 0 auto;
  margin-top: 0;
}

/* A definition list drawn as label-over-value pairs. Grid rather than float,
   so an empty value keeps its label's column and the block cannot collapse
   into a paragraph. */
.facts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  gap: var(--space-3) var(--space-5);
}

.facts dt {
  font-size: var(--text-micro-size);
  line-height: var(--text-micro-line);
  letter-spacing: var(--text-micro-tracking);
  text-transform: uppercase;
  color: var(--text-muted);
}

.facts dd {
  font-size: var(--text-dense-size);
  line-height: var(--text-dense-line);
  color: var(--text-primary);
  overflow-wrap: anywhere;
}

/* A list that is a list to a screen reader and a plain stack to the eye. */
.plain {
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

/* A tiny form that is one control and one button — a row action, not a page's
   form. Each such form is a flex ITEM of the button row, so the sizing has to
   happen inside it: without this the select inherits the input's full width and
   a two-word campaign name gets a 600 px control in a table cell. */
.btn-row form {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
}

.btn-row form .input {
  flex: 0 1 18rem;
  min-width: 0;
}

.timeline {
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  border-left: 2px solid var(--border);
  padding-left: var(--space-3);
}

.timeline li {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  font-size: var(--text-dense-size);
  line-height: var(--text-dense-line);
}

/* The date column is fixed so the sentences line up down the page; it is the
   thing the eye scans, and a ragged left edge would make it unscannable. */
.timeline-when {
  flex: 0 0 10ch;
  color: var(--text-muted);
}

.timeline-what {
  flex: 1 1 24ch;
  color: var(--text-primary);
  max-width: 74ch;
}

/* --- mobile (DESIGN-riktning 9.1) ---------------------------------------
   Logged-out surfaces are FULLY mobile; reading surfaces work on a phone.
   Nothing here removes data — it re-flows it. */

@media (max-width: 720px) {
  .entry {
    padding: var(--space-5) var(--space-3) var(--space-7);
  }

  .entry-card {
    padding: var(--space-5);
  }

  .topbar {
    flex-wrap: wrap;
    padding: var(--space-2) var(--space-3);
    gap: var(--space-2);
  }

  .topbar .spacer {
    display: none;
  }

  .topbar-nav {
    order: 3;
    flex-basis: 100%;
  }

  /* D5's review, finding 20: "Sign out" broke across two lines at 375 px, which
     made the top bar a pixel taller than everything under it and the control
     read as two. The chrome's controls are two or three words at most, so
     refusing the break costs nothing and the row wraps between them instead. */
  .whoami .btn {
    white-space: nowrap;
  }

  .main {
    padding: var(--space-3) var(--space-3) var(--space-7);
  }

  .form-actions .btn {
    flex: 1 1 100%;
  }

  .form-actions form {
    flex: 1 1 100%;
  }

  /* The feed's numbers drop under the site name instead of being pushed off
     the right edge; the counters share the width two at a time. */
  .feed-num {
    margin-left: 0;
    flex-basis: 100%;
    padding-left: calc(44px + var(--space-3));
  }

  .counters {
    gap: var(--space-3) var(--space-5);
  }

  /* A row becomes a block: the same data, the same order, with the column
     heading carried along as a label (DESIGN-riktning 9.1 level 2). */
  .grid.stacks thead {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
  }

  .grid.stacks tbody tr {
    display: block;
    border-bottom: 1px solid var(--border);
    padding: var(--space-2) 0;
  }

  /* align-items is not a nicety here. A flex cell with the default `stretch`
     grows every child to the cell's full height, and a chip is an inline-flex
     box — so a 20 px status chip rendered as a 60 px slab of the darkest colour
     in the palette, which was then the loudest thing on the phone (D4's review,
     finding 5). The children keep their own height and stack from the top. */
  .grid.stacks td {
    display: flex;
    align-items: flex-start;
    gap: var(--space-3);
    border-bottom: 0;
    padding: var(--space-1) var(--space-3);
  }

  /* Every filter takes the full width: two half-width selects side by side on
     a 375 px screen is two controls neither of which can show its own label. */
  .filters .field,
  .filters .form-actions {
    flex: 1 1 100%;
  }

  /* The date stops being a column and becomes the line above the sentence,
     which is what keeps a long Swedish sentence off the right edge. */
  .timeline-when {
    flex: 1 1 100%;
  }

  .grid.stacks td::before {
    content: attr(data-label);
    flex: 0 0 40%;
    font-size: var(--text-micro-size);
    letter-spacing: var(--text-micro-tracking);
    text-transform: uppercase;
    color: var(--text-muted);
  }
}

/* --- charts (F5) --------------------------------------------------------
   Drawn in CSS, not in a library. Three rules run through everything below.

   The DATA is the only loud thing: the marks carry the palette, and every
   label, value and axis word wears a text token. A light series colour is
   illegible as text on the surface, and identity comes from the coloured mark
   BESIDE a word rather than from colouring the word.

   White does the separating. A 2 px gap in the surface colour sits between the
   segments of a stacked column; no mark is given a border to hold it apart from
   its neighbour, because a stroke is ink that is not data.

   And nothing here is a size in pixels that a phone cannot re-flow: the bars
   are percentages of their track, so the chart is as wide as the panel and no
   wider (DESIGN-riktning 9.1 — no horizontal scroll, ever). */

.chart {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

/* --- the horizontal bar list ------------------------------------------- */

.barlist {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  list-style: none;
}

.barrow {
  display: grid;
  grid-template-columns: minmax(6rem, 14rem) 1fr auto;
  align-items: center;
  gap: var(--space-1) var(--space-3);
}

.barrow-label {
  font-size: var(--text-dense-size);
  color: var(--text-primary);
  overflow-wrap: anywhere;
}

.barrow-tail .barrow-label {
  color: var(--text-secondary);
}

.barrow-track {
  display: block;
  width: 100%;
  /* A track rather than gridlines: the row itself says where zero and full
     are, which is one fewer set of lines drawn in ink. */
  background: var(--bg-sunken);
  border-radius: var(--radius-sm);
}

.bar {
  display: block;
  /* Capped rather than filling the row: the leftover is air, and a 24 px slab
     of the darkest colour in the palette per row is the "thick saturated
     blocks" look the direction rules out. */
  height: 14px;
  min-width: 2px;
  background: var(--data-series-1);
  /* Square at the baseline, rounded at the data end -- the end is the part the
     eye measures, and rounding both would soften where the bar starts. */
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
}

/* A row that measured zero still gets a mark, so it reads as counted rather
   than as forgotten. */
.bar-zero {
  background: var(--border-strong);
}

/* The folded tail is not one of the ranked rows and must not look like one.
   It is routinely LONGER than every named bar -- a register's long tail
   usually outweighs its top ten -- and drawn in the same ink it read as a
   sorting mistake rather than as the summary it is.

   Step 4 of the ratified ladder, NOT the lightest one. The light step was the
   first choice and it failed the project's own measurement: at L* 76 against a
   track at L* 93 it clears only about 17, where DESIGN-tokens 8.1 requires
   ΔL* ≥ 20 once both surfaces are above L* 70. Step 4 sits at L* 59 -- about 34
   from the track and about 35 from the ranked bars' step 1 -- so it is plainly
   readable AND plainly not part of the ranking. The muted label and the line
   underneath say the same thing in words, because colour never carries alone. */
.barrow-tail .bar {
  background: var(--data-series-4);
}

.barrow-value {
  font-size: var(--text-dense-size);
  font-variant-numeric: tabular-nums;
  color: var(--text-primary);
}

.barrow-note {
  grid-column: 2 / -1;
}

/* --- stacked columns over time ----------------------------------------- */

.cols {
  display: flex;
  align-items: flex-end;
  gap: var(--space-1);
  /* The container includes the axis band, so the labels are inside the box the
     chart was given rather than in a nested scrollbar. */
  min-height: 200px;
}

.col {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-1);
}

.col-total {
  font-size: var(--text-micro-size);
  font-variant-numeric: tabular-nums;
  color: var(--text-secondary);
  /* Reserved whether or not the month has a total, so the columns all start at
     the same height and the baseline is straight. */
  min-height: 1em;
}

.col-stack {
  display: flex;
  flex-direction: column-reverse;
  justify-content: flex-start;
  align-items: stretch;
  /* The 2 px gap IS the separator between segments (see the note at the top of
     this block). */
  gap: 2px;
  width: 100%;
  max-width: 28px;
  height: 140px;
}

.col-seg {
  display: block;
  min-height: 2px;
  border-radius: var(--radius-sm);
}

.col-label {
  color: var(--text-muted);
  white-space: nowrap;
}

/* --- the legend --------------------------------------------------------- */

.legend {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-4);
  list-style: none;
  font-size: var(--text-dense-size);
  color: var(--text-secondary);
}

.legend li {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

.legend-key {
  width: 12px;
  height: 12px;
  flex: 0 0 auto;
  border-radius: var(--radius-sm);
}

/* --- the table twin, and the gap list ----------------------------------- */

/* Both are <details>. The summary is a real control: it is in the tab order by
   default, it gets the one focus ring, and it meets the tap-target floor. */
.chart-table > summary,
.gaps > summary {
  display: block;
  min-height: var(--hit-min);
  padding: var(--space-2) 0;
  font-size: var(--text-dense-size);
  color: var(--action);
  cursor: pointer;
}

.chart-table > summary:hover,
.gaps > summary:hover {
  text-decoration: underline;
}

.chart-table[open] > summary,
.gaps[open] > summary {
  margin-bottom: var(--space-2);
}

/* --- coverage (FR-D8) --------------------------------------------------- */

.coverage {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding-bottom: var(--space-3);
  border-bottom: 1px solid var(--border);
}

.coverage:last-child {
  border-bottom: 0;
  padding-bottom: 0;
}

.coverage-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-1) var(--space-3);
}

.coverage-name {
  font-weight: 560;
  color: var(--text-primary);
}

/* A standing measurement, not something filling up: it reuses the run view's
   shape so a customer learns one mechanism, and drops the transition so it
   never reads as live. */
.coverage .meter-fill {
  transition: none;
}

.gaplist {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1) var(--space-4);
  list-style: none;
  font-size: var(--text-dense-size);
}

/* The charts on a phone. This is a media query of its OWN rather than four more
   rules inside the big one further up, and the reason is a bug this file caught
   once: a media query adds no specificity, so it only wins on source order. The
   chart rules above are declared after that block, which meant the mobile
   overrides sat there looking correct and doing nothing. Rules that override a
   section belong AFTER that section, wherever the section happens to live. */
@media (max-width: 720px) {
  /* A bar row becomes two lines: a 14 rem label column beside a track on a
     375 px screen leaves the track too short to compare anything with. The
     label takes its own line and the bar gets the full width under it. */
  .barrow {
    grid-template-columns: 1fr auto;
  }

  .barrow-label {
    grid-column: 1 / -1;
  }

  /* Twelve months at 28 px will not fit, so the stack narrows rather than the
     chart scrolling sideways (DESIGN-riktning 9.1). Narrows to 20 px rather
     than to the whole slot: columns that fill their slots touch across a 4 px
     gap and read as one mass instead of twelve months. Only every other month
     is labelled -- overlapping axis text is less readable than half of it. */
  .col-stack {
    max-width: 20px;
    height: 110px;
  }

  .col:nth-child(even) .col-label,
  .col-total {
    display: none;
  }
}

/* --- the register's sortable headers (D5's review, finding 12) ----------- */

/* A column header that orders the table is a LINK, so it works with the
   keyboard, can be opened in a new tab and shows its destination in the status
   bar. It is drawn as the header text it already was rather than as a button:
   the affordance is the whole row of them behaving alike, and a table with three
   buttons in its head reads as a toolbar that landed in the wrong place.

   The direction arrow is decoration on top of `aria-sort`, never the carrier —
   the header that is doing the work is also the only one in the reading weight
   (riktningen 3.1: colour and glyph never carry alone). */
.sortable {
  display: inline-flex;
  align-items: baseline;
  gap: var(--space-1);
  color: inherit;
  text-decoration: none;
  border-bottom: 1px dotted var(--border);
}

.sortable:hover,
.sortable:focus-visible {
  border-bottom-color: var(--text-secondary);
}

.sortable-now {
  color: var(--text-primary);
  font-weight: 600;
  border-bottom-style: solid;
}

/* Reserved whether or not it is filled, so the header row does not shift by a
   character's width when a reader sorts by a different column. */
.sort-mark {
  display: inline-block;
  min-width: 1ch;
}

/* --- two panels side by side on a wide screen (finding 19) --------------- */

/* The detail view is 2 980 px tall on desktop, and the two things riktningen §7
   wants "sida vid sida" — the evidence and the timeline — were panels four and
   five of eight, stacked. They pair here on a wide screen and fall back to the
   ordinary stack below it, which is the same answer every table on this site
   gives: nothing is hidden on a phone, it merely takes its own line. */
.pair {
  display: grid;
  gap: 0 var(--space-4);
  grid-template-columns: 1fr;
  align-items: start;
}

@media (min-width: 900px) {
  .pair {
    grid-template-columns: 1fr 1fr;
  }
}

/* --- a form the reader opens rather than scrolls past (finding 19) ------- */

/* Correcting somebody and reporting an error are things a reader comes to the
   page to do OCCASIONALLY; they were two of the four permanently unfolded forms
   that made the page read as an edit screen with the evidence squeezed in. A
   `<details>` keeps them one click away, findable by the browser's own find,
   and needs no script at all. */
.disclosure {
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--bg-surface);
  margin-bottom: var(--space-4);
}

.disclosure > summary {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-height: var(--hit-min);
  padding: 0 var(--space-3);
  font-size: var(--text-h2-size);
  font-weight: 560;
  color: var(--text-primary);
  cursor: pointer;
}

.disclosure > summary::marker,
.disclosure > summary::-webkit-details-marker {
  color: var(--text-muted);
}

/* No focus rule of its own: the one at the top of this file already gives every
   focusable thing the same ring, and a second one here would be a place for the
   two to drift apart. */

/* --- a grid of choices (F8's column picker) ----------------------------- */

/* Seventeen checkboxes stacked vertically is a scroll; in columns they are a
   glance. `auto-fill` rather than a fixed count so the same markup is one
   column on a phone and four on a desktop without a breakpoint of its own. */
.choicegrid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(210px, 1fr));
  gap: var(--space-2);
  margin-top: var(--space-2);
}

/* A disabled choice is still legible — it is stating a fact ("always
   included"), not refusing an action, so it dims rather than disappears and
   keeps its border. Opacity alone would take the text below the contrast floor. */
.choice input:disabled + .choice-body .choice-label {
  color: var(--text-secondary);
}

.choice:has(input:disabled) {
  cursor: default;
  background: var(--bg-sunken);
}

/* --- screen-reader-only ------------------------------------------------- */

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}
