/* ==========================================================================
   is it slop? — marker zine
   Paper, ink, three marker colours: yellow the primary action, pink the
   machine call, teal the human call. Hand-drawn is the frame and the
   ornament; every figure is set in clean type.
   ========================================================================== */

:root {
  --paper:      #FFF8EC;
  --paper-deep: #F7EBD6;
  --sheet:      #FFFDF6;
  --ink:        #111111;
  --ink-soft:   #6A5D48;   /* tinted from the paper hue, never grey */
  --rule:       rgba(17, 17, 17, .14);

  --hl-yellow:  #FFE34D;
  --hl-pink:    #FF6BAE;
  --hl-teal:    #00C7B7;

  --font-marker: "Shantell Sans", cursive;
  --font-sans:   "Figtree", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
  /* A utility, not a third voice: the platform stack, never self-hosted, and
     sanctioned for exactly one thing — an inline <code> span. DESIGN.md has
     described this token since the system was written; it had simply never
     been declared, because nothing on the tool page sets code. */
  --font-mono:   ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

  --pad: clamp(1.25rem, 4vw, 3.5rem);
  --wrap: 1320px;

  --sketch-a: 255px 15px 225px 15px / 15px 225px 15px 255px;
  --sketch-b: 15px 225px 15px 255px / 255px 15px 225px 15px;

  --ease: cubic-bezier(.16, .84, .28, 1);

  /* type ramp — eight steps, each clearly distinct from its neighbour */
  --t-xs:   .85rem;
  --t-sm:   .9rem;
  --t-md:   .95rem;
  --t-base: 1rem;
  --t-body: 1.0625rem;
  --t-lg:   1.1rem;
  --t-xl:   1.3rem;
  --t-2xl:  1.6rem;

  /* fluid display steps — the marker voice scales with the viewport */
  --t-d-sm:  clamp(1.5rem, 3vw, 2.1rem);
  --t-d-md:  clamp(1.8rem, 4vw, 2.6rem);
  --t-d-lg:  clamp(1.9rem, 5vw, 3rem);
  --t-d-xl:  clamp(2.6rem, 6.4vw, 4.4rem);
  --t-d-2xl: clamp(2.7rem, 8.4vw, 5.6rem);


  /* the whole depth system: two warm brown-tinted shadows, offset and blurred */
  --shadow-key:   0 5px 14px rgba(90, 70, 25, .13);
  --shadow-sheet: 0 8px 22px rgba(90, 70, 25, .12);

  /* utility radii, for hairlines and chips the drawn frames do not cover */
  --r-hair: 2px;
  --r-tick: 3px;
  --r-util: 8px;
  --sketch-chip: 6px 10px 6px 12px;

  /* drawn marker fields: swiped top and bottom edges, stretched to the band */
  --field-y: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1200 200' preserveAspectRatio='none'%3E%3Cpath d='M0 13Q150 3 310 10T620 6T900 14T1200 5L1200 188Q1040 198 880 190T560 195T280 186T0 194Z' fill='%23FFE34D'/%3E%3C/svg%3E");
  --field-p: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1200 200' preserveAspectRatio='none'%3E%3Cpath d='M0 8Q170 18 330 9T640 13T920 4T1200 12L1200 192Q1010 183 850 193T520 187T240 196T0 187Z' fill='%23FF6BAE'/%3E%3C/svg%3E");
  --field-t: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1200 200' preserveAspectRatio='none'%3E%3Cpath d='M0 11Q140 4 300 12T610 5T890 12T1200 7L1200 190Q1060 196 900 187T580 193T260 188T0 195Z' fill='%2300C7B7'/%3E%3C/svg%3E");
  /* the same swiped field, unmarked: an empty slot the verdict floods */
  --field-w:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1200 200' preserveAspectRatio='none'%3E%3Cpath d='M0 13Q150 3 310 10T620 6T900 14T1200 5L1200 188Q1040 198 880 190T560 195T280 186T0 194Z' fill='%23F7EBD6' stroke='%23111111' stroke-width='4' stroke-dasharray='16 13' stroke-linecap='round' vector-effect='non-scaling-stroke' opacity='.85'/%3E%3C/svg%3E");
}

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

/* author `display` rules outrank the UA sheet, so `hidden` needs defending
   or a hidden panel keeps its space in the grid */
[hidden] { display: none !important; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background-color: var(--paper);
  /* paper fibre */
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='f'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3'/%3E%3CfeColorMatrix values='0 0 0 0 .42 0 0 0 0 .34 0 0 0 0 .16 0 0 0 .1 0'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23f)'/%3E%3C/svg%3E");
  color: var(--ink);
  font-family: var(--font-sans);
  font-size: var(--t-body);
  line-height: 1.6;
  font-synthesis-weight: none;
}

h1, h2, h3 { margin: 0; font-weight: 700; }
p { margin: 0; }
ul, ol { margin: 0; padding: 0; list-style: none; }

.sprite { position: absolute; width: 0; height: 0; overflow: hidden; }

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

.skip {
  position: absolute; left: -9999px; top: 0; z-index: 20;
  background: var(--ink); color: var(--paper);
  padding: .7rem 1.1rem; font-weight: 600; border-radius: 0 0 var(--r-util) 0;
}
.skip:focus { left: 0; }

:where(a, button, textarea):focus-visible {
  outline: 3px solid var(--hl-teal);
  outline-offset: 3px;
  border-radius: var(--r-hair);
}

