/* ===========================================================================
   BulkUnfollow — design draft
   ---------------------------------------------------------------------------
   White paper, black ink. Borders are the only delineator; there are no
   shadows and no rounded corners anywhere. Depth comes from inversion and from
   border weight, which means the whole system survives being printed.

   Three rules run through everything below:

     1. BOXES ARE RATIONED. A screen gets at most one bordered panel. On the
        upload screen it is the dropzone (because that is the thing you act on);
        on the board there is none at all — the masthead is carried by rules and
        the cards are the only bordered things under it.

     2. CONTROLS INVERT, LIST ITEMS WASH. Anything you press — button, chip,
        tab, link, tile — turns ink-on-white into white-on-ink on hover and on
        :focus-visible. Anything you merely scan — a row, a card — gets the
        pink `--wash` instead, because inverting a thousand cards under a
        hover-scan is violent and costs a repaint each.

     3. SMALLER TEXT = WIDER TRACKING. The .66rem micro-label at .12em is the
        signature; the 4rem display sits at .01em.

   The Instagram gradient appears exactly three times and never behind text:
   the wordmark square, the 2px rule above the board masthead's tiles, and the
   analysing hatch. Everywhere else, colour is one of four flat IG stops with a
   single sanctioned job each (see --amber / --pink / --magenta / --wash) —
   plus one traffic-light triad (--ok / --mid / --bad) that exists only to
   encode recency and score band, and appears on nothing else.
   =========================================================================== */

/* ---------------------------------------------------------------------------
   Tokens
   --------------------------------------------------------------------------- */
:root {
  color-scheme: light;               /* light only, on purpose — see CLAUDE.md */

  --bg: #ffffff;
  --ink: #0b0b0c;                    /* text AND every border */
  --ink-2: #4c4c52;                  /* secondary prose */
  --ink-3: #85858c;                  /* micro-labels, dim subs */
  --grid: #e8e8e3;                   /* hairline rules */
  --hair: #eae6ee;                   /* column seams (board) */

  /* Instagram palette — one sanctioned use each. */
  --amber: #fcaf45;                  /* highlighter behind black text */
  --pink: #e1306c;                   /* reversed text on ink */
  --magenta: #c13584;                /* data marks on white */
  --wash: #fce9f1;                   /* row / column hover */
  --ig: linear-gradient(90deg,
    #405de6, #5851db, #833ab4, #c13584, #e1306c, #fd1d1d, #f56040, #fcaf45);

  /* The traffic light — the one place in this system where colour carries
     meaning rather than identity, so it is the one place a third hue family is
     allowed in. Two rules keep it from becoming decoration:

       - it appears on exactly two things, the score and the marks;
       - every surface it paints carries BLACK text or a black glyph, which is
         why all three are light. The dark greens that can hold white text are
         too dark to read as green at 17px, and a green nobody reads as green
         is worse than no colour at all.

     Red is Instagram's own #ED4956. The other two are matched to it for weight
     rather than picked for prettiness, so no one band shouts louder. */
  --ok: #3fbf7f;                     /* green  — inside 30 days */
  --mid: #f0912b;                    /* orange — 31 to 90 days */
  --bad: #ed4956;                    /* red    — beyond 90, or never */

  /* Border hierarchy — the only depth cue in the system. */
  --b-loud: 2px solid var(--ink);    /* loud containers, border-block strips */
  --b: 1px solid var(--ink);         /* chips, buttons, cards */
  --b-prov: 1px dashed var(--ink);   /* provisional / destructive / empty */
  --b-hair: 1px solid var(--grid);   /* table rules */

  --e: cubic-bezier(.22, 1, .36, 1);
  --t: .15s;

  --font-display: "Anton", "Arial Narrow", sans-serif;
  --font-body: "Inter", system-ui, -apple-system, sans-serif;

  --wrap: 960px;
}

/* ---------------------------------------------------------------------------
   Reset
   --------------------------------------------------------------------------- */

/* Not a stylistic preference: radius is load-bearing here. One !important at
   the root beats every third-party and every future rule, so nothing can
   quietly soften a corner later. */
* { margin: 0; padding: 0; box-sizing: border-box; border-radius: 0 !important; }

/* The screen router toggles `hidden` on elements that also set a `display`,
   which would otherwise win. */
[hidden] { display: none !important; }

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

body {
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: 16px;
  line-height: 1.5;
  min-height: 100dvh;
  font-variant-numeric: tabular-nums;   /* every numeral in the app lines up */
  -webkit-font-smoothing: antialiased;
}

/* Film grain — breaks digital flatness so the white reads as paper. Fixed and
   multiplied, above everything, inert to the pointer. */
body::after {
  content: "";
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 40;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  opacity: .035;
  mix-blend-mode: multiply;
}

::selection { background: var(--amber); color: var(--ink); }

/* Keyboard users get the same vocabulary as the mouse, not a lesser one. */
:focus-visible { outline: 2px solid var(--ink); outline-offset: 3px; }

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

button {
  font: inherit;
  color: inherit;
  background: none;
  border: none;
  cursor: pointer;
  touch-action: manipulation;
}

a { color: inherit; }

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

/* ---------------------------------------------------------------------------
   Shell
   --------------------------------------------------------------------------- */
/* The feedback strip. Inverted, edge to edge, above everything: the page's one
   standing invitation to talk to us. On ink the controls-invert rule runs
   backwards: the button is white on ink and turns ink on hover/focus. */
.feedback {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px 22px;
  flex-wrap: wrap;
  padding: 10px clamp(16px, 4vw, 32px);
  background: var(--ink);
  color: #fff;
  font-size: .86rem;
  line-height: 1.45;
}
body[data-screen="board"] .feedback { display: none; }
.feedback-text { margin: 0; max-width: 72ch; }
.feedback-text strong { font-weight: 700; }
.feedback-btn {
  flex: none;
  padding: 7px 14px;
  border: 1px solid #fff;
  background: #fff;
  color: var(--ink);
  font-size: .72rem;
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
  text-decoration: none;
  transition: background var(--t) var(--e), color var(--t) var(--e);
}
.feedback-btn:hover,
.feedback-btn:focus-visible { background: var(--ink); color: #fff; outline: none; }
@media (max-width: 600px) {
  .feedback { justify-content: flex-start; }
}

.wrap {
  max-width: var(--wrap);
  margin: 0 auto;
  padding: 18px clamp(16px, 4vw, 32px) 90px;
}

/* The board needs the width the reading screens don't want, and none of the
   bottom air: its columns own their own scrollers. */
body[data-screen="board"] { --wrap: 1400px; }
body[data-screen="board"] .wrap { padding-bottom: 20px; }

/* Staggered entry — blocks rise in one after another, .07s apart. */
@keyframes rise { from { opacity: 0; transform: translateY(16px); } }
.screen > * { animation: rise .65s var(--e) backwards; }
.screen > *:nth-child(2) { animation-delay: .07s; }
.screen > *:nth-child(3) { animation-delay: .14s; }
.screen > *:nth-child(4) { animation-delay: .21s; }
.screen > *:nth-child(5) { animation-delay: .28s; }
.screen > *:nth-child(6) { animation-delay: .35s; }
.screen > *:nth-child(7) { animation-delay: .42s; }
.screen > *:nth-child(8) { animation-delay: .49s; }
.screen > *:nth-child(9) { animation-delay: .56s; }

/* ---------------------------------------------------------------------------
   Type
   ---------------------------------------------------------------------------
   Anton for display, always uppercase, and font-weight re-declared at 400
   everywhere it appears: Anton ships one master, so any inherited 600/700
   would make a browser synthesise a smeared fake bold.

   No monospace anywhere. A handle gets its code-ness from .02em tracking and
   --ink-3, not from a second typeface.
   --------------------------------------------------------------------------- */
h1, h2, h3, .display {
  font-family: var(--font-display);
  font-weight: 400;
  text-transform: uppercase;
  letter-spacing: .01em;
  text-wrap: balance;
}

h1 { font-size: clamp(2.4rem, 6.2vw, 4rem); line-height: 1.02; }
h2 { font-size: clamp(1.35rem, 3vw, 1.7rem); line-height: 1.08; }
h3 { font-size: 1.15rem; line-height: 1.1; }

/* The highlighter. Amber is the only IG stop bright enough to sit behind black
   text; the crushed line-height keeps the block tight to the glyphs so it reads
   as a marker stroke rather than a filled box. */
h1 em, h2 em {
  font-style: normal;
  background: var(--amber);
  display: inline-block;
  line-height: .82;
  padding: 0 .1em;
  vertical-align: baseline;
}

/* The signature micro-label. */
.micro {
  font-size: .66rem;
  font-weight: 700;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--ink-3);
  line-height: 1.4;
}
.micro b, .micro strong { color: var(--ink); font-weight: 700; }

.kicker {
  display: flex;
  align-items: flex-start;   /* the dot stays on the first line when the text wraps */
  gap: 9px;
  font-size: .72rem;
  font-weight: 700;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--ink-2);
}
.kicker .dot {
  width: 9px; height: 9px;
  flex: none;
  margin-top: .32em;
  background: var(--ink-3);
}
/* The only pulsing dot in the app: it says "this page is doing the work". */
.kicker .dot.live {
  background: var(--amber);
  outline: 1px solid var(--ink);
  animation: pulse 1.6s ease-in-out infinite;
}
/* Same dot, at rest — the work is finished, so it stops moving. */
.kicker .dot.done { background: var(--amber); outline: 1px solid var(--ink); }

/* The status line under the wordmark. Its dot is the kicker's 9px square —
   still, and green: --ok saying "checked and working", the same sentence the
   footer's verified date says. Square, not a circle: radius 0 is a page-wide
   rule with exactly one sanctioned exception (.own-face), and this is not it. */
.status-line { display: flex; align-items: center; gap: 9px; }

/* The status and the changelog link share one line — stacked, the link read as
   an afterthought hanging off the bottom of the status. The row carries the
   16px that used to close the status line. */
.status-row { display: flex; align-items: center; flex-wrap: wrap; gap: 0 14px; margin-bottom: 16px; }
.changelog-link {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  color: var(--ink);
  text-decoration: none;
}
/* A hairline divider, not a bullet — and only when the status line is actually
   on screen, or a lone link would start with a stray mark. */
.status-line:not([hidden]) + .changelog-link::before {
  content: "";
  width: 1px;
  height: 11px;
  background: var(--grid);
  margin-right: 7px;
}
.changelog-link span { color: var(--ink-3); transition: transform var(--t); }
.changelog-link:hover, .changelog-link:focus-visible { text-decoration: underline; text-underline-offset: 4px; }
.changelog-link:hover span { transform: translateX(2px); }
.status-dot {
  width: 9px; height: 9px;
  flex: none;
  background: var(--ok);
  outline: 1px solid var(--ink);
  /* The one glow on the page (owner's ask, 2026-07-29) — a breathing green
     halo saying "live now". The zero-box-shadow rule stands everywhere else;
     this is its single sanctioned exception, and reduced-motion gets the halo
     held steady instead of breathing. */
  animation: statusGlow 2s ease-in-out infinite;
}
@keyframes statusGlow {
  0%, 100% { box-shadow: 0 0 3px 0 rgba(63, 191, 127, .55); }
  50% { box-shadow: 0 0 9px 4px rgba(63, 191, 127, .9); }
}
@media (prefers-reduced-motion: reduce) {
  .status-dot { animation: none; box-shadow: 0 0 6px 2px rgba(63, 191, 127, .7); }
}
@keyframes pulse {
  0%, 100% { opacity: 1; transform: scale(1); }
  50% { opacity: .5; transform: scale(.72); }
}

.lede {
  color: var(--ink-2);
  font-size: 1.02rem;
  max-width: 42ch;
  margin-top: 16px;
}
.lede b { color: var(--ink); font-weight: 600; }

.hand { letter-spacing: .02em; color: var(--ink-3); }

/* ---------------------------------------------------------------------------
   Controls — every one of these inverts
   --------------------------------------------------------------------------- */
.btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  border: var(--b);
  background: var(--bg);
  color: var(--ink);
  padding: 7px 14px;
  font-size: .7rem;
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
  transition: background-color var(--t), color var(--t);
}
.btn:hover, .btn:focus-visible { background: var(--ink); color: #fff; }
.btn:active { transform: scale(.97); }

.chip {
  display: inline-block;
  border: var(--b);
  background: var(--bg);
  color: var(--ink-2);
  padding: 7px 12px;
  font-size: .82rem;
  transition: background-color var(--t), color var(--t);
}
.chip b { color: var(--ink); font-weight: 700; }
.chip:hover, .chip:focus-visible { background: var(--ink); color: #fff; }
.chip:hover b { color: #fff; }

.chips { display: flex; flex-wrap: wrap; gap: 8px; }

/* A text link that behaves like the rest of the system rather than like 1996. */
.textlink {
  display: inline-block;
  color: var(--ink-2);
  font-size: .88rem;
  text-decoration: underline;
  text-underline-offset: 4px;
  text-decoration-thickness: 1px;
  padding: 2px 4px;
  transition: background-color var(--t), color var(--t);
}
.textlink:hover, .textlink:focus-visible { background: var(--ink); color: #fff; }

/* ---------------------------------------------------------------------------
   Topbar
   --------------------------------------------------------------------------- */
.topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  padding: 4px 0 26px;
}
/* A <button> off the board and handoff screens (it goes home, session kept —
   see the markup); disabled on the upload screen, where home is where you are.
   The global button reset supplies cursor: pointer for the live state. */
.brand { display: flex; align-items: center; gap: 11px; padding: 0; text-align: left; }
.brand:disabled { cursor: default; }

/* Gradient use 1 of 3. */
.brand .mark {
  width: 18px; height: 18px;
  flex: none;
  background-image: var(--ig);
}
.wordmark {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: 1.02rem;
  letter-spacing: .12em;
  text-transform: uppercase;
  line-height: 1;
}
.tag {
  border: var(--b-prov);
  color: var(--ink-2);
  padding: 4px 8px;
  font-size: .6rem;
  font-weight: 700;
  letter-spacing: .12em;
  text-transform: uppercase;
  line-height: 1;
}
/* flex:1 keeps the actions hard right whether they share the brand's line or
   wrap onto their own. */
.topbar-actions { display: flex; flex: 1; align-items: center; justify-content: flex-end; gap: 10px; }

/* ---------------------------------------------------------------------------
   UPLOAD
   --------------------------------------------------------------------------- */
/* The dropzone stretches to the exact height of the copy beside it. Two blocks
   of equal height with no shared border is what holds the screen together —
   there is no panel or shadow left to do it. */
.hero {
  display: grid;
  grid-template-columns: 1.08fr .92fr;
  gap: clamp(24px, 4vw, 44px);
  align-items: stretch;
}
/* The front page is the front page: there is no export loaded, so "Start over"
   is a control for undoing nothing. It appears the moment there is something to
   undo. */
body[data-screen="upload"] .topbar-actions { display: none; }

.hero h1 { margin: 16px 0 0; }
.hero .chips { margin-top: 24px; }
.hero .lede { margin-top: 20px; }
.hero-note { margin-top: 20px; max-width: 44ch; }

/* The four kinds of profile this finds. Deliberately NOT .steps: the numbered
   rail with its magenta squares already runs down this same screen under "What
   we need from Meta", and a second rail above it reads as one list interrupted
   rather than two. Markers are FILLED ink squares — an open square this size,
   in front of a line you might act on, reads as an unticked checkbox, and
   nothing here is a control. */
.reasons {
  list-style: none;
  margin-top: 4px;
  max-width: 42ch;
}
.reasons li {
  position: relative;
  padding: 7px 0 7px 34px;
  font-size: .98rem;
  line-height: 1.45;
  color: var(--ink-2);
}
/* The bullet's job went to a drawn icon per reason (owner's ask, 2026-07-29,
   same sticker family as the trust icons). Decorative, so alt="" — the words
   still carry the meaning. */
.reasons li .reason-icon {
  position: absolute;
  left: 0; top: 50%;
  transform: translateY(-50%);
  width: 24px; height: 24px;
}
/* The three steps, in the air the panel used to leave empty. Numbers get the
   Anton treatment at tile scale so the count is legible before a word is read;
   the sentences are ink and 600, one line of intent each. Hairlines divide
   them — the panel below is the screen's one bordered box. */
.dz-steps-h { margin-bottom: 7px; }
.dz-steps {
  list-style: none;
  border-top: var(--b-loud);
  margin-bottom: 14px;
}
/* The numeral column is a FIXED track, not `auto`. Anton has no tabular figures
   — "1" measures 12px where "3" measures 17.8px at this size — so an auto track
   made every sentence start at a different x, and the amber on step two shoved
   its own line 22px further right again. A fixed track pins all three numerals
   and all three sentences to one edge each, and lets the highlight fill the
   track instead of padding it wider. */
.dz-steps li {
  display: grid;
  grid-template-columns: 1.5rem minmax(0, 1fr);
  align-items: baseline;
  gap: 0 14px;
  padding: 11px 2px;
  border-bottom: var(--b);
}
.dz-steps .n {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: clamp(1.7rem, 3vw, 2.2rem);
  line-height: 1;
  letter-spacing: .01em;
}
.dz-steps .t {
  font-size: 1.06rem;
  font-weight: 700;
  line-height: 1.32;
  color: var(--ink);
}
/* Step one IS the export page, so on a desktop the whole sentence is the link
   to it (draft.js swaps the span for an anchor carrying this same class). It
   keeps the line's own type and takes the underline every other link here has.
   `justify-self: start` is load-bearing: a grid item stretches to its track, so
   without it the hover invert would paint a black bar across the full column
   rather than behind the words. */
.dz-steps a.t {
  justify-self: start;
  color: inherit;
  text-decoration: underline;
  text-underline-offset: 4px;
  text-decoration-thickness: 1px;
  transition: background-color var(--t), color var(--t);
}
.dz-steps a.t:hover, .dz-steps a.t:focus-visible { background: var(--ink); color: #fff; }

/* The store link in step three is a phrase in a sentence, not a control beside
   one: `.textlink` is inline-block at its own smaller size, which inside this
   line would set two type sizes on one row. It keeps the underline and the
   invert-on-hover and gives back everything else — the same trim the numbered
   rail in `#safety` already makes. */
.dz-steps a.textlink {
  display: inline;
  padding: 0;
  font: inherit;
  color: inherit;
}
/* Step two is the panel directly under it, so its number carries the
   highlighter — the one step this screen is actually for. No padding: the fill
   spans the fixed numeral track, so it cannot push its own sentence out of line
   with the other two. */
.dz-steps .here .n { background: var(--amber); }

.dz-wrap { display: flex; flex-direction: column; min-height: 300px; }

/* `.pending` / `.pending-icon` / @keyframes bannerSpin lived here — the amber
   "extension in review" banner and its spinning dial. Removed with the banner
   itself (2026-08-01); nothing else on the page wore them. */

/* The install steps reuse the numbered rail, one section lower down the page
   than the other two, which is exactly what it is: another list of steps. */
.steps-install { margin-top: 18px; }
/* .textlink is inline-BLOCK, and an inline-block on the first line of a hanging
   indent does not take the negative text-indent with it — the link parks itself
   over the words before it. Inside a step it goes back to being inline. */
.steps a.textlink { display: inline; padding: 0; }

/* The question the drop target provokes, answered on request. Full width of the
   column and hard under the panel, so it reads as the second half of the same
   object rather than a stray control floating beside it.

   Pink, and INK on the pink rather than white: at this size white is 4.34:1,
   which passes as display text and fails as body text, while ink is 4.54:1 —
   the same call the action bar's second line makes. Hover still inverts to ink,
   because every control in this system does. */
.help-btn {
  justify-content: center;
  width: 100%;
  margin-top: 10px;
  padding: 12px 14px;
  border-color: var(--pink);
  background: var(--pink);
  color: var(--ink);
  font-size: .78rem;
}
.help-btn:hover, .help-btn:focus-visible { border-color: var(--ink); }

/* The screen's one bordered panel. It is a <button>, so it is a real tab stop
   and Enter opens the picker; the file input behind it stays out of the way. */
.dropzone {
  display: flex;
  flex: 1;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  width: 100%;
  border: var(--b-loud);
  background: var(--bg);
  color: var(--ink);
  padding: clamp(30px, 5vw, 46px) 24px;
  text-align: center;
  cursor: pointer;
  transition: background-color var(--t), color var(--t);
}
.dropzone:hover { background: var(--wash); }
/* Depth by inversion: the panel becomes the thing you are dropping into.
   No tint, no shadow, no dashed-border wobble. */
.dropzone.over { background: var(--ink); color: #fff; }

/* An open square with a filled one dropping into it. Two rectangles is the
   whole illustration budget of this system. */
.dz-glyph {
  display: block;
  width: 48px; height: 48px;
  margin: 0 auto 24px;
  border: var(--b-loud);
  border-color: currentColor;
  position: relative;
}
.dz-glyph::after {
  content: "";
  position: absolute;
  left: 50%; top: 50%;
  width: 16px; height: 16px;
  margin: -8px 0 0 -8px;
  background: var(--amber);
  outline: 1px solid currentColor;
}

.dz-title {
  display: block;
  font-family: var(--font-display);
  font-weight: 400;
  text-transform: uppercase;
  letter-spacing: .01em;
  font-size: clamp(1.5rem, 3vw, 2rem);
  line-height: 1.05;
}
.dz-sub {
  display: block;
  margin-top: 10px;
  font-size: .86rem;
  color: var(--ink-2);
}
.dropzone.over .dz-sub { color: rgba(255, 255, 255, .72); }
.dz-note {
  display: block;
  max-width: 30ch;
  margin-top: 22px;
  font-size: .66rem;
  font-weight: 700;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.dropzone.over .dz-note { color: rgba(255, 255, 255, .6); }

/* Analysing hatch — gradient use 3 of 3. The IG gradient is the base layer and
   a repeating white stripe is combed over it; only the stripe moves. */
.progress {
  height: 6px;
  margin-top: -2px;              /* sits flush under the dropzone's 2px border */
  background-color: var(--grid);
  background-image:
    repeating-linear-gradient(-45deg,
      rgba(255, 255, 255, .62) 0 7px, rgba(255, 255, 255, 0) 7px 14px),
    var(--ig);
  background-size: 39.6px 100%, 100% 100%;
  animation: hatch .7s linear infinite;
}
@keyframes hatch { to { background-position: 39.6px 0, 0 0; } }

/* ---- the numbered "what we need from Meta" list ---- */
.steps {
  list-style: none;
  border-left: var(--b-loud);
  padding-left: 20px;
  max-width: 62ch;
}
/* Hanging indent by negative text-indent rather than grid: the sentence carries
   inline <b> and <span>, and every one of those would blockify into its own
   grid item. */
.steps li {
  display: block;
  position: relative;
  padding: 12px 2px 12px 2.7rem;
  text-indent: -2.7rem;
  font-size: .95rem;
  line-height: 1.55;
  color: var(--ink-2);
}
.steps li::before {
  content: "";
  position: absolute;
  left: -25px; top: 1.24em;
  width: 8px; height: 8px;
  background: var(--magenta);
  outline: 1.5px solid var(--ink);
}
.steps .n {
  display: inline-block;
  width: 2.7rem;
  text-indent: 0;
  font-size: .66rem;
  font-weight: 700;
  letter-spacing: .12em;
  color: var(--ink-3);
}
.steps b { color: var(--ink); font-weight: 600; }
.steps .say {
  background: var(--amber);
  color: var(--ink);
  font-weight: 600;
  padding: 0 4px;
}
/* The one .say that is also a link (Instagram's export page, desktop only).
   It keeps the highlighter and gains the underline every other link here has,
   so it reads as "this phrase, and you can press it" rather than as a new
   kind of thing. */
a.say {
  color: var(--ink);
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-thickness: 1px;
}
a.say:hover, a.say:focus-visible { background: var(--ink); color: var(--amber); }

/* ---------------------------------------------------------------------------
   PROOF
   ---------------------------------------------------------------------------
   Three counts under the fold's two columns, in the same tile grammar as the
   board's masthead — Anton numbers over a small grey line, divided by hairline
   rules and nothing else. No badges, no stars, no logos: this system says
   things by setting them in the right size and leaving them alone.

   The last tile is the one that matters and it is deliberately last: two
   numbers about other people, then one about the reader's own file.
   --------------------------------------------------------------------------- */
/* The amber block, in the hero's left column (owner's ask, 2026-07-31 — it
   started the day as a full-bleed band under both columns). Two things went
   wrong down there: the left column runs out of copy well above the drop
   panel's bottom edge, so the hero ended on a hole, and a full-width band
   between the hero and the demo pushed the demo off the first screen. In the
   column it fills the hole and gives the hero a bottom edge.

   Still the highlighter doing its usual job — a filled block with a loud rule
   top and bottom — so every word on it goes to full ink, including the grey
   subs, which have no contrast to spare on this fill. */
.proof {
  margin-top: 24px;
  background: var(--amber);
  border-top: var(--b-loud);
  border-bottom: var(--b-loud);
  padding: 16px 14px;
}
.proof-tiles {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  list-style: none;
}
/* Half the width it had as a band, so the 24px the masthead tiles use would
   spend most of a 160px cell on air. */
.proof-tiles .tile { padding: 2px 13px; }
.proof-tiles .tile:first-child { border-left: none; padding-left: 2px; }
/* `.tile .sub` sets the dim grey later in the file at equal specificity, so the
   third class is doing real work here, not decoration. */
.proof-tiles .tile .sub { max-width: 22ch; line-height: 1.35; margin-top: 5px; color: var(--ink); }

@media (max-width: 640px) {
  /* Stacked, the vertical rules would divide nothing. They become horizontal
     ones, which is the same rule doing the same job in the other direction. */
  .proof-tiles { grid-template-columns: 1fr; }
  .proof-tiles .tile { border-left: none; border-top: var(--b); padding: 12px 2px; }
  .proof-tiles .tile:first-child { border-top: none; padding-top: 2px; }
}

/* ---------------------------------------------------------------------------
   HOW IT WORKS, START TO FINISH — the homepage explainer
   ---------------------------------------------------------------------------
   Replaced the interactive demo board (2026-10-18). One fixed 1120x740 browser
   window, scaled to the section's width with a transform and driven by
   explainer.js from one clock. Everything in it is a REPLICA: the app's own
   classes and copy, so a redesign of the upload panel, the cards or the pink
   button changes the film too. The side panel is the extension's own stylesheet,
   renamed and scoped (explainer-panel.css, generated).

   Three things here exist because the frame is a fixed-size screen inside a
   page of ANY width, and the app's CSS is keyed to the viewport:
     - `vw` / `vh` clamps (h1, the dropzone, the card gap) are pinned to their
       desktop values under `.xp-frame`;
     - every `@media (max-width)` and `(pointer: coarse)` block that restyles
       cards, columns, the pink button and buttons is undone for the frame, so a
       phone sees the same desktop product, only smaller;
     - the board is a flex column like the real one at >= 861px.
   `.xp-frame` carries no `.screen` class and no ids, on purpose: see THE TRAP in
   app/CLAUDE.md. Nothing in it is interactive (pointer-events: none).
   --------------------------------------------------------------------------- */
.demo { display: block; margin-top: clamp(40px, 5vw, 62px); }
.demo h2 { margin-top: 6px; }
/* The disclosure is part of the heading, not a footnote to it. */
.demo-tag {
  font-family: var(--font-body);
  font-size: .8rem;
  font-weight: 600;
  letter-spacing: 0;
  text-transform: none;
  color: var(--ink-2);
  white-space: nowrap;
}
.demo-note { margin-top: 12px; max-width: 68ch; }
.demo-foot { margin-top: 12px; }

.xp-host {
  position: relative;
  margin-top: 18px;
  width: 100%;
  aspect-ratio: 1120 / 740;
  overflow: hidden;
  background: #fff;
}
.xp-host::after {
  content: "";
  position: absolute;
  inset: 0;
  border: var(--b-loud);
  pointer-events: none;
  z-index: 5;
}

.xp-frame {
  position: absolute;
  left: 0;
  top: 0;
  width: 1120px;
  height: 740px;
  overflow: hidden;
  transform: scale(var(--xp-s, .8));
  transform-origin: 0 0;
  background: #fff;
  color: var(--ink);
  font-family: var(--font-body);
  font-size: 16px;
  line-height: 1.5;
  font-variant-numeric: tabular-nums;
  -webkit-font-smoothing: antialiased;
  text-align: left;
  pointer-events: none;
  user-select: none;
  -webkit-user-select: none;
}
.xp-frame * { pointer-events: none; }
.xp-frame.xp-fade { opacity: 0; transition: opacity .45s linear; }

/* ---- the browser (not the product, so it may be round) ---- */
.xp-chrome {
  position: absolute;
  left: 0; top: 0;
  width: 100%;
  height: 78px;
  z-index: 30;
  background: #dee1e6;
  font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
}
.xp-tabstrip { display: flex; align-items: flex-end; gap: 4px; height: 38px; padding: 0 12px; }
.xp-lights { align-self: center; flex: none; margin: 0 16px 0 2px; }
.xp-tab {
  display: flex; align-items: center; gap: 8px;
  width: 210px; height: 32px; padding: 0 10px;
  font-size: 12px; color: #3c4043;
  border-radius: 9px 9px 0 0 !important;
}
.xp-tab.xp-on { background: #fff; }
.xp-tab-b:not(.xp-show) { display: none; }
.xp-tab-t { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.xp-tab-x { font-size: 15px; color: #5f6368; line-height: 1; }
.xp-fav { width: 14px; height: 14px; flex: none; }
.xp-fav-bu { background-image: var(--ig); }
.xp-fav-ig { background: #c4c9d0; border-radius: 4px !important; }
.xp-toolbar {
  display: flex; align-items: center; gap: 8px;
  height: 40px; padding: 0 14px;
  background: #fff; border-bottom: 1px solid #dadce0;
}
.xp-nav { flex: none; margin: 0 5px; }
.xp-omni {
  flex: 1; display: flex; align-items: center; gap: 8px;
  height: 30px; padding: 0 14px; margin: 0 8px;
  background: #f1f3f4; color: #202124; font-size: 13px;
  border-radius: 15px !important;
}
.xp-tools { display: flex; align-items: center; gap: 4px; }
.xp-tool {
  display: grid; place-items: center;
  width: 32px; height: 32px;
  border-radius: 50% !important;
}
.xp-tool.xp-h { background: #e8eaed; }
.xp-tool.xp-press { background: #dadce0; }
.xp-avatar::after {
  content: ""; width: 24px; height: 24px;
  background: linear-gradient(135deg, #8ab4f8, #c58af9);
  border-radius: 50% !important;
}

/* ---- the page ---- */
.xp-view {
  position: absolute;
  left: 0; top: 78px;
  width: 1120px; height: 662px;
  overflow: hidden;
  background: #fff;
}
.xp-scene { position: absolute; inset: 0; opacity: 0; visibility: hidden; background: #fff; }
.xp-pagewrap {
  height: 100%;
  max-width: 960px;
  margin: 0 auto;
  padding: 16px 32px 20px;
  display: flex;
  flex-direction: column;
}
.xp-boardwrap { max-width: 1400px; padding-bottom: 20px; }

/* The viewport-keyed rules, pinned to what a 1120px desktop window gets. */
.xp-frame h1 { font-size: 4rem; }
.xp-frame .lede { font-size: 1.02rem; }
.xp-frame .topbar { padding: 0 0 14px; }
.xp-frame .hero { grid-template-columns: 1.08fr .92fr; gap: 44px; }
.xp-frame .hero .dz-wrap { margin-top: 0; min-height: 300px; }
.xp-frame .dz-steps .n { font-size: 2rem; }
.xp-frame .dz-steps li { padding-top: 7px; padding-bottom: 7px; }
.xp-frame .dz-title { font-size: 2rem; }
.xp-frame .dropzone { padding: 34px 24px; }
.xp-frame .hero .dz-steps-h { margin-bottom: 5px; }
.xp-frame .btn, .xp-frame .chip { padding-top: 7px; padding-bottom: 7px; }
.xp-frame .btn.next { padding: 13px 32px; }
.xp-frame .help-btn { padding: 12px 14px; }
.xp-frame .card { grid-template-columns: 42px minmax(0, 1fr) auto; row-gap: 3px; padding: 9px 10px; }
.xp-frame .card.pinned, .xp-frame .card.stalker { padding-left: 8px; }
.xp-frame .c-meta { grid-column: 3; grid-row: 1; justify-content: normal; padding-top: 2px; }
.xp-frame .c-why { grid-column: 2; grid-row: 2; }
.xp-frame .mk { width: 17px; height: 17px; }
.xp-frame .mk svg { width: 11px; height: 11px; }
.xp-frame .go { width: auto; order: 0; padding: 11px 26px 10px; }
.xp-frame .tile .val { font-size: 2rem; }

/* The board, as the real one lays out at >= 861px: a flex column whose
   scrollers take whatever the action rule and bar leave. */
.xp-frame .actionrule { gap: 26px; }
.xp-frame .board { grid-template-columns: repeat(3, minmax(0, 1fr)); flex: 1; min-height: 0; margin-top: 16px; }
.xp-frame .col { display: flex; flex-direction: column; min-height: 0; }
.xp-frame .col + .col { border-left: var(--b-hair); border-top: none; }
.xp-frame .col-scroll { flex: 1; min-height: 0; max-height: none; overflow-y: auto; padding: 0 16px 20px; }
.xp-frame .col:first-child .col-scroll { padding-left: 0; }
.xp-frame .col:last-child .col-scroll { padding-right: 0; }
.xp-frame .col[data-xp-col="keep"] .col-head h3 { background: var(--ok); }
.xp-frame .col[data-xp-col="unsure"] .col-head h3 { background: var(--amber); }
.xp-frame .col[data-xp-col="unfollow"] .col-head h3 { background: var(--bad); }
.xp-frame .col.xp-over .col-scroll { background: var(--wash); }
.xp-frame .actionbar { --chat-clear: 0px; gap: 22px; }
.xp-frame .card .c-years { margin-top: 5px; }

/* :hover and :active, as classes, because nothing here is ever really hovered. */
.xp-frame .card.xp-h { background: var(--wash); }
.xp-frame .go.xp-h { background: var(--ink); color: var(--pink); }
.xp-frame .go.xp-h .go-sub { color: #fff; }
.xp-frame .go.xp-press { transform: scale(.985); }
.xp-frame .btn.next.xp-h { background: var(--ink); color: var(--ok); }
.xp-frame .btn.xp-press { transform: scale(.97); }
.xp-frame .card.xp-hide { display: none; }
.xp-frame .c-mono.xp-h { background: var(--ink); color: #fff; }
.xp-frame .col-filter { white-space: nowrap; padding: 3px 6px; gap: 5px; letter-spacing: .1em; }
.xp-frame .col-head { gap: 8px; }
.xp-frame .col-filter.xp-h { background: var(--ink); color: #fff; }
.xp-frame .col-filter.xp-press { transform: scale(.96); }
.xp-frame .col.filtered .col-count { display: none; }
.xp-frame .dropzone.over { background: var(--ink); color: #fff; }

/* The walkthrough and the handoff, with the page-width constraints the real
   screens take from their ids. */
.xp-working { max-width: 62ch; padding-top: 12px; }
.xp-working .work-h, .xp-hand .work-h { max-width: 14ch; margin-top: 14px; }
.xp-working .work-bar { margin-top: 26px; }
.xp-working .work-steps { margin-top: 18px; }
.xp-hand { max-width: 68ch; padding-top: 8px; }
.xp-hand-owner .owner { padding: 0; margin-top: 18px; gap: 12px; }
.xp-hand-owner .own-face { width: 52px; height: 52px; }
.xp-hand-owner .own-name { font-size: 1rem; }
.xp-hand .go { padding: 11px 22px 10px; font-size: 1rem; }
.xp-hand .ready { margin-top: 22px; }
.xp-off { visibility: hidden; }
.xp-working .work-go, .xp-working .wstep { transition: opacity .3s linear; }
.xp-working .xp-off { opacity: 0; }

/* The page being worked on: a generic profile, deliberately without anyone's
   logo or trade dress. */
.xp-scene-ig { background: #fafafa; font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; color: #262626; }
.xp-ig { padding: 44px 56px 0; }
.xp-ig-head { display: flex; gap: 56px; align-items: flex-start; }
.xp-ig-av { flex: none; }
.xp-ig-info { min-width: 0; padding-top: 6px; }
.xp-ig-line { position: relative; display: flex; align-items: center; gap: 10px; }
.xp-ig-handle { font-size: 20px; margin-right: 14px; }
.xp-ig-btn {
  padding: 7px 18px; font-size: 14px; font-weight: 600;
  background: #efefef; color: #262626;
  border-radius: 8px !important;
}
.xp-ig-btn.xp-follow { background: #0095f6; color: #fff; }
.xp-ig-btn.xp-press { transform: scale(.94); }
.xp-ig-ring {
  position: absolute; left: 150px; top: 50%;
  width: 90px; height: 40px; margin: -20px 0 0 -45px;
  border: 3px solid #c13584;
  border-radius: 10px !important;
  opacity: 0;
}
.xp-ig-stats { display: flex; gap: 40px; margin-top: 22px; font-size: 16px; }
.xp-ig-name { margin-top: 18px; font-size: 14px; font-weight: 600; }
.xp-ig-bio { margin-top: 4px; font-size: 14px; }
.xp-ig-grid {
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px;
  margin-top: 56px; border-top: 1px solid #dbdbdb; padding-top: 24px;
}
.xp-ig-tile { aspect-ratio: 1; background: #dfe3e8; }
.xp-ig-tile.xp-t1 { background: #e9e3df; }
.xp-ig-tile.xp-t2 { background: #dde6e0; }
.xp-ig-tile.xp-t3 { background: #e4dfe9; }
.xp-ig-tile.xp-t4 { background: #e8e8e3; }

.xp-lapse {
  position: absolute; left: 24px; bottom: 20px;
  padding: 6px 10px;
  background: var(--ink); color: #fff;
  font-size: 12px; font-weight: 700; letter-spacing: .1em; text-transform: uppercase;
  opacity: 0;
}

/* The drawn toolbar arrow: #toolbarArrow's own geometry, aimed by JS. */
.xp-arrow {
  position: absolute;
  top: 84px;
  right: 88px;
  z-index: 25;
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: start;
  gap: 10px;
  max-width: 330px;
  opacity: 0;
}
.xp-arrow svg {
  width: 72px; height: 72px; margin-top: -6px;
  fill: none; stroke: var(--magenta); stroke-width: 3.5; stroke-linecap: square;
}
.xp-ta-line { stroke-dasharray: 200; stroke-dashoffset: 200; }
.xp-ta-head { opacity: 0; }
.xp-arrow p {
  margin: 16px 0 0; justify-self: end; text-align: right;
  font-size: .82rem; font-weight: 600; line-height: 1.35;
  color: var(--ink); background: var(--amber); border: var(--b); padding: 7px 10px;
}

/* The side panel: a Chrome side panel, 380px, under the toolbar. */
.xp-frame .xp-panel {
  position: absolute;
  top: 78px;
  right: 0;
  width: 380px;
  height: calc(100% - 78px);
  z-index: 20;
  border-left: 1px solid #c4c7c5;
  transform: translateX(380px);
}
.xp-p-title { font-family: var(--font-display); font-weight: 400; }
.xp-panel .xp-p-go { display: block; }
.xp-panel .xp-p-go.xp-h { background: var(--ink); color: var(--pink); }
.xp-panel .xp-p-go.xp-press { transform: scale(.985); }
.xp-panel .xp-p-tab.xp-h:not([aria-pressed="true"]) { background: var(--wash); color: var(--ink); }
.xp-panel .xp-p-tab.xp-press { transform: scale(.97); }
.xp-panel .xp-p-btn { display: inline-block; }
.xp-panel .xp-p-pane[hidden] { display: none !important; }

/* The cursor, the dragged file, the card in flight, the click ripple. */
.xp-cursor { position: absolute; left: 0; top: 0; z-index: 60; opacity: 0; transform-origin: 2px 1px; }
.xp-ripple {
  position: absolute; left: 0; top: 0; z-index: 59;
  width: 56px; height: 56px; margin: -28px 0 0 -28px;
  border: 3px solid var(--magenta);
  border-radius: 50% !important;
  opacity: 0;
}
.xp-chip {
  position: absolute; left: 0; top: 0; z-index: 58;
  display: flex; align-items: center; gap: 10px;
  padding: 8px 14px 8px 8px;
  background: #fff; border: var(--b);
  font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  font-size: 13px; font-weight: 600; color: var(--ink);
  opacity: 0; transform-origin: 0 0;
}
.xp-chip-ico {
  position: relative; display: grid; place-items: end center;
  width: 30px; height: 36px; padding-bottom: 5px;
  background: var(--amber); border: var(--b);
}
.xp-chip-ico i { font-style: normal; font-size: 9px; font-weight: 800; letter-spacing: .08em; }
.xp-ghost { position: absolute; left: 0; top: 0; z-index: 57; }
.xp-ghost-item { position: absolute; left: 0; top: 0; opacity: 0; }
.xp-ghost-item .card { margin: 0; background: var(--bg); }
.xp-ghost-item .card.dragging, .xp-ghost-item .card.xp-h, .xp-ghost-item .card.sel { opacity: 1; }
.xp-frame .card.dragging { opacity: .35; }

/* ---- the five-step rail and the pause button ---- */
.xp-ctl { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: stretch; gap: 0 16px; margin-top: 14px; }
.xp-rail { list-style: none; display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); }
.xp-rail li + li { border-left: var(--b-hair); }
.xp-step {
  position: relative;
  display: flex; align-items: flex-start; gap: 9px;
  width: 100%; height: 100%;
  padding: 14px 10px 12px 12px;
  text-align: left;
  color: var(--ink-3);
  border-top: 2px solid var(--grid);
  transition: background-color var(--t), color var(--t);
}
.xp-rail li:first-child .xp-step { padding-left: 0; }
.xp-step::before {
  content: "";
  position: absolute; left: 0; right: 0; top: -2px; height: 2px;
  background: var(--ink);
  transform-origin: 0 50%;
  transform: scaleX(var(--xp-p, 0));
}
.xp-n { font-family: var(--font-display); font-size: 1.5rem; line-height: 1; flex: none; }
.xp-l { font-size: .84rem; font-weight: 600; line-height: 1.3; padding-top: 2px; }
.xp-step:hover, .xp-step:focus-visible { color: var(--ink); }
.xp-step[aria-current="step"] { color: var(--ink); }
.xp-step[aria-current="step"] .xp-n { background: var(--amber); padding: 0 5px; margin-left: -5px; align-self: flex-start; }
.xp-rail li:first-child .xp-step[aria-current="step"] .xp-n { margin-left: 0; }
.xp-play { align-self: stretch; justify-content: center; min-width: 88px; }
.xp-reduce .xp-play { display: none; }
.xp-reduce .xp-ctl { grid-template-columns: minmax(0, 1fr); }

@media (max-width: 760px) {
  .xp-ctl { grid-template-columns: minmax(0, 1fr); gap: 10px 0; }
  .xp-rail { grid-template-columns: minmax(0, 1fr); }
  .xp-rail li + li { border-left: none; }
  .xp-rail li:first-child .xp-step { padding-left: 0; }
  .xp-step { padding: 10px 6px 10px 0; align-items: center; }
  .xp-step::before { display: none; }
  .xp-step[aria-current="step"] .xp-n { margin-left: 0; }
  .xp-play { justify-self: start; }
}

/* ---------------------------------------------------------------------------
   FAQ + FOOT NAV — the front-door furniture
   ---------------------------------------------------------------------------
   <details> rather than a scripted accordion: it opens without JavaScript, it
   is findable with the browser's own Ctrl-F (Chrome opens a closed <details>
   to reveal a match), and it is one element instead of a widget. The marker is
   a plus that becomes a minus, drawn with borders like everything else.
   --------------------------------------------------------------------------- */
.faq { margin-top: 22px; max-width: 68ch; border-top: var(--b); }
.faq details { border-bottom: var(--b); }
/* The step lists fold away (owner's ask, 2026-07-29; simplified same day):
   closed by default behind one plain button — the summary IS the button,
   wearing the .btn grammar, inverting while the fold is open. */
.fold { margin-top: 14px; }
.fold > summary {
  display: inline-flex;
  align-items: center;
  border: var(--b);
  background: var(--bg);
  color: var(--ink);
  padding: 7px 14px;
  font-size: .7rem;
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
  cursor: pointer;
  list-style: none;
  transition: background-color var(--t), color var(--t);
}
.fold > summary::-webkit-details-marker { display: none; }
.fold > summary:hover, .fold > summary:focus-visible,
.fold[open] > summary { background: var(--ink); color: #fff; }
.faq summary {
  position: relative;
  list-style: none;
  padding: 14px 40px 14px 0;
  font-size: 1rem;
  font-weight: 600;
  color: var(--ink);
  cursor: pointer;
  transition: color var(--t);
}
.faq summary::-webkit-details-marker { display: none; }
.faq summary:hover { color: var(--magenta); }
/* The horizontal bar of the plus is always there; the vertical one is removed
   when the panel is open. Two pseudo-elements, no icon font, no swap. */
.faq summary::before,
.faq summary::after {
  content: "";
  position: absolute;
  right: 6px;
  top: 50%;
  background: var(--ink);
}
.faq summary::before { width: 13px; height: 2px; margin-top: -1px; }
/* Centred on the same point as the bar above — 6px + half of 13px — or the two
   strokes read as a stray tick rather than a plus. */
.faq summary::after {
  width: 2px; height: 13px; margin-top: -6.5px; right: 11.5px;
  transition: transform var(--t);
}
.faq details[open] summary::after { transform: scaleY(0); }
.faq details p {
  padding: 0 40px 16px 0;
  color: var(--ink-2);
  font-size: .95rem;
  line-height: 1.6;
}

/* The store button. It is a link, not a control that does something here, so
   it wears the ink fill rather than the pink one — pink is spoken for twice
   already and a third would make it mean nothing. */
.go-store {
  background: var(--ink);
  border-color: var(--ink);
  color: #fff;
  padding: 11px 18px;
  font-size: .78rem;
}
.go-store:hover, .go-store:focus-visible { background: var(--bg); color: var(--ink); }

.foot-nav {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 20px;
  margin-top: 14px;
}
.foot-nav a {
  color: var(--ink-2);
  font-size: .82rem;
  text-decoration: underline;
  text-underline-offset: 4px;
  text-decoration-thickness: 1px;
}
.foot-nav a:hover, .foot-nav a:focus-visible { color: var(--ink); }
.foot-legal { margin-top: 12px; max-width: 78ch; text-transform: none; letter-spacing: normal; font-weight: 400; }
.foot-legal a { color: inherit; text-decoration: underline; text-underline-offset: 3px; }

/* ---------------------------------------------------------------------------
   Section furniture
   --------------------------------------------------------------------------- */
.block { margin-top: clamp(38px, 6vw, 58px); }
.block > .micro { margin-bottom: 10px; }
.block > h2 + .note { margin-top: 8px; }

/* The two trust-section icons (#safety, #privacy) — generated flat art in the
   app's own palette. Floated into the right margin that the 66ch measure
   leaves empty at desktop; on a phone the clamp shrinks them and the prose
   wraps around. The source PNGs are 512px drawn at <=116px, so they stay
   crisp at any device pixel ratio. First child of their section on purpose:
   a float only wraps text that comes after it. */
.block-icon {
  float: right;
  width: clamp(72px, 13vw, 116px);
  height: auto;
  margin: 6px 0 10px 18px;
}
.note {
  color: var(--ink-2);
  font-size: .92rem;
  max-width: 66ch;
  margin-top: 12px;
}
.note b { color: var(--ink); font-weight: 600; }

.foot {
  margin-top: clamp(44px, 7vw, 70px);
  padding-top: 16px;
  border-top: var(--b-hair);
  /* (Was icon column + text column, 2026-07-29; the icon went 2026-10-18.)
     Originally: icon column + text column: floated, the icon indented only
     the first paragraph and the second started flush left — two ragged edges.
     A grid gives every line one left edge beside the icon. */
  display: grid;
  grid-template-columns: 1fr;
  align-items: start;
}
.foot .micro { max-width: 74ch; line-height: 1.7; }

/* ---------------------------------------------------------------------------
   WORKING — the interstitial
   ---------------------------------------------------------------------------
   No bordered panel: two rules and a list, like every other reading surface
   here. The list is the content, so it gets the width and the air, and the
   headline stays out of its way.
   --------------------------------------------------------------------------- */
#screen-working { max-width: 62ch; padding-top: clamp(12px, 4vh, 40px); }
/* The blanket entry stagger every other screen gets is switched OFF here. This
   screen's whole job is a progressive reveal, and it does that itself, one row
   at a time — layering a second, delayed fade over the frame it starts in only
   creates a window where the headline and the bar are laid out but painting
   nothing. Each step keeps its own `rise`; the furniture around them does not
   need one. */
#screen-working > * { animation: none; }
.work-h { max-width: 14ch; margin-top: 14px; }

/* The bar. Gradient use 3 of 3 was the analysing hatch on the dropzone; this is
   the same idea at full width, and it fills rather than loops — a bar that
   loops says "still going", a bar that fills says "this much is done". */
.work-bar {
  position: relative;
  height: 3px;
  margin-top: clamp(22px, 4vh, 34px);
  background: var(--grid);
  overflow: hidden;
}
.work-fill {
  position: absolute;
  inset: 0 auto 0 0;
  width: calc(var(--p, 0) * 1%);
  background-image: var(--ig);
  background-size: 62ch 100%;   /* the gradient belongs to the BAR, not the fill,
                                   so the colours do not slide as it grows */
  transition: width .45s var(--e);
}

.work-steps { list-style: none; margin-top: 20px; }
.wstep {
  display: grid;
  grid-template-columns: 14px minmax(0, 1fr) auto;
  align-items: baseline;
  gap: 12px;
  padding: 7px 0;
  border-bottom: var(--b-hair);
  animation: rise .4s var(--e) backwards;
}
.wstep:last-child { border-bottom: none; }

/* Open square while it runs, filled once it is done. Fill weight again, not
   hue — a tick would be a fifth glyph in a system that has three. */
.wmark {
  width: 9px;
  height: 9px;
  align-self: center;
  outline: 1px solid var(--ink);
  background: var(--bg);
  animation: pulse 1.1s var(--e) infinite;
}
.wstep.done .wmark { background: var(--ink); animation: none; }

.wlabel { font-size: .92rem; line-height: 1.4; color: var(--ink-3); }
.wstep.done .wlabel { color: var(--ink); }
.wnote {
  font-size: .78rem;
  color: var(--ink-3);
  text-align: right;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

.work-foot { margin-top: 22px; max-width: 58ch; animation: rise .4s var(--e) backwards; }

/* The way out. Green like the other button in this app that moves you forward
   rather than spending something, and parked on the right edge — where the eye
   already is, because that is the column every count in the list above ends in.
   The note keeps the left, so the row reads as a statement with an answer. */
.work-go {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  flex-wrap: wrap;
  margin-top: 26px;
  animation: rise .4s var(--e) backwards;
}
.btn.next {
  margin-left: auto;            /* holds the right edge even with no note */
  background: var(--ok);
  color: var(--ink);
  border: var(--b);
  padding: 13px 32px;
  font-size: .84rem;
}
.btn.next:hover, .btn.next:focus-visible { background: var(--ink); color: var(--ok); }
.work-go-note { font-size: .8rem; color: var(--ink-3); line-height: 1.4; max-width: 40ch; }

/* ---------------------------------------------------------------------------
   HANDOFF
   ---------------------------------------------------------------------------
   The same numbered-instructions device as the upload screen's "what we need
   from Meta", and that echo is the point: the last time the user was told to go
   and fetch something, it looked like this, and it worked.

   The one addition is STATE. Those steps were a static list; these three are a
   sequence with a current position, so each carries data-state and the marker
   dot does the talking — filled magenta for the step you are on, solid ink once
   it is done, hollow while it is still out of reach. Border weight and fill,
   the system's existing vocabulary; no new colour and no tick-mark font.
   --------------------------------------------------------------------------- */
#screen-handoff { max-width: 68ch; padding-top: clamp(12px, 4vh, 40px); }
/* NOTE for whoever writes the next headline: the shared `h1 em` highlighter is
   an inline-block with line-height .82, which is what hugs the amber to the
   glyphs — and which collapses if the highlighted phrase ever WRAPS, because
   the crushed line-height then applies to the em's own two lines and they
   overlap. Every headline in this app highlights one short phrase for that
   reason. Keep it to one line, or fix the rule for everybody. */
/* Their own face, carried over from the board's masthead. The whole point is
   recognition, so it uses the same .owner/.own-face components rather than a
   lookalike — including the one sanctioned border-radius exception. */
#handOwner .owner { padding: 0; margin-top: 18px; gap: 12px; }
#handOwner .own-face { width: 52px; height: 52px; }
#handOwner .own-name { font-size: 1rem; }

.hand-lede { margin-top: 12px; max-width: 52ch; }

/* ---- the preview strip ----
   A crowd of monograms: the same initials on the same hues they have been
   dragging round the board, so this reads as THEIR list and not as a number.
   Wrapped, not scrolled — a scroller here would hide exactly the faces someone
   is scanning for. */
.preview { margin-top: 20px; }
.pv-faces { display: flex; flex-wrap: wrap; gap: 4px; }
.pv-face, .pv-more {
  width: 30px;
  height: 30px;
  display: grid;
  place-items: center;
  border: 1px solid var(--ink);
  font-size: .62rem;
  font-weight: 700;
  letter-spacing: .02em;
  overflow: hidden;
}
/* Crushed to a tint, exactly as on the card: 1,200 saturated hues is a rainbow,
   1,200 tinted papers is a list you can scan. */
.pv-face { background: hsl(var(--mh) 38% 92%); color: var(--ink); }
.pv-more { background: var(--ink); color: #fff; letter-spacing: .04em; }
.pv-note { margin: 10px 0 0; font-size: .76rem; color: var(--ink-3); max-width: 52ch; }

/* ---- the short version ----
   What the page becomes when the extension was already there. No step numbers,
   no rule down the left: with one thing left to do there is no sequence to
   draw, and drawing one anyway is how a single button becomes a form. */
.ready { margin-top: 26px; }
.ready-tick { display: flex; align-items: center; flex-wrap: wrap; gap: 12px; margin: 0; }
.ready-note { margin-top: 14px; max-width: 52ch; }
.ready .hand-do { margin-top: 18px; }

.hand-steps {
  list-style: none;
  margin: 30px 0 0;
  padding-left: 20px;
  border-left: var(--b-loud);
  max-width: 62ch;
}
.hand-step {
  display: grid;
  grid-template-columns: 2.7rem minmax(0, 1fr);
  position: relative;
  padding: 16px 0 20px;
  border-bottom: var(--b-hair);
}
.hand-step:last-child { border-bottom: none; }
.hand-step::before {
  content: "";
  position: absolute;
  left: -25px; top: 1.9em;
  width: 8px; height: 8px;
  outline: 1.5px solid var(--ink);
  background: var(--bg);
}
.hand-step[data-state="now"]::before  { background: var(--magenta); }
.hand-step[data-state="done"]::before { background: var(--ink); }

/* A step you cannot act on yet is dimmed, not hidden: the user should be able
   to read ahead and see that handing over the list is one button, before they
   have gone to the trouble of installing anything. */
.hand-step[data-state="todo"] .hand-body { opacity: .5; }
.hand-step[data-state="todo"] h2 { color: var(--ink-2); }

.hand-n {
  font-size: .66rem;
  font-weight: 700;
  letter-spacing: .12em;
  color: var(--ink-3);
  padding-top: .45em;
}
.hand-step[data-state="now"] .hand-n { color: var(--ink); }
.hand-body { min-width: 0; }
.hand-body h2 {
  margin: 0 0 6px;
  font-family: var(--display);
  font-weight: 400;
  font-size: 1.15rem;
  letter-spacing: .02em;
  text-transform: uppercase;
}
.hand-body .note { max-width: 52ch; }
.hand-do {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 12px;
  margin: 14px 0 0;
}
.hand-msg { font-size: .8rem; color: var(--ink-3); max-width: 34ch; }
.hand-msg.ok { color: var(--ink); font-weight: 600; }
.hand-msg.bad { color: var(--bad); }

/* Waiting. The pulsing dot is the page's own kicker device, reused because it
   means the same thing here as it does there: this is live, right now. */
.hand-watch {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: .8rem;
  color: var(--ink-3);
}
.btn.small { padding: 5px 11px; font-size: .68rem; }

/* Confirmation, and the only place --ok fills a surface on this page. It earns
   it the same way --pink earns the action bar: this is the one state on the
   screen the user is actually waiting to see, and it should be legible from
   the other side of the desk without reading a word.
   Ink on green, not white: #0b0b0c on #3fbf7f is 8.9:1, where white would be
   2.1:1 and fail outright. */
.hand-found { display: flex; align-items: center; flex-wrap: wrap; gap: 12px; margin: 14px 0 0; }
.hand-tick {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  background: var(--ok);
  color: var(--ink);
  border: var(--b);
  padding: 9px 15px;
  font-size: .78rem;
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
}
/* Stroked, not filled: a tick is a gesture, and drawing it as one keeps it
   from reading as a badge icon borrowed from somewhere else. */
.hand-tick svg {
  width: 15px;
  height: 15px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2.6;
  stroke-linecap: square;
}
.hand-ver { font-size: .78rem; color: var(--ink-3); }

/* Step 1's instructions, folded away once step 2 confirms. */
.hand-step[data-state="done"] .hand-fold { display: none; }

.btn.dl { text-decoration: none; }

/* The three-click install. Deliberately smaller and tighter than the numbered
   steps above it — it is a footnote to a button, not a second set of
   instructions competing with the first. */
.hand-mini {
  margin: 14px 0 0;
  padding-left: 1.2rem;
  max-width: 52ch;
  font-size: .84rem;
  line-height: 1.6;
  color: var(--ink-2);
}
.hand-mini li { margin-bottom: 3px; }
.hand-mini b { color: var(--ink); font-weight: 600; }
.hand-mini .say, .hand-body .say {
  background: var(--amber);
  color: var(--ink);
  font-weight: 600;
  padding: 0 4px;
}
.hand-why { margin-top: 12px; }

/* The load button is the same pink control as the board's, at the same size,
   because it is the same commitment arriving one screen later. */
#screen-handoff .go { padding: 11px 22px 10px; font-size: 1rem; }

.hand-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 16px;
  margin-top: 30px;
  padding-top: 18px;
  border-top: var(--b-hair);
}
.hand-foot .micro { max-width: 44ch; text-align: right; }

@media (max-width: 640px) {
  .hand-foot { flex-direction: column; align-items: flex-start; }
  .hand-foot .micro { text-align: left; }
}

/* ---------------------------------------------------------------------------
   THE TOOLBAR ARROW
   ---------------------------------------------------------------------------
   The one instruction this flow cannot carry out for you. Chrome will not let
   an extension open its own side panel without a click on the toolbar icon,
   and that icon is outside the document — a place the page can neither reach
   nor draw on. So it points at it: fixed to the top-right, curving up and off
   the page, which is genuinely where the thing went.

   position: fixed and outside .wrap, for the same reason the tooltip is — the
   board is a scroller and a scroller clips on both axes.
   --------------------------------------------------------------------------- */
/* Below the masthead, not level with it: the page's own Start over button lives
   in the top-right corner and an arrow laid across it would be pointing at the
   wrong thing entirely. Starting under the header and curving up-and-right is
   unambiguous anyway — an arrow indicates a direction, not a pixel. */
/* WHERE it points, in two numbers rather than one fudged `right`.
   Chrome's toolbar is not ours to measure — but its right-hand end is stable
   and the extensions puzzle is not at the corner: the ⋮ menu sits about 17px
   in, the profile avatar about 53, and the puzzle about 105. Aiming at the
   corner (the old right: 20px) put the tip under the profile menu, which is a
   wrong thing that also opens something. `--ta-tip` is where the arrowhead
   falls inside the 72px drawing, so the two together aim the TIP rather than
   the box around it. */
#toolbarArrow {
  --toolbar-icon: 105px;
  --ta-tip: 17px;
  position: fixed;
  top: 62px;
  right: calc(var(--toolbar-icon) - var(--ta-tip));
  z-index: 40;
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: start;
  gap: 10px;
  max-width: 330px;
  pointer-events: none;          /* never in the way of the thing it points at */
  animation: arrow-in .45s var(--e) backwards;
}
#toolbarArrow svg {
  width: 72px;
  height: 72px;
  margin-top: -6px;
  fill: none;
  stroke: var(--magenta);
  stroke-width: 3.5;
  stroke-linecap: square;
}
/* Drawn on, rather than appearing. A line that arrives by being drawn reads as
   a gesture — someone pointing — where a static one reads as a diagram. */
.ta-line {
  stroke-dasharray: 200;
  stroke-dashoffset: 200;
  animation: draw .7s var(--e) .12s forwards;
}
.ta-head { opacity: 0; animation: fade .25s linear .75s forwards; }
#toolbarArrow p {
  margin: 16px 0 0;
  justify-self: end;
  text-align: right;
  font-size: .82rem;
  font-weight: 600;
  line-height: 1.35;
  color: var(--ink);
  background: var(--amber);
  border: var(--b);
  padding: 7px 10px;
}
@keyframes draw { to { stroke-dashoffset: 0 } }
@keyframes fade { to { opacity: 1 } }
@keyframes arrow-in {
  from { opacity: 0; transform: translateY(-8px) }
  to { opacity: 1; transform: none }
}

@media (max-width: 640px) {
  /* On a phone the extensions menu is not up there, and pointing off-screen at
     nothing is worse than not pointing. */
  #toolbarArrow { display: none; }
}

/* ---------------------------------------------------------------------------
   BOARD MASTHEAD
   ---------------------------------------------------------------------------
   No bordered panel at all. Three horizontal rules carry it: the gradient
   hairline above the tiles, and — 2px, below the caveats — the action rule's
   own bottom edge, which closes the masthead and opens the columns. The tiles
   themselves carry no bottom border, because two 2px rules a few pixels apart
   is a stripe, not a structure.
   --------------------------------------------------------------------------- */
/* Gradient use 2 of 3. */
.rule-ig {
  height: 2px;
  margin-top: 8px;
  background-image: var(--ig);
}

.tiles {
  display: grid;
  /* The owner takes what it needs and the three counts split the rest, so a
     long display name never squeezes the numbers. */
  grid-template-columns: auto repeat(3, minmax(0, 1fr));
  align-items: center;
  padding: 14px 0 12px;
}
.tile { padding: 2px 24px; border-left: var(--b); }
.tiles > :first-child { border-left: none; padding-left: 2px; }

/* ---- who this is ----
   The only real photograph in the app, and the ONLY round thing in it.

   Everything else here is square by a rule enforced with `!important` at the
   root, which is deliberate: it means a corner cannot be softened by accident.
   Rounding this one is therefore an explicit, single-selector exception, and it
   is the right one to make — a profile picture is round everywhere a person has
   ever seen their own, including on Instagram, and a square one reads as a
   cropping mistake rather than as a style. The monogram takes the same shape,
   so the masthead does not change silhouette depending on whether the export
   happened to include a photo.

   If you add a second exception, stop: the reason this one works is that it is
   the only one. */
.owner {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 2px 26px 2px 2px;
  min-width: 0;
}
.own-face {
  width: 72px;
  height: 72px;
  flex: none;
  border: var(--b);
  object-fit: cover;
  /* Beats the root reset on specificity: .own-face (0,1,0) over * (0,0,0). */
  border-radius: 50% !important;
}
/* The fallback, and it is the same monogram every other account gets: tinted
   paper with the account's own hue, never a grey silhouette. */
.own-mono {
  display: grid;
  place-items: center;
  background: hsl(var(--mh, 0) 38% 92%);
  color: var(--ink);
  font-family: var(--font-display);
  font-weight: 400;
  font-size: 1.7rem;
  line-height: 1;
}
.own-id { min-width: 0; }
.own-name, .own-handle {
  display: block;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 22ch;
}
.own-name { font-size: 1.12rem; font-weight: 600; line-height: 1.25; }
.own-handle { font-size: .85rem; font-weight: 500; letter-spacing: .02em; color: var(--ink-3); }
.tile .cap {
  display: block;
  color: var(--ink-3);
  font-size: .66rem;
  font-weight: 700;
  letter-spacing: .12em;
  text-transform: uppercase;
}
/* Big enough to be read across the room, small enough that four of them plus
   the action rule do not eat the top third of a working surface. */
.tile .val {
  display: block;
  font-family: var(--font-display);
  font-weight: 400;
  font-size: clamp(1.5rem, 2.6vw, 2rem);
  line-height: 1.05;
  margin-top: 2px;
}
.tile .sub { display: block; color: var(--ink-3); font-size: .74rem; margin-top: 2px; }

/* ---------------------------------------------------------------------------
   Warnings
   ---------------------------------------------------------------------------
   Blocking is the loudest treatment in the app: a reversed block, and the only
   reversed thing above the board. It stays open, because it has emptied the
   Unfollow column and nothing under it makes sense without it.

   Info is the opposite claim — "here is a limit of your file, it did not change
   the answer" — so it wears the dashed provisional border and collapses to a
   single summary line. Both severities are always in the DOM; only one of them
   is allowed to cost vertical space.
   --------------------------------------------------------------------------- */
.warnblock { margin-top: 26px; animation: rise .4s var(--e) backwards; }
.warnblock > .micro { margin-bottom: 8px; }

.warn { max-width: 78ch; }
.warn-code {
  font-size: .6rem;
  font-weight: 700;
  letter-spacing: .14em;
  text-transform: uppercase;
}
.warn p { font-size: .9rem; }

.warn.blocking { padding: 16px 18px; background: var(--ink); color: #fff; }
.warn.blocking .warn-code { display: block; margin-bottom: 9px; color: var(--pink); }
.warn.blocking h3 { font-size: 1.08rem; }
.warn.blocking p { margin-top: 8px; color: rgba(255, 255, 255, .78); }

.warn.info { border: var(--b-prov); }
.warn.info + .warn.info { margin-top: 6px; }
/* The row is the control, so it inverts like every other control here. */
.warn.info summary {
  display: flex;
  align-items: baseline;
  gap: 12px;
  padding: 9px 12px;
  font-size: .88rem;
  color: var(--ink-2);
  cursor: pointer;
  list-style: none;               /* and ::-webkit-details-marker, below */
  transition: background-color var(--t), color var(--t);
}
.warn.info summary::-webkit-details-marker { display: none; }
.warn.info summary:hover,
.warn.info summary:focus-visible { background: var(--ink); color: #fff; }
.warn.info summary .warn-code { flex: none; color: var(--ink-3); }
.warn.info summary:hover .warn-code,
.warn.info summary:focus-visible .warn-code { color: var(--amber); }
/* + and − (U+2212), both inside the embedded latin subset — unlike a chevron
   glyph, which would have to be drawn. */
.warn.info summary::after {
  content: '+';
  margin-left: auto;
  padding-left: 12px;
  font-weight: 700;
}
.warn.info[open] summary::after { content: '\2212'; }
.warn.info[open] summary { border-bottom: var(--b-prov); }
.warn.info > p { padding: 10px 12px 12px; color: var(--ink-2); line-height: 1.6; }

/* ---------------------------------------------------------------------------
   BOARD
   ---------------------------------------------------------------------------
   Same rule as everywhere else, applied at 1,263 accounts: boxes are rationed.
   There is no panel around a column and no box around the toolbar, so the card
   is the only bordered thing on the screen — and that is precisely what makes
   it read as the thing you pick up.

   Depth is border weight and background, never shadow. The column header is
   sticky with a 2px bottom rule and the page background behind it; 600 cards
   pass under it and it stays legible, which is the argument for the whole
   system in one component.
   --------------------------------------------------------------------------- */

/* ---- the action rule ---- */
/* Three jobs on one line under a 2px edge: the number you are here to change,
   take the list away, start again. It is also the masthead's closing rule —
   the counts above it are context, everything below it is work. */
.actionrule {
  display: flex;
  align-items: center;
  gap: clamp(12px, 2.4vw, 26px);
  flex-wrap: wrap;
  padding: 4px 0 12px;
  background: var(--bg);
  border-bottom: var(--b-loud);
}
.queue { display: flex; align-items: baseline; gap: 11px; }
.queue-n { font-size: 1.5rem; line-height: 1; }
.actionrule-acts { display: flex; align-items: center; gap: 9px; margin-left: auto; flex-wrap: wrap; }
/* The copy chooser borrows the headers' chip row wholesale; it only needs to
   sit flush against the button that opened it. */
.copy-dests { margin-left: -4px; }
/* Destructive, so it wears the provisional border rather than the solid one. */
.btn.danger { border: var(--b-prov); }

/* ---- the action bar ---- */
/* The bottom bracket. The 2px top edge answers the action rule's 2px above the
   columns, so the board is enclosed rather than fading out at the bottom of the
   viewport — which matters here because the columns scroll inside themselves
   and there is no page scroll to signal an end. */
/* A 1fr auto 1fr GRID, not a flex row, and that is the whole trick: flexed, the
   middle cell spans the gap BETWEEN the two buttons, so anything centred in it
   sits off the bar's true centre by half the difference in their widths — and
   that difference moves every time the count does ("Unfollow 1 person" against
   "Unfollow 1,263 people"). Equal side tracks pin the centre to the bar. */
.actionbar {
  /* The support chat's launcher (ticketdm.com's loader, not ours) floats
     fixed at the bottom-right, 20px in and 56px wide, over whatever is there,
     and on the board that was the pink button. So the bar keeps 84px (20 + 56
     + air) clear on its right, measured from the VIEWPORT's edge: the wrap's
     own margin and gutter are subtracted, so on a wide window, where the wrap
     already stops short of the corner, nothing is added. Never move the widget
     to fix a collision; give it room here. */
  --chat-clear: max(0px, calc(84px - (100vw - min(100vw, var(--wrap))) / 2 - clamp(16px, 4vw, 32px)));
  flex: none;
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: clamp(12px, 2vw, 22px);
  /* A sliver of white above the rule so the last visible card reads as clipped
     by a scroller rather than butted against the edge of the page. */
  margin-top: 10px;
  padding: 12px var(--chat-clear) 2px 0;
  border-top: var(--b-loud);
  background: var(--bg);
}

/* Takes all the slack between the two buttons, so the green one sits hard left
   and the pink one hard right whether or not there is anything in the middle. */
.actionbar-mid {
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
}
/* Placed explicitly, not auto-flowed. The bar is `1fr auto 1fr` and it used to
   have three children; with the account offer gone, auto-placement would put
   the middle in the FIRST column and centre the toast over the left third of
   the screen. Naming the columns makes the layout independent of how many
   children happen to exist. */
.actionbar-mid { grid-column: 2; }
.actionbar .go { grid-column: 3; justify-self: end; }
.actionbar-note {
  min-width: 0;
  font-size: .78rem;
  line-height: 1.4;
  color: var(--ink-2);
  max-width: 62ch;
  text-align: center;
}

/* The loudest control in the app, and the only one that is loud because of what
   it does rather than where it is. Pink is elsewhere reserved for reversed text
   on ink; this is the one surface it fills, and it earns that by being the
   single irreversible action on the screen.

   The type is deliberately large: at 1.2rem/700 white on #e1306c clears the
   contrast floor for display text, which it would not at the .82rem the other
   buttons use. Size here is an accessibility constraint, not emphasis. */
.go {
  display: grid;
  justify-items: center;
  gap: 3px;
  border: var(--b-loud);
  background: var(--pink);
  color: #fff;
  padding: 11px 26px 10px;
  font-size: 1.2rem;
  font-weight: 700;
  line-height: 1.1;
  letter-spacing: .05em;
  text-transform: uppercase;
  transition: background-color var(--t), color var(--t);
}
/* The second action, at a size where white on pink would NOT clear the contrast
   floor (4.34:1 only passes as display text). Ink does: #0b0b0c on #e1306c is
   4.52:1, over the 4.5 needed for body-sized text — and ink on a colour is what
   the highlighter does everywhere else in this system, so the fix is also the
   house style. Hierarchy is carried by size, not by lightening the small one. */
.go-sub {
  color: var(--ink);
  font-size: .66rem;
  letter-spacing: .1em;
}
/* Inverted, the pink is on the text; white carries the sub-line at 19:1 and the
   1.2rem headline still leads. */
.go:hover .go-sub, .go:focus-visible .go-sub { color: #fff; }
.go[disabled] .go-sub { color: var(--ink-3); }
/* Inverts like every other control, into ink with the pink carried by the text
   — which is pink's usual job in this system, so the hover is a homecoming. */
.go:hover, .go:focus-visible { background: var(--ink); color: var(--pink); }
.go:active { transform: scale(.995); }
/* Nothing to unfollow. It keeps its size so the bar does not resize under the
   pointer as the last card leaves the column. */
.go[disabled] {
  background: var(--bg);
  color: var(--ink-3);
  border-color: var(--grid);
  cursor: default;
  transform: none;
}
.go[disabled]:hover { background: var(--bg); color: var(--ink-3); }

/* ---- the full-disconnect option (handoff) ---- */
.hand-mutual {
  display: flex;
  gap: 10px;
  align-items: flex-start;
  max-width: 62ch;
  margin: 18px 0 4px;
  padding: 12px 14px;
  border: var(--b-hair);
  font-size: .86rem;
  line-height: 1.5;
  cursor: pointer;
}
.hand-mutual input { margin-top: 3px; accent-color: var(--ink); width: 15px; height: 15px; flex: none; }

/* ---- resume (upload screen) ---- */
.resume {
  margin-top: 14px;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
}
/* Owner's calls (2026-07-29): Resume wears green, Forget wears danger red,
   and both run the FULL column width — the same width as the pink help button
   below, so the three big controls on this screen agree on a size. Resting
   text is ink (ink clears 4.5:1 on both fills; white fails on the red at this
   size). Hover is the owner's spec, not the system's usual invert-to-ink: the
   same colour lightened, with white text. */
.resume .btn { flex: 1 1 100%; width: 100%; justify-content: center; padding: 12px 14px; }
.btn.resume-go { background: var(--ok); color: var(--ink); }
.btn.resume-go:hover, .btn.resume-go:focus-visible {
  background: color-mix(in srgb, var(--ok) 65%, white);
  border-color: color-mix(in srgb, var(--ok) 65%, white);
  color: #fff;
}
/* Solid border, not the dashed provisional one — a filled surface already
   says "danger" louder than a dash can. */
.resume .btn.danger { background: var(--bad); color: var(--ink); border: var(--b); }
.resume .btn.danger:hover, .resume .btn.danger:focus-visible {
  background: color-mix(in srgb, var(--bad) 65%, white);
  border-color: color-mix(in srgb, var(--bad) 65%, white);
  color: #fff;
}
.resume-note { flex-basis: 100%; margin: 0; }

/* ---- the three columns ---- */
.board {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  margin-top: 16px;
}
.col { min-width: 0; }
/* The seam, not a panel — the .tiles device again. */
.col + .col { border-left: var(--b-hair); }

.col-scroll {
  position: relative;            /* the offset frame every cached card top uses */
  overflow-y: auto;
  overscroll-behavior: contain;  /* a column that runs out must not scroll the page */
  max-height: calc(100dvh - 220px);
  padding: 0 clamp(10px, 1.1vw, 16px) 20px;
}
/* The Unfollow column packs 5,800px of cards into one screen of scroller. The
   OS overlay scrollbar gives that a fingernail of a thumb and hides it between
   scrolls — which is how "can't drag to the bottom" got reported as a bug.
   Styling ::-webkit-scrollbar is what opts out of the overlay: the track is
   always there, in the system's own grammar — square, hairline, ink on paper.
   NOTE: setting the standard scrollbar-width/scrollbar-color ANYWHERE on this
   element makes Chrome ignore every ::-webkit-scrollbar rule below, so the
   standard pair lives in a Firefox-only @supports instead. */
.col-scroll::-webkit-scrollbar { width: 11px; }
.col-scroll::-webkit-scrollbar-track { background: var(--bg); border-left: var(--b-hair); }
.col-scroll::-webkit-scrollbar-thumb {
  background: var(--ink-3);
  border: 2px solid var(--bg);   /* inset the thumb without rounding a corner */
}
.col-scroll::-webkit-scrollbar-thumb:hover { background: var(--ink); }
@supports (-moz-appearance: none) {
  .col-scroll { scrollbar-width: thin; scrollbar-color: var(--ink-3) transparent; }
}
.col:first-child .col-scroll { padding-left: 0; }
.col:last-child .col-scroll { padding-right: 0; }

/* Side by side, the board screen is a full-height flex column and the scrollers
   take whatever the masthead leaves. That is not a refinement of the fixed
   `100dvh - 220px` above, it is the fix for it: the masthead's height is data
   (a caveat may be there or not, a long handle may wrap) and no constant can
   track it. Get it wrong upward and the columns run off the bottom of a page
   that cannot scroll — `overscroll-behavior: contain` means a pointer over a
   column never chains to the page, so those cards would be unreachable.
   The stacked layout below 861px keeps the fixed height and page-scrolls. */
@media (min-width: 861px) {
  /* height, not min-height: a flex column that is only told a MINIMUM grows to
     fit its children, and children that can grow never have to shrink — the
     scrollers would size to all 578 cards and run off a page that cannot
     scroll. Fixing the height is what forces the shrink back onto them. */
  body[data-screen="board"] .wrap { display: flex; flex-direction: column; height: 100dvh; }
  body[data-screen="board"] main,
  body[data-screen="board"] #screen-board { display: flex; flex-direction: column; flex: 1; min-height: 0; }
  .board { flex: 1; min-height: 0; }
  /* min-height on the GRID item, for the same reason min-width: 0 is already on
     it: a grid item's automatic minimum size is its content, so without this the
     row refuses to shrink and 578 cards decide the height of the board. */
  .col { display: flex; flex-direction: column; min-height: 0; }
  .col-scroll { flex: 1; min-height: 0; max-height: none; }
}

/* The column being dragged over washes, exactly like any other list surface.
   The cards keep the page background, so they lift off it without a shadow. */
.col.over .col-scroll { background: var(--wash); }

.col-head {
  position: sticky;
  top: 0;
  z-index: 2;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 0 8px;
  margin-bottom: 12px;
  background: var(--bg);
  border-bottom: var(--b-loud);
}
.col-head h3 { font-size: 1rem; line-height: 1; }

/* The select-all box, first thing in the header: it is about every card below,
   so it reads before the title. Hand-drawn (appearance: none) because a native
   checkbox is the one rounded, shadowed control this system cannot re-skin;
   the three states are the selection said back — empty, a bar for "some"
   (indeterminate, script-set only), a check for "all". */
.col-check {
  appearance: none;
  width: 16px;
  height: 16px;
  flex: none;
  display: grid;
  place-items: center;
  border: var(--b);
  background: var(--bg);
  cursor: pointer;
  transition: background-color var(--t);
}
.col-check:hover, .col-check:focus-visible { background: var(--wash); }
.col-check:checked, .col-check:indeterminate { background: var(--ink); }
.col-check:checked::after {
  content: "";
  width: 9px;
  height: 5px;
  border-left: 2px solid #fff;
  border-bottom: 2px solid #fff;
  transform: translateY(-1px) rotate(-45deg);
}
.col-check:indeterminate::after { content: ""; width: 8px; height: 2px; background: #fff; }
.col-check:disabled { border-color: var(--grid); cursor: default; background: var(--bg); }

/* The column's verdict is worn by the TITLE itself (owner's call, 2026-07-29 —
   the old fill-weight dot said the same thing and said it too quietly to earn
   its square): highlighter treatment, ink text on all three fills — the board's
   own band colours, with amber standing in for "orange" because it is the one
   mid colour this palette sanctions behind black text. */
.col-head h3 { padding: 2px 6px; color: var(--ink); }
.col[data-col="keep"] .col-head h3 { background: var(--ok); }
.col[data-col="unsure"] .col-head h3 { background: var(--amber); }
.col[data-col="unfollow"] .col-head h3 { background: var(--bad); }
/* The filter chip. A control, so it inverts; pressed, it STAYS inverted, which
   is the only way a toggle in this system can show it is on without inventing a
   second vocabulary for it. */
.col-filter {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 3px 8px;
  border: var(--b);
  font-size: .6rem;
  font-weight: 700;
  letter-spacing: .12em;
  text-transform: uppercase;
  transition: background-color var(--t), color var(--t);
}
.col-filter:hover, .col-filter:focus-visible { background: var(--ink); color: #fff; }
.col-filter[aria-pressed="true"] { background: var(--ink); color: #fff; }
.col-filter[aria-pressed="true"]:hover { background: var(--bg); color: var(--ink); }
.col-filter-n { font-variant-numeric: tabular-nums; opacity: .75; }
/* The pair, as one object. The chips share an edge rather than sitting near
   each other: 1px of overlap so the two borders read as a single outline, and
   the group takes the auto margin so the pair moves as a unit. Nothing here is
   a new shape — it is the same chip twice with the gap taken out, which is the
   difference between "two controls" and "one control with two halves". */
.col-filter-group {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
}
.col-filter-group .col-filter { margin-left: 0; }
.col-filter-group .col-filter + .col-filter { margin-left: -1px; }

/* Pressed, the filter is ink-filled — and Move all, attached to it, inherits
   that state visually by sitting inside the same outline, in the amber that
   means "this is the thing being pointed at" everywhere else in the app. It is
   never on screen unless the filter above is pressed, so this styling only
   ever appears next to an inverted chip. */
.col-move-all {
  background: var(--amber);
  border-color: var(--ink);
  color: var(--ink);
}
.col-move-all:hover, .col-move-all:focus-visible { background: var(--ink); color: #fff; }
.col-move-all .col-filter-n { opacity: .6; }
/* The destination pair rides in the same segmented outline, appearing when
   "Move all" is expanded. inline-flex keeps the shared-edge grammar. */
.col-move-dests { display: inline-flex; align-items: center; }
.col-move-dests .col-filter { margin-left: -1px; }
/* With the filter on, the count next to the title would be the WHOLE column
   and read as a contradiction, so it steps aside. */
.col.filtered .col-count { display: none; }

/* Beside the title now, not at the far edge — the chips own the right end of
   the header (the group's auto margin above), so the count reads as part of
   the title: "Keep, 591". */
.col-count {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: 1rem;
  line-height: 1;
}

/* Why the Unfollow column can be empty on a blocked export. */
.col-note {
  border: var(--b-prov);
  padding: 12px 13px;
  margin-bottom: 12px;
  font-size: .78rem;
  line-height: 1.5;
  color: var(--ink-2);
}
.col-note b { color: var(--ink); font-weight: 600; }

/* The break between "you decided this" and "we ranked this". */
.pin-divider { display: flex; align-items: center; gap: 10px; margin: 0 0 10px; }
.pin-divider span {
  font-size: .66rem;
  font-weight: 700;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--ink-3);
  white-space: nowrap;
}
.pin-divider::after { content: ""; flex: 1; height: 1px; background: var(--grid); }

/* An empty column is a place you can put something, so it has to be big enough
   to aim at and marked as provisional. */
.col-empty {
  display: grid;
  place-items: center;
  min-height: 8rem;
  padding: 12px;
  border: var(--b-prov);
  text-align: center;
  font-size: .66rem;
  font-weight: 700;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--ink-3);
}

.more {
  display: block;
  width: 100%;
  margin-top: 4px;
  padding: 10px;
  border: var(--b-hair);
  background: var(--bg);
  color: var(--ink-2);
  font-size: .66rem;
  font-weight: 700;
  letter-spacing: .12em;
  text-transform: uppercase;
  transition: background-color var(--t), color var(--t), border-color var(--t);
}
.more:hover, .more:focus-visible { background: var(--ink); border-color: var(--ink); color: #fff; }

.sentinel { height: 1px; }

/* ---------------------------------------------------------------------------
   The card
   ---------------------------------------------------------------------------
   The one thing on this screen allowed to be a box, and it earns it because
   nothing around it is one. Three columns: monogram, identity, verdict.
   --------------------------------------------------------------------------- */
.card {
  display: grid;
  grid-template-columns: 42px minmax(0, 1fr) auto;
  gap: 3px 10px;
  align-items: start;
  padding: 9px 10px;
  margin-bottom: 6px;
  border: var(--b);
  background: var(--bg);
  cursor: grab;
  transition: background-color var(--t);
}
/* List items wash, controls invert (see rule 2 at the top of this file):
   inverting a card under a 600-row hover-scan would be violent. */
.card:hover { background: var(--wash); }
.card:focus-visible { background: var(--wash); }
.card:active { cursor: grabbing; }
/* The one sanctioned opacity in the app. It is a drag state, not a hover, and
   it is added a frame late on purpose — see the note in draft.js. */
.card.dragging { opacity: .35; }
/* A pinned card is heavier on the edge it was dragged from. The harness used an
   inset shadow here; there are no shadows in this system. */
.card.pinned { border-left-width: 3px; padding-left: 8px; }

/* Tinted paper, not a rainbow: the library's per-account hue crushed to a wash
   so 1,263 monograms stay scannable without becoming a second colour system. */
.c-mono {
  grid-column: 1;
  grid-row: 1;
  display: grid;
  place-items: center;
  justify-self: start;
  width: 34px;
  height: 34px;
  text-decoration: none;
  transition: background-color var(--t), color var(--t);
  border: var(--b);
  background: hsl(var(--mh, 0) 38% 92%);
  color: var(--ink);
  font-family: var(--font-display);
  font-weight: 400;
  font-size: .82rem;
  line-height: 1;
  letter-spacing: .01em;
}
/* It is the select control now, so it inverts on hover like every other
   control here — and STAYS inverted while the card is selected, the same
   pressed grammar as the filter chips. */
.c-mono:hover, .c-mono:focus-visible { background: var(--ink); color: #fff; }

/* Selected: the initials give way to a drawn check (drawn, not typed — the
   embedded fonts are latin-subset and a ✓ would arrive in system-ui), and the
   whole card washes so a scattered selection can be read back at a glance. */
.mono-check { display: none; width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 3; }
.card.sel { background: var(--wash); }
.card.sel .c-mono { background: var(--ink); color: #fff; }
.card.sel .c-mono:hover, .card.sel .c-mono:focus-visible { background: var(--bg); color: var(--ink); }
.card.sel .mono-i { display: none; }
.card.sel .mono-check { display: block; }

/* How long they have been in your following list, under the monogram.
   Deliberately coarse and deliberately quiet: it is context for the numbers to
   the right, not a number of its own. Tabular so a column of them lines up. */
.c-years {
  grid-column: 1;
  grid-row: 2;
  justify-self: start;
  align-self: start;
  margin-top: 5px;
  padding: 1px 4px;
  border: var(--b-hair);
  color: var(--ink-3);
  font-size: .56rem;
  font-weight: 700;
  letter-spacing: .02em;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

.c-id { grid-column: 2; grid-row: 1; min-width: 0; }
.c-name, .c-handle {
  display: block;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.c-name { font-size: .84rem; font-weight: 600; line-height: 1.25; }
/* No monospace anywhere: a handle gets its code-ness from tracking. */
.c-handle { font-size: .74rem; font-weight: 500; letter-spacing: .02em; color: var(--ink-3); line-height: 1.3; }
.c-id.solo .c-handle { font-size: .82rem; color: var(--ink-2); line-height: 1.5; }
/* Name and handle are both the profile link now — two spellings of one
   identity, one destination, one hover grammar. */
.c-name a, .c-handle a { text-decoration: none; }
.c-name a:hover, .c-name a:focus-visible,
.c-handle a:hover, .c-handle a:focus-visible { background: var(--ink); color: #fff; }

.c-meta { grid-column: 3; grid-row: 1; display: flex; align-items: center; gap: 9px; padding-top: 2px; }

/* ---- the three marks ----
   Colour says WHEN, the glyph says WHAT. Both are needed: the fills are the
   fastest read on the card, and the glyph is what is left when someone cannot
   tell them apart. Every mark also carries its state in its title/aria text, so
   nothing here is ONLY a colour.

   All three slots are always drawn, present or not. A variable number of icons
   would make every row a different shape and the column unscannable; an absent
   mark is a hairline outline with a faded glyph, which reads as "no" at a glance
   without competing with the ones that read as "yes". */
.c-marks { display: flex; align-items: center; gap: 4px; flex: none; }
.mk {
  display: grid;
  place-items: center;
  width: 17px;
  height: 17px;
  border: 1px solid var(--ink);
  color: var(--ink);
}
.mk svg { width: 11px; height: 11px; fill: currentColor; stroke: none; }

.mk.m-active { background: var(--ok); }
.mk.m-fading { background: var(--mid); }
.mk.m-cold { background: var(--bad); }
/* On, but with no recency to report: close friends is a list, not a moment, so
   it takes the ink treatment rather than a colour that would imply a date. */
.mk.m-on { background: var(--ink); color: #fff; }
/* Real contact, no date anywhere in the export. Dashed is this system's word
   for provisional, and inventing a colour here would be inventing a date. */
.mk.m-undated { border-style: dashed; background: var(--bg); }
/* Absent: still a slot, but it recedes. */
.mk.m-none { border-color: var(--grid); color: var(--grid); }

/* The mark that stands where a score would. Outlined rather than filled: it is
   a statement about which way the relationship points, not a verdict with a
   number behind it, and filling it would put it in the same visual class as the
   three bands. */
.c-stalk {
  display: grid;
  place-items: center;
  width: 26px;
  height: 22px;
  border: var(--b);
  color: var(--ink);
}
.c-stalk svg { width: 16px; height: 16px; fill: currentColor; }
/* A hairline left edge marks the whole card as a different kind of row without
   spending a colour on it. */
.card.stalker { border-left-width: 3px; padding-left: 8px; }
.card.stalker.pinned { border-left-width: 3px; }

/* ---- the score ----
   The card's headline number, and the one place a continuous scale collapses
   into a verdict: three bands, three fills, black text on all of them. Ink on
   colour rather than white on colour because these are small numerals and the
   greens dark enough to carry white text are too dark to read as green. */
.c-score {
  font-size: .78rem;
  font-weight: 700;
  line-height: 1.3;
  padding: 1px 7px;
  border: var(--b);
  font-variant-numeric: tabular-nums;
}
.c-score.b-keep { background: var(--ok); }
.c-score.b-unsure { background: var(--mid); }
.c-score.b-unfollow { background: var(--bad); }

.c-why {
  grid-column: 2;
  grid-row: 2;
  font-size: .73rem;
  line-height: 1.35;
  color: var(--ink-3);
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  overflow: hidden;
}

/* There are no per-card move buttons. A card is a drag handle and a way into
   the drawer; the pointer route to moving one is the drag it already looks like
   it wants. Keyboard still moves a card with the arrow keys, and touch — where
   HTML5 drag-and-drop never fires at all — moves it from the drawer's column
   switcher, which is why that control is not optional. */

/* The landing flash — background-color only, so nothing moves. */
@keyframes flash { from { background-color: var(--amber); } }
.card.just-moved { animation: flash .9s var(--e); }

/* The drag placeholder. Sized to the card being dragged (Trellis's fixed 40px
   mismatches every tall card) and dashed, because "a card would go here" is
   the definition of provisional. */
.ph {
  height: var(--ph-h, 64px);
  margin-bottom: 6px;
  border: var(--b-prov);
  background: var(--wash);
}

/* ---------------------------------------------------------------------------
   THE DRAWER
   ---------------------------------------------------------------------------
   A native <dialog> opened with showModal(), which hands over the four things a
   hand-rolled drawer always gets wrong — focus trap, Esc, an inert background
   and top-layer stacking — and nothing below re-implements any of them.

   It is the app's one modal surface, so it is allowed one loud edge: a 2px left
   border and nothing else. No shadow lifts it off the page; the dimmed
   ::backdrop does that, the same way the reversed warning block separates
   itself from a reading screen.
   --------------------------------------------------------------------------- */
dialog {
  width: min(460px, 92vw);
  max-width: none;
  height: 100dvh;
  max-height: 100dvh;
  margin: 0 0 0 auto;                /* flush to the right edge, full height */
  padding: 0;
  overflow: hidden;                  /* .dw does the scrolling — see ::after */
  border: 0;
  border-left: var(--b-loud);
  background: var(--bg);
  color: var(--ink);
  transform: translateX(100%);
  transition: transform .18s var(--e);
}
dialog.slid { transform: none; }
dialog::backdrop { background: rgba(11, 11, 12, .55); }

/* The grain, and the only copy of it in this file. A modal <dialog> paints in
   the TOP LAYER, above body::after, so without this the drawer would be the one
   ungrained surface in the app — which nobody can name but everybody can see.
   It is a child of the dialog rather than of the scroller, so it stays put while
   the content moves under it. */
dialog::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 5;
  pointer-events: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  opacity: .035;
  mix-blend-mode: multiply;
}

/* The export instructions. The account drawer is a drawer because it is ABOUT
   something on the board behind it — you keep the board in view and slide the
   detail in beside it. This is about a different application entirely, and
   nothing behind it is worth keeping in view while you read it, so it takes the
   middle of the screen and the page dims out of the way.

   It undoes the drawer rule rather than escaping it: same <dialog>, same
   backdrop, same grain, all four borders instead of one, and it grows to its
   content rather than to the window. Wider than the drawer for one reason:
   `.steps` is a hanging-indent list with a rail down its left, and at 460px the
   four sentences wrap often enough to lose the shape that makes them steps. */
#metaHelp {
  width: min(600px, 92vw);
  /* fit-content, NOT auto. A modal dialog is `position: fixed; inset: 0` in the
     UA sheet, and `height: auto` against a fixed box with both insets set means
     "fill the viewport" — which is how the drawer gets its full height for
     free. The UA's own centring depends on `height: fit-content`, so putting it
     back is what makes this box shrink to its four steps. */
  height: fit-content;
  max-height: min(84dvh, 780px);
  margin: auto;                      /* undoes the drawer's `0 0 0 auto` */
  border: var(--b-loud);
  transform: translateY(8px);
  opacity: 0;
  transition: transform .18s var(--e), opacity .18s var(--e);
}
#metaHelp.slid { transform: none; opacity: 1; }

/* The Unsure heads-up and the board-entry question: metaHelp's centred-modal
   geometry, one size down. */
#unsureCheck, #boardIntro {
  width: min(480px, 92vw);
  height: fit-content;               /* same UA-sheet trap as #metaHelp above */
  margin: auto;
  border: var(--b-loud);
  transform: translateY(8px);
  opacity: 0;
  transition: transform .18s var(--e), opacity .18s var(--e);
}
#unsureCheck.slid, #boardIntro.slid { transform: none; opacity: 1; }
.unsure-acts { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 20px; }
.dw.help {
  height: auto;
  max-height: min(84dvh, 780px);
  padding-bottom: clamp(18px, 3.4vw, 26px);
}
.help-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 14px;
}
.dw.help h2 { margin-top: 4px; }
.dw.help .note { margin-top: 10px; max-width: none; }
.dw.help .steps { margin-top: 22px; max-width: none; }
.help-foot { margin-top: 26px; }

.dw {
  --dw-pad: clamp(16px, 3.4vw, 26px);
  height: 100%;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: clamp(18px, 3.4vw, 26px) var(--dw-pad) 0;
}
.dw:focus { outline: none; }         /* it is focused on open, not tabbed to */

/* ---- header ---- */
/* The display name is INTER 700, not Anton, and that is a rule rather than a
   preference: a display name is arbitrary user text, and Anton is an
   uppercase-only latin face. A CJK or emoji name set in it is a broken
   rendering. Anton gets the score instead — a number we generate. */
.dw-head { display: grid; grid-template-columns: 48px minmax(0, 1fr) 34px; gap: 14px; align-items: start; }
.dw-mono {
  display: grid;
  place-items: center;
  width: 48px; height: 48px;
  border: var(--b);
  background: hsl(var(--mh, 0) 38% 92%);
  font-family: var(--font-display);
  font-weight: 400;
  font-size: 1.2rem;
  line-height: 1;
}
.dw-id { min-width: 0; padding-top: 2px; }
.dw-name {
  display: block;
  font-size: 1.1rem;
  font-weight: 700;
  line-height: 1.2;
  overflow-wrap: anywhere;
}
/* An account with no harvested display name puts its handle in the name slot,
   where it keeps the handle's wider tracking rather than pretending to be a
   name. Still the link, still the only keyboard route to a profile. */
.dw-name a.as-name { text-decoration: none; letter-spacing: .02em; padding: 2px 3px; margin-left: -3px; }
.dw-name a.as-name:hover, .dw-name a.as-name:focus-visible { background: var(--ink); color: #fff; }

/* On the board the profile link is tabindex="-1" so a card stays one tab stop,
   which makes this the only keyboard route to a profile in the whole app. */
.dw-handle { display: block; margin-top: 4px; }
.dw-handle a {
  display: inline-block;
  font-size: .66rem;
  font-weight: 700;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--ink-3);
  text-decoration: none;
  padding: 2px 3px;
  margin-left: -3px;
  transition: background-color var(--t), color var(--t);
}
.dw-handle a:hover, .dw-handle a:focus-visible { background: var(--ink); color: #fff; }

.dw-x {
  display: grid;
  place-items: center;
  width: 34px; height: 34px;
  border: var(--b-loud);
  background: var(--bg);
  color: var(--ink);
  font-size: 1.15rem;
  line-height: 1;
  transition: background-color var(--t), color var(--t);
}
.dw-x:hover, .dw-x:focus-visible { background: var(--ink); color: #fff; }

/* ---- the verdict sentence ---- */
/* The tier, in the colour its verdict deserves. The highlighter was amber for
   every account, which made the app's clearest sentence its least informative
   pixel — "very engaged" and "feed clutter" looked identical until you read
   them. Silent stays neutral on purpose: it is not a bad verdict, it is the
   absence of one, and painting it red would claim evidence we do not have. */
.hl.t-protected { background: var(--ok); }
.hl.t-engaged { background: var(--mid); }
.hl.t-clutter { background: var(--bad); }
.hl.t-silent { background: var(--grid); }

.dw-verdict { margin-top: 20px; color: var(--ink-2); font-size: .96rem; line-height: 1.5; }
/* The highlighter, inline this time: a marker stroke behind the tier word. */
.hl {
  font-style: normal;
  background: var(--amber);
  color: var(--ink);
  font-weight: 600;
  padding: 0 .18em;
}

/* ---- the mini tile strip ---- */
/* The masthead's .tiles device at drawer scale. Score takes the Anton treatment
   and is the largest thing in here; last contact gets the leftover width
   because it is the only value that can be a word. */
.dw-tiles {
  display: grid;
  grid-template-columns: auto auto minmax(0, 1fr);
  border-block: var(--b-loud);
  padding: 14px 0;
  margin-top: 22px;
}
.dw-tiles .tile { padding: 0 14px; }
.dw-tiles .tile:first-child { padding-left: 0; }
.dw-tiles .val { font-size: 1.4rem; margin-top: 3px; }
.dw-tiles .tile.score .val {
  font-size: 2.6rem;
  line-height: .92;
  /* The one number the whole drawer is about, wearing its band. Padded and
     bordered so the fill reads as a deliberate block rather than a highlight
     that has slipped behind the digits. */
  display: inline-block;
  border: var(--b);
  padding: 2px 10px 4px;
}
.dw-tiles .tile.b-keep .val { background: var(--ok); }
.dw-tiles .tile.b-unsure .val { background: var(--mid); }
.dw-tiles .tile.b-unfollow .val { background: var(--bad); }
.dw-tiles .sub { font-size: .66rem; line-height: 1.35; margin-top: 3px; }

/* ---- section furniture inside the drawer ---- */
.dw-sec { margin-top: 26px; }
.dw-sec > .micro { margin-bottom: 2px; }

/* ---- the signed-points table ---- */
/* Polarity is carried by POSITION, WEIGHT and MARK — never by red and green,
   even now that the card next door has a traffic light on it. Two reasons it
   stays that way: a negative line is not an error, it is evidence; and the score
   at the bottom of this table is ALREADY coloured by band, so colouring each
   contributing line as well would say each one had a verdict of its own.

   A positive line (earns points, argues for keeping) gets the magenta edge and a
   black 700 numeral; a negative one recedes to a grey edge and --ink-3 600. The
   bar is 2px and left-aligned so it reads as a rule under the numeral rather
   than as a chart nobody asked for. */
.pts { width: 100%; border-collapse: collapse; margin-top: 10px; }
.pts td { border-bottom: 1px solid var(--hair); padding: 9px 0; vertical-align: baseline; }
.pts .n {
  width: 1%;
  white-space: nowrap;
  padding-left: 11px;
  padding-right: 13px;
  font-size: .84rem;
}
.pts .l { font-size: .8rem; line-height: 1.4; color: var(--ink-2); }
.pts .b { width: 62px; padding-left: 12px; }
.pts .bar {
  display: block;
  height: 2px;
  width: calc(var(--w, 0) * 1%);
  min-width: 2px;
  background: var(--grid);
}
.pts tr.pos .n { border-left: 3px solid var(--magenta); color: var(--ink); font-weight: 700; }
.pts tr.pos .bar { background: var(--magenta); }
.pts tr.neg .n { border-left: 3px solid var(--grid); color: var(--ink-3); font-weight: 600; }

/* The baseline: the line every other line is measured from, so it is set apart
   from both polarities rather than dressed as a gain. */
.pts tr.base .n { border-left: 3px solid var(--ink); color: var(--ink); font-weight: 700; }
.pts tr.base .l { color: var(--ink); }
.pts tr.base td { border-bottom: var(--b); }

/* The score is clamped to 0-100, so on a strongly protected account the lines
   above genuinely do not add up to it. Say so in a row of its own rather than
   quietly printing a total that is not the sum of anything. */
.pts tr.clamp .n { border-left: 3px solid transparent; color: var(--ink-2); font-weight: 600; }
.pts tr.clamp .l { color: var(--ink-3); font-size: .72rem; }

/* The reversed moment: the loudest treatment in the system lands on the most
   important number in the app. The band shows up as the numeral's own colour
   against the ink, which is the only way to keep both the reversal and the
   traffic light without one of them cancelling the other. */
.pts tr.tot td { background: var(--ink); color: #fff; border-bottom: 0; padding: 12px 0; }
.pts tr.tot .n { border-left: 3px solid var(--ink); color: var(--pink); font-weight: 700; font-size: 1.1rem; }
.pts tr.tot.b-keep .n { color: var(--ok); }
.pts tr.tot.b-unsure .n { color: var(--mid); }
.pts tr.tot.b-unfollow .n { color: var(--bad); }
.pts tr.tot .l {
  color: #fff;
  font-size: .68rem;
  font-weight: 700;
  letter-spacing: .12em;
  text-transform: uppercase;
}

/* ---- reasons ---- */
/* The upload screen's numbered-list device, without the numbers. */
.rail { list-style: none; border-left: var(--b-loud); padding-left: 17px; margin-top: 12px; }
.rail li { position: relative; padding: 8px 0; font-size: .82rem; line-height: 1.45; color: var(--ink-2); }
.rail li::before {
  content: "";
  position: absolute;
  left: -21px; top: 1.05em;
  width: 7px; height: 7px;
  background: var(--magenta);
  outline: 1.5px solid var(--ink);
}

/* ---- signals ---- */
.sig { width: 100%; border-collapse: collapse; margin-top: 10px; }
.sig th {
  text-align: left;
  padding: 0 0 7px;
  border-bottom: 1px solid var(--hair);
  font-size: .66rem;
  font-weight: 700;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.sig td { border-bottom: 1px solid var(--hair); padding: 7px 0; font-size: .8rem; color: var(--ink); }
.sig th + th, .sig td + td { width: 1%; text-align: right; padding-left: 12px; white-space: nowrap; }
.sig td + td { color: var(--ink-3); }
.dw-age { margin-top: 10px; }
.dw-empty { margin-top: 10px; font-size: .82rem; color: var(--ink-2); }

/* ---- the column switcher ---- */
/* Sticky, because the drawer is long and the whole point of reading it is to
   then decide. In flow it would sit below reasons and signals, which on a
   1,263-account board means scrolling to make every single call. */
/* Full-bleed rather than inset, for two reasons: its 2px top edge then reads as
   a floor under the drawer instead of a rule inside it, and its background has
   to cover the whole width — the .rail's markers sit slightly outside the
   content box by design, and they would otherwise poke out from behind it. */
.dw-switch {
  position: sticky;
  bottom: 0;
  margin: 26px calc(var(--dw-pad) * -1) 0;
  padding: 12px var(--dw-pad) 13px;
  background: var(--bg);
  border-top: var(--b-loud);
}
.dw-switch .micro { margin-bottom: 8px; }
.dw-sw { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
.sw {
  border: var(--b);
  background: var(--bg);
  color: var(--ink);
  padding: 10px 4px;
  font-size: .66rem;
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
  transition: background-color var(--t), color var(--t);
}
.sw:hover, .sw:focus-visible { background: var(--ink); color: #fff; }
/* Where it already is. Amber, and it stays amber under the pointer: there is
   nothing to press here. */
.sw[aria-pressed="true"] { background: var(--amber); color: var(--ink); cursor: default; }
.sw[aria-pressed="true"]:hover { background: var(--amber); color: var(--ink); }

.dw-fine { padding: 12px 0 20px; }

/* ---------------------------------------------------------------------------
   THE UNDO TOAST
   ---------------------------------------------------------------------------
   Reversed, flush into the bottom-left corner, no border and nothing floating:
   it is a strip the page slides out from under itself, not a card. UNDO is the
   third and final sanctioned use of reversed --pink (the others: a high score
   badge, and the score in the points table's total row).

   z-index sits UNDER body::after on purpose, so the grain runs across it like
   everything else.
   --------------------------------------------------------------------------- */
.toast {
  display: flex;
  align-items: center;
  gap: 16px;
  max-width: min(430px, 100%);
  padding: 9px 14px;
  background: var(--ink);
  color: #fff;
  font-size: .8rem;
  animation: toastin .18s var(--e);
}
.toast-msg { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.toast-msg b { font-weight: 600; letter-spacing: .02em; }
.toast-undo {
  flex: none;
  margin-left: auto;
  padding: 5px 7px;
  color: var(--pink);
  font-size: .66rem;
  font-weight: 700;
  letter-spacing: .12em;
  text-transform: uppercase;
  transition: background-color var(--t), color var(--t);
}
.toast-undo:hover, .toast-undo:focus-visible { background: var(--pink); color: var(--ink); }
.toast-undo:focus-visible { outline-color: #fff; }
/* It sits in the bar now instead of sliding in from the viewport edge, so it
   arrives rather than travels — a strip that shoves the bar's own contents
   around every time you move a card is a strip you learn to dread. */
@keyframes toastin { from { opacity: 0; } }

/* ---------------------------------------------------------------------------
   Tooltip
   ---------------------------------------------------------------------------
   One node for the whole board. Reversed, because it is transient and has to
   read instantly over a page of white cards, and because the only other
   reversed things here — the blocking warning, the score total, the toast — are
   also things that interrupt.

   position:fixed and outside .wrap on purpose: the columns are scrollers, and a
   scroller clips both axes, so a tooltip drawn inside one would be cut off at
   the column edge. Coordinates arrive as custom properties, never as `left`
   and `top` in a style attribute — the CSP forbids that attribute outright.
   --------------------------------------------------------------------------- */
.tip {
  position: fixed;
  left: var(--tip-x, 0);
  top: var(--tip-y, 0);
  z-index: 50;                  /* over the grain, under the drawer */
  max-width: 30ch;
  padding: 8px 11px;
  background: var(--ink);
  color: #fff;
  font-size: .76rem;
  line-height: 1.4;
  pointer-events: none;         /* never steals the hover that summoned it */
}

/* ---------------------------------------------------------------------------
   Dev banner — draft-only, deleted on promotion to site/app/
   --------------------------------------------------------------------------- */
.devbanner {
  border: var(--b-prov);
  background: var(--amber);
  color: var(--ink);
  padding: 10px 14px;
  margin-bottom: 20px;
  font-size: .74rem;
  font-weight: 600;
}
.devbanner b { letter-spacing: .1em; text-transform: uppercase; font-size: .66rem; }

/* ---------------------------------------------------------------------------
   Responsive
   --------------------------------------------------------------------------- */
@media (max-width: 1100px) {
  /* The card's third column stops paying for itself well before it stops
     fitting: a why-line that clamps at four words has stopped being scannable.
     So the marks and the score drop under the name and get a row of their own. */
  .card { grid-template-columns: 42px minmax(0, 1fr); row-gap: 5px; }
  .c-meta { grid-column: 2; grid-row: 2; justify-content: flex-start; padding-top: 0; }
  .c-why { grid-column: 2; grid-row: 3; }
}

@media (max-width: 860px) {
  .hero { grid-template-columns: 1fr; }
  .hero .dz-wrap { margin-top: 6px; min-height: 260px; }

  /* Stacked, and the seam turns from a left edge into a top edge. Each column
     keeps its own scroller so a 578-card Unfollow list never becomes a 578-card
     page scroll. */
  .board { grid-template-columns: minmax(0, 1fr); }
  .col + .col { border-left: none; border-top: var(--b-hair); }
  /* overscroll-behavior flips back to auto here, and it matters: stacked, the
     column scroller is the full width of the screen, so containing the scroll
     would strand a touch user at the bottom of Keep with nowhere left to swipe.
     Side by side there is always somewhere else to put your finger; stacked
     there is not, so the scroll has to chain to the page. */
  .col-scroll {
    max-height: min(62dvh, 560px);
    overscroll-behavior: auto;
    padding-left: 0;
    padding-right: 0;
  }
}

@media (max-width: 720px) {
  /* The owner takes the full first row; the three counts pair up under it. */
  .tiles { grid-template-columns: 1fr 1fr; row-gap: 20px; }
  .owner { grid-column: 1 / -1; padding-right: 0; }
  .tiles > .tile:nth-child(2) { border-left: none; padding-left: 2px; }
  /* Two lines of caption reserved, so the four huge numerals share a baseline
     even when one label wraps. */
  .tile .cap { display: block; min-height: 2.7em; }

  /* The code and the sentence stop competing for one line. */
  .warn.info summary { flex-wrap: wrap; gap: 4px 12px; }
  .warn.info summary .warn-code { flex-basis: 100%; }
}

@media (max-width: 520px) {
  .topbar { padding-bottom: 20px; }
  .lede { font-size: .98rem; }

  /* Stacked, and the irreversible one goes last: on a narrow screen the bottom
     of a stack is where the thumb lands, and that should be the deliberate
     press rather than the one you reach by accident. */
  .actionbar { grid-template-columns: 1fr; }
  .actionbar .btn { width: 100%; justify-self: stretch; }
  .go { width: 100%; order: 3; justify-self: stretch; }
  .actionbar-mid { order: 2; width: 100%; }
  .toast { max-width: none; width: 100%; }
  .actionbar-note { max-width: none; }
}

/* Coarse pointers get real targets. */
@media (pointer: coarse) {
  .btn, .chip { padding-top: 11px; padding-bottom: 11px; }
  .textlink { padding: 10px 6px; }

  .card { padding: 11px 12px; }
  .go { padding: 16px 26px; }
  /* The marks are titles-on-hover for a mouse, which touch does not have, so
     give them enough room to be tapped for their tooltip. */
  .mk { width: 22px; height: 22px; }
  .mk svg { width: 13px; height: 13px; }

  /* HTML5 drag-and-drop never fires on touch — not "fires badly", never fires —
     and there are no per-card move buttons any more. So on a touch screen the
     drawer's column switcher is the ONLY way to move a card, which makes the
     tap target on the card and the switcher itself load-bearing rather than a
     convenience. */
  .dw-x { width: 44px; height: 44px; }
  .dw-head { grid-template-columns: 48px minmax(0, 1fr) 44px; }
  .sw { padding: 15px 4px; }
  .dw-handle a { padding: 8px 6px; margin-left: -6px; }
  .toast-undo { padding: 12px 10px; }
}

/* ---------------------------------------------------------------------------
   Reduced motion
   ---------------------------------------------------------------------------
   Every animation introduced above is named here rather than swept up by a
   wildcard, so adding a new one and forgetting this block is a visible
   omission rather than a silent one. The wildcard is kept as a backstop.
   --------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    animation-delay: 0ms !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
  /* rise: no travel, and no invisible first frame if the animation is dropped. */
  .screen > * { animation: none !important; opacity: 1; transform: none; }
  /* pulse: the dot stays lit rather than flickering once and stopping. */
  .kicker .dot.live { animation: none !important; opacity: 1; transform: none; }
  /* the interstitial: steps appear rather than rise, the running marker holds
     steady, and the bar jumps to each new value. draft.js also shortens every
     dwell, because pacing is not motion and reduced-motion users should still
     get to see what was read — just not for five seconds. */
  .wstep, .work-foot, .work-go, .warnblock { animation: none !important; }
  .wmark { animation: none !important; }
  .work-fill { transition: none !important; }
  /* hatch: a static barber pole still reads as "working". */
  .progress { animation: none !important; }
  .btn:active, .go:active { transform: none; }
  /* flash: the card lands where it lands, no amber sweep. The FLIP that slides
     the cards under it is skipped in draft.js — a Web Animation is not a CSS
     animation and this block cannot reach it. */
  .card.just-moved { animation: none !important; }
  /* The drawer slide: it is simply there. draft.js sets the open class in the
     same tick as showModal(), so dropping the transition leaves no first frame
     off-screen to be seen. */
  dialog { transition: none !important; }
  /* toastin: the strip appears, it does not travel. */
  .toast { animation: none !important; }
  /* The toolbar arrow: fully drawn from the first frame. Its dash offset has to
     be reset explicitly — an un-run animation would otherwise leave the line
     hidden at dashoffset 160, and an arrow that never draws is no arrow. */
  #toolbarArrow { animation: none !important; }
  .ta-line { animation: none !important; stroke-dashoffset: 0; }
  .ta-head { animation: none !important; opacity: 1; }
}

/* The owner's callout inside step 01 of the Meta dialog (2026-07-29): what the
   minimum viable export is, and the more-data-better-scores trade. Pink at the
   owner's request — ink on the fill, same contrast arithmetic as every other
   pink surface here. */
.steps .help-min {
  display: block;
  text-indent: 0; /* the li hangs its number with a negative indent; the box must not inherit it */
  margin-top: 9px;
  padding: 10px 12px;
  background: var(--pink);
  color: var(--ink);
  font-size: .8rem;
  line-height: 1.5;
}
