/*
 * Watermerken — design tokens and components.
 *
 * Light is the default. Dark applies when the visitor's OS asks for it, or when
 * they pick it explicitly (the toggle sets data-theme on <html>). An explicit
 * choice always beats the OS preference, in both directions.
 *
 * Every colour is declared in the :root block below, so a token always has a
 * value; the theme blocks only ever replace one.
 */

:root {
  --ground: #EDE8DE;
  --surface: #FBF7F0;
  --surface-2: #E4DDD0;
  --ink: #2E2A25;
  --ink-strong: #191612;
  --muted: #555047;
  --faint: #8A8378;
  --faint-2: #A29B8F;
  --line: #D8D0C2;
  --line-soft: #E3DCCF;
  --field-line: #C6BDAD;
  --row-hover: #F4F0E8;
  /* One rung above --row-hover, for a row that is chosen and under the pointer
     at the same time. It cannot be written as one of the tokens above, because
     which of them is "lighter still" swaps between the palettes: in light that
     is --surface, in dark it is --surface-2. */
  --row-chosen: #FBF7F0;
  --accent: #1B3A96;
  --on-accent: #FBF7F0;
  --danger: #A33121;
  --scrim: oklch(0.24 0.02 60 / 0.46);
  /* A scrim deeper than the menu's and the list's. It came with the stack's
     first-run sheet, which the practice card has since replaced; /submit's
     shadows still use it. */
  --scrim-strong: oklch(0.24 0.02 60 / 0.62);

  /* What the browser paints itself: the date picker's icon and its dropdown,
     the scrollbars, a select's own list. Without it they stay light on a dark
     page. */
  color-scheme: light;

  /* The vote bar once it has been answered but has too few votes to show a
     split: the bar's own surface with a trace of the accent stirred into it.
     Mixed from two themed tokens rather than given a value per palette, so it
     follows both on its own — a dark accent on a light surface settles, a light
     accent on a dark one lifts. */
  --tint: color-mix(in oklab, var(--accent) 16%, var(--surface));

  /* The area under the dashboard's chart line. The same trick as --tint, at
     the 13% the design uses, and its own token rather than a share of --tint's:
     that one is the vote bar's waiting state and the two should be free to
     move apart. */
  --chart-fill: color-mix(in oklab, var(--accent) 13%, var(--surface));

  /* Not themed: each is cast against the surface it lifts off, and the design
     uses one value for either palette. Three, because a dialog, a sheet and a
     card the size of a paragraph do not throw the same shadow. */
  --shadow: 0 24px 60px -20px oklch(0.22 0.03 60 / 0.42);
  --shadow-sheet: 0 -20px 50px -24px oklch(0.22 0.03 60 / 0.5);
  --shadow-pop: 0 16px 36px -20px oklch(0.22 0.03 60 / 0.5);

  /* The fourth, and the shallowest: a bar lifting a few pixels off the page it
     is holding still, rather than a box standing over it. */
  --shadow-bar: 0 16px 24px -22px oklch(0.22 0.03 60 / 0.9);

  /* Not a shadow at all: the ground, carried 12px past both sides of what
     stays pinned on /admin. A row there is lit on a band reaching 12px past
     the columns (.row--mark::before, .row--queue::before), and without this
     the ends of that band showed beside the band and the heading row as the
     row passed under them. Change one 12px and change the other. */
  --ground-reach: -12px 0 0 var(--ground), 12px 0 0 var(--ground);

  /* What the two bars that stay occupy, for whatever has to clear them.
     --bar-h is a floor and not a measurement: the register's bar holds a field
     whose row can grow, and it is only ever used by scroll-margin-top, where
     being a few pixels generous costs nothing and being short hides a row.
     --admin-bar is exact, because .row--head sticks directly under it and a
     gap or an overlap would both show — a tab strip is one line of pills at a
     fixed height inside a fixed padding, with overflow-x: auto making sure it
     can never wrap. Do not tidy the two into one. */
  --bar-h: 120px;
  --admin-bar: 68px;

  /* Newsreader carries the words the register is about — the term and the
     brand. Archivo carries everything that talks about them. */
  --display: "Newsreader", ui-serif, Georgia, serif;
  --body: "Archivo", ui-sans-serif, system-ui, "Segoe UI", sans-serif;

  /* How a sheet arrives and leaves. Named here because two different things
     use it — the register's own bottom sheet, which is a <div> put into the
     document and taken out again, and the <dialog> behind /submit and /admin,
     which is toggled in the top layer. The mechanisms cannot be shared; the
     distance and the timing are, so the two move alike. */
  --sheet-ease: cubic-bezier(0.32, 0.72, 0, 1);
  --sheet-rise: 0.26s;
  --sheet-fade: 0.24s;

  /* How far a card in the stack has to travel before letting go of it votes
     and moves on. One length and the three things that follow from it —
     where the bar is full, when the hint says "loslaten", and where a release
     counts — all move with it. stack.js reads it from here. */
  --swipe-reach: 120px;

  --radius: 3px;
  --radius-lg: 5px;
  --page: 1120px;
  --gutter: 18px;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --ground: #0A0C10;
    --surface: #12161C;
    --surface-2: #1A1F27;
    --ink: #DCE2EA;
    --ink-strong: #EDF1F6;
    --muted: #8A93A1;
    --faint: #69717D;
    --faint-2: #565E69;
    --line: #212731;
    --line-soft: #1B2129;
    --field-line: #2E3540;
    --row-hover: #10141A;
    --row-chosen: #1A1F27;
    --accent: #8FB3FF;
    --on-accent: #0A0C10;
    --danger: #F0806A;
    --scrim: oklch(0.16 0.02 265 / 0.66);
    --scrim-strong: oklch(0.16 0.02 265 / 0.8);
    color-scheme: dark;
  }
}

:root[data-theme="dark"] {
  --ground: #0A0C10;
  --surface: #12161C;
  --surface-2: #1A1F27;
  --ink: #DCE2EA;
  --ink-strong: #EDF1F6;
  --muted: #8A93A1;
  --faint: #69717D;
  --faint-2: #565E69;
  --line: #212731;
  --line-soft: #1B2129;
  --field-line: #2E3540;
  --row-hover: #10141A;
  --row-chosen: #1A1F27;
  --accent: #8FB3FF;
  --on-accent: #0A0C10;
  --danger: #F0806A;
  --scrim: oklch(0.16 0.02 265 / 0.66);
  --scrim-strong: oklch(0.16 0.02 265 / 0.8);
  color-scheme: dark;
}

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

/*
 * The gutter stays reserved whether or not the page happens to scroll, so
 * nothing shifts sideways when a dialog locks the page or a short list stops
 * needing a scrollbar. Reserving it only while locked would fix the long page
 * and break the short one, which would then shift the other way. A no-op where
 * the browser draws its scrollbars over the content.
 */
html {
  scrollbar-gutter: stable;
}

body {
  margin: 0;
  background: var(--ground);
  color: var(--ink);
  font-family: var(--body);
  font-size: 16px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

a {
  color: var(--accent);
  text-underline-offset: 3px;
}

button,
a,
[role="button"] {
  -webkit-tap-highlight-color: transparent;
}

::selection {
  background: var(--accent);
  color: var(--on-accent);
}

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

input,
textarea,
button {
  font: inherit;
  color: inherit;
}

input,
textarea,
select {
  font-family: inherit;
  color: var(--ink-strong);
}

input::placeholder,
textarea::placeholder {
  color: var(--faint);
  opacity: 1;
}

input[type="search"]::-webkit-search-cancel-button,
input[type="search"]::-webkit-search-decoration {
  -webkit-appearance: none;
  appearance: none;
  display: none;
}

/*
 * Shown only when JavaScript ran, or only when it did not; the head script adds
 * .js to <html>.
 *
 * Both are written as ":root(:not).js x" rather than the shorter ".js x" so
 * they outrank a component that sets its own display. A plain ".js-only" loses
 * to ".btn" on source order alone, which left "onthul alles" — a button that
 * does nothing without script — sitting in the header of a page that had none.
 *
 * These only ever hide. An element that is shown keeps whatever display its own
 * rules give it, so nothing here has to guess at "inline-flex".
 */
:root:not(.js) .js-only {
  display: none;
}

:root.js .no-js-only {
  display: none;
}

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

/* ---------- layout ---------- */

.page {
  max-width: var(--page);
  margin: 0 auto;
  padding: 0 32px 120px;
}

main {
  display: block;
}

/*
 * The register's own canvas — a plain block now. The grid that used to sit
 * here is on .register-bar below: it existed so one row could come from two
 * render sources without a box between them, and the bar is that box.
 */

/*
 * The bar that stays. Everything a visitor steers the list with — the search,
 * the sorts, the count, the heading row — pinned at the top while the header
 * scrolls away underneath. Design canvas "Vaste kop voorstellen", option 2B.
 *
 * The canvas sticks one container at top: -kophoogte with the header inside
 * it. This is the same thing on screen — a container at -H lets go at exactly
 * the scroll offset a static header comes back into view — without a header
 * height written down anywhere, and the height moves with the tagline, the
 * language and whether a narrow header wraps.
 *
 * It has to be a box rather than two grid items: a sticky grid item is clamped
 * to its grid area, and a one-row area gives it nowhere to travel.
 *
 * The containing block is main.register-main, which runs to the foot of the
 * page, so the bar holds over the closing line too — as the canvas has it.
 */
.register-bar {
  position: sticky;
  top: 0;
  z-index: 8;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: center;
  column-gap: 24px;
  row-gap: 18px;
  padding: 22px 0 0;
  background: var(--ground);
}

/*
 * Above 5, which is the vote's lemma card: it sits in content that comes
 * after the bar in the document, so at equal weight it would pass over it
 * rather than under. The compact switch's unfolded strip is the other way
 * round — it comes before the bar and hangs down over it — so it stands
 * above, at 9.
 *
 * Below 10, which is .dialog. That number only ever counts on the no-script
 * path, where the server renders the dialog [open] and it is not modal — with
 * script showModal() puts it in the top layer, where z-index does not apply.
 * A bar painted over a non-modal dialog would cut into the box itself.
 *
 * What that costs, and the condition that would make it a bug: a menu opened
 * from inside a pinned bar (which /admin has) puts its scrim and sheet, 75 and
 * 80, inside a stacking context at 8, under a non-modal dialog at 10. The two
 * cannot meet — a no-script dialog arrives only by page load, a page load
 * starts at the top, and at the top neither bar is pinned and the burger has
 * no width. Put anything in either bar that can open a dialog and this has to
 * be looked at again.
 */

.register-bar > .search {
  grid-column: 1;
}

/* The partial's root, so the sortbar and the heading row are grid items of the
   bar. A box here would make the two of them one column of it. */
.register-controls {
  display: contents;
}

.register-bar .register__head {
  grid-column: 1 / -1;
}

/*
 * Only once there is something under it to lift off — and only under it. The
 * shadow is clipped at the bar's two sides, so it stops where the bar stops
 * rather than fading out a few pixels past either edge (design canvas
 * "Watermerken", the bars' clipPath).
 *
 * The clip is on a layer of its own and not on the bar: a clip-path clips
 * everything inside the box, so on the bar it would take the focus ring off
 * whatever stands against its edge — and on /privacy and /about, where the
 * header is the bar, the menu, whose scrim and sheet are fixed descendants of
 * that header and still inside the clip. z-index -1 keeps the layer under the
 * bar's content; it covers nothing of the bar itself, because an outer shadow
 * is never drawn inside its own box.
 *
 * One rule for the three kinds of bar that stay: this one, /admin's band, and
 * the header of /privacy and /about.
 */
.register-bar.is-lifted::after,
.admin-bar.is-lifted::after,
.site-header--stays.is-lifted::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  box-shadow: var(--shadow-bar);
  clip-path: inset(0 0 -200px 0);
}

/*
 * The wordmark, small, once the big one has gone. It flips at one point and
 * always finishes its movement — never dragged along with the scroll — which
 * is why site.js sets a class rather than a fraction.
 *
 * Two things below are load-bearing, not incidental: .search is a flex row
 * that does not wrap, and .field is min-width: 0 on a narrow screen. Let the
 * row wrap and the bar's height would change under the threshold that opened
 * it, which oscillates.
 */
.register-bar__mark {
  flex: 0 0 auto;
  width: 0;
  overflow: hidden;
  opacity: 0;
  margin-right: -8px;
  font-family: var(--display);
  font-size: 22px;
  font-weight: 600;
  line-height: 1;
  letter-spacing: -0.02em;
  white-space: nowrap;
  color: var(--ink-strong);
  transition:
    width 0.3s var(--sheet-ease),
    opacity 0.22s ease 0.04s,
    margin-right 0.3s var(--sheet-ease);
}

.register-bar.is-pinned .register-bar__mark {
  width: 126px;
  margin-right: 4px;
  opacity: 1;
}

/* Clear of the pinned bar, not under it. This is the only thing that puts a
   row where "#entry-N" promises it — the way back from a vote posted without
   script, and the way in from another language's register. */
.entry {
  scroll-margin-top: calc(var(--bar-h) + 8px);
}

.site-header {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: 32px;
  padding: 40px 0 28px;
  border-bottom: 1px solid var(--line);
}

.site-header__brand {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.site-title {
  margin: 0;
  font-family: var(--display);
  font-size: 44px;
  font-weight: 600;
  line-height: 1;
  letter-spacing: -0.022em;
  color: var(--ink-strong);
  text-wrap: balance;
}

.site-title a {
  color: inherit;
  text-decoration: none;
}

.site-tagline {
  margin: 0;
  max-width: 34ch;
  font-size: 14px;
  color: var(--muted);
  text-wrap: pretty;
}

/* Two taglines are rendered on /, and a narrow screen swaps them — see the
   820px block. The narrow one carries the way to /about inside its sentence,
   underlined in the field line so it reads as a link without shouting. */
.site-tagline--narrow {
  display: none;
}

.site-tagline a {
  color: inherit;
  text-decoration-color: var(--field-line);
  text-decoration-thickness: 1px;
}

.site-tagline a:hover {
  color: var(--ink-strong);
  text-decoration-color: currentColor;
}

/* "Wat is een watermerk?" under the wide tagline. The rule under it is always
   drawn, in the line colour, so it is visibly a way somewhere; hovering inks
   the words and the rule together. */
.about-link {
  display: inline-flex;
  align-self: flex-start;
  align-items: center;
  gap: 6px;
  margin-top: 2px;
  padding-bottom: 1px;
  border-bottom: 1px solid var(--line);
  font-size: 14px;
  color: var(--muted);
  text-decoration: none;
}

.about-link:hover {
  color: var(--ink-strong);
  border-bottom-color: var(--ink-strong);
}

.site-header__aside {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
}

/*
 * The header that is its own bar, on /privacy and /about. Neither page has a
 * list to steer, so what stays is the header itself — the name, the way back,
 * and the switches or the burger (design canvas "Watermerken", pvHeadStyle and
 * uxHeadStyle). At every width: on a phone it is what keeps the burger within
 * reach.
 *
 * z-index 8 for the bars' reason, written out at .register-bar. It makes the
 * header a stacking context, and that puts the menu's scrim and sheet, 75 and
 * 80, inside it — harmless on these two pages, where nothing outside the
 * header stands higher than 8; the compact switch's 9 is in here too.
 */
.site-header--stays {
  position: sticky;
  top: 0;
  z-index: 8;
  background: var(--ground);
}

/* A link focused from below is scrolled to the top edge, which is now under
   the header. A floor, the way --bar-h is one: a little generous costs
   nothing, short hides the link. */
html:has(.site-header--stays) {
  scroll-padding-top: 160px;
}

.closing {
  max-width: 52ch;
  margin: 56px auto 0;
  padding: 0 var(--gutter);
  text-align: center;
  font-size: 14px;
  line-height: 1.6;
  color: var(--faint);
  text-wrap: pretty;
}

/* The colofon, under the closing on every page: the three pages that are not
   the register, quiet until they are pointed at, and the one you are on in
   ink. See _Colofon. */
.colofon {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  gap: 8px 14px;
  margin: 28px auto 0;
  padding: 0 var(--gutter) 8px;
  font-size: 13px;
  color: var(--faint-2);
}

.colofon a {
  color: var(--faint);
  text-decoration: none;
}

.colofon a:hover {
  color: var(--ink-strong);
}

.colofon__here {
  color: var(--ink-strong);
}

/* ---------- controls ---------- */

/*
 * The burger's stand-in, in whichever bar stays: beside the search once the
 * register's bar is pinned, beside the tab strip once /admin's band is lifted.
 * The real one is a <details> in the header, and this opens it rather than
 * being a second one — so there is one open state, one sheet, one Escape.
 *
 * Sized where it is used, because both places have a neighbour to match: the
 * register's row is 46px like "onthul alles", and /admin's is the height of
 * the tab strip, which is not the same at both widths.
 */
.bar-menu {
  display: none;
  flex: 0 0 auto;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 1px solid var(--field-line);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--ink);
  cursor: pointer;
}

.bar-menu:hover {
  border-color: var(--ink-strong);
}

/*
 * On /admin the stand-in arrives rather than appearing: it comes in from the
 * band's own edge as the header goes, widening out of nothing with its border
 * drawn on at the end (design canvas 2B, tabBurgerStyle). Which is why it is
 * laid out at all times and only its width is nought — display cannot be
 * animated, and a button that blinks into place is not what the canvas draws.
 *
 * It shrinks the strip beside it as it comes, which is the other half of the
 * movement: the tabs slide sideways under overflow-x rather than being pushed
 * off the edge.
 */
@media (max-width: 820px) {
  .admin-bar .bar-menu {
    display: flex;
    order: 1;
    width: 0;
    height: 48px;
    margin-left: 0;
    overflow: hidden;
    border-width: 0;
    opacity: 0;
    transition:
      width 0.3s var(--sheet-ease),
      border-width 0.3s var(--sheet-ease),
      opacity 0.22s ease,
      margin-left 0.3s var(--sheet-ease);
  }

  .admin-bar.is-lifted .bar-menu {
    width: 48px;
    margin-left: 10px;
    border-width: 1px;
    opacity: 1;
  }
}

/* The form, holding the wordmark, the field and — without script — the submit
   button beside it. The row's height is .register-bar's padding now: there is
   one box again, so neither half says it any more. Flex and not wrapping, on
   purpose — see .register-bar__mark. */
.search {
  display: flex;
  align-items: center;
  gap: 8px;
}

.field {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 310px;
  height: 38px;
  padding: 0 12px;
  background: var(--surface);
  border: 1px solid var(--field-line);
  border-radius: var(--radius);
}

.field:focus-within {
  border-color: var(--accent);
}

.field__icon {
  display: flex;
  flex-shrink: 0;
  color: var(--faint);
}

.field input {
  flex-grow: 1;
  min-width: 0;
  border: 0;
  outline: none;
  background: transparent;
  font-size: 14px;
}

.field__clear {
  display: flex;
  flex-shrink: 0;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  color: var(--faint);
}

.field__clear:hover {
  color: var(--ink-strong);
}

/* Nothing to clear while the placeholder is showing, which is exactly when the
   field is empty. Keeps the cross in step with the input without script. */
.field input:placeholder-shown ~ .field__clear {
  display: none;
}

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  height: 38px;
  padding: 0 15px;
  border: 1px solid var(--field-line);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--ink);
  font-size: 12.5px;
  letter-spacing: 0.06em;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
}

.btn:hover {
  border-color: var(--ink-strong);
  color: var(--ink);
}

.btn--accent {
  border-color: var(--accent);
  background: var(--accent);
  color: var(--on-accent);
}

.btn--accent:hover {
  border-color: var(--accent);
  color: var(--on-accent);
  opacity: 0.9;
}

/* Outlined, not filled: this one is offered beside "opslaan", so it must not
   read as the louder of the two. */
.btn--danger {
  padding: 0 14px;
  background: transparent;
  color: var(--danger);
}

.btn--danger:hover {
  border-color: var(--danger);
  color: var(--danger);
}

/* Filled, and only ever on the button that does the deleting. */
.btn--danger-solid {
  border-color: var(--danger);
  background: var(--danger);
  color: var(--on-accent);
}

.btn--danger-solid:hover {
  border-color: var(--danger);
  color: var(--on-accent);
  opacity: 0.9;
}

/* The js-only utility above hides this without script; it does not hand out a
   display, so the button brings its own. flex-shrink keeps it square: it is the
   only item in the aside that can shrink, and it was being squeezed out of
   shape. */
.theme-toggle {
  display: inline-flex;
  flex-shrink: 0;
  align-items: center;
  justify-content: center;
  width: 38px;
  height: 38px;
  padding: 0;
  background: var(--surface);
  border: 1px solid var(--field-line);
  border-radius: var(--radius);
  color: var(--muted);
  cursor: pointer;
}

.theme-toggle:hover {
  color: var(--ink-strong);
  border-color: var(--ink-strong);
}

/*
 * All three icons sit inside the button and exactly one is shown.
 *
 * The icon reports the chosen mode, not the resulting appearance: in browser
 * mode it stays the monitor whether the OS asks for light or dark. That is why
 * this keys off data-theme-mode and not prefers-color-scheme.
 *
 * Hiding is done on ".theme-toggle svg" on purpose. A plain
 * ".theme-toggle__sun { display: none }" would lose to it on specificity and
 * every icon would show at once — which is exactly what happened once already.
 *
 * The third selector also covers a missing attribute, so the monitor is what
 * shows if the mode was never set.
 */
.theme-toggle svg {
  display: none;
  flex-shrink: 0;
}

:root[data-theme-mode="light"] .theme-toggle__sun,
:root[data-theme-mode="dark"] .theme-toggle__moon,
:root:not([data-theme-mode="light"]):not([data-theme-mode="dark"]) .theme-toggle__system {
  display: block;
}

/*
 * The language and the theme, each a strip with a button per option: flags
 * for the one, sun, moon and monitor for the other. The language switch is a
 * form, so it needs its margin taken off.
 *
 * Drawn two ways (SwitchLook). Full in the menu's foot, where every option is
 * on screen. Compact in a wide header, where only the chosen one shows until
 * the switch is hovered or reached with the keyboard, and the strip then
 * unfolds downwards, over the page under the header, so nothing in the header
 * moves.
 */
.lang-switch {
  display: inline-flex;
  flex-shrink: 0;
  margin: 0;
}

.switch {
  display: inline-flex;
  flex-shrink: 0;
}

.switch__strip {
  display: flex;
  align-items: center;
  gap: 3px;
  padding: 3px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}

.switch__option {
  display: inline-flex;
  flex-shrink: 0;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 32px;
  padding: 0;
  background: transparent;
  border: 1px solid transparent;
  border-radius: 2px;
  color: var(--muted);
  cursor: pointer;
  transition:
    opacity 0.16s ease,
    filter 0.16s ease,
    background-color 0.16s ease,
    border-color 0.16s ease,
    color 0.16s ease;
}

.switch__option:hover {
  color: var(--ink-strong);
}

/* A flag not chosen is faded and grey, so the chosen one is the only colour
   in the strip; the frame round it says the same thing to anybody who cannot
   tell the colours apart. */
.switch--flags .switch__option {
  width: 44px;
  opacity: 0.4;
  filter: grayscale(1);
}

.switch--flags .switch__option:hover {
  opacity: 0.75;
  filter: none;
}

.switch--flags .switch__option[aria-current="true"] {
  opacity: 1;
  filter: none;
  background: var(--surface);
  border-color: var(--ink-strong);
}

.flag {
  display: block;
  border-radius: 1px;
}

/* A language with no flag drawn in _Flag.cshtml yet. */
.flag--code {
  font-size: 12px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-strong);
}

/* The chosen mode is filled, and read off data-theme-mode, which the head
   script sets before first paint — so this is right before site.js runs. The
   third selector also covers a missing attribute, as the toggle's does. */
:root[data-theme-mode="light"] .switch__option[data-theme-set="light"],
:root[data-theme-mode="dark"] .switch__option[data-theme-set="dark"],
:root:not([data-theme-mode="light"]):not([data-theme-mode="dark"]) .switch__option[data-theme-set="system"] {
  background: var(--ink-strong);
  border-color: var(--ink-strong);
  color: var(--on-accent);
}

/*
 * Compact: a box the size of one option, with the strip laid over it and
 * anchored at its top edge, so an unfolding strip grows down over the page
 * rather than pushing the header apart. Folded it is 38px, the height of
 * every other control in a wide header.
 */
.switch--compact {
  position: relative;
  width: 38px;
  height: 38px;
}

.switch--compact.switch--flags {
  width: 48px;
}

.switch--compact .switch__strip {
  position: absolute;
  top: 0;
  left: 0;
  flex-direction: column;
  border-color: var(--field-line);
}

.switch--compact .switch__option {
  width: 30px;
  height: 30px;
}

.switch--compact.switch--flags .switch__option {
  width: 40px;
}

/*
 * Open. With script, while site.js says so (.is-open, from the pointer's
 * enter and leave) or while focus the keyboard put there is inside and no
 * choice has folded it; without, while it is hovered or focused. The two are
 * kept apart by :root.js on purpose — with script, :hover has no say at all,
 * and site.js, under "the compact switches", says why it could not keep one.
 *
 * z-index 9, over the bars' 8: on / the strip hangs out of the header and
 * down over .register-bar, which comes after it in the document and would
 * otherwise cut it off at its top edge. Below 10 for .dialog's reason. 9 is
 * also .admin-float's, and the two never meet: /admin has no compact switch.
 */