/* --- hand-drawn frame: two passes of a wobbling marker ------------------- */
.sketchy { position: relative; border: 2.5px solid var(--ink); border-radius: var(--sketch-a); }
.sketchy::after {
  content: ""; position: absolute; inset: -6px;
  border: 2px solid var(--ink); border-radius: var(--sketch-b);
  opacity: .34; pointer-events: none;
}

/* ===================== masthead ========================================== */
.masthead {
  max-width: var(--wrap); margin-inline: auto;
  padding: 1.4rem var(--pad) .6rem;
  display: flex; align-items: center; justify-content: space-between; gap: 1rem;
  flex-wrap: wrap;
}
.brand { display: flex; align-items: center; gap: .55rem; text-decoration: none; color: var(--ink); }
.brand-mark { width: 34px; height: 40px; color: var(--ink); }
.brand-word {
  font-family: var(--font-marker); font-size: var(--t-2xl); font-weight: 700;
  letter-spacing: -.02em; line-height: 1;
}
.brand-q { color: var(--hl-pink); -webkit-text-stroke: .5px var(--ink); }

.meter { display: flex; align-items: center; gap: .6rem; }
/* an author `display` beats the UA's [hidden] rule, so state it here or the
   attribute does nothing on this element */
.meter[hidden] { display: none; }
/* The tally grows a mark at a time, so its width comes from its own viewBox
   rather than a fixed box that would squeeze the strokes thinner as it fills.
   The painter overwrites the width against this height — stated as a length
   rather than left to `auto`, because flex needs a definite size here to know
   the masthead has stopped fitting and the meter should drop to its own line. */
.tally {
  --tally-h: 24px;
  height: var(--tally-h); width: calc(var(--tally-h) * 84 / 30);
  color: var(--ink); flex: none;
}
.meter-text { font-family: var(--font-marker); font-size: var(--t-body); line-height: 1.2; }
/* spent state, kept for the metered countdown */
.meter.is-out .meter-text { color: var(--hl-pink); -webkit-text-stroke: .4px var(--ink); }

/* The newest mark is scratched in rather than appearing — one short stroke on
   the house easing, sized by pathLength so an upright and a slash draw at the
   same rate. Everything not marked .is-new is simply already there. */
@keyframes tally-scratch { from { stroke-dashoffset: 1; } to { stroke-dashoffset: 0; } }
.tally .is-new { stroke-dasharray: 1; animation: tally-scratch .26s var(--ease) both; }

@media (prefers-reduced-motion: reduce) { .tally .is-new { animation-duration: .001ms; } }

/* ===================== the desk ========================================== */
.desk {
  max-width: var(--wrap); margin-inline: auto;
  padding: clamp(1rem, 3vw, 2.4rem) var(--pad) clamp(2rem, 4vw, 3.25rem);
  display: grid; gap: clamp(2rem, 5vw, 4rem);
  align-items: start;
}
@media (min-width: 900px) {
  .desk {
    grid-template-columns: 1fr 1.02fr;
    grid-template-rows: auto 1fr;
    gap: 0 clamp(2rem, 3.4vw, 3.4rem);
    align-items: stretch;
  }
  /* the label gets a row of its own, so the sheet and the stage — the two
     shapes that actually read as panels — start on the same line */
  .sheet-label { grid-column: 1; grid-row: 1; }
  .desk-sheet  { grid-column: 1; grid-row: 2; }
  .stage       { grid-column: 2; grid-row: 2; }
  /* the placeholder carries the result's own rhythm, so swapping one for the
     other reads as a component changing state, not a different layout */
  .stage-default {
    display: flex; flex-direction: column; height: 100%;
    gap: clamp(1.6rem, 3vw, 2.2rem);
  }
  .stage-default .caution { margin-top: auto; margin-bottom: .3rem; }
  /* the passage stays in view while the findings scroll past it */
  .desk-sheet { position: sticky; top: 1.5rem; align-self: start; }
}

@media (prefers-reduced-motion: reduce) { .desk-sheet { position: static; } }
/* the paste box comes before the action on narrow screens: you cannot press
   check before you have pasted */
@media (max-width: 899px) {
  /* both columns dissolve into one flow, so the headline can introduce the
     product, the field can follow it directly, and the answer can land
     immediately under the button you just pressed */
  .desk { gap: 1.15rem; }
  .stage, .stage-default, .desk-sheet { display: contents; }
  /* the field leads on a phone: paste, press, and the answer lands directly
     under the button in the slot the waiting band was holding */
  .sheet-label { order: 1 }
  .sheet-wrap { order: 2 }
  .act { order: 3; margin-top: .3rem }
  .verdict--waiting { order: 4 }
  .notes--intro { order: 5 }
  .report { order: 6 }
  .caution { order: 7; margin-top: .6rem }
  /* the empty scales are a desktop device for holding the column's shape;
     on a phone the states are sequential, so they would only cost reach */
}

.lede {
  font-family: var(--font-marker);
  font-size: var(--t-d-xl);
  line-height: .95; letter-spacing: -.035em;
  text-wrap: balance;
}
/* the same authored swipe every other highlight uses — one way to draw a mark */
.lede em {
  font-style: normal; white-space: nowrap; padding-bottom: .06em;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 40' preserveAspectRatio='none'%3E%3Cpath d='M3 8Q30 4 60 7T124 6T196 9Q200 22 194 34Q150 37 100 34T6 33Q0 19 3 8Z' fill='%2300C7B7'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-size: 100% .17em; background-position: 0 100%;
}

/* no artificial cap: the grid column is the measure, so the copy fills it
   instead of leaving a dead band down the middle of the page */
.sub {
  margin-top: 1.5rem;
  font-size: var(--t-lg); color: var(--ink-soft);
}

