/* Breacer Notify -- index.
 *
 * Step 2: top bar, header, input pill, corner icons and the glow.
 *
 * This layer sits AFTER design.css (typographic scale, motion) and
 * tokens.css (spacing, radius, surfaces). Anything here that is a number
 * instead of a token has a reason given next to it.
 */

/* This page's tokens (colors, border, glass, the two tempos) have lived
 * in tokens.css and design.css since phase 2.1: they were byte-identical to
 * dash.css's and the other pages', and componenten.css needs them everywhere.
 * Nothing page-specific is left here. */

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

/* A <button> does NOT inherit the page's font; without this rule it shows
 * Arial, even when a font-family is set further down on body. Invisible for
 * now (it only holds an icon), but the first button label would silently
 * appear in the wrong font. */
button { font: inherit; }

html {
  /* Tells the browser this is a dark page, so scrollbars,
   * form fields and the overscroll edge tint along instead of flashing
   * white. The meta tag alone doesn't cover that. */
  color-scheme: dark;
  background: var(--surface-0);
}

body {
  margin: 0;
  min-height: 100dvh;
  background: var(--surface-0);   /* #000000 -- truly off, not almost off */
  color: var(--tekst);
  font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
  font-size: var(--fs-md);
  line-height: var(--lh-base);
  -webkit-font-smoothing: antialiased;

  /* Longhand, not the shorthand: design.css sets its own padding-left/right for
   * the safe area, and a shorthand here would silently override that. */
  padding-top: env(safe-area-inset-top, 0px);
  padding-bottom: env(safe-area-inset-bottom, 0px);
  padding-left: env(safe-area-inset-left, 0px);
  padding-right: env(safe-area-inset-right, 0px);
}

/* --- The glow --------------------------------------------------------------
 * One blue oval behind the input field, that rises on load.
 *
 * It runs ONCE and not forever: motion that keeps going at rest is noise,
 * not atmosphere. Only opacity and transform move -- that's compositor work
 * and costs no layout or paint per frame.
 */
.glow {
  position: fixed;
  left: 50%;
  top: 46%;
  z-index: 0;
  width: min(1180px, 132vw);
  aspect-ratio: 16 / 10;
  transform: translate(-50%, -50%);
  opacity: 0.88;
  pointer-events: none;
  background: radial-gradient(closest-side ellipse at 50% 50%,
      rgba(0,122,255,0.30)  0%,
      rgba(0,90,224,0.17)  36%,
      rgba(0,48,168,0.075) 62%,
      rgba(0,0,0,0)       100%);
  /* `backwards` and not `both`: with `both` the animation's end state stays
   * on the element as a fill, and that beats any ordinary rule -- the glow
   * could then never react to anything again. With `backwards` the start
   * state applies before it begins and we fall back afterward to the rules
   * above, which are exactly equal to the end state. No jump, but still
   * controllable. */
  animation: gloedKomtOp 1400ms var(--ease-enter) backwards;
  transition: opacity var(--dur-scherm) var(--ease),
              transform var(--dur-scherm) var(--ease);
}

@keyframes gloedKomtOp {
  from { opacity: 0; transform: translate(-50%, -44%) scale(0.90); }
  to   { opacity: 0.88; transform: translate(-50%, -50%) scale(1); }
}

/* --- Top bar ------------------------------------------------------------- */
.bar {
  --merk-hoogte: 32px;                 /* see .brand-mark in componenten.css */
  --merk-naam-grootte: var(--fs-md);
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-4);
  padding: var(--sp-5) var(--sp-6);
}




.bar-nav {
  display: flex;
  align-items: center;
  gap: var(--sp-5);
}

.bar-link {
  padding: 12px 0;
  color: var(--tekst);
  text-decoration: none;
  font-size: var(--fs-md);
  font-weight: 500;
  letter-spacing: var(--track-md);
  transition: opacity var(--dur-fast) var(--ease);
}
.bar-link:hover { opacity: 0.72; }


/* --- Middle ----------------------------------------------------------------
 * Header and pill sit together at ~40% of the height, so above the middle.
 * The emptiness below is not leftover space: it carries the glow.
 */
.center {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-8);
  padding: 14vh var(--sp-5) 0;
}

/* <breacer-keten> (assets/flow.js) carries the flow and is otherwise
 * invisible: with display:contents the pill and the screens remain direct
 * flex items of .center, exactly as when the markup stood here loose. */
breacer-keten { display: contents; }

.heading {
  margin: 0;
  /* NO width limit: it used to be 22ch and silently broke the header onto two
   * lines, while the requirement is 'one line'. On a phone a sentence of 43
   * characters physically doesn't fit on one line at a readable size -- it
   * still breaks there, and text-wrap:balance then splits the two halves
   * evenly. */
  max-width: 100%;
  text-align: center;
  /* Both ends are tokens; in between it scales with the screen. */
  font-size: clamp(var(--fs-2xl), 4.4vw, var(--fs-3xl));
  font-weight: 400;              /* light, like the example -- not bold   */
  line-height: var(--lh-tight);
  letter-spacing: var(--track-lg);
  color: var(--tekst);
  text-wrap: balance;
}

/* --- The input pill ---------------------------------------------------------- */
.pill {
  position: relative;          /* the loading pill lives inside here */
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  width: min(720px, 100%);
  min-height: 58px;
  padding: var(--sp-2) var(--sp-4);
  border: var(--rand-dikte) solid var(--rand);
  border-radius: var(--r-full);
  background: var(--surface-0);   /* black, not gray */
  transition: border-color var(--dur-base) var(--ease),
              background-color var(--dur-base) var(--ease);
}
/* The pill shows focus with its OWN border, not a ring around it. There
 * was `box-shadow: 0 0 0 3px rgba(0,122,255,0.28)`; that put a blue halo
 * around a surface that otherwise consists only of black and a white
 * hairline. Going from 0.22 to 0.55 white is a clearly visible change and
 * stays the same material. The width does NOT change -- 1px to 2px would
 * shift the content by a pixel on every click. */
.pill:focus-within {
  /* No extra ring anymore. It was there because a 1px border did not meet
   * WCAG 2.2's thickness requirement (2px); with --rand-dikte at 2px the
   * border itself meets it, and two rings on top of each other is needless
   * weight. If --rand-dikte ever drops back to 1px, that ring needs to come
   * back. */
  border-color: rgba(255,255,255,0.55);
}

.pill-plus {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 44px;
  height: 44px;
  margin: -1px 0;             /* the pill is 58 tall with 8px of padding: 42 inside; 44 pokes 1px into the padding */
  padding: 0;                 /* without this the browser default 1px 6px shows through */
  border: 0;
  border-radius: var(--r-full);
  background: transparent;
  color: var(--tekst);
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease);
}
.pill-plus:hover { background: var(--surface-2); }

.pill-input {
  flex: 1;
  min-width: 0;
  border: 0;
  outline: 0;
  background: transparent;
  color: var(--tekst);
  font-family: inherit;
  font-size: var(--fs-lg);
  line-height: var(--lh-snug);
}
.pill-input::placeholder { color: var(--tekst-gedempt); }

/* NO own focus ring on this field. The general rule further down puts a
 * blue outline with a 3px offset around it, and that draws a rectangle
 * INSIDE the round pill -- visible as a blue box. Chromium also applies
 * :focus-visible to text fields on a mouse click, so you'd always get it.
 * It's fine to omit it here because the pill itself already lights up;
 * that is the indicator. */
.pill-input:focus-visible { outline: 0; }

/* --- Bottom-left corner ------------------------------------------------------- */
.corner {
  position: fixed;
  z-index: 1;
  left: calc(var(--sp-5) + env(safe-area-inset-left, 0px));
  bottom: calc(var(--sp-5) + env(safe-area-inset-bottom, 0px));
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
}

/* The <button> browser default must be EXPLICITLY removed here. These
 * buttons used to be <a> elements; since they open a menu they are buttons,
 * and then a background of rgb(107,107,107), a white 2px border and 1px 6px
 * padding came through -- visible as two gray dots with a white ring. A
 * class selector only overrides what it itself sets. */
.corner-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  padding: 0;
  border: 0;
  background: transparent;
  border-radius: var(--r-full);
  color: var(--tekst);
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease);
}
.corner-btn:hover { background: var(--surface-2); }


/* --- Reduced motion -------------------------------------------------------
 * The glow then doesn't rise, it's simply there. Omitting it would remove
 * the page's only atmospheric element; the point is the motion, not the light.
 */
@media (prefers-reduced-motion: reduce) {
  .glow { animation: none; }
  * { transition-duration: 1ms !important; }
}