:root.js .switch--compact.is-open .switch__strip,
:root.js .switch--compact:not(.is-folded):has(:focus-visible) .switch__strip,
:root:not(.js) .switch--compact:hover .switch__strip,
:root:not(.js) .switch--compact:focus-within .switch__strip {
  z-index: 9;
  border-color: var(--ink-strong);
  box-shadow: var(--shadow-pop);
}

/*
 * The chosen option is put first, at the anchored top, so it stays exactly
 * where it was when the strip unfolds. Kept in markup order it would slide
 * away, and whatever took its place under the pointer would take the click
 * that was meant to open the switch.
 */
.switch--compact :is(
  .switch__option[aria-current="true"],
  :root[data-theme-mode="light"] .switch__option[data-theme-set="light"],
  :root[data-theme-mode="dark"] .switch__option[data-theme-set="dark"],
  :root:not([data-theme-mode="light"]):not([data-theme-mode="dark"]) .switch__option[data-theme-set="system"]
) {
  order: -1;
}

/*
 * Folded, only the chosen one is there, and it sits as quietly as the
 * header's other buttons: no fill and no frame until the strip is open.
 * Folded is simply not open, as the rule above has it, in both halves.
 *
 * The keyboard's half is :focus-visible, not :focus-within: a mouse click
 * leaves the focus on the button it pressed, and :focus-within kept every
 * clicked strip open after the pointer had gone.
 *
 * The options a folded strip hides are laid over the chosen one, invisible
 * and deaf to the pointer, rather than display: none — because they have to
 * stay focusable. Tab from one option to the next blurs the first before it
 * focuses the second, the strip folds in that instant, and an option that is
 * display: none by then cannot take the focus: it went to the page instead.
 * Laid over like this, the second is still there to arrive at, and arriving
 * opens the strip again. Hidden at once, shown with the fade.
 */
:root.js .switch--compact:not(.is-open):is(.is-folded, :not(:has(:focus-visible))) .switch__option:not(:is(
  .switch__option[aria-current="true"],
  :root[data-theme-mode="light"] .switch__option[data-theme-set="light"],
  :root[data-theme-mode="dark"] .switch__option[data-theme-set="dark"],
  :root:not([data-theme-mode="light"]):not([data-theme-mode="dark"]) .switch__option[data-theme-set="system"]
)),
:root:not(.js) .switch--compact:not(:hover):not(:focus-within) .switch__option:not(:is(
  .switch__option[aria-current="true"],
  :root[data-theme-mode="light"] .switch__option[data-theme-set="light"],
  :root[data-theme-mode="dark"] .switch__option[data-theme-set="dark"],
  :root:not([data-theme-mode="light"]):not([data-theme-mode="dark"]) .switch__option[data-theme-set="system"]
)) {
  position: absolute;
  top: 3px;
  left: 3px;
  opacity: 0;
  pointer-events: none;
  transition: none;
}

:root.js .switch--compact:not(.is-open):is(.is-folded, :not(:has(:focus-visible))) :is(
  .switch__option[aria-current="true"],
  :root[data-theme-mode="light"] .switch__option[data-theme-set="light"],
  :root[data-theme-mode="dark"] .switch__option[data-theme-set="dark"],
  :root:not([data-theme-mode="light"]):not([data-theme-mode="dark"]) .switch__option[data-theme-set="system"]
),
:root:not(.js) .switch--compact:not(:hover):not(:focus-within) :is(
  .switch__option[aria-current="true"],
  :root[data-theme-mode="light"] .switch__option[data-theme-set="light"],
  :root[data-theme-mode="dark"] .switch__option[data-theme-set="dark"],
  :root:not([data-theme-mode="light"]):not([data-theme-mode="dark"]) .switch__option[data-theme-set="system"]
) {
  background: transparent;
  border-color: transparent;
  color: var(--muted);
}

/* ---------- sort bar ---------- */

/* The right-hand half of the sort row: the sorts and the count, pushed to the
   end of the second column. The search is the first column, and the page
   renders it — see .register-bar. */
.sortbar {
  display: flex;
  align-items: baseline;
  justify-content: flex-end;
  gap: 24px;
}

.sortbar__sorts {
  display: flex;
  align-items: baseline;
  gap: 12px;
}

.sortbar__label {
  font-size: 11px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--faint);
}

.sortbar__sort {
  display: inline-flex;
  align-items: baseline;
  padding-bottom: 2px;
  border-bottom: 1px solid transparent;
  color: var(--muted);
  font-size: 13px;
  text-decoration: none;
}

.sortbar__sort:hover {
  color: var(--ink-strong);
}

.sortbar__sort[aria-current="true"] {
  color: var(--ink-strong);
  border-bottom-color: var(--accent);
}

/* Always in the flow, so the label does not shift sideways when the arrow
   appears on the column the visitor just picked. */
.sortbar__arrow {
  display: inline-block;
  min-width: 0.6em;
  margin-left: 4px;
  font-size: 12px;
  color: var(--accent);
}

.sortbar__count {
  margin: 0;
  font-size: 13px;
  color: var(--faint);
}

/* The way back from a filtered list, for the narrow screen that has no search
   field to clear. A wide screen has the field and its cross. */
.sortbar__all {
  display: none;
  font-size: 13px;
}

/* ---------- the register ---------- */

.register__head {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  border-top: 1.5px solid var(--ink-strong);
  font-size: 11px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--faint);
}

.register__head-term {
  padding: 10px 30px 10px 0;
  text-align: right;
}

.register__head-brand {
  padding: 10px 0 10px 30px;
  border-left: 1px solid var(--line);
}

.entry {
  border-bottom: 1px solid var(--line);
}

/*
 * The whole row is the control. A <summary> is already a button, so there is
 * no button inside it — nesting one would be invalid and would take the row
 * off the keyboard.
 */
.entry__summary {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  align-items: stretch;
  list-style: none;
  cursor: pointer;
  transition: background 0.2s ease;
}

.entry__summary::-webkit-details-marker {
  display: none;
}

.entry__summary:hover {
  background: var(--row-hover);
}

.entry__side {
  display: flex;
  min-width: 0;
}

.entry__side--term {
  flex-direction: column;
  align-items: flex-end;
  gap: 6px;
  padding: 22px 30px 22px 0;
  text-align: right;
}

.entry__word {
  font-family: var(--display);
  font-size: 34px;
  font-weight: 600;
  line-height: 1.05;
  letter-spacing: -0.018em;
  color: var(--ink-strong);
}

.entry__category {
  font-size: 11px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--faint);
}

.entry__side--brand {
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  padding: 22px 24px 22px 30px;
  border-left: 1px solid var(--line);
}

.entry__reveal {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 5px;
  min-width: 0;
  text-align: left;
}

/*
 * The brand under a block of ink. The word stays in the document — this hides
 * it from the eye, not from a screen reader or from a browser with styles off.
 *
 * Two selectors uncover it, and they are what makes the same markup behave
 * differently with and without script. [open] is the plain <details> and the
 * only one that ever matches when script is off. .is-revealed is set by
 * site.js and never removed, so once a visitor has seen a brand, collapsing
 * the row hides the notes again but leaves the word uncovered — the two states
 * the design has, out of one element.
 *
 * The negative margin cancels the padding the ink block needs, so the word
 * does not shift sideways as the cover comes off.
 */
.entry__brand {
  padding: 0 4px;
  margin: 0 -4px;
  background: var(--ink-strong);
  color: transparent;
  font-family: var(--display);
  font-size: 34px;
  font-weight: 600;
  line-height: 1.05;
  letter-spacing: -0.018em;
  transition: background-color 0.28s ease, color 0.28s ease;
}

.entry[open] .entry__brand,
.entry.is-revealed .entry__brand {
  background: transparent;
  color: var(--accent);
}

.entry__hint,
.entry__owner {
  font-size: 11px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

.entry__hint {
  color: var(--faint-2);
}

.entry__owner {
  display: none;
  color: var(--muted);
}

.entry[open] .entry__hint,
.entry.is-revealed .entry__hint {
  display: none;
}

.entry[open] .entry__owner,
.entry.is-revealed .entry__owner {
  display: block;
}

.entry__marker {
  display: flex;
  flex: 0 0 auto;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  color: var(--faint-2);
  transition: transform 0.2s ease, color 0.2s ease, border-color 0.2s ease;
}

.entry__summary:hover .entry__marker,
.entry[open] .entry__marker {
  border-color: var(--accent);
  color: var(--accent);
}

.entry[open] .entry__marker {
  transform: rotate(180deg);
}

/* The wide screen shows a chevron, the narrow one an "i". */
.entry__info {
  display: none;
}

.entry__panel {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  border-top: 1px dotted var(--line);
}

.entry__meta {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 4px;
  padding: 16px 30px 26px 0;
  text-align: right;
  font-size: 12px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--faint-2);
}

.entry__meta > span:first-child {
  color: var(--faint);
}

.entry__notes {
  display: flex;
  flex-direction: column;
  gap: 12px;
  max-width: 60ch;
  padding: 16px 24px 26px 30px;
  border-left: 1px solid var(--line);
}

.entry__notes p {
  margin: 0;
  font-size: 15.5px;
  line-height: 1.65;
  color: var(--ink);
  text-wrap: pretty;
}

.entry__notes a {
  text-decoration: underline;
  text-decoration-thickness: 1px;
}

.entry__out {
  margin-left: 2px;
  vertical-align: baseline;
}

/* ---------- the vote ---------- */

/*
 * The band under the notes, in the same two columns the panel uses. The bar is
 * the button: its left half is "oneens" and its right half is "eens", and once
 * the visitor has answered the same bar shows the split by how far the fill has
 * come. One element for the question and the answer, so nothing moves when the
 * answer arrives — the row does not reflow under the finger that just voted.
 *
 * --split is the seam: where the fill starts, where the divider sits and how
 * wide the left half is. Half and half until there is a result to show.
 */
.vote {
  /* The main column's own insets. Named, because the lemma below hangs over the
     bar while its trigger sits in the other column: it is placed against the
     band and has to arrive at the bar's edges from outside. Changing the
     padding alone would leave the card behind. */
  --vote-top: 13px;
  --vote-gap: 30px;
  --vote-end: 24px;

  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  border-top: 1px dotted var(--line);
  background: var(--surface-2);
}

.vote__aside {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 5px;
  padding: 15px 30px 19px 0;
  text-align: right;
}

.vote__contested {
  display: inline-flex;
  align-items: center;
  height: 19px;
  padding: 0 7px;
  border: 1px solid var(--accent);
  border-radius: 2px;
  font-size: 9.5px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--accent);
}

/*
 * The question and the answer are the same line of the aside, so they are the
 * same box: the rule under "verwaterd?" is reserved here in every state, and the
 * line box is a length rather than "normal" so the mark beside the question
 * cannot make it taller than the answer. Without either, voting moved the count
 * under it — in the one band whose whole point is that nothing moves when the
 * answer arrives.
 */
.vote__label {
  padding-bottom: 2px;
  border-bottom: 1px dotted transparent;
  font-size: 12px;
  line-height: 16px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--faint);
}

.vote__count {
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--faint-2);
}

/* The card's spelling of the answer line; a row says it under the bar. */
.vote__aside .vote__mine {
  display: none;
}

/*
 * The lemma behind the question. While "verwaterd?" is still a question its
 * label is a <details>, and what it opens is a dictionary entry for the word:
 * the meaning, and then what each half of the bar would be claiming. It is read
 * where it is about to be answered — the card hangs over the bar, not beside
 * the label that opens it — so it spans the other column from the outside.
 *
 * Once the visitor has answered, the label is a number and none of this is on
 * the page at all.
 */
.vote__why {
  /* Deliberately not a positioning context: the card is placed against .vote. */
  position: static;

  /* A flex column rather than a block, so the summary is a flex item and not an
     inline-flex box on a line of its own: a line box would put its own leading
     above the label, and the answer that replaces it has none. */
  display: flex;
  flex-direction: column;
  align-items: flex-end;
}

summary.vote__label {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  border-bottom-color: var(--field-line);
  list-style: none;
  cursor: pointer;
  transition: color 0.15s ease, border-color 0.15s ease;
}

summary.vote__label::-webkit-details-marker {
  display: none;
}

.vote__why:hover > .vote__label,
.vote__why[open] > .vote__label {
  border-bottom-color: var(--ink-strong);
  color: var(--ink-strong);
}

/* Quiet enough to be an aside, round enough to be read as a control. */
.vote__mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 14px;
  height: 14px;
  border: 1px solid var(--field-line);
  border-radius: 50%;
  font-size: 9px;
  letter-spacing: 0;
  line-height: 1;
  color: var(--faint-2);
  transition: color 0.15s ease, border-color 0.15s ease;
}

.vote__why[open] .vote__mark {
  border-color: var(--accent);
  color: var(--accent);
}

/* Spans the bar exactly and stands clear of it. The extra pixel is the border
   .vote__main carries on its left edge, which its padding does not include. */
.vote__lemma {
  position: absolute;
  z-index: 5;
  right: var(--vote-end);
  bottom: calc(100% - var(--vote-top) + 11px);
  left: calc(50% + var(--vote-gap) + 1px);
  display: flex;
  flex-direction: column;
  gap: 9px;
  padding: 14px 16px;
  border: 1px solid var(--field-line);
  border-radius: var(--radius);
  background: var(--surface);
  box-shadow: var(--shadow-pop);
  text-align: left;
  animation: lemma-rise 0.16s ease;
}

/* Centred on a card that spans the bar, which puts it on the dashed seam. */
.vote__nib {
  position: absolute;
  bottom: -5px;
  left: 50%;
  width: 9px;
  height: 9px;
  margin-left: -4px;
  border-right: 1px solid var(--field-line);
  border-bottom: 1px solid var(--field-line);
  background: var(--surface);
  transform: rotate(45deg);
}

.vote__lemma-head {
  display: flex;
  align-items: baseline;
  gap: 8px;
}

.vote__term {
  font-family: var(--display);
  font-weight: 600;
  font-size: 25px;
  line-height: 1;
  letter-spacing: -0.018em;
  color: var(--ink-strong);
}

.vote__pos {
  font-family: var(--display);
  font-style: italic;
  font-size: 15px;
  color: var(--faint);
}

.vote__close {
  flex: none;
  align-self: flex-start;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  margin: -2px -4px 0 auto;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: none;
  color: var(--faint-2);
  cursor: pointer;
  transition: color 0.15s ease, border-color 0.15s ease;
}

.vote__close:hover {
  border-color: var(--ink-strong);
  color: var(--ink-strong);
}

.vote__gloss {
  margin: 0;
  font-size: 13.5px;
  line-height: 1.6;
  color: var(--ink);
  text-wrap: pretty;
}

/* Left and right as the halves of the bar are, so each case is already above
   the button that answers it. */
.vote__cases {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 16px;
  padding-top: 10px;
  border-top: 1px dotted var(--field-line);
}

.vote__case {
  display: flex;
  flex-direction: column;
  gap: 3px;
}

.vote__case--for {
  text-align: right;
}

.vote__case-label {
  font-size: 10.5px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--faint);
}

/* The side the fill runs to, in the colour it runs in. */
.vote__case--for .vote__case-label {
  color: var(--accent);
}

.vote__case-text {
  font-size: 13px;
  line-height: 1.5;
  color: var(--ink);
}

/* Up from the bar it points at; down from the label, where it opens in the
   flow instead. */
@keyframes lemma-rise {
  from { opacity: 0; transform: translateY(4px); }
  to { opacity: 1; transform: translateY(0); }
}

@keyframes lemma-drop {
  from { opacity: 0; transform: translateY(-4px); }
  to { opacity: 1; transform: translateY(0); }
}

.vote__main {
  display: flex;
  flex-direction: column;
  gap: 7px;
  padding: var(--vote-top) var(--vote-end) 19px var(--vote-gap);
  border-left: 1px solid var(--line);
}

.vote__bar {
  position: relative;
  height: 24px;
  overflow: hidden;
  border: 1px solid var(--field-line);
  background: var(--surface);
}

/* Runs from the seam to the right edge, so a full bar means everyone agrees.
   Transparent until there is something to say. */
.vote__fill {
  position: absolute;
  inset: 0 0 0 var(--split);
  background: var(--accent);
  opacity: 0;
  transition: left 0.6s cubic-bezier(0.32, 0.72, 0, 1), opacity 0.3s ease;
}

.vote--shown .vote__fill {
  opacity: 1;
}

/* Dashed while the bar is still a question, solid once it is an answer. */
.vote__divider {
  position: absolute;
  top: 0;
  bottom: 0;
  left: var(--split);
  width: 0;
  border-left: 1px dashed var(--field-line);
  transition: left 0.6s cubic-bezier(0.32, 0.72, 0, 1);
}

.vote--voted .vote__divider {
  border-left-style: solid;
}

/*
 * Answered, but not enough votes for a split. The bar used to stand empty here
 * — no fill, no words, a seam down the middle of nothing — which reads as
 * broken rather than as withheld. So it fills, and says what it is waiting for.
 * The seam goes with it: half of nothing is still a proportion, and there is
 * none to show yet.
 */
.vote--waiting .vote__bar {
  background: var(--tint);
}

.vote--waiting .vote__divider {
  opacity: 0;
}

/* Sits under .vote__halves, which is wordless in this state and paints nothing
   over it. Darker than the hint on the empty bar, the way the percentage on the
   fill is: text on a filled band, not beside one. */
.vote__waiting {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  padding: 0 8px;
  overflow: hidden;
  color: var(--ink);
  font-size: 11.5px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  white-space: nowrap;
}

.vote__halves {
  position: absolute;
  inset: 0;
  z-index: 1;
  display: flex;
}

.vote__half {
  padding: 0 8px;
  overflow: hidden;
  border: 0;
  background: none;
  color: var(--muted);
  font-family: inherit;
  font-size: 11.5px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  white-space: nowrap;
  cursor: pointer;
  transition: width 0.6s cubic-bezier(0.32, 0.72, 0, 1), background 0.15s ease, color 0.15s ease;
}

/* The left half is sized to the seam; the right takes what is left, so the two
   always meet exactly on it. */
.vote__half--against {
  flex: none;
  width: var(--split);
  text-align: left;
}

.vote__half--for {
  flex: 1;
  min-width: 0;
  text-align: right;
}

.vote--shown .vote__half--for {
  color: var(--on-accent);
}

.vote__half:enabled:hover,
.vote__half:enabled:focus-visible {
  background: var(--surface-2);
  box-shadow: inset 0 -2px 0 0 var(--accent);
  color: var(--ink-strong);
}

/* Answered: the halves are a picture now, not a choice. "wijzig" is the way
   back, so the bar itself stops taking clicks. */
.vote__half:disabled {
  cursor: default;
}

/* An answer on its way. site.js drops a second submit for the term until the
   first has come back; this only keeps the halves from lighting up for a
   click that will do nothing. */
.vote.is-pending .vote__half,
.vote.is-pending .vote__change {
  pointer-events: none;
}

.vote__note {
  display: flex;
  align-items: baseline;
  gap: 10px;
  margin: 0;
  font-size: 12.5px;
  color: var(--muted);
}

.vote__note--braked {
  color: var(--faint);
}

.vote__hint {
  color: var(--faint);
}

/* Two spellings of one sentence; the narrow block below swaps them. */
.vote__hint--tap {
  display: none;
}

.vote__change {
  padding: 0;
  border: 0;
  background: none;
  color: var(--accent);
  font-family: inherit;
  font-size: 12.5px;
  text-decoration: underline;
  cursor: pointer;
}

.vote__change:hover {
  color: var(--ink-strong);
}

.empty {
  margin: 0;
  padding: 80px 0;
  text-align: center;
  font-size: 15px;
  color: var(--faint);
}

.register--empty {
  border-top: 1.5px solid var(--ink-strong);
}

@media (prefers-reduced-motion: reduce) {
  .entry__summary,
  .entry__brand,
  .entry__marker,
  .vote__fill,
  .vote__divider,
  .vote__half {
    transition: none;
  }

  .vote__lemma {
    animation: none;
  }
}

/*
 * While site.js puts back the brands this visitor had already uncovered. Those
 * covers must be off before the first paint, not animate off after it — the
 * page should look like they were already revealed, not like it is revealing
 * them again on every load. The class is gone two frames later.
 */
.is-restoring .entry__summary,
.is-restoring .entry__brand,
.is-restoring .entry__marker {
  transition: none;
}

/* ---------- the register on a narrow screen ---------- */

/*
 * Below this width the row stops being two columns. Closed it is just the
 * term; opened, the term shrinks to a struck-through label and the brand takes
 * its place — the same swap the wide row does side by side, done top to bottom.
 *
 * 820px is the same breakpoint the bottom sheet in site.js watches. Keep the
 * two in step: the sheet replaces the panel this hides.
 */
@media (max-width: 820px) {
  .page {
    padding: 0 0 100px;
  }

  .site-header {
    flex-direction: column;
    align-items: stretch;
    gap: 16px;
    padding: 26px var(--gutter) 18px;
  }

  .site-header__aside {
    width: 100%;
    gap: 8px;
  }

  /* One column. The bar runs edge to edge here, so what is in it carries the
     gutter itself rather than the bar padding it. */
  .register-bar {
    grid-template-columns: minmax(0, 1fr);
    row-gap: 0;
    padding: 0;
  }

  /*
   * No search on a narrow screen, with or without script. Without it the
   * search's row goes whole — nothing else in it is shown without script —
   * and the sorts come straight under the header. With it the stack takes the
   * one control left in that row, "onthul alles", into the head of the
   * register; see the stack below. A list that arrives filtered by a link
   * carries its own way back beside the count (.sortbar__all).
   */
  .search .field,
  .search .btn {
    display: none;
  }

  :root:not(.js) .register-bar > .search {
    display: none;
  }

  .btn,
  .theme-toggle {
    height: 44px;
  }

  .theme-toggle {
    width: 44px;
  }

  .sortbar {
    flex-wrap: wrap;
    justify-content: space-between;
    padding: 12px var(--gutter) 10px;
  }

  .sortbar__all {
    display: inline;
  }

  .register__head {
    display: none;
  }

  /* With no heading row here the heavy rule is the bar's own bottom edge.
     It has to be on the bar rather than on the list: pinned, a toolbar with
     no edge lets the rows run up under it. Full width, like the --line rule
     over it — insetting only this one would leave the two disagreeing. */
  .register-bar {
    border-bottom: 1.5px solid var(--ink-strong);
  }

  .entry {
    border-bottom: 0;
  }

  .entry__summary {
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-areas:
      "term marker"
      "brand marker";
    align-items: center;
    gap: 12px;
    padding: 15px var(--gutter);
    border-bottom: 1px solid var(--line);
  }

  .entry[open] .entry__summary,
  .entry.is-revealed .entry__summary {
    background: var(--row-hover);
  }

  /* Lets the two children below take their own grid area, so the brand can sit
     under the term while the marker stays beside both. */
  .entry__side--brand {
    display: contents;
  }

  .entry__side--term {
    grid-area: term;
    align-items: flex-start;
    gap: 3px;
    padding: 0;
    text-align: left;
  }

  .entry__reveal {
    grid-area: brand;
    display: none;
  }

  .entry__marker {
    grid-area: marker;
    width: 44px;
    height: 44px;
  }

  .entry__word {
    font-size: 32px;
  }

  .entry__category {
    font-size: 10.5px;
  }

  .entry[open] .entry__reveal,
  .entry.is-revealed .entry__reveal {
    display: flex;
  }

  /* Opened: the term steps back to a label with a line through it, and the
     category joins it on the same line. */
  .entry[open] .entry__side--term,
  .entry.is-revealed .entry__side--term {
    flex-direction: row;
    align-items: baseline;
    gap: 5px;
  }

  .entry[open] .entry__word,
  .entry.is-revealed .entry__word {
    font-family: var(--body);
    font-size: 10.5px;
    font-weight: 400;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--faint);
    text-decoration: line-through;
    text-decoration-color: var(--field-line);
  }

  .entry[open] .entry__category::before,
  .entry.is-revealed .entry__category::before {
    content: "· ";
  }

  .entry[open] .entry__brand,
  .entry.is-revealed .entry__brand {
    font-size: 32px;
    line-height: 1.08;
  }

  /* The narrow row is only the strip and the brand — the owner waits in the
     sheet. The [open] and .is-revealed selectors have to be repeated here to
     beat the rule outside this query that shows the owner once a row opens. */
  .entry__hint,
  .entry__owner,
  .entry[open] .entry__owner,
  .entry.is-revealed .entry__owner {
    display: none;
  }

  /* An "i" rather than a chevron, and it does not turn: on a narrow screen it
     opens a panel beside the row rather than below it. */
  .entry__chevron {
    display: none;
  }

  .entry__info {
    display: block;
    font-family: var(--display);
    font-style: italic;
    font-weight: 600;
    font-size: 21px;
    line-height: 1;
    transform: translate(-0.8px, 1px);
  }

  .entry[open] .entry__marker {
    transform: none;
  }

  /* The gutter sits on the wrapper, not on the panel: the vote band is the
     panel's sibling, and indenting only the panel let the bar run off the
     side of the screen. */
  .entry__body {
    padding: 0 var(--gutter);
  }

  .entry__panel {
    grid-template-columns: minmax(0, 1fr);
    border-top: 0;
  }

  .entry__meta {
    align-items: flex-start;
    padding: 14px 0 10px;
    text-align: left;
  }

  .entry__notes {
    max-width: none;
    padding: 0 0 22px;
    border-left: 0;
  }

  /* With script the panel and the vote move into the bottom sheet together, so
     they must not also sit in the row. Without script they stay exactly where
     they are. */
  .js .entry__body {
    display: none;
  }

  /* Stacked, and the band stops being a band: no fill behind it, one dotted
     rule to part it from the notes. */
  .vote {
    grid-template-columns: minmax(0, 1fr);
    gap: 8px;
    padding: 14px 0 22px;
    border-top: 1px dotted var(--field-line);
    background: none;
  }

  .vote__aside {
    flex-direction: row;
    align-items: baseline;
    gap: 10px;
    padding: 0;
    text-align: left;
  }

  .vote__count {
    margin-left: auto;
  }

  /*
   * The trigger takes the line to itself, so its card can run the full width of
   * the band and push the bar down instead of hanging over the one thing a
   * thumb is aiming at. Only the question has a trigger, so "omstreden" and a
   * percentage never share this layout — which is what lets the count come out
   * of the flow without anything else losing its place.
   */
  .vote__aside:has(.vote__why) {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: baseline;
  }

  /* Both in the first row, on top of one another: the trigger has the whole
     width so its card does too, and the count is laid over the empty half of
     the line it leaves. Baselines rather than boxes, so the count sits exactly
     where the answered row — a plain flex line — will put it. */
  .vote__aside:has(.vote__why) .vote__why {
    grid-area: 1 / 1 / 2 / 3;
  }

  .vote__aside:has(.vote__why) .vote__count {
    grid-area: 1 / 2 / 2 / 3;
    justify-self: end;
    margin-left: 0;
  }

  /* Left-aligned here, and the card takes the width the summary does not. */
  .vote__why {
    align-items: flex-start;
  }

  .vote__mark {
    width: 16px;
    height: 16px;
    font-size: 10px;
  }

  /* In the flow, between the label and the bar. Relative rather than static, so
     the nib still hangs off the card and not off the aside around it. */
  .vote__lemma {
    position: relative;
    right: auto;
    bottom: auto;
    left: auto;
    align-self: stretch;
    margin: 6px 0 11px;
    animation-name: lemma-drop;
  }

  .vote__close {
    width: 34px;
    height: 34px;
  }

  .vote__main {
    padding: 0;
    border-left: 0;
  }

  /* Big enough for a thumb, and the bar is the only control there is. */
  .vote__bar {
    height: 44px;
    background: var(--ground);
  }

  .vote__half,
  .vote__waiting {
    padding: 0 10px;
    font-size: 12px;
  }

  .vote__hint--click {
    display: none;
  }

  .vote__hint--tap {
    display: inline;
  }

  .empty {
    padding: 60px var(--gutter);
  }
}