/* the action sits directly under the field it acts on, never before it */
.act {
  margin-top: clamp(1.5rem, 2.4vw, 2rem);
  display: flex; align-items: center; gap: 1.3rem; flex-wrap: wrap;
}
.act-note { font-size: var(--t-sm); color: var(--ink-soft); font-family: var(--font-marker); }

.btn {
  font-family: var(--font-marker); font-size: var(--t-xl); font-weight: 700; line-height: 1;
  color: var(--ink); background: var(--hl-yellow);
  border: 2.5px solid var(--ink); border-radius: var(--sketch-a);
  padding: .78em 1.5em; cursor: pointer; position: relative;
  transition: transform .18s var(--ease), background-color .18s var(--ease);
  box-shadow: var(--shadow-key);
}
.btn::after {
  content: ""; position: absolute; inset: -6px;
  border: 2px solid var(--ink); border-radius: var(--sketch-b);
  opacity: .38; transition: opacity .18s var(--ease);
}
.btn:hover { transform: rotate(-.8deg) translateY(-2px); }
.btn:hover::after { opacity: .7; }
.btn:active { transform: translateY(1px) rotate(0deg); }
.btn[disabled] { opacity: .45; cursor: not-allowed; transform: none; box-shadow: none; }
.btn.is-busy { background: var(--paper-deep); }
.btn-pink { background: var(--hl-pink); }

.linky {
  font-family: var(--font-marker); font-size: var(--t-base); color: var(--ink-soft);
  background: none; border: 0; padding: 0; cursor: pointer;
  text-decoration: underline; text-decoration-thickness: 2px; text-underline-offset: 3px;
  text-decoration-color: var(--hl-pink);
}
.linky:hover { color: var(--ink); }

/* --- the sheet ----------------------------------------------------------- */
/* .desk-sheet deliberately has no position here: it would outrank the sticky
   rule above by source order, and the mascot anchors to .sheet-wrap anyway */
.sheet-wrap { position: relative; }
.sheet-label {
  display: block; font-family: var(--font-marker); font-size: var(--t-body);
  margin-bottom: .55rem; color: var(--ink-soft);
}
.sheet { background: var(--sheet); box-shadow: var(--shadow-sheet); }
.sheet textarea {
  /* one pitch governs the ruling, the leading and the scroll phase, so the
     paper cannot come apart from the writing sitting on it */
  --rule-pitch: 28px;
  display: block; width: 100%; resize: vertical;
  min-height: clamp(230px, 38vh, 380px);
  border: 0; background: transparent; color: var(--ink);
  font-family: var(--font-sans); font-size: var(--t-body); line-height: var(--rule-pitch);
  padding: 20px 22px 8px;
  background-image: repeating-linear-gradient(to bottom,
    transparent 0 calc(var(--rule-pitch) - 1px),
    var(--rule) calc(var(--rule-pitch) - 1px) var(--rule-pitch));
  /* The rules belong to the writing, not to the box, so they have to travel
     with it. `background-attachment: local` does that in Chrome and is ignored
     on a textarea by other engines, where the lines sit still while the text
     slides over them. So the offset is driven from scrollTop in app.js instead
     and the attachment is pinned to the element — one behaviour everywhere.
     app.js feeds the phase, never the raw distance: see the note there. */
  background-attachment: scroll;
  background-position: 0 calc(20px - var(--rule-shift, 0px));
}
.sheet textarea::placeholder { color: var(--ink-soft); opacity: 1; }
.sheet textarea:focus { outline: 0; }
.sheet:focus-within { box-shadow: var(--shadow-sheet), 0 0 0 3px var(--hl-teal); }
/* left-grouped so the mascot can lean on the corner without covering it */
.sheet-foot {
  display: flex; align-items: center; gap: 1.4rem;
  padding: .5rem 96px 1rem 22px; font-family: var(--font-marker);
  font-size: var(--t-md); color: var(--ink-soft);
}
/* Past the detector's ceiling. The same pink the spent meter uses, for the same
   reason: something is about to be smaller than the reader expects. It is a
   warning, not a refusal — the check still runs, on the words up to the cap. */
#count.is-over { color: var(--hl-pink); -webkit-text-stroke: .4px var(--ink); }
/* Declared after the base rule it overrides, not before it — same specificity,
   so source order decides. A shorter field keeps the action it feeds on screen. */
@media (max-width: 899px) {
  .sheet textarea { min-height: clamp(180px, 26vh, 260px); }
}

.mascot { width: clamp(78px, 11vw, 108px); height: auto; color: var(--ink); pointer-events: none; }

/* the float rides a wrapper, so the filtered drawing inside rasterises once
   and the browser only moves the resulting layer */
.mascot-float {
  position: absolute; right: clamp(-10px, 0.5vw, 4px); bottom: clamp(-52px, -4vw, -34px);
  display: block; pointer-events: none;
  will-change: transform;
  animation: bob 5.4s ease-in-out infinite;
}
.mascot-desk { display: block; filter: none; }

@keyframes bob {
  0%, 100% { transform: translateY(0) rotate(-1.1deg); }
  50%      { transform: translateY(-8px) rotate(1.3deg); }
}

/* one face at a time */
.mascot .face { display: none; }
.mascot[data-face="wait"]  .face--wait,
.mascot[data-face="think"] .face--think,
.mascot[data-face="flat"]  .face--flat,
.mascot[data-face="shrug"] .face--shrug,
.mascot[data-face="nod"]   .face--nod { display: inline; }