/* --- Narrow screen ----------------------------------------------------------- */
@media (max-width: 640px) {
  .bar { padding: var(--sp-4); }
  .center { padding-top: 12vh; gap: var(--sp-6); }
  .bar-nav { gap: var(--sp-4); }
  /* On a phone the bar doesn't fit: brand + two text links + the button is
     441px on a screen of 390 (measured, 1 Sep 2026 -- the button fell half
     out of view and the brand name wrapped to two lines). The text links
     also live in the bottom-left profile menu; only the button stays here. */
  .bar-link { display: none; }
  .bar-nav { flex: none; }
}

/* ===========================================================================
 * Step 3: session states, menus and the send button
 * ========================================================================= */


/* --- Top bar: second state ----------------------------------------------
 * .balk-inloggen is now called .bar-btn: the same pill serves "Sign in"
 * when signed out and "My dashboard" when signed in.
 */
/* NO solid blue anymore. On this page blue means "this is the next step",
 * and that can only be ONE thing at a time: on the answer screen that's
 * Activate, on the confirmation it's Test it now. The top bar is
 * navigation, not a step. Same material as the pill and the menus: black
 * with a hairline. */
.bar-btn {
  display: inline-flex;
  align-items: center;
  padding: 10px var(--sp-5);
  border: var(--rand-dikte) solid var(--rand);
  border-radius: var(--r-full);
  background: var(--surface-0);
  color: var(--tekst);
  text-decoration: none;
  font-size: var(--fs-md);
  font-weight: 600;
  letter-spacing: var(--track-md);
  white-space: nowrap;
  transition: transform var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease);
}
.bar-btn:hover { border-color: var(--rand-actief); }
.bar-btn:active { transform: scale(0.97); }

/* --- The send button -------------------------------------------------------
 * Appears as soon as there's text. It's ALWAYS in the layout and is only
 * made visible: if it only took up space then, the input field would shift
 * exactly at the moment you type the first character.
 * visibility (not display) also keeps it out of tab order while it's away.
 */