/* ---------- bottom sheet (narrow, script only) ---------- */

/*
 * Built by site.js, which clones the row's panel into it rather than building
 * the notes from text — the markup is already encoded by Razor, so cloning it
 * cannot introduce markup a note did not have.
 */
.sheet-scrim {
  position: fixed;
  inset: 0;
  z-index: 65;
  background: var(--scrim);
  opacity: 1;
  transition: opacity var(--sheet-fade) ease;
  animation: sheet-fade 0.16s ease;
}

.sheet {
  position: fixed;
  right: 0;
  bottom: 0;
  left: 0;
  z-index: 70;
  display: flex;
  flex-direction: column;
  gap: 16px;
  max-height: 86vh;
  padding: 10px var(--gutter) calc(40px + env(safe-area-inset-bottom, 0px));
  background: var(--surface);
  border-top: 1px solid var(--line);
  border-radius: var(--radius-lg) var(--radius-lg) 0 0;
  box-shadow: var(--shadow-sheet);
  touch-action: none;
  transition: translate var(--sheet-rise) var(--sheet-ease);
  animation: sheet-rise var(--sheet-rise) var(--sheet-ease);
}

/* While a finger is on it the surface follows the finger, so it must not also
   be easing towards a target. Both surfaces take the gesture from the same
   routine, so both answer to the class it sets. */
.sheet.is-dragging,
.dialog.is-dragging {
  transition: none;
}

.sheet.is-closing,
.sheet-scrim.is-closing {
  animation: none;
}

.sheet.is-closing {
  translate: 0 100%;
}

.sheet-scrim.is-closing {
  opacity: 0;
}

.sheet__grip {
  display: flex;
  align-self: stretch;
  align-items: center;
  justify-content: center;
  height: 24px;
  margin: -2px 0 -8px;
  cursor: grab;
}

.sheet__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
}

.sheet__title {
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-width: 0;
}

.sheet__strip {
  font-size: 10.5px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--faint);
}

.sheet__strip s {
  text-decoration-color: var(--field-line);
}

.sheet__brand {
  font-family: var(--display);
  font-size: 40px;
  font-weight: 600;
  line-height: 1;
  letter-spacing: -0.024em;
  color: var(--accent);
}

.sheet__close {
  display: flex;
  flex: 0 0 auto;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: transparent;
  color: var(--muted);
  cursor: pointer;
}

.sheet__close:hover {
  color: var(--ink-strong);
  border-color: var(--ink-strong);
}

.sheet__body {
  overflow-y: auto;
  overscroll-behavior: contain;
  touch-action: pan-y;
}

/* The cloned panel arrives with the row's own layout; inside the sheet it is
   simply one column. The wrapper is hidden in the row it came from, which is
   the point — this is the copy that shows. */
.sheet__body .entry__body {
  display: block;
  padding: 0;
}

.sheet__body .entry__meta {
  padding-top: 0;
}

.sheet__body .entry__notes {
  padding-bottom: 0;
}

.sheet__body .vote {
  padding-bottom: 0;
}

/* Keeps the page behind the sheet from scrolling with it. */
.is-sheet-open {
  overflow: hidden;
}

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

@keyframes sheet-rise {
  from { translate: 0 100%; }
  to { translate: 0 0; }
}

@media (prefers-reduced-motion: reduce) {
  .sheet,
  .sheet-scrim {
    animation: none;
    transition: none;
  }

  /* The dialog keeps the discrete halves of its transition — they are what put
     it on screen and take it off again, not what animates it — and drops the
     movement and the fade. */
  .dialog {
    opacity: 1;
    translate: 0 0;
    transition:
      overlay var(--sheet-rise) allow-discrete,
      display var(--sheet-rise) allow-discrete;
  }

  .dialog::backdrop {
    opacity: 1;
    transition: none;
  }
}

/* ---------- the stack (narrow) ---------- */

/*
 * The register read one term at a time. Below 820px, with script, the page is
 * a column one screen tall — the header as it always was, the cards, and the
 * rail at the foot — and the register itself becomes the sheet the rail pulls
 * up. Design canvas "Mobiel stapel".
 *
 * Laid out here, from :root.js, before stack.js has run: waiting for the
 * script would show the plain register first and then snap to the stack on
 * every page load. stack.js only fills the cards in.
 *
 * Colours are the palette's own. The one the canvas has that the palette did
 * not is --scrim-strong, for the first-run sheet the practice card has since
 * replaced — /submit still uses it; the current row's ground is --row-hover,
 * which already had exactly that value.
 */
.stack,
.stack__scrim {
  display: none;
}

/*
 * One factor from the height of the screen, for everything that has to give
 * on a short one: paddings and gaps take --k, type takes --kt, which gives a
 * little less. The canvas's own four rungs. The card's body scrolls as well,
 * so on the shortest screen the notes are still there to be reached.
 */
.stack {
  --k: 1;
  --kt: 1;
}

@media (max-height: 759px) {
  .stack {
    --k: 0.88;
    --kt: 0.94;
  }
}

@media (max-height: 659px) {
  .stack {
    --k: 0.76;
    --kt: 0.82;
  }
}

@media (max-height: 569px) {
  .stack {
    --k: 0.66;
    --kt: 0.72;
  }
}

@media (max-width: 820px) {
  /* Nothing to pull the page past: the stack is exactly one screen. */
  :root.js:has(.page--stack) {
    overscroll-behavior: none;
  }

  :root.js .page--stack {
    display: flex;
    flex-direction: column;
    height: 100vh;
    height: 100dvh;
    padding: 0;
    overflow: hidden;
  }

  :root.js .page--stack .site-header {
    flex: none;
  }

  /* A tablet held upright is narrow too, and a card the width of one is not
     what the canvas draws. The column keeps a phone's width and centres. */
  :root.js .stack {
    display: flex;
    flex: 1 1 auto;
    flex-direction: column;
    width: 100%;
    max-width: 430px;
    min-height: 0;
    margin: 0 auto;
  }

  /* ----- the deck ----- */

  .stack__deck {
    --rise: 0;

    position: relative;
    flex: 1 1 auto;
    min-height: 0;
    padding: 18px 18px 10px;
  }

  .stack__front {
    position: relative;
    z-index: 2;
    height: 100%;
  }

  /*
   * The two layers behind the card. At rest they sit a little in and a little
   * down; --rise, which the swipe drives, brings them up flush. The one in
   * front is the real next term.
   */
  .stack__peek,
  .stack__back {
    position: absolute;
    top: calc(18px + var(--drop) * (1 - var(--rise)));
    right: calc(18px + var(--inset) * (1 - var(--rise)));
    bottom: 10px;
    left: calc(18px + var(--inset) * (1 - var(--rise)));
    transition:
      top 0.28s var(--sheet-ease),
      right 0.28s var(--sheet-ease),
      left 0.28s var(--sheet-ease),
      opacity 0.2s ease;
  }

  .stack__peek {
    --inset: 7px;
    --drop: 11px;

    z-index: 1;
  }

  .stack__back {
    --inset: 14px;
    --drop: 22px;

    z-index: 0;
    border: 1px solid var(--field-line);
    border-radius: 4px;
    background: var(--row-hover);
  }

  .stack__deck.is-last .stack__peek,
  .stack__deck.is-last .stack__back {
    opacity: 0;
  }

  .stack__deck.is-dragging .stack__peek,
  .stack__deck.is-dragging .stack__back,
  .stack__deck.is-dragging .card--peek *,
  .stack__deck.is-still .stack__peek,
  .stack__deck.is-still .stack__back,
  .stack__deck.is-still .card--peek * {
    transition: none;
  }

  .stack__deck.is-done .stack__front,
  .stack__deck.is-done .stack__peek,
  .stack__deck.is-done .stack__back {
    display: none;
  }

  /* ----- a card ----- */

  .card {
    position: relative;
    display: flex;
    flex-direction: column;
    height: 100%;
    padding: calc(24px * var(--k)) 20px calc(16px * var(--k));
    overflow: hidden;
    border: 1px solid var(--field-line);
    border-top: 2px solid var(--ink-strong);
    border-radius: 4px;
    background: var(--surface);
    box-shadow: var(--shadow-pop);
    touch-action: pan-y;
    user-select: none;
    -webkit-user-select: none;
  }

  .card:not(.is-revealed) {
    cursor: pointer;
  }

  /* Covered, a card is not scrolled: a finger going up is the push that skips
     it, and anything the browser took for a pan would cancel that halfway.
     Its body says so too, being a scroll container — see .card__body.
     Uncovered, it keeps pan-y for its notes, and stack.js refuses the
     touchmove of a push upwards it takes instead. */
  .card:not(.is-revealed),
  .card:not(.is-revealed) .card__body {
    touch-action: none;
  }

  /* Swiped sideways while covered: locked, and saying where to go instead —
     the top edge and the block that uncovers it light up in the accent. */
  .card.is-locked:not(.is-revealed) {
    border-top-color: var(--accent);
  }

  /* Follows the finger while it is held, and eases back — or on — once it is
     let go. The card stays opaque the whole way: nothing is seen through it,
     the next card comes out from beside it. */
  .card {
    transition:
      transform 0.3s var(--sheet-ease),
      border-top-color 0.18s ease;
  }

  .card.is-dragging {
    cursor: grabbing;
    transition: border-top-color 0.18s ease;
  }

  .card.is-dragging * {
    user-select: none;
    -webkit-user-select: none;
  }

  /* Leaning towards "eens" on a card that can still be answered, the top edge
     takes the accent the right half is filling with. */
  .card.is-revealed.is-leaning-for:not(:has(.vote--voted, .vote.is-pending)) {
    border-top-color: var(--accent);
  }

  .card--peek {
    box-shadow: none;
  }

  /* Scrolls on its own, so the notes and the hint are always within reach on a
     short screen; the band stays pinned to the foot under it.

     And says pan-y for itself. A touch-action is only intersected up to the
     nearest scroll container, and this is one, so the card's own pan-y never
     reached a finger that landed here: Safari took the sideways swipe as a
     scroll of the body and the card never moved. pan-y is not all of it
     either — Safari still pans this the moment a swipe drifts downwards and
     cancels it halfway, which follow() in site.js answers by refusing the
     touchmove once a sideways drag is taken. */
  .card__body {
    display: flex;
    flex: 1 1 auto;
    flex-direction: column;
    min-height: 0;
    overflow-y: auto;
    overscroll-behavior: contain;
    touch-action: pan-y;
  }

  .card__head {
    display: flex;
    flex: none;
    flex-direction: column;
    gap: 9px;
  }

  .card__label {
    font-size: 10.5px;
    letter-spacing: 0.18em;
    text-transform: uppercase;
    color: var(--faint);
  }

  .card__label-full,
  .card.has-category .card__label-plain {
    display: none;
  }

  .card.has-category .card__label-full {
    display: inline;
  }

  /* The ladder: the longer the word, the smaller it is set, one rung per few
     letters, and never under 26px however short the screen. */
  .card__term {
    --size: 52px;

    font-family: var(--display);
    font-size: max(26px, calc(var(--size) * var(--kt)));
    font-weight: 600;
    line-height: 0.98;
    letter-spacing: -0.028em;
    color: var(--ink-strong);
    text-wrap: balance;
    overflow-wrap: anywhere;
  }

  .card__term[data-size="2"] {
    --size: 46px;
  }

  .card__term[data-size="3"] {
    --size: 40px;
  }

  .card__term[data-size="4"] {
    --size: 34px;
  }

  /* Uncovered, the word steps back: the brand is what it lost to. Not on the
     practice card, whose "term" is a sentence and lost to nothing. */
  .card.is-revealed:not(.card--practice) .card__term {
    text-decoration: line-through;
    text-decoration-thickness: 2px;
    text-decoration-color: var(--field-line);
  }

  .card__brand {
    display: flex;
    flex: none;
    flex-direction: column;
    gap: calc(9px * var(--k));
    margin-top: calc(20px * var(--k));
    padding-top: calc(15px * var(--k));
    border-top: 1px dotted var(--field-line);
  }

  .card__label--brand {
    color: var(--faint-2);
  }

  /* Where the brand will appear, as a block of ink. The whole card uncovers it
     when tapped; this is the part a keyboard can reach. */
  .card__cover {
    display: block;
    width: 68%;
    min-width: 170px;
    height: max(32px, calc(46px * var(--k)));
    padding: 0;
    border: 0;
    border-radius: 0;
    background: var(--ink-strong);
    cursor: pointer;
    transition: box-shadow 0.18s ease;
  }

  .card.is-locked:not(.is-revealed) .card__cover {
    box-shadow:
      0 0 0 3px var(--surface),
      0 0 0 5px var(--accent);
  }

  .card__reveal {
    font-size: 11.5px;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--accent);
  }

  .card__name,
  .card__panel {
    display: none;
  }

  .card.is-revealed .card__cover,
  .card.is-revealed .card__reveal {
    display: none;
  }

  .card.is-revealed .card__name,
  .card.is-revealed .card__panel {
    display: block;
  }

  .card__name {
    --size: 46px;

    font-family: var(--display);
    font-size: max(26px, calc(var(--size) * var(--kt)));
    font-weight: 600;
    line-height: 1;
    letter-spacing: -0.026em;
    color: var(--accent);
    overflow-wrap: anywhere;
  }

  .card__name[data-size="2"] {
    --size: 38px;
  }

  /* The row's panel, cloned: its facts on one line under the brand, then the
     notes. The row lays them out in two columns; on a card they are one. */
  .card .entry__panel {
    display: block;
    border: 0;
  }

  /* The canvas draws two facts here, the holder and the year; a row can carry
     five, and they wrap. Spaced apart rather than joined with a dot, so a line
     never starts with one. */
  .card .entry__meta {
    flex-direction: row;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 3px 1.1em;
    padding: 0;
    text-align: left;
    font-size: 10.5px;
    letter-spacing: 0.14em;
    color: var(--muted);
  }

  .card .entry__meta > span:first-child {
    color: var(--muted);
  }

  .card .entry__notes {
    gap: 10px;
    max-width: none;
    padding: calc(12px * var(--k)) 0 0;
    border: 0;
  }

  .card .entry__notes p {
    font-size: 14.5px;
    line-height: 1.5;
    user-select: text;
    -webkit-user-select: text;
  }

  /* ----- the band, at the foot of the card ----- */

  .card__foot {
    display: flex;
    flex: none;
    flex-direction: column;
    gap: calc(8px * var(--k));
    padding-top: calc(16px * var(--k));
  }

  .card__locked {
    font-size: 10.5px;
    letter-spacing: 0.18em;
    text-transform: uppercase;
    color: var(--faint);
  }

  .card.is-revealed .card__locked {
    display: none;
  }

  /* The row's own band, in the card's geometry: the same halves, the same
     three states, taller, and flush with the card's edges. */
  .card .vote {
    gap: calc(8px * var(--k));
    padding: 0;
    border: 0;
  }

  .card .vote__label {
    font-size: 10.5px;
    letter-spacing: 0.18em;
  }

  /*
   * The lemma lies over the card. A row lets it drop into the flow and push
   * the bar down, but here the band is pinned to the card's foot, so in the
   * flow it grew upwards instead: the body above it shrank and began to
   * scroll, and "verwaterd?" and the count stood on its top edge. Placed
   * against the aside it takes no room at all, and stands 11px clear of the
   * bar as it does on a wide screen — the band's own gap is already part of
   * that. The label and the count are under it, so they are hidden rather
   * than left to show at its foot; the cross, Escape and a tap anywhere else
   * close it, and the card only exists with script. Its insides take --k like
   * the rest of the card, which is what keeps its top edge inside the card on
   * a 320 × 568 screen. And it rises into place as the wide one does: it
   * hangs over the bar the way that one does, not under a label.
   */
  .card .vote__aside:has(.vote__why[open]) {
    position: relative;
  }

  .card .vote__why[open] .vote__lemma {
    position: absolute;
    right: 0;
    bottom: calc(11px - 8px * var(--k));
    left: 0;
    gap: calc(9px * var(--k));
    margin: 0;
    padding: calc(14px * var(--k)) 16px;
    animation-name: lemma-rise;
  }

  .card .vote__why[open] > summary,
  .card .vote__aside:has(.vote__why[open]) .vote__count {
    visibility: hidden;
  }

  .card .vote__bar {
    height: 46px;
  }

  .card .vote__half,
  .card .vote__waiting {
    padding: 0 13px;
    font-size: 12px;
  }

  .card.is-revealed .vote:not(.vote--voted) .vote__half--for {
    color: var(--ink-strong);
  }

  /*
   * While a card is swiped, the half it leans towards fills — ink for
   * "oneens", the accent for "eens" — and is full exactly at the reach, where
   * letting go votes. Past half full its word turns to the surface colour so
   * it stays legible on the fill. Under the halves, which are the buttons.
   */
  .card .vote__bar::before,
  .card .vote__bar::after {
    content: "";
    position: absolute;
    top: 0;
    bottom: 0;
    width: 0;
    transition: width 0.18s ease;
  }

  .card .vote__bar::before {
    left: 0;
    background: var(--ink-strong);
  }

  .card .vote__bar::after {
    right: 0;
    background: var(--accent);
  }

  .card.is-dragging .vote__bar::before,
  .card.is-dragging .vote__bar::after {
    transition: none;
  }

  .card.is-revealed.is-leaning-against .vote:not(.vote--voted, .is-pending) .vote__bar::before,
  .card.is-revealed.is-leaning-for .vote:not(.vote--voted, .is-pending) .vote__bar::after {
    width: calc(var(--pull, 0) * 50%);
  }

  .card.is-revealed.is-deep.is-leaning-against .vote:not(.vote--voted, .is-pending) .vote__half--against,
  .card.is-revealed.is-deep.is-leaning-for .vote:not(.vote--voted, .is-pending) .vote__half--for {
    color: var(--surface);
  }

  /* The card says how to answer in its own words, below; the band's own hint
     is for a row. */
  .card .vote:not(.vote--voted) .vote__note:not(.vote__note--braked) {
    display: none;
  }

  /*
   * Answered, a card says what the visitor answered where the question stood,
   * and nothing is added under the bar — a line that arrived there with the
   * answer pushed the bar up, since the band sits on the card's foot. The
   * percentage the row reads there is already in the bar, so on a card it
   * is kept for a screen reader only (the bar is aria-hidden); "wijzig"
   * takes the right end of the hint line; and "omstreden" takes the count's
   * place, because beside the sentence and the count it does not fit a
   * phone.
   */
  .card .vote__answer {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }

  .card .vote__aside .vote__mine {
    display: block;
  }

  .card .vote__note .vote__mine {
    display: none;
  }

  .card .vote__aside:has(.vote__contested) .vote__count {
    display: none;
  }

  /* Centred rather than on the baseline, where its box stood a pixel below
     the label's and made the line taller than the question it replaces. */
  .card .vote__contested {
    order: 1;
    align-self: center;
    margin-left: auto;
  }

  /* Placed against the band's slot and one foot gap below it, which is where
     the hint line starts — not against the foot's bottom, where "volgende
     term" stands once a keyboard focuses it. The band is static for it while
     answered: the only time the note shows here, and a time the lemma the
     band is otherwise a positioning context for is not on the page. */
  .card__vote {
    position: relative;
  }

  .card .vote--voted {
    position: static;
  }

  .card .vote--voted .vote__note:not(.vote__note--braked) {
    position: absolute;
    top: calc(100% + 8px * var(--k));
    right: 0;
    line-height: 16px;
  }

  .card .vote__change {
    font-size: 11.5px;
    letter-spacing: 0.06em;
  }

  /* Covered, the band is a promise rather than a question: dashed, empty,
     inert — a tap on it uncovers the card instead — and "eerst onthullen"
     where the question will be. */
  .card:not(.is-revealed) .vote__aside,
  .card:not(.is-revealed) .vote__note {
    display: none;
  }

  .card:not(.is-revealed) .vote__bar {
    border-style: dashed;
    background: transparent;
  }

  .card:not(.is-revealed) .vote__divider {
    opacity: 0;
  }

  .card:not(.is-revealed) .vote__half {
    color: var(--faint-2);
  }

  /* A term can be answered and covered again — "verberg alles" covers every
     brand and leaves every vote — and the promise says nothing about either:
     no split, no waiting, only the dashed box. */
  .card:not(.is-revealed) .vote__fill,
  .card:not(.is-revealed) .vote__waiting,
  .card:not(.is-revealed) .vote--voted .vote__halves {
    visibility: hidden;
  }

  /* One line box of a fixed height, shared with "wijzig" at its right end so
     the two stand on one baseline. */
  .card__hints {
    min-height: 16px;
    margin: 0;
    line-height: 16px;
    font-size: 11.5px;
    letter-spacing: 0.06em;
    color: var(--faint);
  }

  .card__hint {
    display: none;
  }

  /* The practice card's own spelling of a hint, where it has one. */
  .card--practice .card__say:not(.card__say--practice),
  .card:not(.card--practice) .card__say--practice {
    display: none;
  }

  .card:not(.is-revealed) .card__hint--covered,
  .card.is-revealed .card__hint--vote {
    display: inline;
  }

  .card.is-revealed:has(.vote--voted, .vote.is-pending) .card__hint--vote {
    display: none;
  }

  .card.is-revealed:has(.vote--voted, .vote.is-pending) .card__hint--next {
    display: inline;
  }

  /* Swiped sideways while covered: locked, and said so — in the colour a
     refusal is, until a moment after the finger is gone. */
  .card:not(.is-revealed).is-locked .card__hint--covered {
    display: none;
  }

  .card:not(.is-revealed).is-locked .card__hint--locked {
    display: inline;
    color: var(--danger);
  }

  /* Pushed up past the line: what letting go will do, and that it does not
     vote — on a covered card, and on an uncovered one still a question. An
     answered card only moves on, and keeps saying so. */
  .card:not(.is-revealed).is-lifting .card__hint:not(.card__hint--lifting),
  .card.is-revealed.is-lifting:not(:has(.vote--voted, .vote.is-pending)) .card__hint:not(.card__hint--lifting) {
    display: none;
  }

  .card:not(.is-revealed).is-lifting .card__hint--lifting,
  .card.is-revealed.is-lifting:not(:has(.vote--voted, .vote.is-pending)) .card__hint--lifting {
    display: inline;
    color: var(--accent);
  }

  /* Swiped while it is a question: what letting go will do, but only once it
     would — short of the reach the card springs back, and the hint does not
     promise otherwise. */
  .card.is-revealed.is-past:not(:has(.vote--voted, .vote.is-pending)) .card__hint--vote {
    display: none;
  }

  .card.is-revealed.is-past.is-leaning-for:not(:has(.vote--voted, .vote.is-pending)) .card__hint--for {
    display: inline;
    color: var(--accent);
  }

  .card.is-revealed.is-past.is-leaning-against:not(:has(.vote--voted, .vote.is-pending)) .card__hint--against {
    display: inline;
    color: var(--ink-strong);
  }

  /* For a keyboard and a screen reader, which cannot swipe: out of sight until
     it has the focus. */
  .card__next {
    align-self: flex-start;
    padding: 6px 10px;
    border: 1px solid var(--field-line);
    border-radius: var(--radius);
    background: var(--surface);
    font-size: 12.5px;
    cursor: pointer;
  }

  .card__next:not(:focus-visible) {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }

  /* The card underneath: the same sizes as the card on top, dimmer, and none
     of what only the card on top can be used for. */
  .card--peek .card__label,
  .card--peek .card__term,
  .card--peek .card__brand {
    transition: opacity 0.2s ease;
  }

  .card--peek .card__label {
    opacity: calc(0.35 + 0.65 * var(--rise));
  }

  .card--peek .card__term {
    opacity: calc(0.3 + 0.7 * var(--rise));
  }

  .card--peek .card__brand {
    opacity: calc(0.25 + 0.75 * var(--rise));
  }

  .card--peek .card__reveal,
  .card--peek .card__foot {
    visibility: hidden;
  }

  /* ----- the end of the stack ----- */

  .stack__end {
    display: none;
    flex-direction: column;
    height: 100%;
    padding: 28px 22px 20px;
    border: 1px solid var(--field-line);
    border-top: 2px solid var(--ink-strong);
    border-radius: 4px;
    background: var(--surface);
    animation: stack-end 0.3s ease;
  }

  .stack__deck.is-done .stack__end {
    display: flex;
  }

  .stack__kicker {
    font-size: 10.5px;
    letter-spacing: 0.18em;
    text-transform: uppercase;
    color: var(--faint);
  }

  .stack__end-title {
    margin: 10px 0 0;
    font-family: var(--display);
    font-size: 44px;
    font-weight: 600;
    line-height: 1;
    letter-spacing: -0.026em;
    color: var(--ink-strong);
    text-wrap: balance;
  }

  .stack__end-body {
    margin: 16px 0 0;
    font-size: 15px;
    line-height: 1.6;
    color: var(--muted);
    text-wrap: pretty;
  }

  .stack__end-actions {
    display: flex;
    flex-direction: column;
    gap: 10px;
    margin-top: auto;
  }

  /* A button and a link, drawn as one pair of buttons. */
  .stack__end-list,
  .stack__end-submit {
    display: flex;
    align-items: center;
    justify-content: center;
    height: 50px;
    border: 1px solid var(--field-line);
    border-radius: var(--radius);
    background: var(--surface);
    color: var(--ink-strong);
    font-size: 13.5px;
    letter-spacing: 0.04em;
    text-decoration: none;
    cursor: pointer;
  }

  .stack__end-list {
    border-color: var(--accent);
    background: var(--accent);
    color: var(--on-accent);
    letter-spacing: 0.08em;
  }

  /* ----- the rail ----- */

  .stack__rail {
    display: flex;
    flex: none;
    flex-direction: column;
    gap: 9px;
    width: 100%;
    padding: 0 18px 16px;
    border: 0;
    background: none;
    text-align: left;
    cursor: pointer;
    touch-action: none;
    transition: translate 0.22s var(--sheet-ease);
  }

  .stack__rail.is-pulling {
    transition: none;
  }

  /* Pulled far enough to be a pull, the grip says so. */
  .stack__rail.is-lifted .stack__grip {
    width: 64px;
    background: var(--accent);
  }

  .stack__grip {
    display: block;
    align-self: center;
    width: 44px;
    height: 4px;
    margin: 6px 0;
    border-radius: 3px;
    background: var(--field-line);
    transition: width 0.18s ease, background-color 0.18s ease;
  }

  .stack__track {
    position: relative;
    display: block;
    height: 18px;
  }

  .stack__track::before {
    content: "";
    position: absolute;
    right: 0;
    bottom: 0;
    left: 0;
    height: 4px;
    background: var(--field-line);
  }

  /* How much has been answered, and — its own mark — where the stack is. */
  .stack__voted {
    position: absolute;
    bottom: 0;
    left: 0;
    width: calc(var(--voted, 0) * 100%);
    height: 4px;
    background: var(--accent);
    transition: width 0.25s ease;
  }

  .stack__marker {
    position: absolute;
    bottom: 0;
    left: calc(var(--at, 0) * 100%);
    width: 3px;
    height: 14px;
    background: var(--ink-strong);
    transform: translateX(-50%);
    transition: left 0.28s var(--sheet-ease);
  }

  .stack__line {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
  }

  .stack__status {
    font-size: 11px;
    font-variant-numeric: tabular-nums;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--faint);
  }

  .stack__all {
    flex: none;
    font-size: 12.5px;
    color: var(--accent);
  }

  /* ----- the register, as the sheet the rail pulls up ----- */

  :root.js .stack__scrim {
    display: block;
    position: fixed;
    inset: 0;
    z-index: 60;
    background: var(--scrim);
    opacity: 0;
    visibility: hidden;
    transition:
      opacity var(--sheet-fade) ease,
      visibility 0s linear var(--sheet-fade);
  }

  :root.js .stack__scrim.is-open {
    opacity: 1;
    visibility: visible;
    transition: opacity var(--sheet-fade) ease;
  }

  /*
   * Laid out while it is closed — moved below the edge and hidden, never taken
   * out — so the list can be scrolled to the current term before it rises.
   * Hidden rather than only moved, so neither Tab nor a screen reader can
   * wander into a list that is off the screen.
   */
  :root.js .page--stack .register-main {
    position: fixed;
    top: 96px;
    right: 0;
    bottom: 0;
    left: 0;
    z-index: 61;
    overflow-y: auto;
    overscroll-behavior: contain;
    border-top: 1px solid var(--line);
    border-radius: var(--radius-lg) var(--radius-lg) 0 0;
    background: var(--surface);
    box-shadow: var(--shadow-sheet);
    translate: 0 100%;
    visibility: hidden;
    transition:
      translate var(--sheet-rise) var(--sheet-ease),
      visibility 0s linear var(--sheet-rise);
  }

  :root.js .page--stack .register-main.is-open {
    translate: 0 0;
    visibility: visible;
    transition: translate var(--sheet-rise) var(--sheet-ease);
  }

  /* Holds the focus when a finger opened it, so nothing behind it has it;
     a ring round the whole sheet would say nothing. */
  :root.js .page--stack .register-main:focus {
    outline: none;
  }

  /* The colofon is the last thing in the sheet, and the sheet runs to the
     bottom edge: on a page the page's own padding keeps it off the edge, here
     nothing would. The notes sheet's foot, for the same reason. */
  :root.js .page--stack .register-main .colofon {
    padding-bottom: calc(40px + env(safe-area-inset-bottom, 0px));
  }

  /* Following a finger — up from the rail, or down by its own head — the sheet
     is on screen and goes where it is put, without easing. */
  :root.js .page--stack .register-main.is-dragging,
  :root.js .page--stack .register-main.is-rising {
    visibility: visible;
    transition: none;
  }

  :root.js .stack__scrim.is-rising {
    visibility: visible;
    transition: none;
  }

  /*
   * The head: the grip, the count with "onthul alles" beside it, and the sorts
   * under them — two lines, because on a phone's width one breaks. It stays at
   * the top of the sheet while the rows scroll under it, and it is what the
   * sheet is dragged down by.
   */
  :root.js .page--stack .register-bar {
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-areas:
      "grip grip"
      "count reveal"
      "all all"
      "sorts sorts";
    align-items: baseline;
    padding: 0 var(--gutter) 10px;
    background: var(--surface);
    touch-action: none;
    user-select: none;
    -webkit-user-select: none;
  }

  :root.js .page--stack .register-bar::before {
    content: "";
    grid-area: grip;
    justify-self: center;
    width: 54px;
    height: 5px;
    margin: 13px 0 14px;
    border-radius: 3px;
    background: var(--field-line);
  }

  :root.js .page--stack .register-bar > .search,
  :root.js .page--stack .sortbar {
    display: contents;
  }

  :root.js .page--stack .register-bar__mark,
  :root.js .page--stack .register-bar .bar-menu {
    display: none;
  }

  :root.js .page--stack .reveal {
    grid-area: reveal;
  }

  :root.js .page--stack .sortbar__count {
    grid-area: count;
    font-family: var(--display);
    font-size: 22px;
    font-weight: 600;
    letter-spacing: -0.018em;
    color: var(--ink-strong);
    white-space: nowrap;
  }

  :root.js .page--stack .sortbar__all {
    grid-area: all;
    margin-top: 4px;
    font-size: 12.5px;
  }

  :root.js .page--stack .sortbar__sorts {
    grid-area: sorts;
    margin-top: 8px;
  }

  :root.js .page--stack .sortbar__label {
    font-size: 10.5px;
    letter-spacing: 0.16em;
    color: var(--faint-2);
  }

  :root.js .page--stack .sortbar__sort {
    font-size: 12.5px;
  }

  /*
   * Every row the same: the term on the left, and one cell on the right holding
   * either a block of ink or the brand. Nothing else — the category, the hint
   * and the marker are the register's, not the list's. A row is somewhere to
   * go: tapping it moves the stack there.
   */
  :root.js .page--stack .entry__summary,
  :root.js .page--stack .entry[open] .entry__summary,
  :root.js .page--stack .entry.is-revealed .entry__summary {
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-areas: "term cell";
    align-items: center;
    gap: 10px;
    min-height: 58px;
    padding: 0 var(--gutter);
    border-bottom: 1px solid var(--line-soft);
    background: none;
  }

  :root.js .page--stack .entry__side--term,
  :root.js .page--stack .entry[open] .entry__side--term,
  :root.js .page--stack .entry.is-revealed .entry__side--term {
    grid-area: term;
    flex-direction: row;
    align-items: center;
    min-width: 0;
  }

  :root.js .page--stack .entry__word,
  :root.js .page--stack .entry[open] .entry__word,
  :root.js .page--stack .entry.is-revealed .entry__word {
    overflow: hidden;
    font-family: var(--display);
    font-size: 23px;
    font-weight: 600;
    letter-spacing: -0.015em;
    text-decoration: none;
    text-overflow: ellipsis;
    text-transform: none;
    white-space: nowrap;
    color: var(--ink-strong);
  }

  :root.js .page--stack .entry__category,
  :root.js .page--stack .entry__marker {
    display: none;
  }

  :root.js .page--stack .entry__reveal {
    grid-area: cell;
    display: flex;
    align-items: center;
  }

  /* Covered, the cell is a small block of ink rather than a cover the size of
     the word — the list says only whether, not how long. */
  :root.js .page--stack .entry__brand {
    display: block;
    width: 40px;
    height: 11px;
    margin: 0;
    padding: 0;
    overflow: hidden;
    font-size: 19px;
    line-height: 1.1;
  }

  :root.js .page--stack .entry[open] .entry__brand,
  :root.js .page--stack .entry.is-revealed .entry__brand {
    width: auto;
    height: auto;
    font-size: 19px;
  }

  /* Where the stack is standing: the ground and the rule are enough. The
     canvas wrote "hier" beside them as well, and it was taken out again. */
  :root.js .page--stack .entry.is-here .entry__summary {
    background: var(--row-hover);
    box-shadow: inset 2px 0 0 0 var(--accent);
  }
}