/* the blink squashes each eye about its own centre */
.eye-g { transform-box: fill-box; transform-origin: center; }
.mascot.is-blink .eye-g { animation: blink .17s ease-in-out; }
@keyframes blink {
  0%, 100%  { transform: scaleY(1); }
  40%, 60%  { transform: scaleY(.06); }
}

/* a touch of lag on the pupils reads as looking rather than snapping */
.pupil { transition: transform .18s cubic-bezier(.22, 1, .36, 1); }

@media (prefers-reduced-motion: reduce) {
  .mascot-float { animation: none; }
  .mascot.is-blink .eye-g { animation: none; }
}

/* Colour means state; paper means commentary. The three highlighters keep
   their functional meanings and yellow keeps the primary action, so a caution
   reads as a note taped to the page rather than competing with the button. */
.caution {
  margin-top: clamp(2.4rem, 4vw, 2.8rem);
  font-family: var(--font-marker); font-size: var(--t-sm); line-height: 1.45;
  color: var(--ink); background: var(--paper-deep);
  padding: .8rem 1rem; border: 2px solid var(--ink);
  border-radius: var(--sketch-b); transform: rotate(-1deg);
}
/* No tape. At the size this note sits at, a torn edge is ~5px and cannot
   read, so it was ornament pretending to be material. The tilt and the drawn
   border already say "note", which is all this needs to say. */

/* ===================== report =========================================== */
.report { display: grid; gap: clamp(1.6rem, 3vw, 2.2rem); }

/* The call lands on a drawn marker field with a swiped edge — the one focal
   element must not be the only machine-straight rectangle on the page. */
.verdict {
  padding: clamp(1.8rem, 3.2vw, 2.6rem) clamp(1.1rem, 2vw, 1.8rem);
  background-repeat: no-repeat; background-size: 100% 100%;
  background-image: var(--field-y);
  text-align: center; outline: 0;
}
.verdict[data-tone="slop"]    { background-image: var(--field-p); }
.verdict[data-tone="human"]   { background-image: var(--field-t); }
.verdict[data-tone="unsure"]  { background-image: var(--field-y); }
.verdict[data-tone="waiting"] { background-image: var(--field-w); }
.verdict--waiting .verdict-call { color: var(--ink); }
.verdict--waiting .verdict-num { color: var(--ink-soft); }

/* --- the sweep, while a check is out -------------------------------------
   The API can take two seconds and a disabled button cannot fill them. So the
   band keeps its question and its place, and only the line under it changes
   job: it stops instructing and starts naming what is being looked for, one
   feature at a time. The line inks up from soft to full while it works —
   the same copy slot, awake rather than resting.

   Each phrase is written on left to right and swept off the same way, so the
   pen only ever travels in one direction, and no two phrases are on the paper
   at once. That is also what keeps a centred line from jumping: the width
   changes while the box is empty, where there is nothing to see it against.

   The clock is the band's own, declared here and read by app.js, so the
   interval that swaps the words and the animation that draws them cannot
   drift apart. */
.stage-default { --tick: 600ms; }
.verdict--waiting[data-state="checking"] .verdict-num { color: var(--ink); }
.verdict-num.is-writing { animation: sweep var(--tick) both; }
.verdict-num.is-final   { animation: sweep-in .3s var(--ease) both; }

/* The same inset shape the report's own wipe uses, and for the same reason:
   an animated inset with a negative side is composited and then dropped
   outright by Chrome — the value computes, the paint never arrives. Only the
   sweeping edge moves; the other three stay at 0, which clips to the line box
   and leaves the descenders alone, the box being a good 5px taller than the
   glyphs it holds. */
@keyframes sweep {
  0%   { clip-path: inset(0 100% 0 0); animation-timing-function: var(--ease); }
  44%  { clip-path: inset(0 0 0 0);    animation-timing-function: linear; }
  76%  { clip-path: inset(0 0 0 0);    animation-timing-function: linear; }
  100% { clip-path: inset(0 0 0 100%); }
}
@keyframes sweep-in {
  from { clip-path: inset(0 100% 0 0); }
  to   { clip-path: inset(0 0 0 0); }
}

/* The page-wide reduced-motion rule collapses every duration to nothing, and
   this animation's last frame is a line already swept away — collapsed, it
   would leave the sweep permanently blank. So the drawing is dropped outright
   here rather than shortened, and the words simply change, on a slower clock
   that app.js reads from the same property. */
@media (prefers-reduced-motion: reduce) {
  .stage-default { --tick: 1100ms; }
  .verdict-num.is-writing,
  .verdict-num.is-final { animation: none; clip-path: none; }
}

/* a notice is not a verdict */
.verdict[data-kind="notice"] {
  background-image: none; background: var(--paper-deep);
  border-block: 2.5px solid var(--rule);
  padding: clamp(1.3rem, 2.4vw, 1.8rem) clamp(1.1rem, 2vw, 1.6rem);
}
.verdict[data-kind="notice"] .verdict-call { font-size: var(--t-d-sm); }
.verdict[data-kind="notice"] .verdict-num { font-size: var(--t-body); color: var(--ink-soft); }
.report[data-kind="notice"] .notes,
.report[data-kind="notice"] .caveat { display: none; }

.verdict-call {
  font-family: var(--font-marker); font-weight: 700;
  font-size: var(--t-d-xl);
  line-height: .92; letter-spacing: -.04em; text-wrap: balance;
}
.verdict-num {
  margin-top: .9rem; font-size: var(--t-body); font-weight: 600;
  font-variant-numeric: tabular-nums; text-wrap: balance;
}