.pill-send {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 44px;               /* the touch target, same as the plus on the left */
  height: 44px;
  padding: 0;
  border: 0;
  border-radius: var(--r-full);
  background: var(--blauw-knop);
  color: #FFFFFF;
  cursor: pointer;
  visibility: hidden;
  opacity: 0;
  /* Enters from the RIGHT and grows; only opacity and transform, so
   * compositor work with no layout or paint per frame. At --dur-base
   * (200ms) with the normal easing it was already done after 180ms and
   * barely visible; --dur-slow with the spring lets it really arrive. */
  transform: translateX(10px) scale(0.55);
  transition: opacity var(--dur-slow) var(--ease-enter),
              transform var(--dur-slow) var(--ease-spring),
              background var(--dur-fast) var(--ease);
}
.pill.has-text .pill-send {
  visibility: visible;
  opacity: 1;
  transform: translateX(0) scale(1);
}
.pill-send:hover { background: #0A74F0; }
.pill-send:active { transform: translateX(0) scale(0.92); }

/* --- The profile area -------------------------------------------------------
 * Sixteen fixed gradients, chosen on accounts.breacer.com. No uploaded
 * photo: there is no storage for images and an avatar shouldn't be the
 * first thing to ask for that.
 */
.avatar {
  display: block;
  width: 26px;
  height: 26px;
  border-radius: var(--r-full);
  background: radial-gradient(circle at 35% 30%, var(--groen), var(--blauw) 90%);
}
.avatar-large { width: 38px; height: 38px; }

/* --- Menus ----------------------------------------------------------------
 * Black on black, so the border does the work -- same reasoning as the
 * input pill. They open to the RIGHT of their own button and grow upward,
 * so they don't overlap each other and stay within the screen.
 */
.corner-group { position: relative; }


.menu-heading {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-3);
  margin-bottom: var(--sp-1);
  border-bottom: var(--rand-dikte) solid var(--hairline);
}
.menu-heading-id { display: flex; flex-direction: column; min-width: 0; flex: 1; }
.menu-name {
  font-size: var(--fs-md);
  font-weight: 600;
  letter-spacing: var(--track-md);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.menu-email {
  font-size: var(--fs-xs);
  color: var(--tekst-gedempt);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.plan-pill {
  flex: 0 0 auto;
  padding: 2px var(--sp-2);
  border-radius: var(--r-full);
  background: var(--surface-2);
  color: var(--tekst);
  font-size: var(--fs-2xs);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.plan-pill.is-pro { background: var(--blauw-knop); }

.menu-row svg { flex: 0 0 auto; }

/* The theme row: visible, but not something you can press. */
.menu-row.is-off { cursor: default; color: var(--tekst-gedempt); }
.menu-row.is-off:hover { background: transparent; }

.menu-value {
  margin-left: auto;
  font-size: var(--fs-sm);
  color: var(--tekst-gedempt);
}

/* ===========================================================================
 * Step 4: the answer screen
 * ========================================================================= */

.answer {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-5);
  width: min(880px, 100%);
  margin-top: var(--sp-6);
}

/* The confirmation sentence. PILL-SHAPED and exactly the same material as
 * a menu -- black, the same hairline, the same shadow. It takes the place
 * of the input pill that stood there a moment ago, so it should have the
 * same shape.
 * The browser test compares the computed style with that of a menu. */
.answer-sentence {
  margin: 0;
  padding: var(--sp-4) var(--sp-6);
  max-width: 100%;
  border: var(--rand-dikte) solid var(--rand);
  border-radius: var(--r-full);
  background: var(--glas);
  backdrop-filter: var(--glas-waas);
  -webkit-backdrop-filter: var(--glas-waas);
  box-shadow: var(--shadow-raised);
  font-size: var(--fs-lg);
  line-height: var(--lh-snug);
  text-align: center;
}

/* The block below has NO fill of its own. The blue you see in it is the
 * aurora underneath; the hairline and the rounding do the framing.
 *
 * It used to be rgba(0,0,160,0.22). That worked too, but it laid a SECOND
 * blue over the light that was already there -- flat, even in the corners
 * where the glow has long faded. Now the color inside the block is a
 * consequence of where the light comes from rather than a separate
 * decision. The labels' contrast gets roomier from this, not tighter
 * (measured: 5.29:1 with the fill). */
.controls {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: center;
  gap: var(--sp-5);
  width: 100%;
  padding: var(--sp-5);
  border: var(--rand-dikte) solid var(--hairline);
  border-radius: var(--r-md);
}

/* The answer screen is now a sentence with the button below it, not two
 * columns. SCOPED to #answer: .controls also exists in #complete, and
 * that screen still uses the columns below. */
#answer .controls {
  flex-direction: column;
  align-items: center;
  gap: var(--sp-5);
  /* No frame. The bell above already has one, and two nested rounded
     rectangles around four words and a button is one box too many. */
  padding: 0;
  border: 0;
}

.controls-left {
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
  flex: 1 1 260px;
}
.controls-right {
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
  flex: 1 1 220px;
}

/* --- The choice menu ---------------------------------------------------------
 * Built in-house, not a <select>: a system select draws its list with the
 * operating system's colors, and on a black page that's a white surface you
 * can't restyle.
 */
/* --- The four choices as a SENTENCE ------------------------------------------------
 * They no longer sit as a form one below the other but in a running
 * sentence. The mechanism is UNCHANGED -- same ids, same menus, same JS.
 * Only the placement and the label change: the sentence carries the
 * meaning, so the label ("Stop after") no longer needs to sit above it.
 */
.line-sentence {
  max-width: 46ch;
  margin: 0 auto;
  text-align: center;
  color: var(--tekst-gedempt);
  font-size: var(--fs-lg);
  line-height: 2.1;            /* room for the choices that sit in the sentence */
  letter-spacing: var(--track-md);
}

.choice { position: relative; display: flex; flex-direction: column; gap: var(--sp-1); }

/* In the sentence: inline, and the label only for a screen reader now. */
/* Row and not column, and no gap: the (invisible) label sits by default
 * ABOVE the button with var(--sp-1) between them, and that pushed the
 * word's underline away. */
.line-sentence .choice {
  display: inline-flex;
  flex-direction: row;
  gap: 0;
  vertical-align: baseline;
}
.line-sentence .choice-label {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
/* The button is now a word in a sentence, not a field. No frame around it
 * -- that would put four little boxes in a line of text -- but an
 * underline, because that says "you can adjust this" without interrupting
 * the sentence. */
.line-sentence .choice-btn {
  /* Own line height: without this the button inherits the sentence's 2.1,
     making its box 40px tall and floating the underline 20px below the
     word. */
  line-height: 1.25;
  padding: 2px 0 3px;
  border: 0;
  border-bottom: var(--rand-dikte) solid var(--rand-actief);
  border-radius: 0;
  background: transparent;
  color: var(--tekst);
  font-size: inherit;
  font-weight: 600;
  gap: 4px;
}
.line-sentence .choice-btn:hover,
.line-sentence .choice-btn[aria-expanded="true"] { border-bottom-color: var(--blauw); }
.line-sentence .choice-btn svg { opacity: 0.6; width: 14px; height: 14px; }
/* The list hangs under the WORD, so left-align it on the button. */
.line-sentence .choice-list { text-align: left; }

@media (max-width: 560px) {
  .line-sentence { font-size: var(--fs-md); line-height: 2.2; }
}

.choice-label {
  font-size: var(--fs-xs);
  color: var(--tekst-gedempt);
  letter-spacing: 0.02em;
}

.choice-btn {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  width: 100%;
  min-height: 44px;
  padding: 0 var(--sp-4);
  border: var(--rand-dikte) solid var(--rand);
  border-radius: var(--r-full);
  background: var(--surface-0);
  color: var(--tekst);
  font-size: var(--fs-md);
  font-weight: 500;
  text-align: left;
  cursor: pointer;
  transition: border-color var(--dur-fast) var(--ease);
}
.choice-btn:hover { border-color: var(--rand-actief); }
.choice-btn[aria-expanded="true"] { border-color: var(--rand-actief); }

.choice-list {
  position: absolute;
  top: calc(100% + var(--sp-2));
  left: 0;
  /* As wide as the longest option, never narrower than 220px: at the width
     of the word in the sentence ("once") "15 minutes" broke into two lines
     and the PRO badge hung outside the box. Keeping it on screen is
     maakMenu()'s job, with is-links and is-boven. */
  min-width: 220px;
  width: max-content;
  max-width: min(320px, calc(100vw - 2 * var(--sp-4)));
  max-height: min(60vh, 440px);
  overflow-y: auto;
  z-index: 6;
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: var(--sp-2);
  border: var(--rand-dikte) solid var(--rand);
  border-radius: var(--r-md);
  background: var(--glas);
  backdrop-filter: var(--glas-waas);
  -webkit-backdrop-filter: var(--glas-waas);
  box-shadow: var(--shadow-raised);
  animation: menuKomtOp var(--dur-base) var(--ease-enter) both;
}

.choice-list.is-left { left: auto; right: 0; }
.choice-list.is-top { top: auto; bottom: calc(100% + var(--sp-2)); }

.choice-option {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  width: 100%;
  white-space: nowrap;
  padding: 10px var(--sp-3);
  border: 0;
  border-radius: var(--r-sm);
  background: transparent;
  color: var(--tekst);
  font-size: var(--fs-md);
  text-align: left;
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease);
}
.choice-option:hover { background: var(--surface-1); }
.choice-option[aria-selected="true"] { background: var(--surface-2); }

.choice-pro {
  margin-left: auto;
  padding: 1px 6px;
  border-radius: var(--r-full);
  background: var(--surface-2);
  color: var(--tekst-gedempt);
  font-size: var(--fs-2xs);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

/* --- Fine-tuning ---------------------------------------------------------
 * Behind ONE click, not as four extra menus in the sentence: that sentence
 * is what sets this screen apart from a form, and four more choices would
 * make it a settings panel after all. The line above shows only what
 * DEVIATES from the default -- a line that always says the same thing is
 * not information.
 */
.fine-sentence {
  margin: var(--sp-3) 0 0;
  font-size: var(--fs-sm);
  color: var(--text-dim);
  line-height: var(--lh-snug);
}

.fine-btn {
  /* Explicit reset: index.css only sets `button { font: inherit }`, so
     without this the button drags along the browser default -- padding
     rgb(107,107,107) and a 2px outset border. Measured, not guessed. */
  border: 0;
  background: none;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-top: var(--sp-3);
  padding: 4px 0;
  font-size: var(--fs-xs);
  color: var(--text-dim);
  cursor: pointer;
  transition: color var(--dur-fast) var(--ease);
  min-height: 44px;
  padding: 0 var(--sp-3);
}
.fine-btn:hover { color: var(--text); }
.fine-btn svg { transition: transform var(--dur-fast) var(--ease); }
.fine-btn[aria-expanded="true"] svg { transform: rotate(180deg); }

.fine {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  margin-top: var(--sp-3);
  padding: var(--sp-4);
  border: var(--rand-dikte) solid var(--rand);
  border-radius: var(--r-md);
  background: var(--glas);
  -webkit-backdrop-filter: var(--glas-waas);
  backdrop-filter: var(--glas-waas);
}

.fine-row {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  flex-wrap: wrap;
}
.fine-row-tall { align-items: flex-start; }

.fine-label {
  flex: none;
  min-width: 94px;
  font-size: var(--fs-sm);
  color: var(--text-dim);
}

/* A generic element rule (input{}) still applies wherever this class sets
   nothing, so everything we don't want to inherit is set explicitly here. */
/* Only the size; the material is .input from componenten.css. */
.fine-field { flex: 0 1 140px; min-width: 0; }
.fine-field-wide { flex: 1 1 180px; }

@media (max-width: 620px) {
  .fine-label { min-width: 100%; }
  .fine-field, .fine-field-wide { flex: 1 1 100%; }
}

.btn-activate {
  min-height: 48px;
  margin-top: auto;
  padding: 0 var(--sp-6);
  border: 0;
  border-radius: var(--r-full);
  background: var(--blauw-knop);
  color: #FFFFFF;
  font-size: var(--fs-md);
  font-weight: 600;
  letter-spacing: var(--track-md);
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease), transform var(--dur-fast) var(--ease);
}
.btn-activate:hover:not(:disabled) { background: #0A74F0; }
.btn-activate:active:not(:disabled) { transform: scale(0.98); }
.btn-activate:disabled { opacity: 0.55; cursor: default; }

.answer-message {
  margin: 0;
  font-size: var(--fs-sm);
  line-height: var(--lh-snug);
  text-align: center;
  color: var(--tekst-gedempt);
}
.answer-message.is-error { color: #FF9F0A; }
.answer-message.is-good { color: #00C853; }

/* --- The feedback box --------------------------------------------------------
 * Two fields on one stack, in the same material as the input pill and the
 * menus: black glass with the hairline at 22%. No frame of its own around
 * it -- that would be a third kind of surface on a page that deliberately
 * has one.
 *
 * The tokens come FROM this file: --rand and --tekst-gedempt exist here,
 * --rand and --tekst-faint do not. A custom property that doesn't exist
 * falls back SILENTLY, and that has already produced an invisible 0px
 * border twice in this project.
 */
.feedback-form {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  margin-top: var(--sp-4);
}

.feedback-form textarea,
.feedback-form input {
  /* font: inherit, because a textarea doesn't inherit the page font --
     without this line it shows monospace on a page that only knows Inter. */
  font: inherit;
  width: 100%;
  padding: var(--sp-3);
  border-radius: var(--r-md);
  border: 1px solid var(--rand);
  background: rgba(6, 6, 10, 0.58);
  -webkit-backdrop-filter: blur(22px) saturate(150%);
  backdrop-filter: blur(22px) saturate(150%);
  color: var(--tekst);
  line-height: var(--lh-base);
  transition: border-color var(--dur-fast) var(--ease);
}
.feedback-form textarea { resize: vertical; min-height: 96px; }
.feedback-form textarea:focus,
.feedback-form input:focus { outline: none; border-color: var(--blauw); }
.feedback-form textarea::placeholder,
.feedback-form input::placeholder { color: var(--tekst-gedempt); }

@media (max-width: 640px) {
  .controls { padding: var(--sp-4); }
  .answer { margin-top: var(--sp-5); }
}

/* ===========================================================================
 * Step 5: the fill-in screen
 * ========================================================================= */

/* The same pill shape as a choice menu, but for typing into. Set apart
 * and not as a variant of .choice-btn: an <input> doesn't inherit the font
 * and has its own browser default (border, padding, fill) that has to go
 * here. */
.choice-input {
  width: 100%;
  min-height: 44px;
  padding: 0 var(--sp-4);
  border: var(--rand-dikte) solid var(--rand);
  border-radius: var(--r-full);
  background: var(--surface-0);
  color: var(--tekst);
  font-family: inherit;
  font-size: var(--fs-md);
  font-weight: 500;
  transition: border-color var(--dur-fast) var(--ease);
}
.choice-input::placeholder { color: var(--tekst-gedempt); }
.choice-input:hover { border-color: var(--rand-actief); }
.choice-input:focus { outline: 0; border-color: var(--blauw); }

/* The arrow button from the mockup: wide, not round. It says "onward",
 * and that's a direction, not a confirmation -- hence an arrow and not a
 * word. */
.btn-continue {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  min-height: 60px;
  margin-top: auto;
  border: var(--rand-dikte) solid var(--rand);
  border-radius: var(--r-md);
  background: var(--surface-0);
  color: var(--tekst);
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease), transform var(--dur-fast) var(--ease);
}
.btn-continue:hover:not(:disabled) { border-color: var(--rand-actief); background: var(--surface-1); }
.btn-continue:active:not(:disabled) { transform: scale(0.98); }
.btn-continue:disabled { opacity: 0.4; cursor: default; }

/* A choice list with long sentences in it (the interpretations for
 * 'choices') may be wider than its button, otherwise every line gets cut
 * off. */
.choice-option-explanation {
  display: block;
  margin-top: 2px;
  font-size: var(--fs-xs);
  color: var(--tekst-gedempt);
  line-height: var(--lh-snug);
}
.choice-list.is-wide { min-width: min(420px, 78vw); }
.choice-list.is-wide .choice-option { display: block; }

/* ===========================================================================
 * Step 6: the plus menu, the custom value and the limit line
 * ========================================================================= */

.pill-plus-group { position: relative; display: flex; }

/* Same surface as the corner menus -- black with a thin white border --
 * but under the pill and tall enough for a list. Fixed height with its
 * own scroll, so the page itself doesn't scroll. */
.menu-plus {
  top: calc(100% + var(--sp-3));
  bottom: auto;
  left: 0;
  min-width: 320px;
  max-width: min(420px, calc(100vw - 2 * var(--sp-4)));
  max-height: min(62vh, 460px);
  overflow-y: auto;
  overscroll-behavior: contain;
}

/* The plus menu's scrollbar. Without these rules Chromium draws its own
 * bar: a light strip against the edge of a black glass surface, and that's
 * the only element on the page that doesn't come from our own palette.
 * Firefox gets `scrollbar-width/-color`, WebKit the pseudo-elements --
 * neither understands the other, so both. */
.menu-plus {
  scrollbar-width: thin;
  scrollbar-color: var(--rand) transparent;
}
.menu-plus::-webkit-scrollbar { width: 8px; }
.menu-plus::-webkit-scrollbar-track { background: transparent; }
.menu-plus::-webkit-scrollbar-thumb {
  background: var(--rand);
  border-radius: var(--r-full);
  /* A border in the color of nothing keeps the thumb slim and away from the
     edge, without having to invent a second width. */
  border: 2px solid transparent;
  background-clip: content-box;
}
.menu-plus::-webkit-scrollbar-thumb:hover { background: var(--rand-actief); background-clip: content-box; }

.menu-heading-line {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-3);
  color: var(--tekst-gedempt);
  font-size: var(--fs-xs);
}

.menu-row-explanation {
  display: block;
  margin-top: 1px;
  font-size: var(--fs-xs);
  font-weight: 400;
  color: var(--tekst-gedempt);
  line-height: var(--lh-snug);
}
.menu-row-text { display: block; min-width: 0; flex: 1; }

/* The pill on the right of a catalog row: how much effort it takes, or Pro. */
.menu-brand {
  flex: 0 0 auto;
  padding: 1px 6px;
  border-radius: var(--r-full);
  background: var(--surface-2);
  color: var(--tekst-gedempt);
  font-size: var(--fs-2xs);
  font-weight: 600;
  letter-spacing: 0.04em;
}
.menu-brand.is-pro { background: var(--blauw-knop); color: #FFFFFF; }

.limit {
  margin: var(--sp-4) 0 0;
  font-size: var(--fs-sm);
  color: var(--tekst-gedempt);
  text-align: center;
}
.limit a { color: var(--tekst); }
.limit.is-full { color: #FF9F0A; }


/* ===========================================================================
 * Step 7: the pill becomes its own loading indicator while it loads
 * ===========================================================================
 *
 * WHY THIS IS BUILT THIS WAY. The obvious approach -- animating the pill's
 * width to ~200px -- is exactly what this project forbids: animating width
 * and height causes layout and paint work per FRAME, and on a phone that's
 * immediately visible as stutter. The rule lives in design.css.
 *
 * So no layout number changes at all. The loading pill already sits in the
 * same box, absolutely positioned in the middle, and the two swap places
 * using only opacity and transform. What you see is one shape shrinking
 * and growing again; what happens is a cross-fade between two shapes that
 * already both had their place.
 */

.pill-content {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  flex: 1;
  min-width: 0;
  transition: opacity var(--dur-base) var(--ease),
              transform var(--dur-slow) var(--ease-enter);
}

.pill.is-loading .pill-content {
  opacity: 0;
  transform: scale(0.94);
  pointer-events: none;   /* nothing in the pill is clickable while it's loading */
}

/* The large pill disappears entirely: border AND fill. Removing only the
 * border wasn't enough -- its black surface stayed visible as a dark slab
 * against the brightening aurora, and then the loading pill isn't
 * floating but sitting in a box. A 320ms color transition is one-off; the
 * rule about "only opacity and transform" is about what keeps running per
 * frame. */
.pill.is-loading {
  border-color: rgba(255,255,255,0);
  background: rgba(0,0,0,0);
  box-shadow: none;
}

.pill-loading {
  position: absolute;
  left: 50%;
  top: 50%;
  /* Wider than before: there's now text in it about what's happening. */
  display: flex;
  align-items: center;
  justify-content: center;
  width: min(360px, 82%);
  height: 44px;
  border: var(--rand-dikte) solid var(--rand);
  border-radius: var(--r-full);
  background: var(--glas);
  backdrop-filter: var(--glas-waas);
  -webkit-backdrop-filter: var(--glas-waas);
  overflow: hidden;                 /* keeps the light inside the shape */
  opacity: 0;
  transform: translate(-50%, -50%) scale(0.72);
  transition: opacity var(--dur-base) var(--ease),
              transform var(--dur-slow) var(--ease-spring);
  pointer-events: none;
}

.pill.is-loading .pill-loading {
  opacity: 1;
  transform: translate(-50%, -50%) scale(1);
}

/* The light that sweeps through it.
 *
 * THE SIZING IS CALCULATED, not guessed. Two requirements bite each other:
 * the bright core must run exactly from the left to the right edge
 * (otherwise it looks lopsided), and the band must ALWAYS cover the pill
 * completely (otherwise you see a gap at one of the edges).
 *
 * With a band of 200% at left:-50% it runs, untranslated, from -50% to
 * 150%, with the core at 50% -- the middle. Shift it ±25% of its OWN
 * width (= ±50% of the pill), and at the far left it sits at [-100%, 100%]
 * and at the far right at [0%, 200%]: both still fully covering, while the
 * core moves from 0% to 100%. Exactly one even sweep.
 *
 * Two earlier versions went wrong: 220% wide with ±38% pushed the core
 * mostly outside the pill on the left (lopsided), and exactly pill-width
 * with ±100% made it vanish completely for half the cycle (flickering).
 *
 * Moves with translateX, not background-position: the latter is paint
 * work per frame. */
.pill-loading-shine {
  position: absolute;
  top: 0;
  bottom: 0;
  left: -50%;
  width: 200%;
  background: linear-gradient(90deg,
      rgba(0,122,255,0) 0%,
      rgba(0,122,255,0.18) 28%,
      rgba(0,122,255,0.60) 50%,
      rgba(0,122,255,0.18) 72%,
      rgba(0,122,255,0) 100%);
  transform: translateX(-25%);
}

/* The text sits ABOVE the light and doesn't move with it. White, because
 * this is the only thing you read at that moment. */
.pill-loading-text {
  position: relative;
  z-index: 1;
  color: #FFFFFF;
  font-size: var(--fs-sm);
  font-weight: 500;
  letter-spacing: var(--track-md);
  white-space: nowrap;
}
/* Each letter lights up as the light passes roughly under it: the delay
 * tracks the position, over the same period as the sweep. That way it's
 * one motion in two layers instead of two things wobbling independently.
 * Only opacity and transform. */
.pill-loading-letter {
  display: inline-block;
  opacity: 0.55;
  animation: letterGolf var(--veeg-ms, 1150ms) linear infinite;
  animation-delay: calc(var(--i, 0) / var(--n, 1) * var(--veeg-ms, 1150ms));
}

@keyframes letterGolf {
  0%, 100% { opacity: 0.55; transform: translateY(0); }
  18%      { opacity: 1;    transform: translateY(-1px); }
  40%      { opacity: 0.7;  transform: translateY(0); }
}

@media (prefers-reduced-motion: reduce) {
  .pill-loading-letter { animation: none; opacity: 1; }
}

.pill-loading-text.leaving { animation: voorbeeldWeg 200ms var(--ease) both; }
.pill-loading-text.entering  { animation: voorbeeldOp 340ms var(--ease-enter) both; }

/* Runs ONLY as long as the class is on it. So nothing moves at rest --
 * that's the rule this project cleaned up 14 infinite animations for, and
 * the browser test enforces it. */
.pill.is-loading .pill-loading-shine {
  animation: pilLichtVeegt 1150ms linear infinite;
}

@keyframes pilLichtVeegt {
  from { transform: translateX(-25%); }
  to   { transform: translateX(25%); }
}

@media (prefers-reduced-motion: reduce) {
  /* No sweep, but still visibly showing that something is being waited for:
     a static blue glow. It's about the motion, not the signal. */
  .pill.is-loading .pill-loading-shine { animation: none; transform: translateX(0); }
}

/* ===========================================================================
 * Step 8: text arriving, menus rising, the page coming in
 * =========================================================================
 * Everything here runs ONCE. Motion that keeps going at rest is noise; the
 * browser test counts that afterward and fails as soon as anything keeps
 * spinning.
 */

/* --- Text -----------------------------------------------------------------
 * Per word, not per letter. A typewriter effect reads slowly for a long
 * sentence and also suggests someone is sitting there typing; this is an
 * answer dropping into place.
 */
.word {
  display: inline-block;
  animation: woordKomtOp 400ms var(--ease-enter) both;
}

@keyframes woordKomtOp {
  from { opacity: 0; transform: translateY(0.4em); }
  to   { opacity: 1; transform: translateY(0); }
}

/* --- Menu rows -------------------------------------------------------------
 * The rows carry the motion, the panel doesn't: two things sliding at the
 * same time reads as wobbling. --rij is set in JS, also when the plus menu
 * is refilled.
 */
.menu [role="menuitem"],
.choice-list [role="option"] {
  animation: rijKomtOp 240ms var(--ease-enter) both;
  animation-delay: calc(var(--rij, 0) * 22ms);
}

@keyframes rijKomtOp {
  from { opacity: 0; transform: translateY(-7px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* The panel itself keeps only the blur and a slight scale left. */
/* The panel UNFOLDS from the button you pressed. Hence a transform-origin
 * per kind of menu: a panel that scales up out of nowhere in the middle
 * belongs nowhere, a panel that grows from its own button does. Only
 * opacity and transform, and one-off. */

/* The plus menu hangs below the plus, so top-left. */
.menu-plus { transform-origin: top left; }
/* A choice list belongs to the button above it. */
.choice-list { transform-origin: top center; }

/* --- The page on load ----------------------------------------------------- */
.bar { --op: 0; }
.pill  { --op: 1; }
.corner { --op: 2; }

/* BACKWARDS and not BOTH. With `both` the keyframe's end state stays as a
 * fill, and that is `transform: translateY(0)` -- the IDENTITY MATRIX, not
 * `none`. An ancestor with any transform at all creates a backdrop root,
 * and then the plus menu's `backdrop-filter` (which lives inside .pill)
 * samples an EMPTY background: the glass blurred nothing and the page
 * showed through razor-sharp. With `backwards` the start state is ready
 * before the animation and the element then falls back to its normal
 * rules -- so no transform anymore. The same trap as before with the glow,
 * a different symptom. */
.bar, .pill, .corner {
  animation: vlakKomtOp 520ms var(--ease-enter) backwards;
  animation-delay: calc(var(--op, 0) * 90ms);
}

@keyframes vlakKomtOp {
  from { opacity: 0; transform: translateY(12px); }
  to   { opacity: 1; transform: translateY(0); }
}

@media (prefers-reduced-motion: reduce) {
  .word,
  .menu [role="menuitem"],
  .choice-list [role="option"],
  .bar, .pill, .corner {
    animation: none;
  }
}


/* ===========================================================================
 * Step 9: example sentences in the pill
 * =========================================================================
 * WHY A SEPARATE ELEMENT and not the placeholder attribute: that attribute
 * can't be animated and can't be built up per word. This sits on top of it,
 * catches no mouse events, and disappears as soon as there's anything in
 * the field.
 *
 * The attribute stays in the HTML as a fallback in case the script doesn't
 * load; JS removes it as soon as it takes over.
 */

.pill-field { position: relative; flex: 1; min-width: 0; display: flex; }

.pill-example {
  position: absolute;
  left: 0;
  right: 0;
  top: 50%;
  transform: translateY(-50%);
  pointer-events: none;
  color: var(--tekst-gedempt);
  font-size: var(--fs-lg);
  line-height: var(--lh-snug);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Switching: the old sentence sinks away upward, the new one comes from
 * below. Two separate animations, both finite -- neither keeps running at
 * rest. */
.pill-example.leaving { animation: voorbeeldWeg 220ms var(--ease) both; }
.pill-example.entering  { animation: voorbeeldOp 380ms var(--ease-enter) both; }

@keyframes voorbeeldWeg {
  from { opacity: 1; transform: translateY(-50%); }
  to   { opacity: 0; transform: translateY(calc(-50% - 10px)); }
}
@keyframes voorbeeldOp {
  from { opacity: 0; transform: translateY(calc(-50% + 10px)); }
  to   { opacity: 1; transform: translateY(-50%); }
}

@media (prefers-reduced-motion: reduce) {
  .pill-example.leaving, .pill-example.entering { animation: none; }
}


/* ===========================================================================
 * Step 10: the way back
 * =========================================================================
 * Once the input pill disappears after sending, the answer screen is a
 * dead end. This button brings the pill back and clears the answer.
 */
.followup-actions {
  display: flex;
  gap: var(--sp-2);
  justify-content: center;
  --op-stap: 3;
}

.again {
  min-height: 44px;
  padding: var(--sp-2) var(--sp-4);
  border: 0;
  border-radius: var(--r-full);
  background: transparent;
  color: var(--tekst-gedempt);
  font-size: var(--fs-sm);
  cursor: pointer;
  transition: color var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease);
}
.again:hover { color: var(--tekst); background: var(--surface-1); }


/* Fallback for browsers without backdrop-filter: then just black, because
 * a transparent panel WITHOUT blur lets the text underneath show through. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .choice-list, .answer-sentence, .pill-loading { background: var(--surface-0); }
}

/* ===========================================================================
 * Step 11: things also go away again
 * ===========================================================================
 * There were eight animations and all eight were an ENTRANCE. In twenty
 * places something disappeared with `hidden = true`, i.e. with a cut. That
 * is why the whole thing felt jerky even though every individual animation
 * was correct: half of every transition was missing.
 *
 * Leaving is always SHORTER than arriving (--dur-uit against --dur-scherm).
 * Waiting for something to leave feels like delay.
 */

.closing {
  animation: vlakGaatUit var(--dur-uit) var(--ease) both !important;
  pointer-events: none;
}

@keyframes vlakGaatUit {
  from { opacity: 1; transform: translateY(0) scale(1); }
  to   { opacity: 0; transform: translateY(6px) scale(0.98); }
}

/* --- Direction -------------------------------------------------------------
 * A series of cross-fades is not a place. Forward enters from the RIGHT and
 * exits to the LEFT; back is exactly the opposite. One rule, and the whole
 * page feels like something you walk through.
 *
 * The direction sits on <body> and not per element: it applies to the
 * whole screen that's switching, and two surfaces moving against each
 * other reads as a bug.
 */
body[data-direction="vooruit"] .closing {
  animation-name: vlakGaatUitVooruit !important;
}
body[data-direction="terug"] .closing {
  animation-name: vlakGaatUitTerug !important;
}
@keyframes vlakGaatUitVooruit {
  from { opacity: 1; transform: translateX(0) scale(1); }
  to   { opacity: 0; transform: translateX(-28px) scale(0.98); }
}
@keyframes vlakGaatUitTerug {
  from { opacity: 1; transform: translateX(0) scale(1); }
  to   { opacity: 0; transform: translateX(28px) scale(0.98); }
}

body[data-direction="vooruit"] .answer:not([hidden]) > *,
body[data-direction="vooruit"] .complete:not([hidden]) > *,
body[data-direction="vooruit"] .success:not([hidden]) > * {
  animation-name: vlakKomtOpVanRechts;
}
body[data-direction="terug"] .answer:not([hidden]) > *,
body[data-direction="terug"] .complete:not([hidden]) > *,
body[data-direction="terug"] .success:not([hidden]) > * {
  animation-name: vlakKomtOpVanLinks;
}
@keyframes vlakKomtOpVanRechts {
  from { opacity: 0; transform: translateX(28px); }
  to   { opacity: 1; transform: translateX(0); }
}
@keyframes vlakKomtOpVanLinks {
  from { opacity: 0; transform: translateX(-28px); }
  to   { opacity: 1; transform: translateX(0); }
}

/* The input pill belongs with the header, not with the switching screen:
 * it sinks away and comes back in its place. Direction does NOT apply
 * there. */
body[data-direction] .pill.closing { animation-name: pilGaatUit !important; }

/* A menu folds back into the button it came from -- the same
 * transform-origin, so the motion is literally the reverse. */
.menu.closing,
.choice-list.closing {
  animation: menuGaatUit var(--dur-uit) var(--ease) both !important;
}

@keyframes menuGaatUit {
  from { opacity: 1; transform: scale(1) translateY(0); }
  to   { opacity: 0; transform: scale(0.9) translateY(-4px); }
}

/* The input pill makes way for the answer: it sinks away instead of
 * disappearing. */
.pill.closing {
  animation: pilGaatUit var(--dur-uit) var(--ease) both !important;
}

@keyframes pilGaatUit {
  from { opacity: 1; transform: translateY(0) scale(1); }
  to   { opacity: 0; transform: translateY(-10px) scale(0.96); }
}

/* --- The answer screen arrives in steps -----------------------------------
 * First the bell, then the block, then the button. All at once is one
 * bang; three things shortly after each other reads as "something is
 * being built here".
 */
.answer-sentence  { --op-stap: 0; }
.controls     { --op-stap: 1; }
.controls .btn-activate,
.btn-continue   { --op-stap: 2; }
.again       { --op-stap: 3; }

.answer:not([hidden]) > .answer-sentence,
.answer:not([hidden]) > .controls,
.answer:not([hidden]) > .followup-actions,
.complete:not([hidden]) > .answer-sentence,
.complete:not([hidden]) > .controls,
.complete:not([hidden]) > .followup-actions {
  /* `backwards` and NOT `both`. With `both` the end state stays as a fill,
     including `transform: translateY(0)` -- the identity matrix, but still
     a transform. Each of these surfaces then became a STACKING CONTEXT,
     and because .followup-actions sits later in the DOM it painted over
     .controls -- including the dropdown list at z-index 6 that lives
     inside it. Result: a click on "Telegram" in the destination list hit
     the Adjust button instead.

     That went unnoticed as long as that list had only ONE visible option:
     a 405 on notification-settings kept Telegram and Push always hidden,
     and with one option the list is too short to reach .followup-actions. The
     two bugs masked each other. Third time this trap has struck. */
  animation: vlakKomtOp var(--dur-scherm) var(--ease-enter) backwards;
  animation-delay: calc(var(--op-stap, 0) * 110ms);
}

/* --- The glow reacts to the state ----------------------------------------
 * Allowed under our own rule: this expresses a STATE ("work is happening"),
 * and it's a one-off TRANSITION, not a continuous animation. At rest it
 * sits at 0.88 so that "busy" has room to be brighter.
 */
body.is-busy .glow {
  opacity: 1;
  transform: translate(-50%, -50%) scale(1.07);
}

@media (prefers-reduced-motion: reduce) {
  .closing, .menu.closing, .choice-list.closing, .pill.closing,
  .answer:not([hidden]) > *, .complete:not([hidden]) > * { animation: none !important; }
}

html { scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

/* ===========================================================================
 * Maintenance: a bar and a closed screen
 * ===========================================================================
 * Two states, and the difference is the point. 'pause' lets you do
 * everything but says the cron isn't looking for a while -- you need to
 * know that before you turn on something that should fire today. 'closed'
 * replaces the page.
 *
 * Amber and not red: this isn't an outage but a choice we made. Red would
 * mean something is broken, and that's exactly the wrong signal at the
 * moment you're tinkering with it yourself.
 */
.maintenance-bar {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-3);
  width: 100%;
  padding: var(--sp-3) var(--sp-5);
  border-bottom: var(--rand-dikte) solid rgba(255,159,10,0.34);
  background: rgba(255,159,10,0.10);
  color: #FFD08A;
  font-size: var(--fs-sm);
  text-align: center;
}
.maintenance-dot {
  width: 8px;
  height: 8px;
  flex: 0 0 auto;
  border-radius: var(--r-full);
  background: var(--amber);
}
/* The dot pulses, because this is a STATE that keeps running -- the same
 * ground on which the loading sweep is allowed to run. Only opacity. */
@media (prefers-reduced-motion: no-preference) {
  .maintenance-dot { animation: onderhoudPuls 2.4s ease-in-out infinite; }
}
@keyframes onderhoudPuls {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.35; }
}

/* The closed screen. position:fixed and not "hide the rest": that way no
 * other part of the page needs to know anything about maintenance. */
.closed {
  position: fixed;
  inset: 0;
  z-index: 40;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--sp-4);
  padding: var(--sp-6) var(--sp-5);
  background: var(--surface-0);
  text-align: center;
}
.closed-brand {
  height: 52px;
  margin-bottom: var(--sp-2);
}
.closed-heading {
  margin: 0;
  font-size: var(--fs-2xl);
  font-weight: 700;
  letter-spacing: var(--track-lg);
}
.closed-reason {
  margin: 0;
  max-width: 44ch;
  color: var(--tekst-gedempt);
  font-size: var(--fs-lg);
  line-height: var(--lh-snug);
}
.closed-since,
.closed-footer {
  margin: 0;
  max-width: 44ch;
  color: var(--tekst-gedempt);
  font-size: var(--fs-sm);
}
.closed-footer { opacity: 0.72; }

.closed:not([hidden]) > * {
  animation: vlakKomtOp var(--dur-scherm) var(--ease-enter) backwards;
  animation-delay: calc(var(--i, 0) * 80ms);
}
.closed-brand  { --i: 0; }
.closed-heading   { --i: 1; }
.closed-reason { --i: 2; }
.closed-since { --i: 3; }
.closed-footer  { --i: 4; }
@media (prefers-reduced-motion: reduce) {
  .closed:not([hidden]) > * { animation: none !important; }
}

/* ===========================================================================
 * Step 13: the pulse -- three live lines under the pill
 * ===========================================================================
 * A trigger fires here on average once every two months. Without these
 * lines the page shows nothing in the meantime, and there's no reason at
 * all to come back. Signed out they show real public numbers; signed in,
 * your own triggers with what we last saw.
 */
.pulse {
  display: flex;
  flex-direction: column;
  width: min(720px, 100%);
  /* .center already puts var(--sp-8) between them; adding another sp-5 made
     it 72px and then the list hangs loose from the pill it belongs to.
     Negative back down to var(--sp-5): close enough to read as one whole. */
  margin: calc(var(--sp-5) - var(--sp-8)) auto 0;
}

.pulse-row {
  display: flex;
  align-items: baseline;
  gap: var(--sp-3);
  width: 100%;
  min-height: 44px;            /* measured 43.5: the line height just misses 44 */
  padding: var(--sp-3) var(--sp-2);
  /* No dividing lines. Every white border in this file is 2px, and three
     2px lines under an otherwise empty page is heavier than the three
     lines themselves. The spacing already separates them. */
  border: 0;
  background: transparent;
  color: var(--tekst-gedempt);
  font-family: inherit;
  font-size: var(--fs-sm);
  text-align: left;
  cursor: pointer;
  transition: color var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease);
}
.pulse-row:hover { color: var(--tekst); background: var(--surface-1); }
.pulse-row[disabled] { cursor: default; }
.pulse-row[disabled]:hover { color: var(--tekst-gedempt); background: transparent; }

/* The name may be truncated, the NUMBER never: that's what you're looking at. */
.pulse-name {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.pulse-value {
  flex: 0 0 auto;
  color: var(--tekst);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.pulse-when {
  flex: 0 0 auto;
  /* --tekst-gedempt is already the weakest color in this file that still
     meets WCAG AA (5.19:1). An even lighter one would fall below the
     standard; so we make the difference from the name with SIZE, not
     color. */
  color: var(--tekst-gedempt);
  font-size: var(--fs-xs);
  white-space: nowrap;
}

@media (max-width: 480px) {
  /* On a phone the time is dropped: name and value are what count. */
  .pulse-when { display: none; }
}

/* ===========================================================================
 * Step 12: the launch and the confirmation
 * ===========================================================================
 * Press Activate, and the choices go away, something flies off here while
 * save-trigger is busy, and afterward it shows what's now on -- with the
 * input pill back underneath.
 *
 * The vessel comes from Uiverse (anand_4957). What was changed about it,
 * and why:
 *   - the original animates `left` in eight of the nine animations. That's
 *     layout on every frame; here only transform. The long streaks are 20%
 *     wide and covered 400% of the parent: that's exactly 2000% of
 *     themselves, not an estimated number.
 *   - black becomes white, with blue exhaust sparks. Black on black is
 *     nothing.
 * The shape itself is LITERALLY the original: rendered side by side, 0 of
 * the 1634 ink pixels differ (a hull of 99 instead of 100px gives 6). */


.launch {
  position: relative;
  width: 100%;
  height: 120px;
  overflow: hidden;          /* it leaves through the edge, not past it */
  --dx: -13.3px;             /* MEASURED, not guessed: the original hangs on */
  --dy: 4px;                 /* negative top values, so the middle of the   */
}                            /* ink does not line up with top/left 50%.        */

/* --- the long streaks -----------------------------------------------------
 * Original: width 20%, `left: 200% -> -200%` = 400% of the PARENT. An
 * element 20% wide thereby covers 20x its own width: translateX(1000%) to
 * translateX(-1000%). With two of them the original ends at -100% of the
 * parent, which is -500%. */
.stripe-track { position: absolute; inset: 0; transition: opacity var(--dur-uit) var(--ease); }
.stripe-track > span {
  position: absolute;
  left: 0;
  width: 20%;
  height: 2px;
  border-radius: 2px;
  background: rgba(255,255,255,0.34);
}
.stripe-track > span:nth-child(1) { top: 20%; animation: streepLang 0.6s linear infinite; animation-delay: -5s; }
.stripe-track > span:nth-child(2) { top: 40%; animation: streepLang 0.8s linear infinite; animation-delay: -1s; }
.stripe-track > span:nth-child(3) { top: 60%; animation: streepKort 0.6s linear infinite; }
.stripe-track > span:nth-child(4) { top: 80%; animation: streepKort 0.5s linear infinite; animation-delay: -3s; }

@keyframes streepLang {
  from { transform: translateX(1000%); opacity: 1; }
  to   { transform: translateX(-1000%); opacity: 0; }
}
@keyframes streepKort {
  from { transform: translateX(1000%); opacity: 1; }
  to   { transform: translateX(-500%); opacity: 0; }
}

/* The lane carries the placement, the vessel the shake. Those two can't
 * live on the same element: one transform overwrites the other. */
.vehicle-track {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(var(--dx), var(--dy));
}
.vehicle {
  position: absolute;
  margin-left: -50px;
  animation: trilt 0.4s linear infinite;
}

.exhaust {
  position: absolute;
  top: -19px;
  left: 60px;
  width: 35px;
  height: 5px;
  border-radius: 2px 10px 1px 0;
  background: #FFFFFF;
}
.exhaust > span {
  position: absolute;
  left: 0;
  width: 30px;
  height: 1px;
  background: var(--blue);
}
/* Fixed px in the original, so a direct conversion to translateX. */
.exhaust > span:nth-child(1) { animation: vonk1 0.2s linear infinite; }
.exhaust > span:nth-child(2) { top: 3px; animation: vonk2 0.4s linear infinite; }
.exhaust > span:nth-child(3) { top: 1px; animation: vonk3 0.4s linear infinite; animation-delay: -1s; }
.exhaust > span:nth-child(4) { top: 4px; animation: vonk4 1s   linear infinite; animation-delay: -1s; }

@keyframes vonk1 { from { transform: translateX(0); opacity: 1; } to { transform: translateX(-80px);  opacity: 0; } }
@keyframes vonk2 { from { transform: translateX(0); opacity: 1; } to { transform: translateX(-100px); opacity: 0; } }
@keyframes vonk3 { from { transform: translateX(0); opacity: 1; } to { transform: translateX(-50px);  opacity: 0; } }
@keyframes vonk4 { from { transform: translateX(0); opacity: 1; } to { transform: translateX(-150px); opacity: 0; } }

/* The hull is a border-triangle. NOTE: never give this thing a background
 * -- a background extends to UNDER the border and would then exactly plug
 * the transparent border-bottom that creates the angled cut. */
.hull {
  position: absolute;
  width: 0;
  height: 0;
  border-top: 6px solid transparent;
  border-right: 100px solid #FFFFFF;
  border-bottom: 6px solid transparent;
}
.hull::before {
  content: "";
  position: absolute;
  right: -110px;
  top: -16px;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: #FFFFFF;
}
.hull::after {
  content: "";
  position: absolute;
  top: -16px;
  right: -98px;
  width: 0;
  height: 0;
  border-top: 0 solid transparent;
  border-right: 55px solid #FFFFFF;
  border-bottom: 16px solid transparent;
}
/* The face is a <div> in the original. Here it's a <span>, and that's
 * exactly where it went wrong: `.base > span` picked it up too, including
 * that 100px border -- a 120x24 layout box instead of 20x12. Hence a
 * dedicated name for the hull instead of a positional selector. */
.face {
  position: absolute;
  right: -125px;
  top: -15px;
  width: 20px;
  height: 12px;
  border-radius: 20px 20px 0 0;
  background: #FFFFFF;
  transform: rotate(-40deg);
}
.face::after {
  content: "";
  position: absolute;
  right: 4px;
  top: 7px;
  width: 12px;
  height: 12px;
  border-radius: 0 0 0 2px;
  background: #FFFFFF;
  transform: rotate(40deg);
  transform-origin: 50% 50%;
}

@keyframes trilt {
  0%   { transform: translate(2px, 1px)   rotate(0deg); }
  10%  { transform: translate(-1px, -3px) rotate(-1deg); }
  20%  { transform: translate(-2px, 0px)  rotate(1deg); }
  30%  { transform: translate(1px, 2px)   rotate(0deg); }
  40%  { transform: translate(1px, -1px)  rotate(1deg); }
  50%  { transform: translate(-1px, 3px)  rotate(-1deg); }
  60%  { transform: translate(-1px, 1px)  rotate(0deg); }
  70%  { transform: translate(3px, 1px)   rotate(-1deg); }
  80%  { transform: translate(-2px, -1px) rotate(1deg); }
  90%  { transform: translate(2px, 1px)   rotate(0deg); }
  100% { transform: translate(1px, -2px)  rotate(-1deg); }
}

/* The departure. Accelerating out (ease-IN), because taking off should
 * start slow and end fast -- the other way round it reads as braking. */
/* 100vw and not a percentage: .vehicle-track has NO width of its OWN
 * (all its children are absolutely positioned), so a percentage in
 * translate is computed against 0 and moves nothing. That's exactly what
 * happened: 130% gave 0px. */
.launch.departing .vehicle-track {
  transform: translate(calc(var(--dx) + 100vw), var(--dy));
  transition: transform 420ms cubic-bezier(0.5, 0, 1, 1);
}
.launch.departing .stripe-track { opacity: 0; }

@media (prefers-reduced-motion: reduce) {
  /* No shake, no streaks, no departure -- but still visibly SHOWING that
     something is happening: the vessel is simply there. */
  .vehicle, .stripe-track > span, .exhaust > span { animation: none !important; }
  .stripe-track > span { opacity: 0.5; transform: translateX(0); }
  .launch.departing .vehicle-track { transition: none; }
}

/* --- The confirmation -------------------------------------------------------- */
.success {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-3);
  width: min(720px, 100%);
  margin: 0 auto;
  text-align: center;
}
.success-heading {
  margin: 0;
  color: var(--tekst);
  font-size: var(--fs-lg);
  font-weight: 600;
  letter-spacing: var(--track-md);
}
.success-detail {
  margin: 0;
  max-width: 46ch;
  color: var(--tekst-gedempt);
  font-size: var(--fs-sm);
  line-height: var(--lh-base);
}
/* Your own sentence, in quotes and muted: this is a quote of what you
 * typed, not text of ours. */
.success-sentence {
  margin: 0;
  max-width: 46ch;
  color: var(--tekst-gedempt);
  font-size: var(--fs-md);
  line-height: var(--lh-snug);
}
/* tabular-nums, otherwise the counter jumps at every second. */
.success-counter {
  margin: 0;
  color: var(--tekst);
  font-size: var(--fs-sm);
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  letter-spacing: var(--track-md);
}
.success-buttons {
  display: flex;
  gap: var(--sp-3);
  align-items: center;
  flex-wrap: wrap;
  justify-content: center;
}
.success-test {
  margin: 0;
  max-width: 46ch;
  color: var(--tekst-gedempt);
  font-size: var(--fs-sm);
  line-height: var(--lh-base);
}


.success-heading     { --op-stap: 0; }
.success-sentence     { --op-stap: 1; }
.success-counter  { --op-stap: 2; }
.success-detail  { --op-stap: 3; }
.share-diamond      { --op-stap: 4; }
.success-buttons { --op-stap: 5; }
.success-test    { --op-stap: 6; }

.success:not([hidden]) > * {
  animation: vlakKomtOp var(--dur-scherm) var(--ease-enter) both;
  animation-delay: calc(var(--op-stap, 0) * 110ms);
}
@media (prefers-reduced-motion: reduce) {
  .success:not([hidden]) > * { animation: none !important; }
}

/* --- Checkable destinations ----------------------------------------------
 * More than one destination at a time. The list shares .choice-list's
 * recipe (black glass, hairline); only the rows differ.
 *
 * Real checkboxes, not buttons with aria-selected: that way they keep
 * their keyboard behavior, their label linkage and their role without us
 * having to rebuild those by hand.
 */
.choice-check { padding: 6px; }

.check-option span { white-space: nowrap; }
.check-option {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 10px;
  border-radius: var(--r-sm);
  font-size: var(--fs-sm);
  cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease);
}
.check-option:hover { background: rgba(255,255,255,0.06); }

/* The last checkbox can't be turned off: a trigger without a channel
   runs, matches and reaches no one. Disabled says that without a line of
   explanation. */
.check-option:has(input:disabled) { cursor: default; }
.check-option:has(input:disabled):hover { background: none; }

.check-option input {
  appearance: none;
  -webkit-appearance: none;
  flex: none;
  position: relative;
  width: 18px;
  height: 18px;
  border: var(--rand-dikte) solid var(--rand);
  border-radius: 5px;
  cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease),
              border-color var(--dur-fast) var(--ease);
}
.check-option input:hover { border-color: var(--rand-actief); }

/* Blue, because this IS the interaction -- you're choosing something
   here. Green would mean status, and that's not what this is. */
.check-option input:checked {
  background: var(--blauw-knop);
  border-color: var(--blauw-knop);
}
.check-option input:checked::after {
  content: '';
  position: absolute;
  left: 4px;
  top: 0;
  width: 5px;
  height: 10px;
  border: solid #FFFFFF;
  border-width: 0 2px 2px 0;
  transform: rotate(45deg);
}
.check-option input:disabled { opacity: 0.55; cursor: default; }

@media (prefers-reduced-motion: reduce) {
  .check-option, .check-option input { transition: none; }
}

/* --- Revealing text ---------------------------------------------------------
 * Rebuilt from magicui's `dia-text-reveal`. A colored band sweeps across
 * the text; ahead of the band the text is still transparent, behind the
 * band it's ordinary text color. The GRADIENT is built per frame in JS
 * (`diaOnthul` in index.js) -- here is only the recipe that turns a
 * background into a text fill.
 *
 * `color: transparent` means: no background = no text. So JS only adds
 * this class once it's actually about to draw, and every exit ends in the
 * end state (solid text color) instead of the start state.
 *
 * One-off, so this falls outside the rule that only a STATE may run
 * forever -- and there's no CSS animation on it, so it doesn't show up in
 * document.getAnimations() either. */
.slide-reveal {
  color: transparent;
  -webkit-background-clip: text;
  background-clip: text;
  background-size: 100% 100%;
  background-repeat: no-repeat;
}

/* ---------------------------------------------------------------------------
 * LAYER 1 OF THE PLUS MENU — a list, not a grid
 * -------------------------------------------------------------------------
 * This used to be a grid of tile boxes. With 44 categories that became a
 * wall of little boxes: each box set its own border, its own height and
 * its own center, and the lane headers between them drowned in all those
 * lines. You had to search instead of read.
 *
 * Now layer 1 is the same `.menu-row` as layers 2 and 3 -- one menu
 * component instead of two that can drift apart. `.menu-lane-row` only
 * sets the compact size; the behavior comes from `.menu-row`.
 *
 * The structure now lives in the HEADERS and the white space, not in
 * borders. That's also why the panel is narrow again: one column of text
 * doesn't need 620px, and a narrow column reads faster than a wide one.
 * ------------------------------------------------------------------------ */
.menu-plus.is-grid {
  min-width: min(300px, calc(100vw - 2 * var(--sp-4)));
  max-width: min(340px, calc(100vw - 2 * var(--sp-4)));
  max-height: min(66vh, 480px);
  padding-bottom: var(--sp-2);
}

/* The lane tabs: one row of short words on the same glass as the menu,
   with an underline for the lane that's in view (index.js sets
   aria-current while scrolling). No pills, no black block: this is
   navigation inside the menu, and the material is the menu's own. */
.menu-tabs {
  position: sticky;
  top: 0;
  z-index: 1;
  display: flex;
  gap: 2px;
  padding: 2px var(--sp-2) 0;
  background: var(--glas);
  backdrop-filter: var(--glas-waas);
  -webkit-backdrop-filter: var(--glas-waas);
  border-bottom: 1px solid var(--hairline);
}
.menu-tab {
  position: relative;
  flex: 1 1 0;
  min-width: 0;
  min-height: 44px;
  padding: 0 4px;
  border: 0;
  background: transparent;
  color: var(--tekst-gedempt);
  font: inherit;
  font-size: var(--fs-xs);
  font-weight: 600;
  letter-spacing: 0.02em;
  white-space: nowrap;
  cursor: pointer;
  transition: color var(--dur-fast) var(--ease);
}
.menu-tab::after {
  content: '';
  position: absolute;
  left: 6px;
  right: 6px;
  bottom: 0;
  height: 2px;
  border-radius: 2px 2px 0 0;
  background-color: transparent;
  transition: background-color var(--dur-fast) var(--ease);
}
.menu-tab:hover, .menu-tab:focus-visible { color: var(--tekst); }
.menu-tab[aria-current="true"] { color: var(--tekst); }
.menu-tab[aria-current="true"]::after { background-color: var(--tekst); }

.menu-lane-heading {
  padding: var(--sp-3) var(--sp-3) 2px;
  color: var(--tekst-gedempt);
  font-size: var(--fs-xs);        /* a lane header is navigation, not a badge */
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.menu-lane-heading:first-child { padding-top: var(--sp-2); }

/* More compact than a regular menu row: there are 44 of these stacked,
   and then every pixel of line spacing counts four times as heavy as in a
   list of four. */
.menu-row.menu-lane-row {
  gap: 10px;
  min-height: 44px;
  padding: 8px var(--sp-3);
  font-size: var(--fs-md);
  font-weight: 400;
}

.menu-row-icon {
  display: inline-flex;
  flex: 0 0 auto;
  color: var(--tekst-gedempt);
  transition: color var(--dur-fast) var(--ease);
}
.menu-row-icon svg { display: block; }
/* The icon only lights up on hover. At rest it's a mark you read past;
   blue on every row would ask for attention 44 times over. */
.menu-lane-row:hover .menu-row-icon,
.menu-lane-row:focus-visible .menu-row-icon { color: var(--blauw); }

/* A bare number, not a pill: 44 filled little pills is more ink than
   information, and the number alone says the same thing. */
.menu-row-count {
  flex: 0 0 auto;
  color: var(--tekst-gedempt);
  font-size: var(--fs-2xs);
  font-variant-numeric: tabular-nums;
}
.menu-row-flag {
  flex: 0 0 auto;
  color: var(--amber);
  font-size: var(--fs-2xs);
  font-weight: 600;
}

/* Layer 3: a row that can be switched on IMMEDIATELY. Green means status
   in this project, so this isn't a button color but a message: this click
   is done. */
.menu-row-on .menu-brand { background: rgba(0,200,83,0.16); color: var(--groen); }
.menu-other .menu-row-text { color: var(--blauw); }

/* --- The share diamond -----------------------------------------------------
 * One round button on the success screen that blooms open into a diamond
 * of three actions (NotifyShare on top, copy on the left, the system
 * share sheet on the right) with a close button below — the floating
 * action button pattern, in our own visual language: black with the 22%
 * hairline, not blue (blue is the next step, and that's "New notify";
 * sharing is optional).
 *
 * MOTION: the satellites depart from the center (transform+opacity,
 * staggered by 40ms) and then hold still — nothing loops forever. Closing
 * is shorter than opening: waiting for something to leave feels like
 * delay. The container's margin grows along with it so the diamond
 * doesn't overlap the text above or the buttons below. */
.share-diamond {
  position: relative;
  width: 56px;
  height: 56px;
  margin: 10px auto 0;
  transition: margin var(--dur-base) var(--ease);
}
.share-diamond.open { margin: 78px auto 68px; }

.diamond-btn {
  position: absolute;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 48px;
  height: 48px;
  border-radius: 50%;
  background: var(--bg);
  border: 1px solid var(--rand);
  color: var(--tekst);
  cursor: pointer;
  padding: 0;
  transition: border-color var(--dur-fast) var(--ease);
}
.diamond-btn:hover:not(:disabled) { border-color: var(--rand-actief); }
.diamond-btn:disabled { cursor: default; }

.diamond-center {
  width: 56px;
  height: 56px;
  left: 0;
  top: 0;
}

/* The satellites start UNDER the center (scale .5, transparent) and fly
 * to their spot as soon as .open is added. The transition itself lives on
 * the button, the stagger per direction. */
.diamond-satellite {
  left: 4px;
  top: 4px;
  opacity: 0;
  transform: translate(0, 0) scale(0.5);
  pointer-events: none;
  transition: transform 0.14s var(--ease), opacity 0.12s var(--ease);
}
.share-diamond.open .diamond-satellite {
  opacity: 1;
  pointer-events: auto;
}
.share-diamond.open .diamond-top  { transform: translate(0, -72px) scale(1); transition-delay: 0ms; }
.share-diamond.open .diamond-left  { transform: translate(-72px, 0) scale(1); transition-delay: 40ms; }
.share-diamond.open .diamond-right { transform: translate(72px, 0) scale(1); transition-delay: 80ms; }
.share-diamond.open .diamond-bottom  { transform: translate(0, 62px) scale(1); transition-delay: 40ms; }

/* Success: the share or copy icon flips to a checkmark. Green is status
   here ("this has happened"), exactly what green is for. */
.diamond-btn[data-done="ja"] { border-color: rgba(0, 200, 83, 0.5); color: var(--groen, #00C853); }

@media (prefers-reduced-motion: reduce) {
  .share-diamond, .diamond-satellite { transition: none; }
}

/* Phase 4.4: the label next to Telegram on a touchscreen ('best on a
   phone'). */
.check-caption{ margin-left:auto; padding-left:8px; font-size:var(--fs-xs); color:var(--tekst-gedempt); white-space:nowrap; }