@keyframes stack-end {
  from {
    translate: 0 14px;
    opacity: 0;
  }

  to {
    translate: 0 0;
    opacity: 1;
  }
}

@media (prefers-reduced-motion: reduce) {
  .stack__peek,
  .stack__back,
  .stack__voted,
  .stack__marker,
  .stack__grip,
  :root.js .stack__scrim,
  :root.js .page--stack .register-main {
    transition: none;
  }

  .stack__end {
    animation: none;
  }

  .card,
  .card .vote__bar::before,
  .card .vote__bar::after,
  .card--peek .card__label,
  .card--peek .card__term,
  .card--peek .card__brand,
  .stack__rail {
    transition: none;
  }
}

/* "toon de uitlegkaart" is a button among links in the menu, and looks like
   one of them. */
button.menu__item {
  width: 100%;
  padding-right: 0;
  padding-left: 0;
  border-right: 0;
  border-bottom: 0;
  border-left: 0;
  background: none;
  font: inherit;
  text-align: left;
  cursor: pointer;
}

/* ---------- the menu (narrow) ---------- */

/*
 * Below 820px the header's row of controls goes into a sheet behind a burger:
 * one big line for "woord aandragen", two small ones, and the language and the
 * theme in a foot of their own. Design canvas "menu in smalle weergave", 6B.
 *
 * A <details> — see _Menu.cshtml for why. Everything that makes it a sheet is
 * here, and all of it holds without script: the scrim is the summary's
 * ::before spread over the screen, so a tap on it is a tap on the summary,
 * and the page behind is held still by a :has() on [open]. site.js only adds
 * the ways out a <details> does not have and the leaving movement.
 */
.menu {
  display: none;
}

.menu__burger {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 46px;
  height: 46px;
  list-style: none;
  background: var(--surface);
  border: 1px solid var(--field-line);
  border-radius: var(--radius);
  color: var(--ink);
  cursor: pointer;
  transition:
    background-color 0.16s ease,
    border-color 0.16s ease,
    color 0.16s ease;
}

.menu__burger::-webkit-details-marker {
  display: none;
}

.menu__burger-shut,
.menu[open] > .menu__burger .menu__burger-open {
  display: none;
}

.menu[open] > .menu__burger .menu__burger-shut {
  display: block;
}

.menu[open] > .menu__burger {
  background: var(--ink-strong);
  border-color: var(--ink-strong);
  color: var(--ground);
}

.menu[open] > .menu__burger::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 75;
  background: var(--scrim);
  cursor: default;
  transition: opacity var(--sheet-fade) ease;
  animation: sheet-fade 0.16s ease;
}

.menu__sheet {
  position: fixed;
  right: 0;
  bottom: 0;
  left: 0;
  z-index: 80;
  display: flex;
  flex-direction: column;
  padding: 0 var(--gutter);
  background: var(--surface);
  border-top: 1px solid var(--line);
  border-radius: var(--radius-lg) var(--radius-lg) 0 0;
  box-shadow: var(--shadow-sheet);
  touch-action: none;
  transition: translate var(--sheet-rise) var(--sheet-ease);
  animation: sheet-rise var(--sheet-rise) var(--sheet-ease);
}

.menu__sheet.is-dragging {
  transition: none;
}

.menu.is-closing > .menu__burger::before {
  opacity: 0;
  animation: none;
}

.menu.is-closing .menu__sheet {
  translate: 0 100%;
  animation: none;
}

.menu__grip {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 22px;
  cursor: grab;
}

.menu__actions {
  display: flex;
  flex-direction: column;
}

/* The icons stand in a gutter at the start of the line, the text beside them:
   the signs read down one column and the words start at the reading edge. */
.menu__item {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 11px 0;
  border-top: 1px solid var(--line-soft);
  color: var(--ink-strong);
  text-decoration: none;
}

/* No rule over the first line, whichever it is: it stands under the grip, not
   under another line. Only the register's menu starts with the big one. */
.menu__item:first-child {
  border-top: 0;
}

.menu__item--big {
  padding: 12px 0 18px;
}

.menu__icon {
  display: flex;
  flex: 0 0 auto;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  color: var(--muted);
}

.menu__item--big .menu__icon {
  color: var(--accent);
}

.menu__text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.menu__label {
  font-size: 15.5px;
  line-height: 1.3;
  color: var(--ink-strong);
}

.menu__item--big .menu__label {
  font-family: var(--display);
  font-size: 30px;
  font-weight: 600;
  line-height: 1.05;
  letter-spacing: -0.02em;
}

.menu__item:hover .menu__label {
  color: var(--accent);
}

.menu__hint {
  font-size: 12px;
  line-height: 1.4;
  color: var(--muted);
}

.menu__item--big .menu__hint {
  font-size: 12.5px;
}

/*
 * The settings sink into a foot: the page's own ground, a full rule over it,
 * and running out to both edges and down past the bottom one. What separates
 * doing from setting is the material, not only a line — a hairline between
 * "privacy" and "taal" read as one more line between buttons.
 */
.menu__foot {
  display: flex;
  flex-direction: column;
  margin: 8px calc(var(--gutter) * -1) 0;
  padding: 2px var(--gutter) calc(10px + env(safe-area-inset-bottom, 0px));
  background: var(--ground);
  border-top: 1px solid var(--line);
}

.menu__pref {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 11px 0;
}

.menu__pref + .menu__pref {
  border-top: 1px solid var(--line);
}

.menu__pref-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.menu__cap {
  font-size: 10.5px;
  line-height: 1.3;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--muted);
}

.menu__value {
  font-size: 13.5px;
  line-height: 1.3;
  color: var(--ink-strong);
}

/* Every mode's word is in the markup and the one data-theme-mode names is
   shown. In browser mode the word says what the browser is asking for too,
   and that half is prefers-color-scheme's. */
.menu__theme-value [data-mode] {
  display: none;
}

:root[data-theme-mode="light"] .menu__theme-value [data-mode="light"],
:root[data-theme-mode="dark"] .menu__theme-value [data-mode="dark"],
:root:not([data-theme-mode="light"]):not([data-theme-mode="dark"]) .menu__theme-value [data-mode="system-light"] {
  display: inline;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme-mode="light"]):not([data-theme-mode="dark"]) .menu__theme-value [data-mode="system-light"] {
    display: none;
  }

  :root:not([data-theme-mode="light"]):not([data-theme-mode="dark"]) .menu__theme-value [data-mode="system-dark"] {
    display: inline;
  }
}

/* The search row's own "onthul alles", only ever shown on a narrow screen. */
.reveal {
  display: none;
}

/*
 * 820px again, the breakpoint the register, the bottom sheet and site.js all
 * share. The header keeps its brand on the left and the burger on the right,
 * and everything else in it goes into the menu.
 */