/* --- the dial ------------------------------------------------------------
   The score, drawn. Not a stroked ring: the radius drifts the way a hand's
   does, the mark is scratched into a dashed slot the same way the waiting
   field's edge is dashed, and it is pen-on-marker throughout — the band's own
   colour is the verdict, so the instrument on top of it stays ink.
   The arc geometry is generated in app.js; this only sizes and centres it. */
/* The measure lives on the side block, not the dial, because the hedge beneath
   is capped to the same width: the arc sets the column and the line below wraps
   inside it, instead of the two disagreeing about how wide the column is. */
.verdict-side { --dial: clamp(178px, 48vw, 264px); }
.dial {
  position: relative; width: var(--dial); aspect-ratio: 200 / 158;
  /* The cropped box still carries ~11px of the arc's own air top and bottom,
     so these are the visible gaps minus that, not the gaps themselves. */
  margin: clamp(1rem, 2vw, 1.4rem) auto clamp(.5rem, 1.1vw, .8rem);
}
.dial-art { display: block; width: 100%; height: 100%; color: var(--ink); }

/* The reading centres on the arc's own circle, which the cropped box puts at
   57% of its height — so the block is inset from the top to bring the two
   centres back together. */
/* The side padding is a proportion of the dial, not a measure in characters:
   `ch` in the marker face is .70em, so a cap that looked generous sat above the
   text's real width and let the legend run out over the arc at the small size.
   A percentage scales with the drawing and cannot come uncoupled from it. */
.dial-read {
  position: absolute; inset: 14% 0 0;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  padding: 0 23%; text-align: center; pointer-events: none;
}
/* sans, because it is a measurement; the % is set down so the digits lead */
.dial-pct {
  display: flex; align-items: baseline; justify-content: center;
  font-family: var(--font-sans); font-weight: 700; font-size: var(--t-d-md);
  line-height: 1; letter-spacing: -.03em; font-variant-numeric: tabular-nums;
}
.dial-unit { font-size: .46em; font-weight: 600; margin-left: .12em; }
/* The legend, spoken rather than measured — so it takes the marker hand. Capped
   below its own one-line width so it breaks the same way at every dial size,
   rather than setting on one line at the widest and two at the narrowest. */
.dial-of {
  margin-top: .5em; max-width: 9ch;
  font-family: var(--font-marker); font-size: var(--t-sm); line-height: 1.2;
}
/* The hedge is part of the finding, not a disclaimer under it, so it sits
   inside the band with the editor's own caution mark beside it. */
/* The mark and the two halves of the line are all flex items on a wrapping
   line, centred as a line rather than as a box. Centring the box does nothing
   once its content is wider than the dial it is capped to: it fills the column,
   and the text sits against the left edge under a centred arc. Wrapping puts
   the break where the markup allows one — at the dot — and centres whatever
   ends up on each line. */
.verdict-flag {
  margin-top: .6rem;
  display: inline-flex; flex-wrap: wrap; align-items: start;
  justify-content: center; gap: 0 .55rem;
  font-family: var(--font-marker); font-size: var(--t-base); line-height: 1.35;
}
.verdict-flag svg { width: 21px; height: 21px; flex: none; margin-top: .12em; }
.flag-a { white-space: nowrap; }

/* --- two columns: the instrument beside the call -------------------------
   Above the desk's own breakpoint the band has the width to set the dial and
   the call side by side, which suits a band that is wider than it is tall.
   Below it the band is one narrow column and the two stack, call first.

   Every rule here is scoped to `.report`, so this is the *answer's* layout and
   nothing else's. The waiting band keeps the single centred column it has
   always had: it holds no dial to set beside anything, and its call wants the
   full measure and the unrebased display step rather than a column's worth.

   Flex rather than grid, deliberately: a notice has no dial, and a flex line
   with one item fills the row instead of leaving an empty column behind.

   The call leads and the instrument follows, in both layouts, which is why
   nothing here reorders anything. The arc was on the left first and the hedge
   went with it, which put the caveat earlier in the scan than the verdict it
   qualifies. It also reads with the motion now: the band wipes left to right,
   so the call is lettered in as it is uncovered and the mark draws as the arc
   arrives, instead of the wipe opening on an empty slot. */
@media (min-width: 900px) {
  /* The gap is not what separates these two, which is why it is the smaller
     number here and the padding is the larger one. `.verdict-main` is the
     flexible item, so it absorbs the row's slack and sits wider than the call's
     longest wrapped line — that trailing rag was most of the space between the
     text and the arc, and giving the gap back to it changes nothing. Padding is
     the lever: it shrinks the row, which shrinks the rag. */
  .report .verdict:not([data-kind="notice"]) {
    display: flex; align-items: center; justify-content: center;
    gap: clamp(.75rem, 1.5vw, 1.35rem);
    padding-inline: clamp(1.75rem, 4.4vw, 3.4rem);
    text-align: left;
  }
  .report .verdict-side { flex: none; }
  /* `min-content` so the block hugs its wrapped lines instead of holding the
     row's slack as trailing rag. Left to shrink-to-fit, the rag is whatever the
     verdict string happens to leave — 0px behind "Looks human?" and 104px
     behind "It's slop." — and it lands between the call and the arc, where a
     reader can measure it against an adjacent object. Hugging moves that
     variation out to the band's swiped edges, where there is no hard boundary
     to measure against, and leaves the gap as the only thing between the two.
     `min-width: 0` stays as the valve: a single word wider than the row shrinks
     rather than pushing the arc out of the band.

     Scoped off the notice for the same reason the flex row above is: hugging
     is what stops a call leaving rag beside the arc, and a notice has no arc.
     Left unscoped it set "Not enough to go on." as four stacked words in a
     sliver down the left of a full-width band. */
  .report .verdict:not([data-kind="notice"]) .verdict-main { min-width: 0; width: min-content; }

  /* The same ramp step, rebased on the narrower column the call now gets. The
     step's ceiling is unchanged — this is where it starts from that changes, so
     the call still fits its column at 900px instead of overflowing it. */
  .report .verdict-call { --t-d-xl: clamp(2rem, 4.6vw, 4.4rem); }

  /* the arc gives up some size to buy the call its column */
  .report .verdict-side { --dial: clamp(150px, 15.5vw, 208px); text-align: center; }
  .report .dial { margin: 0 auto; }
  .report .dial-pct { font-size: var(--t-d-sm); }
  .report .dial-of  { font-size: var(--t-xs); }

  /* a caption under the arc now, rather than a line across the band */
  .report .verdict-flag { margin-top: .7rem; font-size: var(--t-sm); max-width: var(--dial); }
  .report .verdict-flag svg { width: 18px; height: 18px; }
}