@media (max-width: 820px) {
  .site-header--menu {
    flex-direction: row;
    flex-wrap: nowrap;
    align-items: flex-start;
    gap: 14px;
    padding: 22px var(--gutter) 16px;
  }

  .site-header--menu .site-header__brand {
    flex: 1 1 auto;
    min-width: 0;
  }

  .site-header--menu .site-header__aside {
    display: none;
  }

  html:has(.site-header--stays) {
    scroll-padding-top: 120px;
  }

  .site-header--menu .site-title {
    font-size: 34px;
  }

  .site-header--menu .site-tagline {
    font-size: 12.5px;
  }

  /* No columns to explain on a phone: the narrow tagline says what the words
     are and carries the way to /about, and the wide one and its link go. */
  .site-header__brand > .site-tagline--wide {
    display: none;
  }

  .site-header__brand > .site-tagline--narrow {
    display: block;
  }

  .menu {
    display: block;
    flex: 0 0 auto;
  }

  html:has(.menu[open]) {
    overflow: hidden;
  }

  /* "onthul alles" beside the count, at the head of the register where the
     stack pulls it up as a sheet: a word in the accent, as the canvas draws
     it, and no longer a filled button beside a search field that is gone. */
  .reveal {
    display: inline;
    padding: 0;
    border: 0;
    background: none;
    color: var(--accent);
    font-size: 12.5px;
    white-space: nowrap;
    cursor: pointer;
  }

  .reveal[hidden] {
    display: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  .menu__sheet,
  .menu[open] > .menu__burger::before {
    animation: none;
    transition: none;
  }

  /* The wordmark and the admin band's burger still arrive and go, they just
     stop sliding. The shadow was never animated. */
  .register-bar__mark,
  .admin-bar .bar-menu {
    transition: none;
  }
}

/* ---------- admin ---------- */

/*
 * /admin carries seven columns where the register carries two, so the design
 * gives it a slightly wider canvas. The layout puts the modifier on .page.
 */
.page--wide {
  max-width: 1180px;
}

.admin-title {
  margin: 0;
  font-family: var(--display);
  font-size: clamp(32px, 5vw, 44px);
  font-weight: 600;
  line-height: 1;
  letter-spacing: -0.022em;
  color: var(--ink-strong);
}

/* Arrow and text on one centre line; as plain inline content the arrow would
   sit on the baseline. The underline is always there so nothing shifts when it
   colours in on hover. */
.back-link {
  display: inline-flex;
  align-self: flex-start;
  align-items: center;
  gap: 7px;
  padding-bottom: 1px;
  border-bottom: 1px solid transparent;
  font-size: 14px;
  color: var(--muted);
  text-decoration: none;
}

.back-link:hover {
  color: var(--ink-strong);
  border-bottom-color: var(--field-line);
}

/* The count in each /admin header — entries, today's votes, rows waiting. */
.count {
  font-size: 13px;
  color: var(--faint);
  white-space: nowrap;
}

/*
 * The three admin pages, as one segmented control: a band a shade darker than
 * the ground with the page you are on filled in. Links, so they work with no
 * script — the design draws buttons because a canvas has no pages to go to.
 *
 * width: fit-content rather than the design's inline-flex. The two shrink to
 * the same size, but an inline-level box sits on a text line and drags a
 * descender's worth of space under it, which would put the margin below out by
 * a few pixels for no reason anyone could see in the markup.
 *
 * 4px around 3px is one radius, not two: the strip's padding is 1px wider than
 * its border, so the pill inside it stays concentric.
 */
/*
 * The bar that stays on /admin: the tab strip, and on a narrow screen the
 * burger that arrives once the header has gone. Same arrangement as the
 * register's .register-bar, and the same z-index for the same reasons — the
 * argument is written out there.
 *
 * The strip's own margin lives here. Left on .admin-tabs it would collapse
 * through a padding-less wrapper and stand the band eight pixels short of
 * where .row--head expects it.
 */
.admin-bar {
  position: sticky;
  top: 0;
  z-index: 8;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 0 16px;
  background: var(--ground);
  box-shadow: var(--ground-reach);
}

/*
 * /admin's burger sits in the header, where the canvas draws it, and only
 * below 820px — the display rules it follows are .menu's own. Wide, the header
 * keeps its row of controls and there is nothing to fold away; narrow, the row
 * goes and the sheet carries the same things. Once the header scrolls away,
 * .bar-menu in the band takes over.
 */
.menu--admin {
  align-self: flex-start;
}

.admin-tabs {
  display: flex;
  flex: 0 1 auto;
  align-items: center;
  gap: 4px;
  width: fit-content;
  min-width: 0;
  max-width: 100%;
  padding: 4px;
  overflow-x: auto;
  border: 1px solid var(--line);
  border-radius: 4px;
  background: var(--surface-2);
}

/* It never grows — a strip stretched over 1180px would undo the band above,
   which is a ground shrunk to what it holds. It does shrink under its own
   content when the burger takes width beside it, and then overflow-x carries
   the tabs sideways rather than pushing the burger off the edge. That is the
   whole of what a fourth tab would need. */

.admin-tabs__tab {
  display: inline-flex;
  flex: 0 0 auto;
  align-items: center;
  gap: 8px;
  height: 34px;
  padding: 0 14px;
  border-radius: var(--radius);
  font-size: 14px;
  letter-spacing: 0.01em;
  white-space: nowrap;
  color: var(--muted);
  text-decoration: none;
}

.admin-tabs__tab:hover {
  color: var(--ink-strong);
}

/* Filled, not coloured. The tab used to take the accent because it stood on
   the page's own ground with nothing but colour to say which one it was; on a
   band of its own the fill says it, and the accent goes back to meaning a
   link. */
.admin-tabs__tab[aria-current="page"],
.admin-tabs__tab[aria-current="page"]:hover {
  background: var(--ink-strong);
  color: var(--on-accent);
}

.admin-tabs__badge {
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  color: var(--muted);
}

/* Quieter than the label without being a second colour: the same ink, thinned.
   Mixed from --on-accent rather than given a value per palette, so it follows
   both from the one token it is made of. */
.admin-tabs__tab[aria-current="page"] .admin-tabs__badge {
  color: color-mix(in srgb, var(--on-accent) 66%, transparent);
}

/*
 * The rule under the header and the strip are two edges a few pixels apart,
 * and only one of them is doing anything: the strip has a ground of its own
 * and separates the header from the page by being there. So on the pages that
 * carry it, the header stops at the words.
 *
 * Written as :has rather than a class on the three headers because that is the
 * actual condition — the rule goes exactly where the strip replaces it, and a
 * fourth admin page cannot forget to say so. A general sibling and not the
 * next one, because .admin-float stands between the two.
 */
.site-header:has(~ main .admin-tabs) {
  padding-bottom: 14px;
  border-bottom: 0;
}

/*
 * The header's own row — the count, "nieuw woord", the theme — stays when the
 * header goes, and comes to rest at the right end of the band, level with the
 * tab strip (design canvas "Watermerken", admFloatStyle). It cannot be in the
 * header: nothing sticky outlives the box it is in, and the header scrolls
 * away. So it stands straight after it, a layer with no height, and puts the
 * row back where the header had it.
 *
 * Anchored to the header's foot and not its head, which is what keeps a header
 * height from being written down anywhere — the thing the bars were built to
 * avoid (see .register-bar). The canvas measures one in script; this needs
 * none, and holds with the script off. The row stands on the header's bottom
 * padding, 14px above this layer, and the layer sticks at 63px: that puts the
 * row's 38px between 11 and 49, centred on the strip, whose middle is the
 * band's 8px padding and half of its 44px. Change the padding, the strip or
 * the buttons and this number moves with them. min-height holds the 38px when
 * the row has no button: without script /admin/votes and /admin/submissions
 * carry only their count, the theme toggle being js-only.
 *
 * Above the band, which is sticky too and later in the document. Below 10 for
 * .dialog's reason: "nieuw woord" opens one, and a no-script dialog is not
 * modal and has to cover this where the two meet.
 *
 * Wide only. Below 820px the row is folded into the menu, and the burger keeps
 * the arrangement it has always had — see .bar-menu.
 */
.admin-float {
  position: sticky;
  top: 63px;
  z-index: 9;
  height: 0;
}

.admin-float > .site-header__aside {
  position: absolute;
  right: 0;
  bottom: 14px;
  flex-wrap: nowrap;
  min-height: 38px;
}

/* The heading row carried the space under the header on its own. With the band
   above it that space is already there, and stacking the two opens a gap.
   The row is a nephew of the band rather than its sibling, because it sits
   inside the element a sort swaps the table into — the form the bulk bar posts
   on /admin/submissions, a plain div on /admin. Same gap, one selector
   further. The band is what this names, not the strip inside it: wrap either
   of the two in anything else and the gap comes back. */
.admin-bar + div > .row--head,
.admin-bar + form > .row--head {
  margin-top: 0;
}

/* ---------- the admin list ---------- */

/*
 * No card around the rows: they sit straight on the ground the way the
 * register's do, and the column headings are ruled off with the one heavy
 * line on the page.
 */
.row {
  display: grid;
  gap: 14px;
  align-items: center;
  padding: 11px 0;
  border-bottom: 1px solid var(--line);
  transition: background 0.18s ease;
}

/*
 * The register's own row: one trademark. Every flexible column has a floor —
 * minmax(0, …) let a long name squeeze the four beside it down to their
 * ellipses; with a minimum the row runs out of give in one place instead of
 * four, and what does not fit is reachable from the title on each cell.
 */
.row--mark {
  /* Holds the band every row is lit on, below. */
  position: relative;
  grid-template-columns:
    minmax(110px, 1.1fr) minmax(64px, 1fr) minmax(64px, 1fr)
    minmax(120px, 1.9fr) 108px 56px;
}

.row:hover {
  background: var(--row-hover);
}

/* The row the dialog is open on. */
.row--active {
  background: var(--surface-2);
}

/*
 * Pinned directly under the band, because sorting by a column you can no
 * longer see is the thing this phase is for. --admin-bar is exact rather than
 * a floor: a gap or an overlap would both show, and unlike a header a tab
 * strip cannot grow — one line of pills at a fixed height inside a fixed
 * padding, with overflow-x: auto making sure it never wraps. Wide and narrow
 * both come to 68px, from different numbers.
 *
 * Written .row.row--head so it outranks .row--mark's position: relative on
 * weight rather than on source order. That relative is only there to hold the
 * band the rows are lit on, which :not(.row--head) already keeps off this one.
 */
.row.row--head {
  position: sticky;
  top: var(--admin-bar);
  z-index: 7;
  margin-top: 26px;
  border-top: 1.5px solid var(--ink-strong);
  border-bottom: 0;
  background: var(--ground);
  box-shadow: var(--ground-reach);
}

/* Its own ground, not none: transparent, the rows would show through it as
   they pass under. */
.row--head:hover {
  background: var(--ground);
}

/*
 * Every ground a mark can be on — under the pointer, or the one the dialog is
 * open on — is one layer behind it, reaching 12px past the columns at both
 * ends, the way the queue's rows are lit.
 *
 * Wider than the columns because the name starts at the first one and the
 * pencil sits at the end of the grid: a band that stopped at the columns would
 * run straight through them and leave both against an edge. Behind rather than
 * as a background because the row's own box is where the rule under it is, and
 * widening that would rule the list off wider than the heading over it. Nothing
 * on this page paints between the two, so a negative z-index puts the band under
 * the content without burying it under the page.
 *
 * The layer replaces .row's own background, so the two rules the row inherits
 * are turned off here — both are outranked from this point on source order
 * alone.
 */
.row--mark:not(.row--head)::before {
  content: "";
  position: absolute;
  inset: 0 -12px;
  z-index: -1;
  border-radius: var(--radius);
  transition: background 0.18s ease;
}

.row--mark:hover,
.row--mark.row--active {
  background: transparent;
}

.row--mark:not(.row--head):hover::before {
  background: var(--row-hover);
}

/* The mark the dialog is open on. Last, and carrying a :not() it does not
   need, so it outranks the hover above rather than tying with it: without
   script the dialog is not modal — the server renders it with [open] — and the
   list behind it can still be hovered, which would otherwise wipe this shade
   off the one mark the dialog is about. */
.row--mark.row--active:not(.row--head)::before {
  background: var(--surface-2);
}

/* With script a click anywhere on a mark's row opens it. Without, only the
   chips and the pencil do, and a pointer would promise the rest. */
:root.js .row--mark:not(.row--head) {
  cursor: pointer;
}

/* The column headings are the sort control; the arrow marks the active one. */
.row__sort {
  display: inline-flex;
  align-items: baseline;
  justify-self: start;
  /* A heading is as narrow as its column now that the column has a floor
     rather than a zero: it ellipsises like the cells under it, and carries the
     same title. */
  min-width: 0;
  max-width: 100%;
  padding-bottom: 2px;
  border-bottom: 1px solid transparent;
  font-size: 11px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  white-space: nowrap;
  color: var(--faint);
  text-decoration: none;
}

.row__sort:hover {
  color: var(--ink-strong);
}

.row__sort[aria-current="true"] {
  color: var(--ink-strong);
  border-bottom-color: var(--accent);
}

.row__sort-label {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Always in the flow, so the label does not shift sideways when the arrow
   appears — the same trick the register's sort bar uses. flex: 0 0 auto keeps
   it out of the ellipsis: it is the label that gives way, never the mark
   saying which way the column runs. */
.row__sort-arrow {
  flex: 0 0 auto;
  margin-left: 4px;
  font-size: 11px;
  color: var(--accent);
}

.row__term {
  font-family: var(--display);
  font-size: 21px;
  font-weight: 600;
  letter-spacing: -0.015em;
  color: var(--ink-strong);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The brand is what the register keeps covered. Here — the one page where it
   is never hidden — it carries the accent. */
.row__brand {
  font-size: 14.5px;
  color: var(--accent);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/*
 * Owner, category and year are three grid columns on a wide screen and one
 * wrapping line on a narrow one. display: contents lets the wrapper step out
 * of the way so the three spans land in their own columns; the narrow block
 * below turns it back into a real box.
 */
.row__facts {
  display: contents;
}

.row__owner {
  font-size: 14px;
  color: var(--muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.row__category {
  font-size: 13px;
  color: var(--faint);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}


.row__added {
  font-size: 13px;
  color: var(--faint-2);
  white-space: nowrap;
}

/* An empty cell reads as an em dash, dimmer than a filled one. */
.row__none {
  color: var(--faint-2);
}

/* The full stamp spells out what a bare date would only mean under a column
   heading, so it belongs to the narrow row, which has none. */
.row__stamp {
  display: none;
}

.row__actions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 6px;
}

.icon-btn {
  display: flex;
  flex: 0 0 auto;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: transparent;
  color: var(--faint-2);
  text-decoration: none;
  cursor: pointer;
}

.icon-btn:hover {
  color: var(--ink-strong);
  border-color: var(--field-line);
}

.icon-btn--danger:hover {
  color: var(--danger);
  border-color: var(--danger);
}

.admin-empty {
  padding: 80px 0;
  text-align: center;
  font-size: 15px;
  color: var(--faint);
}

/* ---------- a trademark and its words ---------- */

/* The name and the holder, as .row__facts is the credit: out of the way on a
   wide screen, a line of its own on a narrow one. */
.row__lead {
  display: contents;
}

/* The mark as registered, in the accent the brand carries on this page. */
.row__mark {
  min-width: 0;
  font-family: var(--display);
  font-size: 21px;
  font-weight: 600;
  letter-spacing: -0.015em;
  color: var(--accent);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.row__credit {
  font-size: 13.5px;
  color: var(--muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Only on a narrow row, where "aangedragen door" is not written at the top of
   a column any more. */
.row__credit-long {
  display: none;
}

.row__words {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  min-width: 0;
}

.row__words-head {
  font-size: 11px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--faint);
  white-space: nowrap;
}

/*
 * A word on its mark's row, and the way into it: the dialog, on that word's
 * register. Its code is small capitals rather than a .lang-pill — a filled box
 * inside a box — and comes first, so the ellipsis on a long term never takes it.
 *
 * The chip's ground is one rung up the row's own ladder, so it does not sink
 * into the band when the pointer lights the row, and it turns to the accent
 * under the pointer because it is the part of the row that answers a click.
 */
.word-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  max-width: 100%;
  height: 24px;
  padding: 0 8px;
  border: 1px solid var(--line);
  border-radius: 2px;
  background: var(--row-hover);
  font-size: 13px;
  color: var(--ink);
  text-decoration: none;
  transition: background 0.18s ease, border-color 0.18s ease, color 0.18s ease;
}

.row--mark:hover .word-chip {
  background: var(--row-chosen);
}

.word-chip:hover {
  border-color: var(--accent);
  color: var(--accent);
}

.word-chip__code {
  flex: 0 0 auto;
  font-size: 9.5px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--faint);
}

.word-chip__term {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/*
 * A language, labelled: the tag on a term in the queue, on a contested term and
 * in the review dialog's title, where it says which register that row is in.
 * First in the cell, so the ellipsis on a long term never takes it, and
 * followed by a real space rather than only a margin, so a screen reader says
 * "en hot tub" and not "enhot".
 */
.lang-pill {
  display: inline-flex;
  align-items: center;
  height: 20px;
  padding: 0 7px;
  border: 1px solid var(--ink);
  border-radius: 2px;
  background: var(--ink);
  /* Named because it also sits inside a term and a dialog title, both of which
     are set in the display face at 600. */
  font-family: var(--body);
  font-size: 10px;
  font-weight: 400;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  white-space: nowrap;
  vertical-align: 0.25em;
  color: var(--surface);
}

/* ---------- the admin list on a narrow screen ---------- */

/*
 * The row stops being a table and stacks: term and brand above each other,
 * the three facts on one wrapping line, and the stamp last. The actions stay
 * top right.
 *
 * 820px is the same breakpoint the register and the bottom sheet in site.js
 * use. Keep all three in step.
 */
@media (max-width: 820px) {
  /* The register runs edge to edge here; the admin list keeps its gutter, so
     the page carries it and the header stops repeating it. */
  .page--wide {
    padding: 0 var(--gutter) 100px;
  }

  /* The same trade a public header makes: the row of controls goes into the
     sheet, and what is left — the title, the way back, the burger — stands on
     one line rather than in the column the generic rule stacks it into. */
  .page--wide .site-header {
    flex-direction: row;
    flex-wrap: nowrap;
    align-items: flex-start;
    gap: 14px;
    padding: 28px 0 22px;
  }

  .page--wide .site-header__aside {
    display: none;
  }

  /* Same trade as the wide screen, a little tighter, and the taller pill a
     thumb needs. */
  .site-header:has(~ main .admin-tabs) {
    padding-bottom: 10px;
  }

  .admin-float {
    display: none;
  }

  .admin-bar {
    padding: 6px 0 14px;
  }

  .admin-tabs__tab {
    height: 38px;
    font-size: 14.5px;
  }

  /* The counts and the strip's badges are two ways of saying the same number,
     and on a phone only one of them fits. The tab keeps its word. */
  .admin-tabs__badge {
    display: none;
  }

  .row {
    row-gap: 3px;
    column-gap: 14px;
    padding: 16px 0;
  }

  /* The queue's row: term and brand above each other, the three facts on one
     wrapping line, the stamp last. */
  .row--queue {
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-areas:
      "term    actions"
      "brand   actions"
      "facts   facts"
      "stamp   stamp";
  }

  /* The register's row the same way: the mark with its holder after it, then
     the credit when there is one, then its words. */
  .row--mark {
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-areas:
      "lead    actions"
      "facts   facts"
      "words   words"
      "stamp   stamp";
  }

  /* The holder beside the name on their shared baseline, and under it only
     when the two do not fit. */
  .row__lead {
    grid-area: lead;
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 2px 12px;
    min-width: 0;
  }

  .row__lead > .row__owner {
    font-size: 14px;
    color: var(--muted);
    overflow: visible;
    white-space: normal;
  }

  .row__mark {
    font-size: 28px;
    line-height: 1.1;
    letter-spacing: -0.018em;
    white-space: normal;
  }

  .row__words {
    grid-area: words;
    margin-top: 8px;
  }

  /* Something a thumb aims at now, not only something to read. */
  .word-chip {
    height: 32px;
    padding: 0 11px;
    font-size: 14px;
  }

  .word-chip__code {
    font-size: 10px;
  }

  /* An em-dash under no column heading says nothing, so an empty cell simply
     goes; and the credit takes the long form, which names itself. */
  .row--mark .row__none,
  .row__credit {
    display: none;
  }

  /* The credit is all that is left in it, so without one the line goes
     rather than standing empty between the name and the words. */
  .row--mark .row__facts:not(:has(.row__credit-long)) {
    display: none;
  }

  .row__credit-long {
    display: inline;
  }

  .row--head .row__words-head {
    display: none;
  }

  .row__term {
    grid-area: term;
    font-size: 28px;
    line-height: 1.1;
    letter-spacing: -0.018em;
    white-space: normal;
  }

  .row__brand {
    grid-area: brand;
    font-size: 15px;
  }

  /* Back to a real box, so the three facts share one line instead of one
     column each. The 5px lifts the gap from the 3px above to the 8px the
     design puts between the name and the facts. */
  .row__facts {
    grid-area: facts;
    display: flex;
    flex-wrap: wrap;
    gap: 4px 14px;
    margin-top: 5px;
  }

  .row__facts > span {
    font-size: 13px;
    color: var(--faint);
    overflow: visible;
    white-space: normal;
  }

  /* The long forms take over; there are no column headings to lean on. */
  .row__added {
    display: none;
  }

  .row__stamp {
    display: block;
    grid-area: stamp;
    margin-top: 5px;
    font-size: 11px;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--faint-2);
  }

  .row__actions {
    grid-area: actions;
    align-self: start;
  }

  .row__actions .icon-btn {
    width: 42px;
    height: 42px;
  }

  .row__actions .icon-btn svg {
    width: 18px;
    height: 18px;
  }

  /* Stacked rows label themselves, so the heading row drops the grid and keeps
     only its links — a compact sort strip, and the one way to sort here. */
  .row--head {
    display: flex;
    align-items: center;
    gap: 16px;
    overflow-x: auto;
    padding: 14px 0;
    border-bottom: 1px solid var(--line);
  }

  .row--head > span:last-child {
    display: none;
  }
}

/* ---------- dialog ---------- */

/*
 * While the dialog is open the page behind it holds still; only the dialog's
 * own middle scrolls. Keyed off [open], which the server sets on the no-script
 * path and showModal() sets on the scripted one, so one rule covers both and
 * none of it needs script.
 *
 * The gutter is held open for every page further up, so the page behind does
 * not shift sideways by a scrollbar width as the dialog arrives.
 */
html:has(.dialog[open]) {
  overflow: hidden;
}

.dialog {
  width: min(660px, calc(100vw - 32px));
  max-height: 88vh;
  padding: 0;
  /*
   * clip rather than hidden. Both cut the corners off the head and the foot,
   * but `hidden` leaves the box a scroll container — one nothing can reach by
   * hand, and one the browser will still scroll itself to bring a focused
   * descendant into view. That is what let a click on a chip drag the head out
   * of the box and leave a band of empty surface under the foot. `clip` makes
   * no scroll container at all, so there is nothing to scroll; the middle,
   * which is meant to scroll, has its own overflow.
   */
  overflow: clip;
  background: var(--surface);
  color: var(--ink);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow);
}

/* The delete question stands on its own and needs far less room. */
/*
 * The insides are swapped in one piece, so admin.js needs one element to put
 * them in — and that element must not become a box. As a box it is the dialog's
 * only flex child, and the head, the body and the foot stop being flex items:
 * the body no longer shrinks to fit and everything under it is clipped away by
 * the overflow above. Same reason [data-results] is display: contents on the
 * register.
 */
.dialog > [data-dialog-content] {
  display: contents;
}

.dialog--confirm {
  width: min(460px, calc(100vw - 32px));
}

/*
 * The submission panel sits on --ground rather than --surface, so the drawer
 * holding it does too. Otherwise the only part of the dialog the panel does not
 * cover — the strip the grip is drawn in — stays --surface, and the grabber
 * arrives on a lighter band than the sheet's own, which is one colour all the
 * way down.
 */
.dialog--drawer {
  background: var(--ground);
}

/*
 * On a phone the drawer stops short of the top, so the card it was opened from
 * is still standing behind it and it is clear the stack is where this goes back
 * to. Design canvas "Aandragen smal 5d": three short steps exist so that this is
 * possible at all. Only the drawer — /admin's dialogs open over a list that has
 * nothing to show above them, and they keep the 94vh the narrow block gives.
 *
 * dvh rather than vh, because Safari's floating toolbar is exactly the thing
 * that would otherwise eat the gap this is reserving.
 */
@media (max-width: 820px) {
  .dialog--drawer {
    max-height: calc(100dvh - 64px);
  }
}

.dialog::backdrop {
  background: var(--scrim);
}

/*
 * Without JavaScript the dialog renders inline with [open]; centre it by hand
 * so the no-script path looks the same as showModal().
 *
 * `display` is the only one of these that hangs on [open], and it has to: the
 * browser's own stylesheet hides a closed <dialog> with display: none, and an
 * author rule on .dialog would outrank it and leave the thing on screen.
 *
 * Everything that *places* the box sits on .dialog instead, because [open] is
 * gone the instant it closes — and a box that loses its position, its offsets
 * and its stacking order in that instant is somewhere else entirely by the time
 * it starts to leave, however long the transition below holds it on screen.
 * None of it applies until it opens anyway: a closed dialog is display: none.
 */
.dialog {
  flex-direction: column;
  position: fixed;
  top: 12vh;
  left: 50%;
  transform: translateX(-50%);
  margin: 0;
  z-index: 10;
}

.dialog[open] {
  display: flex;
}

/*
 * A dialog arrives and leaves the way the register's sheet does. It cannot use
 * the sheet's own animation: that one runs on an element being put into the
 * document and taken out of it, while a <dialog> is toggled in the top layer
 * and close() would otherwise take it off screen in the same frame. So the
 * mechanism is the one this element actually has — @starting-style for the way
 * in, and `overlay` and `display` transitioned as discrete properties to hold
 * it on screen long enough to leave — and what is shared with the sheet is the
 * distance and the timing above.
 *
 * The movement is written as `translate` and not as `transform`, because the
 * transform is already spent on centring the thing.
 */
.dialog {
  opacity: 0;
  translate: 0 12px;
  transition:
    opacity var(--sheet-fade) ease,
    translate var(--sheet-rise) var(--sheet-ease),
    overlay var(--sheet-rise) allow-discrete,
    display var(--sheet-rise) allow-discrete;
}

.dialog[open] {
  opacity: 1;
  translate: 0 0;
}

/*
 * On its way out, put there by site.js — which then waits for this to finish
 * before closing the dialog for real. The two discrete transitions above would
 * do the same on their own, but only in Chrome, and this way it leaves alike
 * everywhere. It has to sit after the rule above: same weight, so it is source
 * order that lets it win while the dialog is still open.
 */
.dialog.is-closing {
  opacity: 0;
  translate: 0 12px;
}

@starting-style {
  .dialog[open] {
    opacity: 0;
    translate: 0 12px;
  }
}

/* The scrim fades with it rather than snapping on. */
.dialog::backdrop {
  opacity: 0;
  transition:
    opacity var(--sheet-fade) ease,
    overlay var(--sheet-fade) allow-discrete,
    display var(--sheet-fade) allow-discrete;
}

.dialog[open]::backdrop {
  opacity: 1;
}

/* And fades again on the way out. Below the rule above rather than beside the
   rest of .is-closing: the two weigh the same, so this only wins from here —
   above it, the scrim stayed put until the dialog had gone and then dropped in
   one frame, which is the one thing this whole arrangement is against. */
.dialog.is-closing::backdrop {
  opacity: 0;
}

@starting-style {
  .dialog[open]::backdrop {
    opacity: 0;
  }
}

/*
 * The grabber, defined once for both surfaces that draw one. The sheet builds a
 * real element for it because it builds its whole shell; the dialog draws it as
 * a pseudo-element, because otherwise both pages that open one would have to
 * grow a div for it in markup. Same pill either way.
 */
.sheet__grip::after,
.menu__grip::after,
.dialog::before {
  content: "";
  width: 54px;
  height: 5px;
  border-radius: 3px;
  background: var(--field-line);
}

/* A dialog is only a sheet on a narrow screen, so its grip only exists there. */
.dialog::before {
  display: none;
}

.dialog__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  padding: 22px 24px 18px;
  border-bottom: 1px solid var(--line-soft);
}

.dialog__title {
  margin: 0;
  font-family: var(--display);
  font-size: 28px;
  font-weight: 600;
  line-height: 1.1;
  letter-spacing: -0.018em;
  color: var(--ink-strong);
}

.dialog__subject {
  display: block;
  margin-top: 4px;
  font-size: 13.5px;
  color: var(--muted);
}

/* Over the title rather than under it. /admin names the dialog and then says
   which brand it holds; the review dialog is always the same errand, so there
   the errand is the small line and the word being judged is the big one. */
.dialog__kicker {
  display: block;
  margin-bottom: 4px;
  font-size: 11px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--faint);
}

/* Empty for a new brand, and collapses to nothing when it is. A flex row rather
   than a block because on /admin/submissions it carries the queue badge as well
   as the line, and a 20px pill dropped into a line of ten-point capitals sits
   nowhere in particular. */
.dialog__stamp:not(:empty) {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 4px 8px;
  margin-top: 6px;
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--faint-2);
}

.icon-btn--close {
  width: 34px;
  height: 34px;
}

/* The form itself is the dialog's scrolling middle, so the head and the foot
   stay put while a long note scrolls. */
.dialog__body {
  display: flex;
  flex-direction: column;
  gap: 16px;
  padding: 22px 24px;
  overflow-y: auto;
  /* The wheel stops here. Without it, scrolling past the end of a long note
     carries on into the page behind — which the lock on <html> does not catch,
     because chaining is not the visitor scrolling the document. */
  overscroll-behavior: contain;
}

.dialog__foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 16px 24px 20px;
  border-top: 1px solid var(--line-soft);
  background: var(--surface-2);
}

.dialog__actions {
  display: flex;
  align-items: center;
  gap: 10px;
}

/*
 * [hidden] is display: none in the browser's own stylesheet, which loses to
 * .btn's display on source order alone — the same trap the .js-only utilities
 * are written around. Spelled out here so a hidden button is really hidden.
 */
.dialog__foot [hidden] {
  display: none;
}

/*
 * The question a dialog asks before letting go of something unsaved. It takes
 * the foot's place rather than sitting under it or over the form: the buttons
 * that would have saved or cancelled are what it stands in for, and a second
 * row of buttons under them would ask two things at once. Drawn the way the
 * foot is, so the dialog does not change shape while it asks.
 */
.dialog__unsaved {
  flex: 0 0 auto;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 16px 24px 20px;
  border-top: 1px solid var(--line-soft);
  background: var(--surface-2);
}

.dialog__unsaved[hidden],
.dialog.is-unsaved .dialog__foot,
.dialog.is-unsaved .submit__foot {
  display: none;
}

.dialog--drawer .dialog__unsaved {
  padding: 14px 26px 18px;
}

.dialog__unsaved-text {
  font-size: 14.5px;
  line-height: 1.5;
  color: var(--ink-strong);
}

/* The same 820px as the sheet itself: a thumb gets two whole-width buttons. */
@media (max-width: 820px) {
  .dialog__unsaved {
    flex-direction: column;
    align-items: stretch;
  }

  .dialog__unsaved .dialog__actions .btn {
    flex: 1 1 auto;
  }
}

/*
 * The delete question replaces the form rather than sitting under it, so the
 * dialog never shows the thing it is about to throw away — and so the question
 * is never read across a form scrolled to some arbitrary point, which is what
 * putting it under the foot turned out to mean once the dialog had a height to
 * run out of.
 */
.confirm {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 24px 24px 20px;
}

.confirm__heading {
  margin: 0;
  font-family: var(--display);
  font-size: 26px;
  font-weight: 600;
  line-height: 1.15;
  letter-spacing: -0.018em;
  color: var(--ink-strong);
}

.confirm p {
  margin: 0;
  font-size: 14.5px;
  line-height: 1.6;
  color: var(--muted);
  text-wrap: pretty;
}

.form-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 16px;
}

/*
 * The trademark's three fields, which are short and belong on one line. Aligned
 * on their bottom edge rather than their top: "naam zoals geregistreerd" wraps
 * to two lines where "merkhouder" does not, and a label that wraps must not
 * drop its input below the other two. Which label wraps changes with the
 * language, so reserving a second line for all three would be wrong somewhere.
 */
.form-grid--three {
  grid-template-columns: repeat(3, minmax(0, 1fr));
  align-items: end;
}

/* What holds for every register, set apart from the panels that do not. */
.dialog__section {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.dialog__eyebrow {
  display: flex;
  align-items: center;
  gap: 7px;
  font-size: 11px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--faint);
}

/* ---------- one panel per register ---------- */

/*
 * Tabs without a line of script. Each tab is a <label> wrapping its own radio,
 * and the panel it opens is its sibling, so both the strip and the panel follow
 * :checked — the fourth thing on this site a :has() selector does that would
 * otherwise need JavaScript, beside the dialog's scroll lock, the sheet's, and
 * the bulk bar's count.
 *
 * Nothing here names a language. The wrapper is display: contents so that a
 * head and its body are both flex items of the strip; the heads take the first
 * line, the open body takes the second on its own, and the filler span carries
 * the rule on to the right of the last tab, which the heads cannot do between
 * them because they stop where their labels do.
 */
.form-tabs {
  display: flex;
  flex-wrap: wrap;
  column-gap: 2px;
}

.form-tab {
  display: contents;
}

.form-tab__head {
  order: 1;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  height: 38px;
  margin-bottom: -1px;
  padding: 0 14px;
  border: 1px solid transparent;
  border-radius: var(--radius) var(--radius) 0 0;
  color: var(--muted);
  font-size: 13.5px;
  white-space: nowrap;
  cursor: pointer;
}

/* The tab sits over the rule the panel below draws, and the open one covers its
   own stretch of it in its own colour. That is what attaches a tab to its
   panel, and it is why the rule is the panel's and not a filler element's: a
   filler has to be the last thing on the strip's line, and a strip that wraps
   leaves it on the wrong one. */
.form-tab__head:has(.form-tab__radio:checked) {
  background: var(--surface-2);
  border-color: var(--field-line);
  border-bottom-color: var(--surface-2);
  color: var(--ink-strong);
}

/* Filled where the register has a word; a plus where it has none. */
.form-tab__dot {
  flex: 0 0 auto;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--ink);
}

.form-tab__plus {
  flex: 0 0 auto;
  color: var(--faint);
}

/* A register with no word yet is drawn as an outline rather than as a warning:
   missing is often the right answer. */
.form-tab__head--empty {
  color: var(--faint);
}

.form-tab__head--empty:has(.form-tab__radio:checked) {
  border-style: dashed;
  border-bottom-style: solid;
}

.form-tab__head:has(.form-tab__radio:focus-visible) {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* The radio is the state and nothing else; the label is what is seen. */
.form-tab__radio {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.form-tab__note {
  font-size: 11px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--faint-2);
}

.form-tab__head:has(.form-tab__radio:checked) .form-tab__note {
  color: var(--faint);
}

.form-tab__body {
  order: 2;
  flex: 0 0 100%;
  display: none;
  flex-direction: column;
  gap: 16px;
  padding-top: 18px;
  border-top: 1px solid var(--line);
}

.form-tab:has(> .form-tab__head > .form-tab__radio:checked) > .form-tab__body {
  display: flex;
}

.form-tab__hint {
  margin: 0;
  max-width: 62ch;
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--muted);
  text-wrap: pretty;
}

/* Beside the word it removes, not in the dialog's foot: the foot is shared by
   every register and cannot be told which one is showing without a language
   code being written into this file. */
.form-tab__foot {
  display: flex;
  padding-top: 2px;
}

.form-field {
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-width: 0;
}

/* The label, and only the label: the hint and the validation message are
   spans in here too, and neither is set in capitals. */
.form-field > span:first-child {
  font-size: 11px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--faint);
}

.form-field input,
.form-field select,
.form-field textarea {
  width: 100%;
  height: 38px;
  padding: 0 11px;
  background: var(--surface);
  border: 1px solid var(--field-line);
  border-radius: var(--radius);
  color: var(--ink);
  font-size: 14.5px;
  outline: none;
}

.form-field textarea {
  height: auto;
  padding: 10px 11px;
  line-height: 1.6;
  resize: vertical;
}

/*
 * A date field writes its own placeholder — dd/mm/yyyy — and that is the
 * browser's text rather than ours. ::placeholder never reaches it, and neither
 * does ::-webkit-datetime-edit: on this engine those pseudo-elements take no
 * colour at all, not even a forced one. What the text does follow is the
 * input's own colour, so the input is what changes — marked by the server on
 * render, and kept right by admin.js while somebody is typing into it, which is
 * the only part a page load would not already have got right.
 */
.form-field input[type="date"].is-empty {
  color: var(--faint-2);
}

/*
 * Half a date is not a placeholder. Typing one subfield leaves the value empty
 * — so the mark above is still on — but the control is then holding input it
 * cannot turn into a date, which is what badInput says and what :invalid reads
 * back. Nothing else here can make one of these invalid: they carry no
 * required, no min and no max.
 */
.form-field input[type="date"].is-empty:invalid {
  color: var(--ink);
}

.form-field input:focus,
.form-field select:focus,
.form-field textarea:focus {
  border-color: var(--accent);
}

/*
 * A field that is almost always the same as the one before it, folded away
 * until it is not. The summary is set as the labels are, so a closed one reads
 * as a field's label with nothing under it yet — a question the form is not
 * asking unless you want it to.
 */
.form-more {
  min-width: 0;
}

.form-more > summary {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 2px 0;
  font-size: 11px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--faint);
  cursor: pointer;
  list-style: none;
}

.form-more > summary::-webkit-details-marker {
  display: none;
}

.form-more > summary::before {
  content: "+";
  font-size: 13px;
  letter-spacing: 0;
  color: var(--accent);
}

.form-more[open] > summary::before {
  content: "\2212";
}

.form-more > summary:hover {
  color: var(--ink-strong);
}

.form-more > .form-field {
  margin-top: 8px;
}

/* Says what the notes field does with what is typed into it. */
.form-hint {
  font-size: 12px;
  color: var(--faint-2);
}

.validation:not(:empty) {
  font-size: 12.5px;
  color: var(--danger);
}

/* The summary renders its errors as a list. */
.validation ul {
  margin: 0;
  padding-left: 18px;
}

/* A field's message is rendered whether or not anything is wrong with it, and
   an empty span would still take a gap in the column it sits in. */
.form-field .validation:empty {
  display: none;
}

@media (max-width: 820px) {
  /*
   * Here the lock further up is not enough on its own. `overflow: hidden` stops
   * the visitor scrolling but leaves the document its full height, and Safari on
   * iPhone scrolls it itself to clear a field for the keyboard — dragging the
   * sheet up off the bottom edge and showing what the document holds down
   * there: the register, undimmed, because ::backdrop only ever covers the
   * viewport. Clamping the document to the fold leaves nothing to show.
   *
   * `:focus-within` and not [open], because the clamp is not free: a document
   * ending at the fold is one Safari stops running under its own bottom bar, so
   * the sheet gives up a bar's height of the edge it is drawn to meet. Tied to
   * the focus it lasts exactly as long as the keyboard that is the reason for
   * it, and every other moment the sheet reaches the edge.
   *
   * Only at this width, too, where the sheet covers the screen and nobody can
   * see the page behind it move. Wide, the dialog is centred with the register
   * in view all around it, and clamping would jump the list to the top in plain
   * sight for a keyboard that never comes.
   *
   * It costs the scroll offset, since a document that cannot scroll is at the
   * top, and site.js puts that back when the dialog closes — which covers a
   * field blurred without closing too, the clamp lifting on an offset already
   * spent. Without script there is nothing to put back: a dialog is a page load
   * away there.
   */
  html:has(.dialog[open]:focus-within),
  html:has(.dialog[open]:focus-within) body {
    height: 100%;
    overflow: hidden;
  }

  /*
   * Clamped, there is nothing under the sheet to show — but empty is not the
   * same as invisible. Safari still drags the sheet up off the bottom edge and
   * what stands under it then is the canvas, in the register's own ground,
   * which reads as a gap the design does not have. So while the clamp holds it
   * takes the colour the sheet's foot is, and the sheet simply carries on past
   * the bottom of the screen.
   *
   * On <body>, whose background is what the canvas is painted from — <html>
   * was tried and does not reach it. Which means the register behind takes the
   * same colour, a step of two per cent under a scrim, and nowhere it shows
   * but the sliver above the sheet.
   */
  html:has(.dialog[open]:focus-within) body {
    background: var(--surface-2);
  }

  /* The dialog meets the bottom edge, the way the register's sheet does. */
  .dialog,
  .dialog--confirm {
    width: 100%;
    max-width: 100%;
    max-height: 94vh;
    border-radius: var(--radius-lg) var(--radius-lg) 0 0;
  }

  .dialog {
    top: auto;
    bottom: 0;
  }

  /* Meeting that edge, it comes up off it — the whole height rather than the
     12px a centred dialog rises, which is the sheet's own way in. */
  .dialog {
    translate: 0 100%;
  }

  @starting-style {
    .dialog[open] {
      translate: 0 100%;
    }
  }

  /* It came up off the edge, so it goes back down to it. */
  .dialog.is-closing {
    translate: 0 100%;
  }

  .dialog::before {
    display: block;
    flex: 0 0 auto;
    align-self: center;
    margin: 10px 0 2px;
  }

  /* The surface takes the downward drag and its scrolling middle keeps it, the
     arrangement .sheet and .sheet__body already have. Without the second rule
     the drawer could not be scrolled by thumb at all. */
  .dialog {
    touch-action: none;
  }

  .dialog__body,
  .submit__body {
    touch-action: pan-y;
  }

  .form-grid {
    grid-template-columns: minmax(0, 1fr);
  }

  /*
   * The term under a tab's name is what makes the strip too wide for a phone,
   * and a wrapped strip breaks the rule that runs on past the last tab: the
   * filler carrying it wraps too and the line comes out in pieces. The term is
   * in the panel's own first field two lines below, so here the tab is just the
   * register's name.
   */
  .form-tab__note {
    display: none;
  }
}

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

/*
 * Four numbers, a chart and the terms that are still too close to call. Every
 * number on this page is a count over the Votes table; nothing here is per
 * visitor, and nothing here needed a column that did not already exist.
 */
.kpi {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  border-top: 1.5px solid var(--ink-strong);
  border-bottom: 1px solid var(--line);
}

.kpi__cell {
  display: flex;
  flex-direction: column;
  gap: 7px;
  padding: 20px 22px;
}

.kpi__cell + .kpi__cell {
  border-left: 1px solid var(--line);
}

.kpi__label {
  font-size: 11px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--faint);
}

.kpi__value {
  font-family: var(--display);
  font-size: 44px;
  font-weight: 600;
  line-height: 1;
  letter-spacing: -0.02em;
  color: var(--ink-strong);
  font-variant-numeric: tabular-nums;
}

/* A note that only reports takes no colour; one that says which way a number
   went takes the accent for up and the muted ink for down. */
.kpi__note {
  font-size: 12.5px;
  color: var(--faint-2);
}

.kpi__note--up {
  color: var(--accent);
}

.kpi__note--down {
  color: var(--muted);
}

/* Both panels below the numbers: same card, different padding. */
.panel {
  margin-top: 22px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}

.panel--chart {
  padding: 22px 24px;
}

.panel--contested {
  padding: 20px 24px 22px;
}

.panel__head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 24px;
  flex-wrap: wrap;
  padding-bottom: 14px;
}

.panel__heading {
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-width: 0;
}

.panel__label {
  font-size: 11px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--faint);
}

.panel__note {
  font-size: 13px;
  color: var(--faint-2);
}

.panel__empty {
  margin: 0;
  padding: 24px 0 8px;
  font-size: 14px;
  color: var(--faint);
}

.panel__controls {
  display: flex;
  align-items: center;
  gap: 16px;
  flex-wrap: wrap;
}

.seg {
  display: flex;
  align-items: center;
  gap: 2px;
}

.seg__option {
  display: inline-flex;
  align-items: center;
  height: 28px;
  padding: 0 10px;
  border: 1px solid transparent;
  border-radius: 2px;
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  white-space: nowrap;
  color: var(--faint);
  text-decoration: none;
}

.seg__option:hover {
  color: var(--ink-strong);
}

.seg__option[aria-current="true"] {
  border-color: var(--accent);
  background: var(--surface-2);
  color: var(--ink-strong);
}

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

/*
 * One server-rendered geometry, two layouts. The SVG holds nothing but the
 * area and the line, in a 0 0 100 100 viewBox stretched to whatever box this
 * gives it; every axis label, dot and guide is HTML on the same percentages.
 * That is what lets a phone and a desktop share one set of coordinates — see
 * the note in AGENTS.md before putting a <text> or <circle> back in.
 *
 * The padding is the plot's margin, and it is CSS rather than viewBox units
 * for the one reason that matters: it holds text at a real font size, which
 * does not scale with the box.
 */
.chart {
  --chart-pad-t: 18px;
  --chart-pad-r: 12px;
  --chart-pad-b: 26px;
  --chart-pad-l: 56px;
  --chart-ratio: 932 / 238;
  --chart-line: 2px;
  --chart-dot: 6.4px;
  --chart-guide: 9px;
  --chart-axis: 11px;
  --chart-gap: 10px;
}

.chart__readout {
  display: flex;
  align-items: baseline;
  gap: 14px;
  flex-wrap: wrap;
  padding-top: 14px;
  border-top: 1px dotted var(--line);
}

.chart__value {
  font-family: var(--display);
  font-size: 46px;
  font-weight: 600;
  line-height: 1;
  letter-spacing: -0.022em;
  color: var(--accent);
  font-variant-numeric: tabular-nums;
}

.chart__reading {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.chart__unit {
  font-size: 13.5px;
  color: var(--ink);
}

.chart__date {
  font-size: 12.5px;
  color: var(--faint);
}

.chart__delta {
  margin-left: auto;
  font-size: 12.5px;
  letter-spacing: 0.04em;
  color: var(--accent);
}

.chart__delta--down {
  color: var(--muted);
}

.chart__plot {
  padding: var(--chart-pad-t) var(--chart-pad-r) var(--chart-pad-b) var(--chart-pad-l);
}

/* The plot rectangle itself. Every overlay below is positioned against this
   box, so the coordinates are the raw percentages with nothing to subtract. */
.chart__area {
  position: relative;
  aspect-ratio: var(--chart-ratio);
}

.chart__svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

.chart__fill {
  fill: var(--chart-fill);
  stroke: none;
}

/* non-scaling-stroke is what makes preserveAspectRatio="none" safe: the stroke
   is drawn after the viewport transform, so the line stays this thick and its
   joins stay round however the box is stretched. */
.chart__line {
  fill: none;
  stroke: var(--accent);
  stroke-width: var(--chart-line);
  stroke-linejoin: round;
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
}

/* Dashed in CSS pixels rather than as a stroke-dasharray, which a stretched
   viewBox would stretch along with everything else. */
.chart__grid {
  position: absolute;
  left: 0;
  right: 0;
  top: var(--y);
  border-top: 1px dashed var(--line);
}

.chart__grid--base {
  border-top-style: solid;
  border-top-color: var(--field-line);
}

/* Out into the gutter the padding reserved. */
.chart__tick {
  position: absolute;
  top: var(--y);
  right: calc(100% + var(--chart-gap));
  transform: translateY(-50%);
  font-size: var(--chart-axis);
  color: var(--faint-2);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.chart__x-labels {
  position: absolute;
  inset: 0;
}

.chart__x {
  position: absolute;
  left: var(--x);
  top: calc(100% + var(--chart-gap));
  transform: translateX(-50%);
  font-size: var(--chart-axis);
  color: var(--faint);
  white-space: nowrap;
}

/* The two ends would hang off the axis if they stayed centred. */
.chart__x--first {
  transform: none;
}

.chart__x--last {
  transform: translateX(-100%);
}

/* A span rather than a <circle>: under preserveAspectRatio="none" a circle
   comes out an ellipse, and a border-radius cannot. */
.chart__dot {
  position: absolute;
  left: var(--x);
  top: var(--y);
  width: var(--chart-dot);
  height: var(--chart-dot);
  border: var(--chart-line) solid var(--accent);
  border-radius: 50%;
  background: var(--surface);
  transform: translate(-50%, -50%);
}

.chart__guide {
  position: absolute;
  top: 0;
  bottom: 0;
  left: var(--x);
  border-left: 1px dashed var(--accent);
}

.chart__guide-dot {
  position: absolute;
  left: var(--x);
  top: var(--y);
  width: var(--chart-guide);
  height: var(--chart-guide);
  border-radius: 50%;
  background: var(--accent);
  transform: translate(-50%, -50%);
}

/* One column per day, standing over the whole plot so the pointer never has to
   find the line itself. */
.chart__hits {
  position: absolute;
  inset: 0;
}

.chart__hit {
  position: absolute;
  top: 0;
  bottom: 0;
  left: var(--x);
  width: var(--w);
}

/* ---------- omstreden termen ---------- */

.contested__row {
  display: grid;
  grid-template-columns: minmax(0, 1.05fr) minmax(0, 1.5fr) 80px;
  gap: 26px;
  align-items: center;
  padding: 16px 0;
  border-top: 1px dotted var(--line);
}

.contested__names {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.contested__brand {
  font-family: var(--display);
  font-size: 30px;
  font-weight: 600;
  line-height: 1.1;
  letter-spacing: -0.018em;
  color: var(--ink-strong);
}

.contested__term {
  font-size: 13.5px;
  color: var(--accent);
}

.contested__meter {
  display: flex;
  flex-direction: column;
  gap: 5px;
  min-width: 0;
}

/* The same shape as the vote bar, cut at --split: the fill runs from there to
   the right edge, so the left of the seam is "nog een merk". */
.contested__bar {
  position: relative;
  height: 10px;
  border: 1px solid var(--field-line);
  background: var(--ground);
  overflow: hidden;
}

.contested__fill {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: var(--split);
  background: var(--accent);
}

.contested__seam {
  position: absolute;
  top: 0;
  bottom: 0;
  left: var(--split);
  border-left: 1px solid var(--field-line);
}

.contested__labels {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--faint-2);
}

.contested__pct {
  justify-self: end;
  font-family: var(--display);
  font-size: 30px;
  font-weight: 600;
  line-height: 1;
  letter-spacing: -0.02em;
  color: var(--ink-strong);
  font-variant-numeric: tabular-nums;
}

/* ---------- the vote dashboard on a narrow screen ---------- */

/*
 * 820px, the same breakpoint the register, the admin list and site.js use.
 * The chart needs nothing here but a different set of custom properties: one
 * geometry, two layouts.
 */
@media (max-width: 820px) {
  .kpi {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .kpi__cell {
    padding: 15px 14px;
  }

  /* Two up: the left border belongs to the right-hand cell of each pair, and
     the second row is ruled off from the first. */
  .kpi__cell + .kpi__cell {
    border-left: 0;
  }

  .kpi__cell:nth-child(even) {
    border-left: 1px solid var(--line);
  }

  .kpi__cell:nth-child(n + 3) {
    border-top: 1px solid var(--line);
  }

  .kpi__value {
    font-size: 34px;
  }

  .panel--chart {
    padding: 16px 14px 18px;
  }

  .panel--contested {
    padding: 16px 14px 18px;
  }

  .panel__head {
    flex-direction: column;
    align-items: flex-start;
    gap: 14px;
  }

  /* Taller against its width than the wide chart, and shorter in real pixels:
     a phone screen has room the other way round. */
  .chart {
    --chart-pad-t: 16px;
    --chart-pad-r: 8px;
    --chart-pad-b: 30px;
    --chart-pad-l: 46px;
    --chart-ratio: 466 / 284;
    --chart-line: 2.4px;
    --chart-dot: 8px;
    --chart-guide: 11px;
    --chart-axis: 12px;
    --chart-gap: 12px;
  }

  /* Six dates fit across a desktop panel and not across a phone, where the
     same six run into each other at the right-hand end. Every second one goes,
     counted back from the newest so that one always stays. */
  .chart__x-labels span:nth-child(even) {
    display: none;
  }

  .chart__value {
    font-size: 38px;
  }

  .chart__delta {
    margin-left: 0;
  }

  .contested__row {
    grid-template-columns: minmax(0, 1fr);
    gap: 10px;
    padding: 14px 0;
  }

  .contested__brand {
    font-size: 26px;
  }

  .contested__pct {
    justify-self: start;
    font-size: 26px;
  }
}
/* ---------- aandragen ---------- */

/*
 * The submission form. On its own page this panel is the page; with script the
 * register lifts the same markup into a <dialog>, which is the drawer the
 * design draws — so everything here is written to sit in either, and the
 * .dialog rules further up supply the box when it is the second one.
 *
 * The panel sits on --ground rather than --surface, because the row inside it
 * does not: the two cells of the register line are what lift off the page here,
 * the same way they do on the register itself.
 */
.page--narrow {
  max-width: 720px;
}

/*
 * /privacy and /about: text you read rather than a form you fill in, so a
 * little wider than /submit. 800px of text — the page's own padding comes on
 * top, because .page is border-box and 800 is the measure that was asked for.
 */
.page--reading {
  max-width: calc(800px + 64px);
}

.submit {
  display: flex;
  flex-direction: column;
  max-width: 680px;
  margin: 0 auto;
  background: var(--ground);
  border: 1px solid var(--field-line);
  border-radius: 4px;
  overflow: hidden;
}

/* Inside a dialog the box is the dialog's own, so the panel stops drawing one
   and lets it scroll instead — and the centring goes with the box. In a column
   flex container left and right are the cross axis, where an auto margin stops
   an item stretching and eats the slack instead: the panel would sit at its own
   content width with the dialog's lighter surface showing down both sides. Only
   the receipt ever showed it, being the one state narrow enough to leave slack
   to eat; the two forms fill 660px on their own and hid the whole thing. */
.dialog .submit {
  max-width: none;
  margin: 0;
  border: 0;
  border-radius: 0;
  min-height: 0;
  flex: 1 1 auto;
}

.submit form {
  display: flex;
  flex-direction: column;
  min-height: 0;
}

.submit__head {
  flex: 0 0 auto;
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  padding: 20px 26px 14px;
}

.submit__step {
  display: block;
  font-size: 10.5px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--faint);
}

/* An <h2>, so the 4px the design puts between the step and the title has to be
   written as the whole margin: setting only the top leaves the browser's 0.83em
   under it, which is 23px of nothing between the title and the form. */
.submit__title {
  margin: 4px 0 0;
  font-family: var(--display);
  font-size: 28px;
  font-weight: 600;
  line-height: 1.1;
  letter-spacing: -0.018em;
  color: var(--ink-strong);
}

/* The scrolling middle, so the head and the foot stay put while a long note or
   a third of a phone screen's worth of question scrolls under them. */
.submit__body {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  gap: 18px;
  padding: 0 26px 20px;
  overflow-y: auto;
  min-height: 0;
}

.submit__foot {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 14px 26px 18px;
  background: var(--surface-2);
  border-top: 1px solid var(--line-soft);
}

/* Step one has nothing on the left, so both of its buttons sit together on the
   right rather than straddling the bar. */
.submit__foot:has(.submit__cancel) {
  justify-content: flex-end;
}

.submit__actions {
  display: flex;
  align-items: center;
  gap: 10px;
}

/*
 * Everything the design only draws on a phone: the arrow beside the title, the
 * pips under it, the little folded row the later steps carry, the card that
 * grows on the first step while it is typed into, and the check drawn as a card
 * out of the stack. A wide panel says all of it another way — the kicker names
 * the step, the foot holds the way back, and the row being written is on screen
 * the whole time — so here they are simply not there.
 *
 * Their own rules are in the 820px block, together with the wide things a phone
 * turns off in exchange. The two halves are written that way round because this
 * is the panel a wide screen has had since fase 12; the narrow one is the
 * variation.
 */
.submit__step-back,
.submit__pips,
.submit__dock,
.draft__label,
.draft__mark,
.quiz__card,
.quiz__pick,
.sent-card,
.submit__sent-body {
  display: none;
}

/* Only ever set by submit.js — the server never renders this button disabled,
   because that is a step nobody without script could leave. */
.submit__actions .btn[disabled] {
  border-color: var(--field-line);
  background: var(--surface-2);
  color: var(--faint-2);
  cursor: not-allowed;
}

/*
 * The thank-you screen. The design does not draw it as a step: there is no head
 * bar and no foot bar, only one block of air from the top of the panel to the
 * bottom, and the title is set larger and tighter than the two steps' are —
 * this screen has one thing to say and says it. The head and the body are still
 * two elements here, so the 16px between the title and the receipt is written
 * as the head's own padding rather than as the gap it is in the design.
 */
.submit--sent .submit__head {
  padding: 30px 26px 16px;
}

.submit--sent .submit__step {
  font-size: 11px;
}

.submit--sent .submit__title {
  margin-top: 6px;
  font-size: 31px;
  line-height: 1.05;
  letter-spacing: -0.02em;
}

.submit--sent .submit__body {
  gap: 16px;
  padding-bottom: 30px;
}

.submit__thanks {
  max-width: 48ch;
  font-size: 15px;
  line-height: 1.65;
  color: var(--ink);
  text-wrap: pretty;
}

/* The receipt's own way on, under the text rather than in a bar: one button and
   one link, because leaving is not the same size of act as starting again. */
.submit__after {
  display: flex;
  align-items: center;
  gap: 14px;
}

.submit__back {
  color: var(--accent);
  font-size: 14px;
}

/*
 * Step one: a register row being typed into.
 *
 * Eight cells in one grid. The rules are borders on the cells so they run the
 * full width without an element spanning them, and the seam is a left border on
 * every right-hand cell, so it runs unbroken down the middle exactly as it does
 * on the register.
 */
.draft__grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  align-items: start;
}

.draft__cell--l {
  text-align: right;
}

.draft__cell--r {
  border-left: 1px solid var(--line);
}

/* The register's own heavy rule over the column names. */
.draft__cell--head {
  padding: 7px 20px 7px 0;
  font-size: 10.5px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--faint);
  border-top: 1.5px solid var(--ink-strong);
}