/* the marker underline is a recurring device, not a one-off on the lede */
.h-how, .h-wall, .h-faq {
  font-family: var(--font-marker); letter-spacing: -.02em; line-height: 1.05;
  display: inline-block; padding-bottom: .14em;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 40' preserveAspectRatio='none'%3E%3Cpath d='M3 8Q30 4 60 7T124 6T196 9Q200 22 194 34Q150 37 100 34T6 33Q0 19 3 8Z' fill='%2300C7B7'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-size: 100% .19em; background-position: 0 100%;
}



.notes { display: grid; gap: .9rem; }

/* Two columns of what pulled the reading each way. The detector returns names
   only, so these are labels, not measurements — the colour dot ties each group
   to the verdict tone it argues for. */
/* hard evidence, not a lean — it gets the alarm colour the lists never use */
.artifact {
  padding: .9rem 1.1rem; background: var(--hl-pink);
  font-size: var(--t-md); line-height: 1.5;
}

/* one column when the verdict leaned one way, two only when it did not */
.leans { display: grid; gap: 1.1rem 1.6rem; }
@media (min-width: 520px) { .leans[data-cols="2"] { grid-template-columns: 1fr 1fr; } }
.lean-head {
  font-family: var(--font-marker); font-size: var(--t-base);
  display: flex; align-items: center; gap: .45rem; margin-bottom: .4rem;
}
.lean-head::before {
  content: ""; width: .6em; height: .6em; border-radius: 50%;
  border: 1.5px solid var(--ink); flex: none;
}
.lean[data-lean="ai"]    .lean-head::before { background: var(--hl-pink); }
.lean[data-lean="human"] .lean-head::before { background: var(--hl-teal); }
.lean-list { display: grid; gap: .3rem; font-size: var(--t-sm); color: var(--ink-soft); }
.lean-list li { padding-left: .85rem; position: relative; }
.lean-list li::before { content: "–"; position: absolute; left: 0; }

/* When the verdict leaned one way there is only one list, so it takes the
   stage's full width in two columns at reading size rather than sitting in a
   narrow strip. Two lists side by side already fill it, so they stay as they
   are. Multi-column, not grid: reasons should read down a column, not across. */
@media (min-width: 700px) {
  .leans[data-cols="1"] .lean-list {
    display: block; columns: 2; column-gap: 2.4rem;
    font-size: var(--t-body); line-height: 1.5;
  }
  .leans[data-cols="1"] .lean-list li {
    break-inside: avoid; margin-bottom: .5rem;
  }
}
.lean-raw { font-style: italic; }        /* an unlabelled feature name, shown as-is */
.lean-none { opacity: .7; }
.note {
  display: grid; grid-template-columns: 40px 1fr; align-items: start; gap: .7rem;
  font-family: var(--font-marker); font-size: var(--t-base); line-height: 1.45; color: var(--ink);
}
.note svg { width: 40px; height: 22px; color: var(--ink-soft); margin-top: .15rem; }
.note b { font-weight: 700; }

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

.caveat {
  padding: 1.1rem 1.3rem; background: var(--paper-deep);
  font-size: var(--t-md); line-height: 1.55;
}

/* ===================== wall ============================================= */
.wall {
  max-width: var(--wrap); margin-inline: auto;
  padding: clamp(2.6rem, 6vw, 4.5rem) var(--pad) 0; text-align: center;
}
.h-wall { font-size: var(--t-d-lg); }
.wall-sub { margin-top: .7rem; color: var(--ink-soft); font-family: var(--font-marker); font-size: var(--t-lg); }
.plan {
  max-width: 380px; margin: 2.2rem auto 0; padding: 1.8rem 1.6rem;
  background: var(--sheet); text-align: center;
}
.plan-price { font-family: var(--font-marker); font-size: 2rem; line-height: 1.1; }
.placeholder {
  background: var(--hl-pink); padding: .05em .4em;
  border: 2px dashed var(--ink); border-radius: var(--sketch-chip);
}
.plan-per { display: block; margin-top: .6rem; font-size: var(--t-md); line-height: 1.3; color: var(--ink-soft); font-family: var(--font-sans); }
.plan-blank { margin: 1.5rem 0; font-family: var(--font-marker); font-size: var(--t-body); }
.plan-note { margin-top: 1rem; font-size: var(--t-xs); line-height: 1.5; color: var(--ink-soft); }