.draft__cell--head.draft__cell--r {
  padding: 7px 0 7px 20px;
}

.draft__cell--word {
  padding: 10px 20px 0 0;
}

.draft__cell--word.draft__cell--r {
  padding: 10px 0 0 20px;
}

.draft__cell--small {
  padding: 8px 20px 14px 0;
}

.draft__cell--small.draft__cell--r {
  padding: 8px 0 14px 20px;
}

.draft__cell--rule {
  padding: 16px 20px 22px 0;
  border-top: 1px dotted var(--line);
}

.draft__cell--rule.draft__cell--r {
  padding: 14px 0 22px 20px;
  max-width: 60ch;
}

/*
 * The fields draw nothing of their own — no border, no fill. What shapes them
 * is the row around them, which is the whole idea: you are not filling in a
 * form, you are writing a line of the register. Focus lights the text and
 * nothing else.
 */
.draft__word,
.draft__small,
.draft__since input,
.draft__notes {
  width: 100%;
  padding: 0 2px;
  font-family: inherit;
  background: transparent;
  border: 0;
  outline: 0;
}

.draft__word:focus,
.draft__small:focus,
.draft__since input:focus,
.draft__notes:focus {
  background: var(--surface);
}

/* Set in the register's face, at the register's size. */
.draft__word {
  height: 42px;
  font-family: var(--display);
  font-size: 29px;
  font-weight: 600;
  line-height: 1.05;
  letter-spacing: -0.018em;
}

.draft__word--term {
  color: var(--ink-strong);
  text-align: right;
}

.draft__word--brand {
  color: var(--accent);
}

/* The two quiet cells read as the labels they will become on the row. */
.draft__small {
  height: 24px;
  font-size: 10.5px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--muted);
}

.draft__cell--l .draft__small {
  text-align: right;
}

.draft__since {
  display: flex;
  align-items: baseline;
  justify-content: flex-end;
  gap: 6px;
  font-size: 12px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--faint);
}

.draft__since input {
  width: 52px;
  height: 20px;
  font: inherit;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-strong);
  font-variant-numeric: tabular-nums;
}

.draft__notes {
  color: var(--ink);
  font-size: 15.5px;
  line-height: 1.65;
  resize: vertical;
  text-wrap: pretty;
}

.draft__foot {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  padding-top: 9px;
  border-top: 1px dotted var(--line);
}

.draft__rule {
  font-size: 12px;
  color: var(--faint-2);
}

/*
 * A field no person ever sees, and the reason it is moved off-screen rather
 * than given display: none — that is the first thing an automated form-filler
 * learns to skip, and this one is meant to be filled in.
 */
.trap {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

/* The word is already in the register. A find, not an error, so it is not
   styled like one. */
.found {
  display: flex;
  flex-direction: column;
  gap: 9px;
  padding: 14px 16px;
  background: var(--surface);
  border-radius: var(--radius);
}

.found__lead {
  font-size: 12.5px;
  color: var(--muted);
}

.found__row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 14px;
  font-family: var(--display);
  font-size: 21px;
  color: var(--ink-strong);
  text-decoration: none;
}

.found__row:hover .found__term {
  text-decoration: underline;
  text-underline-offset: 3px;
}

.found__brand {
  color: var(--accent);
}

/*
 * Step two asks about the person rather than the word, so it drops the register
 * styling entirely and uses ordinary bordered fields with ordinary labels. That
 * contrast is the point: the first step may be quiet and odd, the second one is
 * about you and about permission and wants no puzzles in it.
 */
.submit .form-grid {
  gap: 12px;
}

/* The name and the address with the link to /privacy under them, closer to
   the fields than the body's gap would put it: it is about those two fields,
   not a block of its own. */
.submit__person {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.submit__privacy {
  margin: 0;
  font-size: 12.5px;
  line-height: 1.5;
}

.submit__privacy a {
  color: var(--muted);
  text-decoration-color: var(--field-line);
  text-decoration-thickness: 1px;
}

.submit__privacy a:hover {
  color: var(--ink-strong);
  text-decoration-color: currentColor;
}

.submit .form-field > span:first-child {
  display: flex;
  align-items: baseline;
  gap: 8px;
  font-size: 11px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--faint);
}

.form-field__optional {
  font-style: normal;
  font-size: 11px;
  letter-spacing: 0;
  text-transform: none;
  color: var(--faint-2);
}

.submit .form-field input {
  height: 38px;
  padding: 0 11px;
  border: 1px solid var(--field-line);
  border-radius: var(--radius);
  background: var(--surface);
  font-size: 15px;
  color: var(--ink);
}

.submit .form-field input:focus {
  border-color: var(--accent);
  outline: 0;
}

.submit .form-hint {
  font-size: 12px;
  line-height: 1.5;
  color: var(--faint);
}

.form-hint--warn {
  color: var(--danger);
}

/*
 * [hidden] is display: none in the browser's own stylesheet, and .credit__option
 * carries a display of its own that outranks it. Said here, it is settled, and
 * the descendant selector beats a class on its own. The same guard the dialog's
 * foot needs, for the same reason.
 */
.credit [hidden],
.form-hint[hidden] {
  display: none;
}

/*
 * Whether a name goes on the row. One box and not two options: it is one thing
 * being agreed to, and the line under it says what agreeing means — design
 * canvas "Aandragen smal 5d". It is only asked once there is a name to ask it
 * about; submit.js takes it away again when the name goes, and stands the
 * sentence explaining why there is no box in its place.
 */
.credit {
  display: flex;
  flex-direction: column;
  gap: 9px;
  min-width: 0;
}

.credit__none {
  max-width: 46ch;
  font-size: 12.5px;
  line-height: 1.55;
  color: var(--faint);
}

.credit__option {
  display: flex;
  align-items: center;
  gap: 11px;
  min-height: 44px;
  cursor: pointer;
}

/*
 * Hidden from the eye, not from the keyboard or from a screen reader: the label
 * is what gets clicked, and the focus ring below is what says what is being
 * aimed at. Clipped rather than faded out — an element at opacity 0 is a
 * control that some tooling stops reporting as one.
 *
 * The tag helper writes a hidden input beside the box, which is what an unticked
 * checkbox posts; that one is display: none on its own and needs nothing here.
 */
.credit__option input[type="checkbox"],
.quiz__input {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
}

.credit__option:has(input:focus-visible) .credit__box {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* The tick is drawn at all times and coloured in only when it is true, so the
   box does not change size as it is ticked. */
.credit__box {
  display: flex;
  flex: 0 0 auto;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  border: 1px solid var(--field-line);
  border-radius: 2px;
  background: var(--surface);
  color: transparent;
}

.credit__option:has(input:checked) .credit__box {
  border-color: var(--accent);
  background: var(--accent);
  color: var(--on-accent);
}

.credit__text {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 0;
  text-align: left;
}

.credit__label {
  font-size: 14.5px;
  line-height: 1.3;
  color: var(--ink-strong);
}

.credit__note {
  font-size: 11.5px;
  line-height: 1.35;
  color: var(--faint);
}

/*
 * Both of them are rendered and the stylesheet shows the one the box is in. A
 * script choosing between them would be a script choosing a Dutch sentence,
 * which is the one thing no script here does — the same arrangement the vote
 * band's two spellings have.
 */
.credit__note--on,
.credit__option:has(input:checked) .credit__note--off {
  display: none;
}

.credit__option:has(input:checked) .credit__note--on {
  display: block;
}


/*
 * The brand question. A dotted rule and some air rather than a box of its own —
 * it is the last thing on the step, not a thing apart from it.
 *
 * Nothing here ever says whether the answer was right, which is the whole of
 * why asking it is worth anything: there is no state below for a correct or an
 * incorrect chip, because there is no such thing on this screen.
 */
.quiz {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding-top: 15px;
  border-top: 1px dotted var(--line);
}

.quiz__lead {
  font-size: 10.5px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--faint);
}

/* A <p>, so the browser gives it a line's worth of margin top and bottom that
   is added to the flex gap either side — which put the question in a hole of
   its own rather than in a stack with the rest of the block. */
.quiz__question {
  margin: 0;
  font-size: 15.5px;
  line-height: 1.5;
  color: var(--ink);
}

.quiz__question em {
  font-family: var(--display);
  font-style: italic;
  font-size: 19px;
  color: var(--ink-strong);
}

.quiz__options {
  display: flex;
  flex-wrap: wrap;
  gap: 7px;
}

.quiz__chip {
  display: inline-flex;
  align-items: center;
  height: 34px;
  padding: 0 16px;
  font-size: 14px;
  color: var(--ink-strong);
  background: var(--surface);
  border: 1px solid var(--field-line);
  border-radius: 999px;
  cursor: pointer;
}

.quiz__input:checked + .quiz__chip {
  color: var(--on-accent);
  background: var(--accent);
  border-color: var(--accent);
}

.quiz__input:focus-visible + .quiz__chip {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.quiz__reroll {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  align-self: flex-start;
  padding: 2px 0;
  font-family: inherit;
  font-size: 13.5px;
  color: var(--accent);
  background: none;
  border: 0;
  cursor: pointer;
}

/*
 * What was sent, in the shape of the row it wants to become: the same heavy
 * rule, the same two columns, the same seam. It is the answer to the form above
 * it, so it is drawn the same way — and it sits on --surface, because by now it
 * is a register line rather than something being typed.
 */
.receipt {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  align-items: start;
  background: var(--surface);
}

.receipt__cell {
  display: flex;
  flex-direction: column;
  gap: 5px;
  min-width: 0;
  overflow-wrap: anywhere;
}

.receipt__cell--l {
  align-items: flex-end;
  padding: 14px 20px 14px 14px;
  text-align: right;
}

.receipt__cell--r {
  padding: 14px 14px 14px 20px;
  border-left: 1px solid var(--line);
}

.receipt__cell--head {
  border-top: 1.5px solid var(--ink-strong);
}

.receipt__cell--rule {
  gap: 4px;
  padding-top: 13px;
  padding-bottom: 15px;
  border-top: 1px dotted var(--line);
}

.receipt__term,
.receipt__brand {
  font-family: var(--display);
  font-size: 26px;
  font-weight: 600;
  line-height: 1.05;
  letter-spacing: -0.018em;
}

.receipt__term {
  color: var(--ink-strong);
}

.receipt__brand {
  color: var(--accent);
}

.receipt__category,
.receipt__meta {
  font-size: 10.5px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
}

.receipt__category {
  color: var(--faint);
}

.receipt__meta {
  color: var(--muted);
}

.receipt__since,
.receipt__wait {
  font-size: 12px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.receipt__since {
  color: var(--faint);
}

.receipt__wait {
  color: var(--faint-2);
}

.receipt__notes {
  max-width: 60ch;
  font-size: 15.5px;
  line-height: 1.65;
  color: var(--ink);
  text-wrap: pretty;
}

/*
 * 820px, the same breakpoint the register, the admin list, the dialog and
 * site.js use. On a phone the drawer is a sheet over the stack and the form is
 * three short steps rather than two long ones — design canvas "Aandragen smal
 * 5d". What that costs and what it buys is written out per block below; the
 * short version is that the register row stops being the thing typed into and
 * becomes a card drawn above the fields, and the two steps after it carry that
 * card folded to one line.
 */
@media (max-width: 820px) {
  /* The sheet runs to both edges. Said again here because this width is
     declared after the dialog's own narrow block and would otherwise hold. */
  .dialog--drawer {
    width: 100%;
    max-width: 100%;
  }

  /* ---- the head: a step, a way back and how far along ---- */

  /*
   * The arrow arrives on the left and the title moves between it and the cross.
   * Less air over it than a wide panel has, because on a sheet the grip is
   * already drawn above it.
   */
  .submit__head {
    align-items: flex-start;
    gap: 10px;
    padding-top: 8px;
    padding-bottom: 12px;
  }

  .submit__head-lines {
    flex: 1 1 auto;
    min-width: 0;
  }

  .submit__step {
    font-size: 10px;
    white-space: nowrap;
  }

  .submit__title {
    font-size: 22px;
    line-height: 1.15;
    letter-spacing: -0.02em;
    text-wrap: pretty;
  }

  /* A step back, beside the step it would go back from. Sized and filled like
     the cross across from it, so the two read as a pair — and scoped to this
     head, because the same cross closes /admin's dialogs, where nothing has
     grown beside it. */
  .submit__step-back,
  .submit__head .icon-btn--close {
    width: 40px;
    height: 40px;
    border-color: var(--field-line);
    background: var(--ground);
  }

  .submit__step-back {
    display: flex;
    color: var(--ink-strong);
  }

  /*
   * The kicker over the title already says "stap 2 van 3"; this is that
   * sentence drawn, under the head and over whatever the step holds. Done is
   * ink, now is the accent, still to come is a rule.
   */
  .submit__pips {
    display: flex;
    flex: 0 0 auto;
    align-items: center;
    gap: 5px;
    padding: 0 18px 12px;
    border-bottom: 1px solid var(--line-soft);
  }

  .submit__pip {
    flex: 1 1 0;
    height: 3px;
    border-radius: 2px;
    background: var(--line);
    transition: background-color 0.2s ease;
  }

  .submit__pip.is-done {
    background: var(--ink-strong);
  }

  .submit__pip.is-now {
    background: var(--accent);
  }

  /* ---- the row, folded, on the steps that are no longer about it ---- */

  /*
   * The register's heavy rule and its seam, at one line high. It is the card
   * from the first step shrunk rather than a summary of it, which is why it
   * keeps both column names and the accent on the brand.
   */
  .submit__dock {
    display: flex;
    flex: 0 0 auto;
    align-items: stretch;
    margin: 11px 18px 0;
    background: var(--ground);
    border: 1px solid var(--line);
    border-top: 2px solid var(--ink-strong);
    border-radius: var(--radius);
  }

  .submit__dock-cell {
    display: flex;
    flex: 1 1 0;
    flex-direction: column;
    gap: 1px;
    min-width: 0;
    padding: 7px 11px 8px;
  }

  .submit__dock-cell--brand {
    border-left: 1px dotted var(--field-line);
  }

  .submit__dock-label {
    font-size: 9px;
    letter-spacing: 0.18em;
    text-transform: uppercase;
    color: var(--faint);
  }

  .submit__dock-cell--brand .submit__dock-label {
    color: var(--faint-2);
  }

  /* One line, cut off rather than wrapped: this is a reminder of what is being
     proposed, and the step it belongs to is the thing being read. */
  .submit__dock-word {
    overflow: hidden;
    font-family: var(--display);
    font-size: 18px;
    font-weight: 600;
    line-height: 1.15;
    letter-spacing: -0.018em;
    color: var(--ink-strong);
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .submit__dock-word--brand {
    color: var(--accent);
  }

  /* Nothing typed yet: the word standing in for it is set as a placeholder is,
     because that is what it is. Only the first step can be in this state. */
  .submit__dock-word.is-empty {
    color: var(--faint);
  }

  /* The brand, still covered. The register keeps it covered until it is asked
     for; here it is covered until it is given — a block of ink at the size this
     card is, rather than the card's own. */
  .submit__dock-cover {
    width: 74%;
    min-width: 86px;
    height: 17px;
    margin: 2px 0 3px;
    border-radius: 2px;
    background: var(--ink-strong);
  }

  .submit__dock.is-covered .submit__dock-word--brand,
  .submit__dock:not(.is-covered) .submit__dock-cover {
    display: none;
  }

  /* On the first step the card sits in the scrolling body with the fields it is
     a picture of, so it takes the body's gutter rather than a margin of its own.
     Pinned under the pips on the steps after it, it needs one. */
  .submit__dock--inline {
    margin: 0 0 16px;
  }

  .submit__dock-edit {
    display: flex;
    flex: 0 0 auto;
    align-items: center;
    justify-content: center;
    width: 38px;
    padding: 0;
    color: var(--accent);
    background: transparent;
    border: 0;
    border-left: 1px solid var(--line);
    cursor: pointer;
  }

  /* ---- step one: the card grows, the cells become fields ---- */

  /*
     Stacked, the row stops being two columns answering each other and becomes
     two blocks read one after the other: the term with its label, then the
     brand with its. A one-column grid cannot do that — it would deal the cells
     out in source order and interleave the two — so the grid becomes a flex
     column and the side each cell belongs to is what orders it. The classes
     that place them left and right are the same ones; only what they mean
     changes.
  */
  .draft__grid {
    display: flex;
    flex-direction: column;

    /* The grid above sets this to start so a tall note does not stretch the
       cell beside it. Down here there is nothing beside anything, and start
       would shrink every cell to its own text. */
    align-items: stretch;
  }

  .draft__cell--l {
    order: 1;
  }

  .draft__cell--r {
    order: 2;
  }

  .draft__cell--rule {
    order: 3;
  }

  .draft__cell--rule.draft__cell--r {
    order: 4;
  }

  /*
   * Categorie, merkhouder en generiek sinds are not on a phone. They are
   * optional, /admin fills them in, and six fields under each other is what
   * made the first step a screen rather than a step. They are still in the
   * form and still posted, empty — which is what they would have been anyway.
   */
  .draft__cell--small,
  .draft__since {
    display: none;
  }

  /* One column, so nothing answers anything across a seam: the type goes left,
     the divider goes, and every field grows to a thumb. Written against the
     grid so it outranks the two-class rules that set the gutters. */
  .draft__grid .draft__cell {
    align-items: flex-start;
    padding-right: 0;
    padding-left: 0;
    text-align: left;
    border-left: 0;
  }

  /*
   * The column names stop being a header row and become field labels, with
   * what the field is worth beside them. No heavy rule over the first of them
   * any more: the card above is what carries the register's rule now, and a
   * second one three lines under it read as a second table.
   */
  .draft__grid .draft__cell--head {
    display: flex;
    align-items: baseline;
    gap: 8px;
    padding-top: 0;
    padding-bottom: 6px;
    font-size: 11px;
    border-top: 0;
  }

  .draft__grid .draft__cell--head ~ .draft__cell--head,
  .draft__grid .draft__cell--rule {
    padding-top: 16px;
  }

  .draft__grid .draft__cell--word {
    padding-top: 0;
    padding-bottom: 0;
  }

  .draft__grid .draft__cell--rule {
    padding-bottom: 0;
    border-top: 0;
  }

  /* The label only a phone renders, over the note. Same line as the two the
     grid already had, so all three read as one kind of thing. */
  .draft__label {
    display: flex;
    align-items: baseline;
    gap: 8px;
    padding-bottom: 6px;
    font-size: 11px;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--faint);
  }

  /* What a field is worth, beside its name: required in the accent, optional
     quiet. Not small capitals — it is a word, not a column head. */
  .draft__mark {
    display: inline;
    font-size: 11px;
    font-style: normal;
    letter-spacing: normal;
    text-transform: none;
    color: var(--accent);
  }

  .draft__mark--soft {
    color: var(--faint-2);
  }

  /*
   * And the fields stop being cells. Wide they draw nothing of their own,
   * because the row around them is their shape; here there is no row, so each
   * of them is an ordinary box a thumb can find. 16.5px rather than the
   * register's face, for the same reason: this is typing, not a line of the
   * register yet.
   */
  .draft__word,
  .draft__notes {
    padding: 0 12px;
    font-family: inherit;
    background: var(--surface);
    border: 1px solid var(--field-line);
    border-radius: var(--radius);
  }

  .draft__word:focus,
  .draft__notes:focus {
    border-color: var(--accent);
  }

  .draft__word,
  .draft__word--term {
    height: 46px;
    font-size: 16.5px;
    font-weight: 400;
    letter-spacing: normal;
    line-height: normal;
    text-align: left;
    color: var(--ink-strong);
  }

  .draft__notes {
    padding: 11px 12px;
    font-size: 16px;
    line-height: 1.55;
  }

  /* The labels now say which fields are required, so the sentence that used to
     say it under them has nothing left to add, and it was all the foot held. */
  .draft__foot {
    display: none;
  }

  /* ---- step three: the check, as a card out of the stack ---- */

  /*
   * The same card, with the brand under the block of ink — so answering the
   * question is the gesture the register already taught. Every pick is
   * rendered and :has() shows the one that was chosen, which is why this works
   * with the script off.
   */
  .quiz__card {
    display: flex;
    flex-direction: column;
    gap: 5px;
    padding: 14px 15px;
    background: var(--ground);
    border: 1px solid var(--field-line);
    border-top: 2px solid var(--ink-strong);
    border-radius: 4px;
    box-shadow: 0 14px 30px -26px var(--scrim-strong);
  }

  .quiz__card-label {
    font-size: 9.5px;
    letter-spacing: 0.18em;
    text-transform: uppercase;
    color: var(--faint);
  }

  .quiz__card-label--brand {
    color: var(--faint-2);
  }

  .quiz__card-word {
    font-family: var(--display);
    font-size: 32px;
    font-weight: 600;
    line-height: 1;
    letter-spacing: -0.026em;
    color: var(--ink-strong);
    overflow-wrap: anywhere;
  }

  .quiz__card-rule {
    height: 0;
    margin: 8px 0 3px;
    border-top: 1px dotted var(--field-line);
  }

  .quiz__card-cover {
    width: 58%;
    min-width: 140px;
    height: 32px;
    border-radius: var(--radius);
    background: var(--ink-strong);
  }

  .quiz__card-pick {
    display: none;
    font-family: var(--display);
    font-size: 27px;
    font-weight: 600;
    line-height: 1.05;
    letter-spacing: -0.02em;
    color: var(--accent);
    overflow-wrap: anywhere;
  }

  /*
   * One rule per choice, because CSS can ask which radio is checked but cannot
   * carry its label across. Three of them, and three is not a number that
   * moves: Quiz.Choices is a const — "fewer would be a coin toss and more would
   * be a memory test". Change it there and these come with it.
   */
  .quiz:has(.quiz__input:checked) .quiz__card-cover {
    display: none;
  }

  .quiz:has(#choice-0:checked) .quiz__card-pick[data-pick="0"],
  .quiz:has(#choice-1:checked) .quiz__card-pick[data-pick="1"],
  .quiz:has(#choice-2:checked) .quiz__card-pick[data-pick="2"] {
    display: block;
  }

  /* The card above says the word, so the question does not have to. */
  .quiz__question {
    display: none;
  }

  .quiz__pick {
    display: block;
    margin: 0;
    font-size: 15px;
    line-height: 1.5;
    color: var(--ink);
  }

  .quiz {
    gap: 12px;
  }

  /* Answering is picking a name for the card, so the names are set the way the
     card will set the one that wins. */
  .quiz__chip {
    min-height: 44px;
    padding: 0 16px;
    font-family: var(--display);
    font-size: 19px;
    font-weight: 600;
    letter-spacing: -0.01em;
    border-radius: var(--radius);
  }

  .quiz__options {
    gap: 8px;
  }

  .quiz__reroll {
    min-height: 44px;
    font-size: 14px;
  }

  .submit .form-field input {
    height: 46px;
  }

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

  /*
     Stacked, with what you came to do on top. Reversed rather than reordered by
     hand: the row reads left-to-right as secondary-then-primary on a wide
     screen, and top-to-bottom as primary-then-secondary on a narrow one, which
     is the same ranking read the way each shape is read.
  */
  .submit__foot {
    flex-direction: column-reverse;
    align-items: stretch;
  }

  .submit__actions {
    width: 100%;
  }

  .submit__actions .btn {
    flex: 1 1 auto;
    justify-content: center;
    min-height: 46px;
  }

  .submit__foot .btn {
    min-height: 46px;
  }

  /* The cross in the head is already the way out of a sheet, and 3b draws no
     second one; the arrow beside it is already the way back, so the foot's own
     back button has nothing left to do either. What the foot holds here is the
     one thing this step is for. */
  .submit__cancel,
  .submit__foot-back {
    display: none;
  }

  .submit__head,
  .submit__body,
  .submit__foot {
    padding-left: 18px;
    padding-right: 18px;
  }

  /* Air under the pips, and under the folded row that hangs off them on the two
     later steps — without it the first label sat straight against the card. A
     wide panel has none, because there the head runs straight into the row being
     typed. Not on the receipt either: its own head already stands off from what
     it introduces. */
  .submit form .submit__body {
    padding-top: 18px;
  }

  /* ---- the receipt ---- */

  /*
   * On a phone the stamp goes. It had nowhere to put the two things somebody
   * most wants to read back — whether their name is going with it and whether
   * they will hear anything — so those are two of four labelled lines instead,
   * with the sentence that used to sit under the stamp moved above them, where
   * it is the thing the lines are the record of.
   */
  .receipt,
  .submit__thanks {
    display: none;
  }

  .submit__sent-body {
    display: block;
    margin: 0;
    font-size: 14.5px;
    line-height: 1.6;
    color: var(--ink);
    text-wrap: pretty;
  }

  /*
   * The big card, arrived at the end. The row as it will stand — the term, and
   * under it the brand it lost to, uncovered — with the three things that are
   * not on a register row under a rule at its foot.
   *
   * It is the card the first step used to carry. The design moved it here,
   * where the row is finished and worth looking at, and left the first step the
   * small one it shares with the two steps after it.
   */
  .sent-card {
    --size: 32px;

    display: flex;
    flex-direction: column;
    gap: 6px;
    padding: 16px 16px 15px;
    background: var(--ground);
    border: 1px solid var(--field-line);
    border-top: 2px solid var(--ink-strong);
    border-radius: 4px;
    box-shadow: 0 14px 30px -26px var(--scrim-strong);
  }

  /* Sized by the longer of the two words, because no script measures text here
     — the same rungs the stack's own cards are cut to. */
  .sent-card[data-size="2"] {
    --size: 28px;
  }

  .sent-card[data-size="3"] {
    --size: 24px;
  }

  .sent-card__label {
    font-size: 9.5px;
    letter-spacing: 0.18em;
    text-transform: uppercase;
    color: var(--faint);
  }

  .sent-card__label--brand {
    color: var(--faint-2);
  }

  .sent-card__term,
  .sent-card__brand {
    font-family: var(--display);
    font-size: var(--size);
    font-weight: 600;
    line-height: 1;
    letter-spacing: -0.026em;
    color: var(--ink-strong);
    overflow-wrap: anywhere;
  }

  .sent-card__brand {
    line-height: 1.05;
    color: var(--accent);
  }

  .sent-card__rule {
    height: 0;
    margin: 8px 0 3px;
    border-top: 1px dotted var(--field-line);
  }

  /* The card's foot: what was sent that a row has no column for. Quieter than
     the two words above it, and under a solid rule rather than the dotted one,
     because this is a different kind of thing and not a third cell. */
  .receipt-lines {
    display: grid;
    grid-template-columns: minmax(66px, auto) minmax(0, 1fr);
    gap: 6px 10px;
    margin-top: 13px;
    padding-top: 11px;
    border-top: 1px solid var(--line);
  }

  /*
   * A grid rather than a row per line, and the row itself display: contents, so
   * that the label and the value are the grid's own items and the label column
   * is one width down the whole card.
   *
   * 66px is the canvas's own, kept as a floor rather than as the width: it fits
   * "uitleg" and is overrun by "explanation", which as a fixed width then began
   * its value further right than the two lines under it. As a minimum the Dutch
   * card is drawn exactly as the canvas draws it and any language that needs
   * more simply takes it — which is the only way to size this, since nothing
   * here may name a language.
   *
   * The row keeps its type: display: contents takes the box away, not the
   * inheritance.
   */
  .receipt-line {
    display: contents;
    font-size: 13px;
    line-height: 1.45;
    color: var(--muted);
  }

  .receipt-line__label {
    padding-top: 2px;
    font-size: 9.5px;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--faint);
  }

  .receipt-line__value {
    min-width: 0;
    overflow-wrap: anywhere;
    text-wrap: pretty;
  }

  /*
   * The thank-you screen on a phone. The eyebrow goes — on a sheet this deep
   * the title is the first thing read and the design lets it be — and the two
   * ways on stop sharing a line: leaving and starting again are both a whole
   * thumb wide here, one under the other, in the order the design puts them.
   */
  .submit--sent .submit__step {
    display: none;
  }

  .submit--sent .submit__title {
    margin-top: 0;
    font-size: 26px;
    line-height: 1.1;
  }

  /* Written out in full: the wide rule above sets the sides as a shorthand and
     outranks the one that gives every part of the panel the sheet's 18px. */
  .submit--sent .submit__head {
    padding: 26px 18px 14px;
  }

  .submit--sent .submit__body {
    gap: 14px;
  }

  .submit__after {
    flex-direction: column;
    align-items: stretch;
    gap: 10px;
  }

  .submit__after .btn {
    height: 48px;
    font-size: 15px;
  }

  .submit__back {
    padding: 8px 0;
    font-size: 15px;
    text-align: center;
  }
}

/*
 * Who brought the word, when they asked to be named. Quiet: it belongs to the
 * row's facts, not to the word. The capitals the rest of that column is set in
 * are for labels, and a person's name is not one — it keeps whatever capitals
 * its owner wrote it with.
 */
.entry__credit {
  font-style: italic;
  text-transform: none;
}

/* ---------- de wachtrij ---------- */

/*
 * What has been proposed, in the shape the register is managed in: one sortable
 * list on the ground, ruled off with the same heavy line. .row and everything
 * hanging off it does the work — this section only says where the eight columns
 * fall and draws the three cells /admin has no use for.
 *
 * The two queues the brand question sorts into used to be two headings with a
 * list under each. They are a column now, which is what lets the rest of the
 * queue be ordered by anything else and still show, per row, which ones want a
 * second look.
 */
.row--queue {
  /* Holds the band every row is lit on, below. */
  position: relative;
  grid-template-columns:
    22px minmax(72px, 1.24fr) minmax(64px, 0.86fr) minmax(88px, 0.62fr)
    minmax(60px, 0.74fr) 68px 96px 140px;
}

/*
 * Every ground a row can be on — under the pointer, chosen, or the one the
 * dialog is open on — is one layer behind the row, reaching 12px past the
 * columns at both ends.
 *
 * Wider than the columns because the tick and the two buttons sit at the ends
 * of the grid: a band that stopped at the columns would run straight through
 * them and leave both against an edge. Behind the row rather than as its
 * background because the row's own box is where the rule under it is, and
 * widening that would rule the list off wider than the heading over it.
 * Nothing on this page paints between the two, so a negative z-index puts the
 * band under the row's content without burying it under the page.
 *
 * The layer replaces .row's own background, so the two rules it inherits are
 * turned off here — both are outranked from this section on source order
 * alone.
 */
.row--queue:not(.row--head)::before {
  content: "";
  position: absolute;
  inset: 0 -12px;
  z-index: -1;
  border-radius: var(--radius);
  transition: background 0.18s ease;
}

.row--queue:hover,
.row--queue.row--active {
  background: transparent;
}

.row--queue:not(.row--head):hover::before {
  background: var(--row-hover);
}

.row--queue:has(.row__pick:checked)::before {
  background: var(--row-hover);
}

/*
 * Chosen and under the pointer. A row that is already lit has to answer the
 * pointer with something, so hover goes one rung further up the same ladder
 * rather than repeating the shade the row is already wearing — which is what
 * made a chosen row feel dead to the mouse. It takes a token of its own
 * because the rung above --row-hover is a different one in each palette; see
 * --row-chosen.
 */
.row--queue:has(.row__pick:checked):hover::before {
  background: var(--row-chosen);
}

/*
 * The row the dialog is open on. Last, and carrying a :not() it does not need,
 * so it outranks the two above rather than tying with them: without script the
 * dialog is not modal — the server renders it with [open] — and the list
 * behind it can still be hovered, which would otherwise wipe this shade off
 * the one row the dialog is about.
 */
.row--queue.row--active:not(.row--head)::before {
  background: var(--surface-2);
}

/* With script a click anywhere on a row opens its review, as on /admin. */
:root.js .row--queue:not(.row--head) {
  cursor: pointer;
}

/* The register's own accent, so the eye can pick the box out of a column of
   ellipsised words without it having to be big. */
.row__pick {
  width: 16px;
  height: 16px;
  margin: 0;
  accent-color: var(--accent);
  cursor: pointer;
}

.row__who {
  font-size: 14px;
  color: var(--ink);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.row__when {
  font-size: 13px;
  color: var(--faint);
  white-space: nowrap;
}

/* Outlined rather than filled: this sits in a column of plain text and only has
   to be legible as a category, not as a warning. The accent is the ordinary
   case and the ink the one asking for a look — which is the right way round,
   because ink is what the rest of the row is set in and therefore what a glance
   does not skip. */
.queue-tag {
  display: inline-flex;
  align-items: center;
  height: 20px;
  padding: 0 7px;
  border: 1px solid var(--accent);
  border-radius: 2px;
  font-size: 10.5px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--accent);
  white-space: nowrap;
}

.queue-tag--extra {
  border-color: var(--ink-strong);
  color: var(--ink-strong);
}

/*
 * .btn is built for the foot of a dialog, where 38px is right. In a row it
 * stands next to a 32px square, and two controls of different heights side by
 * side read as two different kinds of thing.
 */
.row__open-text {
  height: 32px;
  padding: 0 12px;
}

/* Two spellings of one link; the narrow block below swaps them. */
.row__open-icon {
  display: none;
}

/*
 * Out of the way until something is ticked. :has() can see a checked box where
 * a stylesheet otherwise cannot, which is what keeps the whole bulk path free
 * of script — the same reason the dialog's scroll lock is written as
 * html:has(.dialog[open]) rather than put in admin.js.
 */
.bulk-bar {
  display: none;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  padding-top: 16px;
}

/* The number is a counter, not script. counter-increment sees a checked box
   the same way the :has() below does, and the bar is drawn after every row, so
   by the time it is reached the count is complete. That is the whole of why the
   bulk bar now reads the same with script and without.

   It is also why the label lost its plural. CSS can count but it cannot choose
   between "regel" and "regels": asking whether two rows are ticked would need
   :has() inside :has(), which is invalid, and the checkboxes are never
   siblings of each other to be counted any other way. So the wording is one
   that does not inflect, and nothing has to decide.

   The reset is on main because that is what the rule below already reaches
   for, and .row__pick exists on no other page. */
main {
  counter-reset: picked;
}

.row__pick:checked {
  counter-increment: picked;
}

.bulk-bar__tally::before {
  content: counter(picked);
}

main:has(.row__pick:checked) .bulk-bar {
  display: flex;
}

.bulk-bar__count {
  font-size: 13.5px;
  color: var(--muted);
}

.bulk-bar__actions {
  display: flex;
  align-items: center;
  gap: 12px;
}

.bulk-bar__clear {
  font-size: 13.5px;
  color: var(--accent);
}

/* ---------- de wachtrij op een smal scherm ---------- */

/*
 * The row stacks into the card /admin's does, using the same grid areas — term
 * and brand above each other, the three facts on one wrapping line, the stamp
 * last. What differs is what falls away: there is no selection here, because a
 * column of checkboxes beside a stacked card is a column of nothing, and the
 * bar it feeds would never have anything to say.
 *
 * 820px is the shared breakpoint again. Keep it in step with the register and
 * with site.js.
 */
@media (max-width: 820px) {
  .row--queue {
    grid-template-columns: minmax(0, 1fr) auto;
  }

  .row__pick,
  .bulk-bar,
  main:has(.row__pick:checked) .bulk-bar {
    display: none;
  }

  /* And with the tick gone, so is the band answering it. A row can still be
     carrying a check here — ticked wide, then the window narrowed — and a band
     with nothing to explain it reads as a state the reader cannot leave. */
  .row--queue:has(.row__pick:checked)::before {
    content: none;
  }

  /* The badge is a column's worth of shorthand. With the column gone it says
     what it is, in the stamp's own voice. Only on the row: the same badge in
     the review dialog's head has no column to lose and is the one thing there
     saying how much of a look this wants. */
  .row .queue-tag {
    display: none;
  }

  .row__open-text {
    display: none;
  }

  .row__open-icon {
    display: flex;
  }
}

/* ---------- nakijken ---------- */

/*
 * The review dialog, which is three readings of one row: what was sent, what
 * will be published, and who sent it. Only the middle one is a form — the first
 * is deliberately not typeable over, because the whole point of showing it is
 * to have something the edits can be compared against.
 */
.review {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.review--who {
  padding-top: 15px;
  border-top: 1px dotted var(--line);
}

.review__label {
  font-size: 11px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--faint);
}

/* Drawn as a line of the register, because that is the question being asked of
   it: is this a row? The heavy rule over it and the dotted one under are the
   register's own. */
.review__sent {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 12px 14px;
  background: var(--surface-2);
  border-top: 1.5px solid var(--ink-strong);
  border-bottom: 1px dotted var(--line);
}

.review__pair {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 10px;
  font-family: var(--display);
  font-weight: 600;
  line-height: 1.05;
}

.review__term {
  font-size: 23px;
  color: var(--ink-strong);
}

.review__brand {
  font-size: 19px;
  color: var(--accent);
}

.review__meta,
.review__dupe,
.review__who,
.review__why p {
  margin: 0;
}

.review__meta {
  font-size: 11px;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--faint);
}

.review__why {
  display: flex;
  flex-direction: column;
  gap: 8px;
  font-size: 14.5px;
  line-height: 1.6;
  color: var(--ink);
  text-wrap: pretty;
}

.review__why--none {
  color: var(--faint-2);
}

.review__dupe {
  padding: 10px 12px;
  border: 1px solid var(--danger);
  border-radius: var(--radius);
  font-size: 13.5px;
  line-height: 1.55;
  color: var(--ink);
}

/* Beside the label, in the label's own small capitals — it belongs to the field
   and not to the value, so it must not read as one. Reset to its own tracking:
   .form-field's label sets a wider one, which at ten points sprawls. */
.field-tag {
  display: inline-flex;
  align-items: center;
  height: 17px;
  padding: 0 6px;
  border: 1px solid var(--field-line);
  border-radius: 2px;
  background: var(--surface);
  font-size: 10px;
  letter-spacing: 0.12em;
  color: var(--muted);
}

/* Nothing to say, and nothing to take up room either — otherwise every label
   would carry an empty box. */
.field-tag:empty {
  display: none;
}

.review__who {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 4px 12px;
  font-size: 13.5px;
  color: var(--faint);
}

.review__name {
  font-size: 14.5px;
  color: var(--ink);
}

/*
 * Two radios drawn as a pair of buttons. Radios rather than a checkbox because
 * the two states are a choice with two names — "anoniem publiceren" is a thing
 * the maintainer decides, not the absence of a decision — and because a real
 * radio is what makes this work with the keyboard and with no script at all.
 */
.chips {
  display: flex;
  flex-wrap: wrap;
  gap: 7px;
}

.chip {
  position: relative;
  display: inline-flex;
  cursor: pointer;
}

/*
 * Hidden by being invisible, not by being displaced. A radio parked at 1px in
 * the corner is somewhere the browser will scroll to when it takes focus —
 * over the whole dialog, which is a box the reader cannot scroll back. Laid
 * over its own button instead, it is already in view and scrolling it into
 * view is a no-op. It also keeps the hit area honest: the click lands on the
 * control itself rather than on a label pointing at one.
 */
.chip input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  appearance: none;
  opacity: 0;
  cursor: pointer;
}

.chip span {
  display: inline-flex;
  align-items: center;
  height: 34px;
  padding: 0 13px;
  border: 1px solid var(--field-line);
  border-radius: var(--radius);
  background: var(--surface);
  font-size: 13.5px;
  color: var(--ink);
}

.chip input:checked + span {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
}

/* The radio is hidden, so the ring it would have taken has to land on the
   button that stands in for it. */
.chip input:focus-visible + span {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/*
 * Tall enough for a thumb, like every other control at this width. It sits
 * here rather than with the rest of the narrow rules further up because a
 * media query buys no specificity: `.chip span` is defined above and would
 * simply win on source order from anywhere earlier in the file. Same trap the
 * 16px floor at the end of this file is written around.
 */
@media (max-width: 820px) {
  .chip span {
    height: 42px;
    font-size: 15px;
  }
}

/* ---------- het bonnetje ---------- */

/*
 * What publishing did, in three lines. It exists because the submission is gone
 * by the time this renders: there is no row to go back and look at, so the
 * screen has to say what became of it — including the one thing that cannot be
 * looked up afterwards, the address to write to.
 */
.done {
  display: flex;
  flex-direction: column;
  gap: 16px;
  padding: 26px 24px 28px;
}

.done__head {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.done__kicker {
  font-size: 11px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--faint);
}

.done__title {
  margin: 0;
  font-family: var(--display);
  font-size: 29px;
  font-weight: 600;
  line-height: 1.08;
  letter-spacing: -0.02em;
  color: var(--ink-strong);
}

.done__lines {
  display: flex;
  flex-direction: column;
  border-top: 1px solid var(--line);
}

.done__line {
  display: flex;
  gap: 14px;
  padding: 11px 0;
  border-bottom: 1px dotted var(--line);
  font-size: 14px;
  line-height: 1.5;
  color: var(--ink);
  text-wrap: pretty;
}

.done__label {
  flex: 0 0 92px;
  padding-top: 3px;
  font-size: 11px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--faint);
}

.done__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px;
}

.done__link {
  font-size: 14px;
  color: var(--accent);
}

@media (max-width: 820px) {
  .done__line {
    flex-direction: column;
    gap: 2px;
  }

  .done__label {
    flex: 0 0 auto;
    padding-top: 0;
  }
}

/* ---------- privacy ---------- */

/*
 * What the site keeps about a visitor, drawn the way the register draws an
 * open entry: the heavy rule, then rows with the label in a margin against a
 * seam and the text on the other side of it. The labels are set like
 * .entry__meta and the paragraphs like .entry__notes, so the page reads as
 * part of the register rather than as a legal page bolted onto it.
 */
.privacy__lead {
  max-width: 40ch;
  margin: 0;
  padding: 34px 0 30px;
  font-size: 18px;
  line-height: 1.5;
  color: var(--ink-strong);
  text-wrap: pretty;
}

.privacy__rows {
  border-top: 1.5px solid var(--ink-strong);
}

.privacy__row {
  display: grid;
  grid-template-columns: 156px minmax(0, 1fr);
}

.privacy__row + .privacy__row {
  border-top: 1px solid var(--line);
}

/* Four pixels lower than the text beside it, which is what puts the label's
   baseline on the first line's: 12px on a 1.5 line against 15.5px on 1.65. */
.privacy__label {
  margin: 0;
  padding: 22px 24px 26px 0;
  text-align: right;
  font-size: 12px;
  font-weight: 400;
  line-height: 1.5;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--faint);
  text-wrap: balance;
}

.privacy__text {
  display: flex;
  flex-direction: column;
  gap: 12px;
  min-width: 0;
  padding: 18px 0 26px 26px;
  border-left: 1px solid var(--line);
}

.privacy__text p {
  margin: 0;
  font-size: 15.5px;
  line-height: 1.65;
  color: var(--ink);
  text-wrap: pretty;
}

.privacy__text a {
  text-decoration-thickness: 1px;
}

/* "toon het adres": a button, because it posts, set as the link it stands in
   for, because it sits in a sentence. Font and colour come from the base
   button rule; this only takes the button off it. */
.privacy__reveal {
  padding: 0;
  border: 0;
  background: none;
  color: var(--accent);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
  cursor: pointer;
}

@media (max-width: 820px) {
  /* The page runs edge to edge below this width, so the text carries the
     gutter itself — the header already carries its own. */
  .privacy {
    padding: 0 var(--gutter);
  }

  .privacy__lead {
    font-size: 17px;
  }

  /* No room for a margin: the label moves above its text and the seam goes,
     which leaves the one column a phone wants anyway. */
  .privacy__row {
    grid-template-columns: minmax(0, 1fr);
    padding: 20px 0 26px;
  }

  .privacy__label {
    padding: 0 0 10px;
    text-align: left;
    font-size: 11px;
    letter-spacing: 0.18em;
  }

  .privacy__text {
    padding: 0;
    border-left: 0;
  }
}

/* ---------- about ---------- */

/*
 * What a watermerk is: an article, and beside it a column of two lists — the
 * archive under the register's heavy rule, the reading under a light one, which
 * is how the canvas ranks them. The type is the register's: the heading and the
 * lead in the display face, the years and the marks in it too, the rest set
 * like .entry__notes. Below 820px the column drops under the article.
 */
.about__grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 280px;
  gap: 56px;
  padding-top: 40px;
}

.about__article {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.about__kicker {
  margin: 0;
  font-size: 11px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--faint);
}

.about__title {
  max-width: 18ch;
  margin: 14px 0 0;
  font-family: var(--display);
  font-size: 48px;
  font-weight: 600;
  line-height: 1.06;
  letter-spacing: -0.024em;
  color: var(--ink-strong);
  text-wrap: pretty;
}

.about__lead {
  max-width: 58ch;
  margin: 26px 0 0;
  font-family: var(--display);
  font-size: 21px;
  line-height: 1.5;
  color: var(--ink-strong);
  text-wrap: pretty;
}

.about__text {
  max-width: 62ch;
  margin: 18px 0 0;
  font-size: 16px;
  line-height: 1.7;
  color: var(--ink);
  text-wrap: pretty;
}

.about__text a {
  text-decoration-thickness: 1px;
}

.about__actions {
  display: flex;
  padding-top: 30px;
}

.about__aside {
  display: flex;
  flex-direction: column;
  gap: 34px;
  min-width: 0;
}

.about__label {
  margin: 0;
  padding-bottom: 4px;
  border-bottom: 1px solid var(--line);
  font-size: 11px;
  font-weight: 400;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--faint);
}

.about__label--heavy {
  padding-bottom: 10px;
  border-bottom: 1.5px solid var(--ink-strong);
}

.archive,
.reading {
  margin: 0;
  padding: 0;
  list-style: none;
}

/* A year in the margin, and beside it the mark, what became of it, and where
   that is written down. */
.archive__year {
  display: grid;
  grid-template-columns: 52px minmax(0, 1fr);
  gap: 12px;
  padding: 14px 0;
  border-bottom: 1px solid var(--line);
}

.archive__when {
  padding-top: 3px;
  font-family: var(--display);
  font-size: 15px;
  font-weight: 600;
  color: var(--faint);
}

.archive__body {
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
}

.archive__mark {
  font-family: var(--display);
  font-size: 20px;
  font-weight: 600;
  line-height: 1.1;
  letter-spacing: -0.012em;
  color: var(--accent);
}

.archive__text {
  font-size: 13.5px;
  line-height: 1.55;
  color: var(--muted);
  text-wrap: pretty;
}

.archive__source {
  display: inline-flex;
  align-self: flex-start;
  align-items: center;
  gap: 5px;
  padding: 4px 0;
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

/* The whole line is the link, 52px at least, so a thumb has something to
   land on; only the title is underlined, because only the title is a name. */
.reading__link {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 2px;
  min-height: 52px;
  padding: 8px 0;
  border-bottom: 1px solid var(--line-soft);
  text-decoration: none;
}

.reading__title {
  font-size: 14px;
  line-height: 1.4;
  color: var(--accent);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
}

.reading__publisher {
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--faint);
}

@media (max-width: 820px) {
  /* The page runs edge to edge below this width, as on /privacy. */
  .about {
    padding: 0 var(--gutter);
  }

  .about__grid {
    grid-template-columns: minmax(0, 1fr);
    gap: 40px;
    padding-top: 22px;
  }

  .about__title {
    margin-top: 12px;
    font-size: 33px;
    line-height: 1.1;
  }

  .about__lead {
    margin-top: 20px;
    font-size: 19px;
  }

  .about__text {
    margin-top: 14px;
    font-size: 15.5px;
  }

  /* The way back as wide as a thumb is: the whole line, at the height every
     other button on a phone has. */
  .about__cta {
    flex: 1 1 auto;
    height: 46px;
    font-size: 13px;
  }
}

/* ---------- the 16px floor ---------- */

/*
 * Safari on iPhone zooms the page in when the text control it is about to
 * focus is set smaller than 16px. That is not only a change of scale: a zoomed
 * page has a visual viewport smaller than its layout, and that viewport pans —
 * over the whole document, the page behind an open dialog included. No scroll
 * lock reaches it, because panning a zoomed page is not scrolling, and both
 * locks are about scrolling. Tapping "term" in the nieuw-merk sheet left the
 * register loose behind it, and the search field did the same to the register
 * itself.
 *
 * The floor goes on the controls and not in the viewport meta. Writing
 * `maximum-scale=1` there buys the same quiet by taking pinch-zoom away from
 * everybody, which is not a trade to make on somebody else's behalf.
 *
 * 820px is the shared breakpoint again — wider than the phones this is about,
 * which costs a narrowed desktop window slightly larger fields and nothing
 * else. The size each field is given where it is defined stays the wide
 * screen's, so this has to outrank all of them, which is why the section sits
 * last and why `.submit .form-field input` is named again: it carries two
 * classes, and a shorter selector would lose to it however late it came.
 *
 * Every control has to be named, and the list is longer than it looks, because
 * the drawer's first step does not use .form-field at all — the row a visitor
 * proposes is drawn as the register's own row, so its cells are .draft__ and
 * carry the sizes that go with that.
 *
 * Two of those cells used to be named here and no longer are. Categorie,
 * merkhouder and generiek sinds are not on a phone at all since the first step
 * became three fields, so there is nothing left to raise; and the two word
 * fields are set at 16.5px up there, deliberately over this floor rather than
 * on it, because they are what the step is for and a half point reads. Adding
 * them to this list would quietly take that half point away again.
 */
@media (max-width: 820px) {
  .field input,
  .form-field input,
  .form-field select,
  .form-field textarea,
  .submit .form-field input,
  .draft__notes {
    font-size: 16px;
  }
}