/* ===================== how it works ===================================== */
.how {
  max-width: var(--wrap); margin-inline: auto;
  padding: clamp(3.5rem, 8vw, 6rem) var(--pad) clamp(2rem, 4vw, 3rem);
}
.h-how { font-size: var(--t-d-md); margin-bottom: clamp(1.8rem, 4vw, 2.6rem); }
.panels { display: grid; gap: clamp(1.6rem, 3vw, 2.4rem); counter-reset: strip; }
/* The top edge is the alignment line, so nothing reads as broken; heights
   follow their own content, so the row still breathes. Variation earned by
   the content, never an arbitrary offset — that was the version that looked
   like a mistake, and forcing equal heights was the version that looked like
   a stock card grid. */
@media (min-width: 820px) { .panels { grid-template-columns: 1fr 1.18fr 1fr; align-items: start; } }

.strip {
  background: var(--sheet); padding: 1.5rem 1.4rem 1.7rem;
  counter-increment: strip; position: relative;
}
.strip:nth-child(1) { transform: rotate(-.8deg); }
.strip:nth-child(2) { transform: rotate(.6deg); }
.strip:nth-child(3) { transform: rotate(-.5deg); }
.strip::before {
  content: counter(strip); position: absolute; top: -16px; left: -14px;
  width: 40px; height: 40px; display: grid; place-items: center;
  font-family: var(--font-marker); font-size: var(--t-lg); font-weight: 700;
  background: var(--hl-yellow); color: var(--ink);
  border: 2.5px solid var(--ink); border-radius: 50% 48% 52% 50% / 50% 52% 48% 50%;
}
.strip-art { width: 62px; height: 74px; color: var(--ink); filter: url(#rough); }
.strip h3 { font-family: var(--font-marker); font-size: var(--t-xl); margin: .5rem 0 .45rem; letter-spacing: -.01em; }
.strip p { font-size: var(--t-base); color: var(--ink-soft); }

/* ===================== questions ======================================== */
/* Seven notes pinned to the desk. They were one continuous ruled sheet first,
   and a single surface that tall reads as a wall whatever is written on it —
   these answers are discrete, read one at a time, so they are separate pieces
   of paper. Visible rather than folded into an accordion: Google will not use
   FAQPage markup for text a reader cannot see, and a hidden answer is a worse
   answer anyway.
   The page's two voices carry the structure as they do everywhere else — the
   question is spoken, so it takes the marker hand; the answer is read, so it is
   set in the sans on fresh paper at reading size. */
/* The incoming section carries the break, the way `.how` does: its top padding
   plus the trailing padding of the section above is the whole gap. This one used
   to open on clamp(1rem, 3vw, 2rem), which put 78px between the strips and this
   heading against 40px between the heading and its own notes — under twice the
   internal gap, so proximity read the two sections as one block. Matched to the
   desk-to-how boundary and a little over, since this is the page's last turn. */
.faq {
  max-width: var(--wrap); margin-inline: auto;
  padding: clamp(4rem, 8vw, 6.5rem) var(--pad) clamp(3.5rem, 7vw, 5.5rem);
}
.h-faq { font-size: var(--t-d-md); margin-bottom: clamp(2rem, 4vw, 2.8rem); }

/* Two columns, each stacking its own notes. Not one grid of rows: a grid row
   ties a note's height to the tallest note beside it, which is how seven scraps
   become a table. Not `columns` either — a multicol container mispaints each
   note's offset second stroke at the foot of a column, and flowing them means
   nobody decides which question tops the right-hand column. So the split is
   held in the markup and each column is its own stack. */
.faq-list {
  display: grid; grid-template-columns: 1fr 1fr;
  gap: clamp(1.8rem, 3vw, 2.8rem); align-items: start;
}
.faq-col {
  display: grid; gap: clamp(1.6rem, 3vw, 2.4rem); align-content: start;
}
/* one column on a phone: the wrappers step out of the way rather than stacking
   as two blocks, so the seven notes keep one rhythm down the screen */
@media (max-width: 819px) {
  .faq-list { grid-template-columns: 1fr; gap: clamp(1.6rem, 3vw, 2.4rem); }
  .faq-col { display: contents; }
}

/* The frame is the page's double stroke, repeated here rather than borrowed
   from `.sketchy`, the way the button repeats it: the build reads this FAQ by
   its exact class attribute to check the copy against the FAQPage schema, so
   `faq-item` cannot take a second class. */
.faq-item {
  display: grid; position: relative;
  padding: 1.2rem 1.35rem 1.35rem;
  background: var(--sheet);
  border: 2.5px solid var(--ink); border-radius: var(--sketch-a);
}
.faq-item::before {
  content: ""; position: absolute; inset: -6px; pointer-events: none;
  border: 2px solid var(--ink); border-radius: var(--sketch-b); opacity: .34;
}
/* A hand put these down, and no two the same — including across the two
   columns, so a note never leans the same way as the one level with it. Well
   under the tilt budget, because every one of them is full of text. */
.faq-col:first-child  .faq-item:nth-child(1) { transform: rotate(-.65deg); }
.faq-col:first-child  .faq-item:nth-child(2) { transform: rotate(.45deg); }
.faq-col:first-child  .faq-item:nth-child(3) { transform: rotate(-.3deg); }
.faq-col:last-child   .faq-item:nth-child(1) { transform: rotate(.5deg); }
.faq-col:last-child   .faq-item:nth-child(2) { transform: rotate(-.5deg); }
.faq-col:last-child   .faq-item:nth-child(3) { transform: rotate(.35deg); }
.faq-col:last-child   .faq-item:nth-child(4) { transform: rotate(-.45deg); }

/* Held one tier below the three headings, which is why no teal mark sits under
   it: seven underlines down a board would stop meaning anything, and the
   section heading above has already spent that device. The hand is the
   difference — a question is said, an answer is written. */
.faq-item h3 {
  font-family: var(--font-marker); font-size: var(--t-xl);
  line-height: 1.2; letter-spacing: -.01em; margin-bottom: .5rem;
  text-wrap: balance;
}
.faq-item p {
  font-size: var(--t-body); line-height: 1.6; color: var(--ink-soft);
  text-wrap: pretty;
}

/* Marginalia, not icons. Four of the seven notes carry a drawing and the rest
   carry none — a mark on every one would be a column of icons whatever it is a
   picture of. Each answers its own question in the hand the mascot is drawn in,
   takes the illustration filter the strip art takes, and is aria-hidden, since
   the answer beside it is already written out.
   It hangs off the bottom edge, the way the step badge overlaps a strip and the
   mascot leans on the paste sheet: an object resting on the note rather than a
   picture filed inside it. */
.faq-doodle {
  justify-self: end; margin: .6rem -.5rem -2.3rem 0;
  height: 72px; width: auto; color: var(--ink); filter: url(#rough);
}
.faq-item .doodle-tall  { height: 86px; }
.faq-item .doodle-small { height: 64px; }
.faq-item:nth-child(odd) .faq-doodle  { transform: rotate(-3.5deg); }
.faq-item:nth-child(even) .faq-doodle { transform: rotate(2.5deg); }

/* On a phone the note is the full column, so a drawing hanging off its corner
   sits over the one below rather than in a gutter beside it. It comes back
   inside the frame and keeps its own line. */
@media (max-width: 819px) {
  .faq-doodle { margin: .5rem -.1rem -.2rem 0; height: 64px; }
  .faq-item .doodle-tall { height: 76px; }
}

/* ===================== footer =========================================== */
.foot {
  max-width: var(--wrap); margin-inline: auto;
  padding: clamp(2.5rem, 6vw, 4rem) var(--pad) 3rem;
  border-top: 2.5px solid var(--rule); margin-top: clamp(2rem, 5vw, 3rem);
  display: flex; justify-content: space-between; align-items: end; gap: 1.4rem; flex-wrap: wrap;
}
/* the credit belongs to the wordmark, so the two set as one tight block */
.foot-brand { display: grid; gap: .55rem; }
.foot-word { font-family: var(--font-marker); font-size: var(--t-2xl); letter-spacing: -.02em; line-height: 1; }
.foot-by {
  font-family: var(--font-marker); font-size: var(--t-sm); line-height: 1.2;
  color: var(--ink-soft);
}
.foot-by .linky { font-size: inherit; }
/* No measure cap: the 40ch rule is for prose, and this line is a legal notice
   that reads as one unit. Balanced so the narrow-viewport wrap splits evenly
   instead of orphaning the last word onto its own line. */
.foot-note { font-size: var(--t-sm); color: var(--ink-soft); text-wrap: balance; }

/* ===================== the one authored moment ========================== */
/* The marker draws: the band wipes in, the call settles, the mark is scratched
   round the dial, and the hedge lands last — one sequence, still, with the dial
   inside it rather than beside it. */
.report[data-run="1"] .verdict { animation: wipe .5s var(--ease) both; }
.report[data-run="1"] .verdict-call { animation: settle .55s .14s var(--ease) both; }
.report[data-run="1"] .verdict-num { animation: settle .5s .3s var(--ease) both; }
.report[data-run="1"] .dial-mark { animation: draw .66s .3s var(--ease) both; }
.report[data-run="1"] .dial-read { animation: settle .5s .54s var(--ease) both; }
.report[data-run="1"] .dial-skip { animation: inked .4s .84s var(--ease) both; }
.report[data-run="1"] .verdict-flag { animation: settle .45s .92s var(--ease) both; }
.report[data-run="1"] .note { animation: settle .45s var(--ease) both; animation-delay: var(--d, 0s); }

@keyframes wipe   { from { clip-path: inset(0 100% 0 0); } to { clip-path: inset(0 0 0 0); } }
@keyframes settle { from { opacity: 0; transform: translateY(9px) rotate(-.6deg); } to { opacity: 1; transform: none; } }
/* the mark is drawn along its own length; the painter hands the length over as
   --len so this stays one keyframe whatever the score is */
@keyframes draw   { from { stroke-dashoffset: var(--len, 0); } }
/* no `to`, so the skipped mark inks up to whatever opacity the rule below set
   rather than to a number restated here */
@keyframes inked  { from { opacity: 0; } }
/* held below the mark it trails from: the afterthought must not out-weigh the
   reading it trails from */
.dial-skip { opacity: .42; }

/* `use` content lives in a shadow tree, so the animation rides the <svg> itself. */
.mascot.is-busy { animation: nudge 1.05s ease-in-out infinite; transform-origin: 50% 88%; }
@keyframes nudge { 0%, 100% { transform: rotate(-2.4deg); } 50% { transform: rotate(2.8deg); } }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important; animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
  }
}

/* The Turnstile container spans the viewport so its widget centres exactly at
   whatever size Cloudflare renders (see index.html); it waives pointer events
   so the page stays clickable through it, and the widget takes them back here
   so the challenge itself can still be clicked.

   Between challenges the container is marked data-idle and the widget goes
   invisible and click-through with it. Turnstile keeps the widget painted after
   an interactive challenge rather than collapsing it, and a leftover box in the
   middle of the page reads as a dialog that will not close. Hidden by opacity
   and never by display, which Turnstile reads as a hidden widget and answers
   with an interactive challenge every time. */
#ts-widget > * { pointer-events: auto; }
#ts-widget[data-idle] { opacity: 0; }
#ts-widget[data-idle] > * { pointer-events: none; }
