/* ============================================================================
   Godark: godark.app

   The tokens, the type ramp and the ink tiers are lifted from the iOS app's
   Theme.swift rather than invented, so the site and the app are demonstrably
   the same object. Where this deviates from the app it says so and why.

   Everything that looks drawn IS drawn, at runtime, by sketch.js. There are no
   authored SVG outlines in here: a button's box has to be drawn at whatever
   width its label and its breakpoint give it.
   ========================================================================= */

:root {
  /* Theme.swift, exactly */
  --bone: #f1e9e0;
  --ink: #14110e;
  --mark: #d8ccbe;
  --muted: #a2988e;

  /* Theme.swift's `muted` is 2.36:1 on bone, and `mark` is 1.31:1. On a phone
     at native density, with the SmallType contrast recovery on top, the app
     gets away with it. On the web at 14px it is a wall nobody can read, so
     both keep their jobs as INK FOR DRAWN LINES and neither is ever used for
     running text. Text gets these instead: the same hue, walked far enough
     toward ink to clear 4.5:1.

       --say  ink at 0.66 -> #5f5a55, 5.67:1   secondary running copy
       --ink  #14110e     -> 15.65:1           anything primary                */
  --say: rgba(20, 17, 14, 0.66);

  --hand: "Permanent Marker", "Comic Sans MS", cursive;
  --pen: "Rock Salt", "Bradley Hand", cursive;
  --sans: Inter, ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;

  /* Theme.Timing. The welcome screen's pace, in seconds. */
  --char-stagger: 0.032s;
  --char-dur: 0.22s;
  --cta-gap: 0.12s;
  --cta-draw: 0.65s;
  --ease: cubic-bezier(0.2, 0.8, 0.25, 1);

  /* The framework's column, not a wider one. `main` is 440px with 24px of
     padding either side, centred by the body grid, so the readable measure is
     392px. Every landing in this repo uses it and this one is not special. */
  --col: 440px;
  --gutter: 24px;
}

*, *::before, *::after { box-sizing: border-box; }
[hidden] { display: none !important; }

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

body {
  margin: 0;
  min-height: 100vh;
  min-height: 100dvh;
  overflow-x: clip;
  display: grid;
  place-items: center;
  background: var(--bone);
  color: var(--ink);
  font: 400 15px/1.62 var(--sans);
  letter-spacing: -0.006em;
  -webkit-font-smoothing: antialiased;
}

/* Paper. The app renders on a bone field with a faint tooth to it; a flat fill
   under a marker face reads as a screenshot of a whiteboard. Two low-contrast
   layers rather than an image, so it costs nothing and never tiles visibly. */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 0;
  opacity: 0.5;
  /* Above the content, not behind it. Paper texture belongs ON the page: the
     hero clip was rendered on its own near-white field, within two units of
     bone, so the seam around it was never a colour mismatch. It was the
     texture stopping at the video's edge. Laid over everything, there is no
     edge to see. */
  z-index: 2;
  background-image:
    radial-gradient(circle at 20% 30%, rgba(20, 17, 14, 0.022) 0 1px, transparent 1px),
    radial-gradient(circle at 70% 65%, rgba(20, 17, 14, 0.018) 0 1px, transparent 1px);
  background-size: 37px 41px, 53px 47px;
}

/* No z-index on purpose: the texture layer above needs to paint over this. */
main {
  position: relative;
  width: 100%;
  min-width: 0;
  max-width: var(--col);
  padding: 56px var(--gutter);
}

/* ------------------------------------------------------------------ type

   The hand is ramped rather than applied flat. Permanent Marker carries the
   display sizes and every control; Rock Salt carries short asides in the
   margin; Inter carries anything that runs past a couple of lines.

   This is the one deliberate deviation from the app, which sets almost
   everything in Permanent Marker. The app can: its longest body run is a few
   lines on a phone. A landing page has paragraphs, and a paragraph of marker
   is a picture of text rather than text. */

h1, h2, h3, .hand { font-family: var(--hand); font-weight: 400; letter-spacing: 0; }

h1 {
  margin: 0;
  /* Theme.Metrics scales the title with the screen and amplifies the gain, so
     a big screen actually feels big. clamp() is the same intent. */
  /* Sized so "Everything goes dark." holds one line in the 392px measure.
     At 44 it broke to three lines and pushed the form off the fold, which
     defeats the point of putting the form in the hero. */
  /* Measured, not guessed: "Everything goes dark." wants 409px at 39px and the
     column gives 392, so 37.4 is the exact fit and 36 leaves a margin. The
     line has to hold, because breaking it puts "dark." alone on a third line
     and pushes the form off the fold. */
  font-size: clamp(32px, 9vw, 44px);
  line-height: 1.2;
  /* Balance the lines rather than filling the first and orphaning the rest.
     Without it the narrow column leaves "porn." alone on its own line, which
     lands harder than the sentence means to. Ignored by browsers that do not
     have it, which just get the fill-first break. */
  text-wrap: balance;
}

h2 {
  margin: 0 0 14px;
  font-size: clamp(24px, 6.4vw, 28px);
  line-height: 1.26;
  /* Same reason as the title: the narrow column fills the first line and
     orphans the tail, and a marker face makes a stranded word look like a
     mistake rather than a beat. "Press the button to go dark." wants 397px
     against a 392px measure, so it wraps by five pixels and left "dark."
     alone. */
  text-wrap: balance;
}

h3 { margin: 0 0 6px; font-size: 19px; line-height: 1.3; }

p { margin: 0 0 14px; }
p:last-child { margin-bottom: 0; }

.lede { font-size: 17px; line-height: 1.6; }
.muted { color: var(--say); }

/* Theme.SmallType, applied where it is actually needed.

   The rule: below 18px, add letter spacing up to 4% of the size, and walk a
   quiet colour 55% of the way back toward full ink. A marker face set small
   closes into a blot and a muted grey drops off the page, and both failures
   arrive together. The values below are that ramp evaluated at the two sizes
   this site actually sets a hand at, with the arithmetic shown so a future
   size can be worked out rather than guessed.

     ramp(s) = (18 - s) / (18 - 12), clamped to 0..1
     tracking = s * 0.04 * ramp
     muted strength = 0.37 + (1 - 0.37) * 0.55 * ramp        */

/* Rock Salt is gone from the body of the site. It survives in one place, the
   sign-off at the very bottom, where it reads as a signature rather than as a
   second voice competing with the marker. Asides are now the plain face, set
   quieter and a shade smaller than body copy. */
.aside {
  font-family: var(--sans);
  font-size: 14.5px;
  letter-spacing: -0.004em;
  line-height: 1.6;
  /* The ramp asks for 0.543 here (0.37 + 0.63 * 0.55 * 0.50). That is 4.0:1,
     under the bar, so the floor wins. The ramp still shapes everything above
     it: smaller type is still inked harder than larger type. */
  color: rgba(20, 17, 14, 0.66);
}

.aside-sm {
  font-family: var(--sans);
  font-size: 12.5px;
  letter-spacing: -0.002em;
  line-height: 1.65;
  color: rgba(20, 17, 14, 0.717); /* 0.37 + 0.63 * 0.55 * 1.00 */
}

/* ------------------------------------------------------------- drawn things

   sketch.js inserts an <svg class="sk"> as the first child of anything with
   data-sketch. It sits behind the content and inherits `color`, so a drawn
   outline tints with its element. */

[data-sketch] { position: relative; }

.sk {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: calc(100% + 10px);
  overflow: visible;
  pointer-events: none;
}

/* underline and rule sit under the text rather than around it */
[data-sketch="underline"] > .sk,
[data-sketch="rule"] > .sk {
  top: auto;
  bottom: -0.24em;
  transform: translateX(-50%);
}

[data-sketch="underline"] { color: var(--ink); }

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

/* Kept as hooks so the markup does not have to change, but the column is
   `main` now. A section that wants to break out of the measure is a decision
   to make once, here, rather than a width sprinkled through the page. */
.wrap, .wide { width: 100%; }

.sec { padding: 54px 0; }
.sec-tight { padding: 34px 0; }

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

   Theme.CTAStyle is .drawn: an outline in the same hand as the title, not a
   filled pill. The app chose the outline over the pill's contrast on purpose,
   so the control belongs to the same hand as everything around it. */

.btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 62px;
  padding: 0 30px;
  font-family: var(--hand);
  font-size: 20px;
  line-height: 1;
  color: var(--ink);
  text-decoration: none;
  background: none;
  border: 0;
  cursor: pointer;
  transition: transform 0.16s var(--ease), color 0.16s var(--ease);
}

.btn > .sk path { transition: stroke-width 0.16s var(--ease); }
.btn:hover > .sk path { stroke-width: 2.6; }
.btn:active { transform: scale(0.97); }

/* The one filled control on the page.

   The fill goes on the DRAWN path, not on the element: a CSS background would
   be a machine-perfect pill sitting under a hand-drawn outline, and the two
   edges would disagree with each other by exactly the amount of the wobble.
   Filling the path means the black shape IS the drawn shape, wobble and all.

   Theme.CTAStyle in the app is `.drawn` for this reason, and this is the
   deliberate exception: one filled black blob at the top of the page, and
   outlines everywhere else, so there is never a question about which control
   is the one to press. */
.btn-fill { color: var(--ink); }
.btn-fill > .sk path { fill: var(--ink); }
.btn-fill .btn-label { position: relative; color: var(--bone); }
.btn-fill:hover > .sk path { stroke-width: 3.4; }
/* kept for any future full-width CTA; the waitlist uses .send instead */

/* The secondary route is a written line, not a second box. Two drawn boxes
   side by side read as a choice between equals, and this is not one. */
.btn-quiet {
  font-family: var(--sans);
  font-size: 13px;
  font-weight: 500;
  color: rgba(20, 17, 14, 0.717);
  text-decoration: none;
  border-bottom: 0;
}
.btn-quiet:hover { color: var(--ink); }

/* ------------------------------------------------------------------- hero */

.hero { padding: 4px 0 8px; text-align: center; }

.hero h1 { margin-inline: auto; }

/* The clip was rendered on its own near-white field, which sits on bone as a
   visible pasted rectangle. Multiply drops the light pixels out and lets the
   paper through, so he stands ON the page rather than in a window cut into it.
   It works because he is nearly black and his field is nearly white: there is
   no middle tone for the blend to muddy. */
.hero-gorilla {
  display: block;
  width: auto;
  max-width: min(79%, 257px);   /* 1.2x */
  max-height: 41vh;             /* 1.2x */
  height: auto;
  margin: 2px auto 6px;
  /* The clip renders its own field at (244,238,230) against bone's
     (241,233,224): lighter and cooler by a few units, which is enough to read
     as a pasted rectangle. `darken` takes the per channel minimum, so bone
     wins everywhere the field is lighter than it and the gorilla, far darker
     than either, is untouched. Multiply was wrong here: it darkens BOTH
     layers and pushed the field well below the paper. */
  mix-blend-mode: darken;
}

/* The word box from writeOn(): letters land individually, words stay whole. */
.write .w { display: inline-block; white-space: nowrap; }

/* He is the only thing on this page that is not drawn: a solid, matte,
   immovable object with your handwriting moving around him. Do not put a
   sketch outline on this. */
.hero-stage { position: relative; }

.hero-cta { margin-top: 6px; display: flex; flex-direction: column; align-items: center; gap: 16px; }

/* The mark, from WelcomeView: lowercase "godark", the system face at its
   heaviest, tracked in by 4.5% of its own size. Deliberately NOT the marker.
   The app draws it this way so the brand reads as one solid object while the
   voice around it stays handwritten, and the site should not soften that.

   Theme.Timing.wordmarkDuration is 0.85s, arriving from a 1.06 scale, which
   is the app's own entrance for it. */
.mark {
  margin: 0 0 14px;
  font-family: var(--sans);
  font-weight: 900;
  font-size: 19px;
  letter-spacing: -0.045em;
  line-height: 1;
  color: var(--ink);
  opacity: 0;
  transform: scale(1.06);
  animation: mark-in 0.85s var(--ease) forwards;
}
@keyframes mark-in { to { opacity: 1; transform: none; } }

/* Frogi's shape: the promise, the mascot, then the list, with the reassurance
   under it rather than above. Tight, because the whole hero has to fit on a
   screen or the list is not really at the top. */
.hero .wl { margin-bottom: 0; }
.hero .aside { display: block; margin: 11px 0 0; }
.hero .ref-note { margin-bottom: 12px; }

/* Never shown, never filled by a person. Off-screen rather than display:none,
   because some bots skip anything that is not rendered. */
.wl-trap {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}

/* ------------------------------------------------------------------- paper

   The evidence, set as a citation rather than as a marketing claim. It is the
   one place on this site where a plain face and a tight measure matter more
   than the hand does. */
.kicker {
  margin: 0 0 8px;
  font-family: var(--sans);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: rgba(20, 17, 14, 0.717);
}

.paper { padding: 22px 20px 18px; margin: 18px 0 22px; color: var(--mark); }
.paper > *:not(.sk) { position: relative; }
.paper-cite {
  margin: 0 0 12px;
  font-size: 13px;
  line-height: 1.55;
  color: var(--say);
}
.paper-body { margin: 0 0 12px; font-size: 14.5px; line-height: 1.6; color: var(--ink); }
.paper-link {
  font-family: var(--sans);
  font-size: 12.5px;
  font-weight: 500;
  letter-spacing: -0.002em;
  color: rgba(20, 17, 14, 0.717);
  text-decoration: none;
  border-bottom: 1px solid rgba(20, 17, 14, 0.22);
}
.paper-link:hover { color: var(--ink); }

/* ------------------------------------------------------- the write-on reveal

   Theme.Timing, kept in order: the title writes itself a character at a time,
   then one breath, then the pen draws the box. One hand, two strokes, never
   two things happening at once. */

/* Each letter is wiped on from its left edge rather than faded in.

   A fade is a letter that was already there becoming visible. A wipe is a
   letter being made, left to right, which is the direction a hand moves. The
   difference is small per character and obvious across a line.

   The clip insets are negative on three sides so the marker's ascenders,
   descenders and its overshoot past the em box are never cut; only the right
   edge does any work. */
/* `.w > span`, not `span`. The word boxes are spans too, so an unscoped rule
   gave every WORD its own clip and its own animation on top of its letters:
   a second wipe running over the first, with no --i so it fired at 0s. The
   letters are the only thing that should be drawn here. */
/* Duration and delay come from writeOn(), which paces them like a hand rather
   than a metronome. Only the shape of the stroke is here. */
.write .w > span {
  display: inline-block;
  transform: rotate(var(--r, 0deg)) translate(var(--dx, 0), var(--dy, 0));
  animation-name: ink;
  animation-timing-function: cubic-bezier(0.32, 0.12, 0.2, 1);
  animation-fill-mode: both;
}

/* The reveal edge is slanted, not vertical.

   A rectangular wipe is a shutter opening. A pen has a nib held at an angle,
   so the edge of fresh ink leans: the top of a stroke lands before its
   bottom. Twenty percent of the letter's width across its height is about
   what a marker held normally gives you.

   The polygon keeps four points at both ends so it can be interpolated, and
   the left edge is parked far enough out (-60%) that the trailing corner
   never crosses it, which would fold the shape over itself and make the
   letter flicker.

   The easing is deliberately not the page's --ease. A pen leaves the mark at
   speed and decelerates into the end of a stroke; an ease that starts slowly
   reads as a fade wearing a wipe's clothes. */
@keyframes ink {
  from {
    clip-path: polygon(-60% -35%, -10% -35%, -50% 135%, -60% 135%);
    transform: rotate(var(--r, 0deg)) translate(var(--dx, 0), calc(var(--dy, 0px) + 3px));
  }
  to {
    clip-path: polygon(-60% -35%, 160% -35%, 120% 135%, -60% 135%);
    transform: rotate(var(--r, 0deg)) translate(var(--dx, 0), var(--dy, 0));
  }
}

/* the outline strokes itself on, rather than fading in */
.draw-on > .sk path {
  stroke-dasharray: var(--sk-len);
  stroke-dashoffset: var(--sk-len);
  animation: stroke var(--cta-draw) var(--ease) forwards;
  animation-delay: var(--draw-delay, 1s);
}
@keyframes stroke { to { stroke-dashoffset: 0; } }

.rise {
  opacity: 0;
  transform: translateY(10px);
  animation: rise 0.5s var(--ease) forwards;
  animation-delay: var(--delay, 0s);
}
@keyframes rise { to { opacity: 1; transform: none; } }

/* scroll-in, for everything below the fold */
.in { opacity: 0; transform: translateY(14px); }
.in.seen { opacity: 1; transform: none; transition: opacity 0.55s var(--ease), transform 0.55s var(--ease); }
.in.seen[style*="--d"] { transition-delay: var(--d); }

/* the numbered beats of how it works */
.beats { list-style: none; margin: 0; padding: 0; display: grid; gap: 30px; }
.beat { display: grid; grid-template-columns: 54px 1fr; gap: 18px; align-items: start; }
.beat-n { font-family: var(--hand); font-size: 30px; line-height: 1; color: var(--mark); }
.beat h3 { margin-bottom: 4px; }
.beat p { color: var(--say); margin: 0; }

/* Theme.SmallType at 13px: ramp 0.83, so a muted line is walked most of the
   way back toward ink. An attribution nobody can read is not an attribution. */
/* ------------------------------------------------------- the Action Button

   One 6s loop drives the whole scene, so the turn, the press and the apps
   going out cannot drift apart. It runs only while the drawing is on screen.

   The 3D transform is on the <svg>, not on a group inside it: 3D transforms
   on SVG children are inconsistent across browsers, while the element itself
   is just a box in the page. The labels live outside the SVG so they do not
   rotate with it. */
.ks-wrap {
  position: relative;
  width: 100%;
  margin: 18px 0 22px;
  perspective: 900px;
  perspective-origin: 42% 50%;
}

.ks {
  display: block;
  width: 100%;
  height: auto;
  color: #b0a091;
  overflow: visible;
  transform-origin: 34% 50%;
}
.ks-wrap.is-live .ks { animation: ks-turn 6s var(--ease) infinite; }

/* Face on, then it turns its left edge toward you and pushes in, so the
   button stops being a mark on a silhouette. */
@keyframes ks-turn {
  0%, 6%    { transform: rotateY(0deg) scale(1); }
  22%, 76%  { transform: rotateY(27deg) scale(1.14); }
  90%, 100% { transform: rotateY(0deg) scale(1); }
}

.ks-body { stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; fill: none; }

.ks-btn { stroke: var(--ink); stroke-width: 1.6; fill: var(--ink); }
.ks-wrap.is-live .ks-btn { animation: ks-press 6s var(--ease) infinite; }
@keyframes ks-press {
  0%, 26%   { transform: translateX(0); }
  30%, 74%  { transform: translateX(2.6px); }
  80%, 100% { transform: translateX(0); }
}

/* the press, said once and quietly */
.ks-ring { fill: none; stroke: var(--ink); stroke-width: 1.6; opacity: 0; }
.ks-wrap.is-live .ks-ring { animation: ks-ring 6s linear infinite; }
@keyframes ks-ring {
  0%, 26%  { opacity: 0; r: 9; }
  30%      { opacity: 0.5; r: 10; }
  40%      { opacity: 0; r: 21; }
  100%     { opacity: 0; r: 21; }
}

/* and everything inside goes out */
.ks-app { stroke: currentColor; stroke-width: 1.5; fill: rgba(20, 17, 14, 0); }
.ks-wrap.is-live .ks-app { animation: ks-dark 6s var(--ease) infinite; }
@keyframes ks-dark {
  0%, 30%   { fill: rgba(20, 17, 14, 0); stroke: currentColor; }
  42%, 76%  { fill: rgba(20, 17, 14, 1); stroke: rgba(20, 17, 14, 1); }
  84%, 100% { fill: rgba(20, 17, 14, 0); stroke: currentColor; }
}

/* Labels, outside the SVG so the turn does not take them with it. */
.ks-tag {
  position: absolute;
  font-family: var(--sans);
  font-size: 14px;
  font-weight: 500;
  color: rgba(20, 17, 14, 0.717);
  opacity: 0;
  white-space: nowrap;
  pointer-events: none;
}
/* "press", with an arrow reaching toward the button it means */
/* Bigger than the lock's label on purpose: this one is an instruction and it
   is the first thing the section asks for. Left anchored, so it grows toward
   the phone rather than off the page. */
.ks-tag-press {
  left: 1%;
  top: 29%;
  font-size: 16px;
  display: flex;
  align-items: center;
  gap: 5px;
}
.ks-point { width: 30px; height: 11px; display: block; flex: none; opacity: 0.75; }

/* and the lock shutting, over the words */
.ks-tag-dark {
  right: 0;
  top: 52%;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
}
.ks-lock { width: 34px; height: 48px; display: block; color: rgba(20, 17, 14, 0.62); overflow: visible; }
.ks-tag-text { display: block; }

.ks-lk-body, .ks-lk-line { fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round; }
.ks-lk-key { fill: var(--ink); stroke: none; opacity: 0.62; }

/* Open, then shut, and it lands almost as it arrives.

   The label fades in over 46 to 49 and the shackle is already dropping at 48,
   so the two overlap and there is no moment where an open lock sits waiting.
   Same rule the bulb was given: the thing does not consider doing it. A short
   open state is still needed, or there is no closing to watch, just a closed
   lock that turned up. */
.ks-lk-shackle { transform: translateY(-6px); }
.ks-wrap.is-live .ks-lk-shackle { animation: ks-shut 6s var(--ease) infinite; }
@keyframes ks-shut {
  0%, 48%   { transform: translateY(-6px); }
  53%, 86%  { transform: translateY(0); }
  92%, 100% { transform: translateY(-6px); }
}

/* The body takes the hit. A shackle that stops dead reads as a sticker; two
   pixels of recoil is the difference between an object and a picture. */
.ks-wrap.is-live .ks-lk-seat { animation: ks-seat 6s var(--ease) infinite; }
@keyframes ks-seat {
  0%, 51%  { transform: translateY(0); }
  54%      { transform: translateY(1.6px); }
  58%, 100% { transform: translateY(0); }
}

/* and three marks for the frame it lands, where the bulb had seven shards */
/* `transform-box: fill-box` or the scale below is measured from the SVG's
   origin rather than the mark's own centre, and three ticks fly off toward the
   top left corner instead of popping in place. */
.ks-lk-tick {
  fill: none; stroke: currentColor; stroke-width: 1.4; stroke-linecap: round; opacity: 0;
  transform-box: fill-box; transform-origin: center;
}
.ks-wrap.is-live .ks-lk-tick { animation: ks-clack 6s var(--ease) infinite; animation-delay: calc(var(--i) * 0.02s); }
@keyframes ks-clack {
  0%, 51%   { opacity: 0; transform: scale(0.5); }
  55%       { opacity: 0.85; transform: scale(1); }
  62%, 100% { opacity: 0; transform: scale(1.15); }
}

/* Two labels, two jobs, so two keyframe sets rather than one with a delay.
   `press` belongs to the button and leaves once the button has been pressed.
   Offsetting a shared animation forced them to the same shape, and they are
   not the same shape. */
.ks-wrap.is-live .ks-tag-press { animation: ks-say-press 6s var(--ease) infinite; }
@keyframes ks-say-press {
  0%, 16%   { opacity: 0; transform: translateY(3px); }
  22%, 44%  { opacity: 1; transform: none; }
  52%, 100% { opacity: 0; transform: translateY(3px); }
}

/* The bulb is the payoff and arrives last: after the phone has turned, been
   pressed, and gone dark. Breaking at 32% put it on top of the press, which
   read as part of the gesture rather than as its result, and had the bulb in
   pieces before the apps on the screen had finished darkening.

   It arrives ALREADY GOING. The fade in runs 46 to 49 and the first crack is
   drawn at 48, so the two overlap by a frame or two and there is no moment
   where a whole bulb sits there waiting. It held whole for 480ms once and
   that beat read as hesitation: the light does not consider going out. */
.ks-wrap.is-live .ks-tag-dark { animation: ks-say-dark 6s var(--ease) infinite; }
@keyframes ks-say-dark {
  0%, 46%   { opacity: 0; transform: translateY(3px); }
  49%, 82%  { opacity: 1; transform: none; }
  90%, 100% { opacity: 0; transform: translateY(3px); }
}

/* Asked for less motion: the drawing is simply the finished state, turned
   just enough to show the button, with the apps already dark. */
.ks-wrap.is-still .ks { transform: rotateY(24deg) scale(1.1); }
.ks-wrap.is-still .ks-app { fill: rgba(20, 17, 14, 1); stroke: rgba(20, 17, 14, 1); }
.ks-wrap.is-still .ks-tag { opacity: 1; }
.ks-wrap.is-still .ks-lk-shackle { transform: translateY(0); }

.credit { font-size: 12.5px; line-height: 1.55; color: rgba(20, 17, 14, 0.657); letter-spacing: 0.1px; }
.credit a { color: inherit; }

/* ----------------------------------------------------------------- the loop

   The science as the drawing, scrubbed by the reader's scroll. Every moving
   part is derived from one custom property, --p, which the scroll handler
   writes onto the wrapper. No keyframes drive anything here, which is what
   lets the drawing be stopped, slowed and read backwards.

   Nothing here is a chart. No axes, no numbers, no curve. The papers describe
   a relationship between cues, craving and inhibitory control and do not
   yield one universal curve, so drawing one would present invented data as
   measured. */

/* Registered so it can be interpolated and clamped inside calc. Without this
   --p is an unparsed token and every calc built on it fails silently. */
@property --p {
  syntax: "<number>";
  inherits: true;
  initial-value: 0;
}

/* The scroll the drawing is scrubbed across. Progress is measured on THIS
   element, never on the pinned child: a sticky element barely moves while the
   page scrolls past it, so measuring it gives a progress value that starts
   near halfway and finishes before the reader has seen anything. */
/* No top margin, and no top padding on the section either (below). The loop
   section lost its heading, so its own leading space stopped separating two
   things and became a hole: 44px of section padding, 44px more, and 20px of
   track margin put 108px of nothing between the phone and the drawing that
   continues its sentence. The separation is now just the previous section's
   bottom padding, which is the rhythm every other section already keeps. */
/* Two classes on purpose. `.sec { padding: 44px 0 }` in the 480px block sits
   LATER in this file at equal specificity, so a single-class rule here loses
   on phones and wins on desktop, which is the worst way for it to fail. */
.sec.loop-sec { padding-top: 0; }

/* NOT PINNED. This was `min-height: 104vh` on the track and `position: sticky`
   on the child, so the drawing froze in place while 470px of page slid past
   it. That is what makes the sections either side feel dragged away: they are
   the only things moving, and the eye reads the still object as the fixed
   point and everything else as being pulled off it.

   The scrub does not need the pin. It is keyed to the drawing's own position
   in the viewport, so it still walks the steps over about 760px of scroll on
   a phone, which is the same walk the pin was buying. It just does it while
   the drawing travels with the page like everything else. */
.loop-scroll { position: relative; margin: 0 0 14px; }
.loop-wrap { position: relative; width: 100%; }
.loop { display: block; width: 100%; height: auto; color: #b0a091; overflow: visible; }

.lp-live, .lp-dead, .lp-out {
  fill: none;
  stroke: currentColor;
  stroke-linecap: round;
  stroke-dasharray: var(--len);
}
/* the pen has drawn this much of the line, and no more */
.lp-live { stroke-dashoffset: calc(var(--len) * (1 - clamp(0, var(--p) / 0.34, 1))); }
/* and this is the half it never reaches: dashed, grey, a road not taken */
.lp-dead {
  stroke: #d3c6b6;
  stroke-dasharray: 7 8;
  stroke-dashoffset: calc(var(--len) * (1 - clamp(0, (var(--p) - 0.46) / 0.18, 1)));
}

/* the tail: solid and inked, because this is the road you are on rather than
   the one that was closed */
.lp-out {
  stroke: var(--ink);
  stroke-dashoffset: calc(var(--len) * (1 - clamp(0, (var(--p) - 0.66) / 0.11, 1)));
}

.lp-dot { fill: currentColor; }
.lp-dot-0 { opacity: clamp(0, var(--p) / 0.05, 1); }
.lp-dot-1 { opacity: clamp(0, (var(--p) - 0.2) / 0.06, 1); }
.lp-dot-2, .lp-dot-3 { fill: #cdbfae; }
.lp-dot-2 { opacity: clamp(0, (var(--p) - 0.5) / 0.06, 1); }
.lp-dot-3 { opacity: clamp(0, (var(--p) - 0.58) / 0.06, 1); }
.lp-dot-4 { fill: var(--ink); opacity: clamp(0, (var(--p) - 0.74) / 0.07, 1); }

/* the nib, riding the line to where the bar stops it */
.lp-pen {
  fill: var(--ink);
  opacity: clamp(0, var(--p) / 0.04, 1);
  /* Comes to rest inside the bar, not against its top edge. The pen is
     stopped BY the block, so it has to be seen to reach it. */
  transform: translateY(calc(44px + 162px * clamp(0, var(--p) / 0.34, 1)));
}

.lp-lock {
  transform-box: fill-box;
  transform-origin: center;
  opacity: clamp(0, (var(--p) - 0.36) / 0.07, 1);
  transform:
    translateY(calc(-14px * (1 - clamp(0, (var(--p) - 0.36) / 0.09, 1))))
    scaleX(calc(0.72 + 0.28 * clamp(0, (var(--p) - 0.36) / 0.09, 1)));
}
.lp-lock-body { fill: var(--ink); stroke: var(--ink); stroke-width: 1.6; }

/* ---- the stations, HTML over the drawing ---- */
.loop-steps { position: absolute; inset: 0; margin: 0; padding: 0; list-style: none; }
/* Two columns: the glyph, then the words. A grid rather than a flex row
   INSIDE the <b>, because `.lp-off b` is struck through and a line-through on
   a flex container takes the icon with it. Keeping the icon out of the <b>
   keeps the strike on the text where it belongs. */
.lp {
  position: absolute;
  left: 15%;
  right: 6%;
  transform: translateY(-50%);
  display: grid;
  grid-template-columns: 22px 1fr;
  column-gap: 10px;
  row-gap: 5px;
}
.lp-ic {
  grid-area: 1 / 1;
  align-self: center;
  width: 22px;
  height: 22px;
  overflow: visible;
}
.lp-ic path { fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round; }
.lp b { grid-area: 1 / 2; align-self: center; font-family: var(--hand); font-size: 19px; line-height: 1.1; color: var(--ink); }
.lp i { grid-area: 2 / 2; font-style: normal; font-size: 13px; line-height: 1.4; color: var(--say); }

/* percentages of the 404-unit viewBox, so they track the drawing as it scales */
/* percentages of the 452-unit viewBox, so they track the drawing as it scales */
.lp-0   { top: 9.7%;  opacity: clamp(0, var(--p) / 0.06, 1); }
.lp-1   { top: 28.3%; opacity: clamp(0, (var(--p) - 0.2) / 0.07, 1); }
/* Kept inside the bar's real extent. The bar runs 34 to 314 of the 392 unit
   viewBox, so the label's box has to stop before 80% or it overhangs the ink
   it is supposed to be sitting on. */
.lp-cut { top: 46%; left: 11%; right: 30%; opacity: clamp(0, (var(--p) - 0.4) / 0.07, 1); }
.lp-2   { top: 64.6%; opacity: clamp(0, (var(--p) - 0.5) / 0.07, 1); }
.lp-3   { top: 77.9%; opacity: clamp(0, (var(--p) - 0.58) / 0.07, 1); }
.lp-4   { top: 93.8%; opacity: clamp(0, (var(--p) - 0.74) / 0.08, 1); }

/* On the bar. The row gap does the work the old margin-bottom did. */
.lp-cut { row-gap: 6px; }
.lp-cut b { color: var(--bone); font-size: 20px; }
.lp-cut i { color: rgba(241, 233, 224, 0.82); font-size: 12px; }
.lp-cut .lp-ic { color: var(--bone); }

/* The two that were prevented. The glyph is struck as well as the words: a
   missing icon says nothing, a crossed out one says it was there.

   The strike is DRAWN BY THE SCROLL, not applied. `text-decoration:
   line-through` arrives whole the moment the row does, so the word turned up
   already cancelled and the one thing on this drawing that is an action read
   as a state. Now the pen crosses it out while you scroll, like every other
   line here.

   `justify-self: start` matters: the <b> is a grid item and stretches to the
   whole column by default. text-decoration only ever marked the glyph run so
   that never showed, but a bar spanning the element would have run the strike
   halfway across the page. */
.lp-off b {
  color: var(--say);
  justify-self: start;
  position: relative;
}
.lp-off b::after {
  content: "";
  position: absolute;
  left: -3px;               /* overshoot both ends, the way a hand does */
  right: -3px;
  top: 54%;
  height: 1.6px;
  border-radius: 1px;
  background: currentColor;
  transform-origin: left center;
  transform: scaleX(var(--cut, 0));
}
.lp-off .lp-ic { color: var(--say); }

/* and the same progress drives the strike inside the glyph */
.lp-ic-cut {
  stroke-dasharray: var(--len);
  stroke-dashoffset: calc(var(--len) * (1 - var(--cut, 0)));
}

/* Each one starts as its own word finishes arriving. They cannot overlap: two
   pens crossing out two words at once is a machine, not a hand. */
.lp-2 { --cut: clamp(0, (var(--p) - 0.56) / 0.07, 1); }
.lp-3 { --cut: clamp(0, (var(--p) - 0.645) / 0.07, 1); }
/* the outcome, in full ink and not struck: the two above it are things
   prevented, this is the thing you get */
.lp-4 b { font-size: 21px; }
.lp-4 .lp-ic { color: var(--ink); }

/* ------------------------------------------------------------ the evidence

   Set as a reference list, not as cards. Credibility in this register is
   typographic rather than illustrative: full author lists, real volume and
   page numbers, DOIs printed as text, and no decoration at all.

   The marker face appears once, in the heading. Everywhere else this is
   somebody else's work being quoted, and quoting it in our own handwriting is
   what made the earlier version read as unserious. */
.papers-sec h2 { margin-bottom: 18px; }

.rl { list-style: none; margin: 0; padding: 0; }
.rl-item { border-top: 1px solid rgba(20, 17, 14, 0.12); }
.rl-item:last-child { border-bottom: 1px solid rgba(20, 17, 14, 0.12); }

/* The handle. Author and year in ink because that is the claim being made,
   the title after it in the secondary tone because it is the detail. Aligned
   to the top rather than centred: two of these wrap on a phone and a number
   floating beside the middle of a wrapped line reads as a mistake. */
.rl-q {
  display: grid;
  grid-template-columns: 28px 1fr 13px;
  align-items: start;
  gap: 10px;
  width: 100%;
  padding: 16px 2px;
  background: none;
  border: 0;
  cursor: pointer;
  text-align: left;
  color: inherit;
}
.rl-head { font-size: 13.5px; line-height: 1.5; color: var(--say); text-wrap: pretty; }
.rl-head b { font-weight: 600; color: var(--ink); transition: color 0.18s var(--ease); }
.rl-q:hover .rl-head b { color: rgba(20, 17, 14, 0.72); }
.rl-q .qa-ic { margin-top: 4px; }

.rl-a {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 0.26s var(--ease);
}
.rl-item.is-open .rl-a { grid-template-rows: 1fr; }
.rl-a-inner { overflow: hidden; }
/* 38px is the number column plus the gap, so the record hangs under the head
   rather than under the number. */
.rl-a-body { padding: 0 26px 18px 38px; }

.rl-n {
  font: 600 12px/1.55 var(--sans);
  font-variant-numeric: tabular-nums;
  color: rgba(20, 17, 14, 0.42);
}

/* A hanging indent, which is what a reference list actually looks like and
   what makes a run of citations scannable rather than a wall. */
.rl-cite {
  margin: 0 0 7px;
  font-size: 13.5px;
  line-height: 1.55;
  color: var(--ink);
  text-indent: -14px;
  padding-left: 14px;
}

.rl-doi {
  display: inline-block;
  font: 400 12px/1 var(--sans);
  color: rgba(20, 17, 14, 0.62);
  text-decoration: none;
  border-bottom: 1px solid rgba(20, 17, 14, 0.2);
  transition: color 0.18s var(--ease), border-color 0.18s var(--ease);
}
.rl-doi:hover { color: var(--ink); border-bottom-color: var(--ink); }

.rl-took {
  margin: 10px 0 0;
  font-size: 13px;
  line-height: 1.55;
  color: var(--say);
  text-wrap: pretty;
}
/* the one editorial voice in the section, and it is labelled as ours */
.rl-took span {
  display: inline-block;
  margin-right: 7px;
  font: 500 10.5px/1 var(--sans);
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: rgba(20, 17, 14, 0.45);
  transform: translateY(-1px);
}

.rl-foot {
  margin: 18px 0 0;
  font-size: 12px;
  line-height: 1.55;
  color: rgba(20, 17, 14, 0.62);
}

/* ------------------------------------------------------------ the spec strip

   Label and value on one row, hairline between. A grid rather than a real
   two-column table so the value can wrap under nothing: `dt` holds its column
   and `dd` takes the rest, which is what makes five facts read as a strip
   instead of five paragraphs.

   The labels are 12px, under the 18px where the ramp starts, so they take the
   tracking and some of the contrast back. */
.spec { margin: 0; padding: 0; }
.spec-row {
  display: grid;
  grid-template-columns: 104px 1fr;
  gap: 4px 14px;
  padding: 9px 2px;
  border-top: 1px solid rgba(20, 17, 14, 0.12);
}
.spec-row:last-child { border-bottom: 1px solid rgba(20, 17, 14, 0.12); }
.spec dt {
  font: 500 12px/1.5 var(--sans);
  letter-spacing: 0.36px;
  color: var(--ink);
}
.spec dd {
  margin: 0;
  font-size: 13px;
  line-height: 1.5;
  color: var(--say);
  text-wrap: pretty;
}

/* No stacking breakpoint. The values are short enough to hold their column at
   every width this is built for; they were sentences once and needed one. */

/* --------------------------------------------------------------- the streak */

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

.wl { display: flex; flex-direction: column; align-items: center; gap: 14px; }
/* One control. The send sits inside the field it belongs to, so there is one
   thing on the page to fill in and one place to press, rather than two drawn
   boxes stacked on each other saying nearly the same thing. */
.wl-row { display: flex; flex-direction: column; gap: 12px; align-items: stretch; width: 100%; }

.field {
  position: relative;
  min-height: 62px;
  display: flex;
  align-items: center;
  color: var(--mark);
}
.field {
  justify-content: space-between;
  gap: 6px;
  padding-right: 7px;
  width: 100%;
}
.field input {
  position: relative;
  flex: 1 1 auto;
  min-width: 0;
  padding: 0 6px 0 22px;
  font: 400 16px/1 var(--sans);
  color: var(--ink);
  background: none;
  border: 0;
  outline: 0;
}

/* The send. Its own drawn shape, filled, sitting in the field's right end.
   Filled for the same reason the old CTA was: this is the one thing to press,
   and an outline inside an outline is two rings and no answer. */
.send {
  position: relative;
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 46px;
  height: 46px;
  padding: 0;
  color: var(--ink);
  border: 0;
  cursor: pointer;
  transition: transform 0.16s var(--ease), background-color 0.16s var(--ease);
}
/* A plain circle. Everything else on this page is drawn, and this is the one
   control that should not be: at 46px the hand reads as a wobble in the
   rendering rather than as a pen, and a send button is the last place you
   want a reader wondering whether something went wrong. */
.send {
  background: var(--ink);
  border-radius: 50%;
}
.send-ink { position: relative; display: flex; color: var(--bone); }
.send-arrow { width: 21px; height: 21px; display: block; }
.send:hover { transform: translateX(1.5px); background: #000; }
.send:active { transform: scale(0.94); }
.send:disabled { opacity: 0.55; cursor: default; transform: none; }
.send:focus-visible { outline: 2px solid var(--ink); outline-offset: 4px; }
.field input::placeholder { color: rgba(20, 17, 14, 0.5); }
.field:focus-within { color: var(--ink); }

.wl-said { font-family: var(--sans); font-size: 13px; color: rgba(20, 17, 14, 0.717); min-height: 1.2em; }
/* With nothing to say it takes no room at all. Reserving a line for a message
   that is usually absent put 30px of empty space between the button and the
   line under it: the status line's own height plus the flex gap above it. It
   reappears when there is something to report, which is a shift the reader
   caused and can therefore follow. */
.wl-said:empty { display: none; }

/* --------------------------------------------------------------------- faq */

/* The house accordion: first item open, the rest closed, each toggling
   independently. The open/close is grid-template-rows 0fr to 1fr, which is
   the same mechanism the other sites in this repo use and the only one that
   animates to a height nobody has to measure. */
.faq { display: grid; gap: 0; }
.qa { border-top: 1px solid rgba(20, 17, 14, 0.09); }
.qa:last-child { border-bottom: 1px solid rgba(20, 17, 14, 0.09); }

.qa-q {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  width: 100%;
  padding: 18px 2px;
  background: none;
  border: 0;
  cursor: pointer;
  text-align: left;
  color: inherit;
}
.qa-q-text { font-family: var(--hand); font-size: 17px; line-height: 1.25; color: var(--ink); }
.qa-q:hover .qa-q-text { color: rgba(20, 17, 14, 0.72); }

/* A plus that becomes a minus. Both bars are off true by a fraction of a
   degree, because a ruler-straight cross beside a marker face is the one
   machine-made thing on the page and it shows. */
.qa-ic {
  position: relative;
  flex: none;
  width: 13px;
  height: 13px;
  color: var(--say);
  transition: color 0.2s var(--ease);
}
.qa-ic::before, .qa-ic::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  background: currentColor;
  border-radius: 1px;
}
.qa-ic::before { width: 13px; height: 1.8px; transform: translate(-50%, -50%) rotate(-1.2deg); }
.qa-ic::after {
  width: 1.8px; height: 13px;
  transform: translate(-50%, -50%) rotate(1.4deg);
  transition: transform 0.26s var(--ease), opacity 0.2s var(--ease);
}
/* shared with the reference list, which is the same control */
.is-open .qa-ic { color: var(--ink); }
.is-open .qa-ic::after { transform: translate(-50%, -50%) scaleY(0); opacity: 0; }

.qa-a {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 0.26s var(--ease);
}
.qa.is-open .qa-a { grid-template-rows: 1fr; }
.qa-a-inner { overflow: hidden; }
.qa-a p { margin: 0; padding: 0 26px 18px 2px; color: var(--say); text-wrap: pretty; }

@media (prefers-reduced-motion: reduce) {
  .qa-a, .rl-a, .qa-ic, .qa-ic::after, .rl-head b { transition: none; }
}

/* ------------------------------------------------------------------ footer */

.foot { padding: 54px 0 40px; text-align: center; color: var(--say); font-size: 13px; }
.foot a { color: var(--say); text-decoration: none; border-bottom: 1px solid rgba(20, 17, 14, 0.22); }
.foot a:hover { color: var(--ink); }
.foot-links { display: flex; gap: 20px; justify-content: center; flex-wrap: wrap; margin-bottom: 16px; }

/* The wordmark again at the foot, smaller and without the entrance. It closes
   the page with the same word it opens with, which is what a footer mark is
   for. Not `.mark`: that one animates itself in, and a logo fading up at the
   bottom of a page is a thing arriving where nothing is arriving. */
.foot-mark {
  margin: 0 0 16px;
  font-family: var(--sans);
  font-weight: 900;
  font-size: 15px;
  letter-spacing: -0.045em;
  line-height: 1;
  color: var(--ink);
}

/* Small print, and it reads as small print. 12px is under the 18px where the
   ramp starts, so it takes the tracking and some of the contrast back. */
.foot-legal {
  margin: 0 auto 6px;
  max-width: 30em;
  font-size: 12px;
  letter-spacing: 0.36px;
  line-height: 1.6;
  color: rgba(20, 17, 14, 0.62);
  text-wrap: pretty;
}
.foot-legal:last-of-type { margin-bottom: 12px; }

/* The only Rock Salt left on the site. A signature at the bottom of the page,
   which is what that face is actually for. */
.signoff {
  font-family: var(--pen);
  font-size: 12px;
  letter-spacing: 0.48px;       /* the ramp, at its floor */
  line-height: 1.9;
  color: rgba(20, 17, 14, 0.717);
}

/* The signature is a link now, and it carries the hovercard the other
   landings carry. */
a.signoff {
  display: inline-block;
  position: relative;
  text-decoration: none;
  color: rgba(20, 17, 14, 0.717);
  transition: color 0.18s var(--ease);
}
a.signoff:hover { color: var(--ink); }
a.signoff:focus-visible { outline: 2px solid var(--ink); outline-offset: 5px; border-radius: 4px; }

/* Filled with the page's own bone, which does two things: it hides the
   copyright line the card opens over, and it makes the card's rectangular
   edge invisible, because bone on bone has no seam. The only boundary anyone
   sees is the drawn one, wobble and all. The paper texture sits above
   everything at z-index 2, so it runs across the card too and the card does
   not read as pasted on.

   Frogi's version of this is a blurred glass panel. That is right there and
   wrong here: this page has no glass in it anywhere. */
.sign-card {
  position: absolute;
  left: 50%;
  bottom: calc(100% + 15px);
  z-index: 5;
  width: 244px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 15px 17px 14px;
  background: var(--bone);
  color: var(--mark);            /* the drawn box's ink, not the text's */
  text-align: left;
  opacity: 0;
  pointer-events: none;
  transform: translate(-50%, 6px) scale(0.98);
  transform-origin: 50% 100%;
  transition: opacity 0.14s linear, transform 0.24s var(--ease);
}

/* Pointer only. On touch the tap already opens the profile, and a card that
   appears under a finger that is leaving is a flicker. */
@media (hover: hover) and (pointer: fine) {
  a.signoff:hover .sign-card { opacity: 1; transform: translate(-50%, 0) scale(1); }
}
/* keyboard users get it without needing a pointer */
a.signoff:focus-visible .sign-card { opacity: 1; transform: translate(-50%, 0) scale(1); }

.sign-top { display: flex; align-items: center; gap: 10px; }
.sign-pic { width: 42px; height: 42px; border-radius: 50%; object-fit: cover; flex: none; }
.sign-who { display: flex; flex-direction: column; gap: 2px; margin-right: auto; }
.sign-name { font: 600 13.5px/1.2 var(--sans); letter-spacing: -0.01em; color: var(--ink); }
.sign-at { font: 400 11.5px/1 var(--sans); letter-spacing: 0.04em; color: rgba(20, 17, 14, 0.5); }
.sign-x { display: flex; color: rgba(20, 17, 14, 0.45); }
.sign-x svg { width: 13px; height: 13px; display: block; }
.sign-bio { font: 400 12.5px/1.45 var(--sans); color: var(--say); }
.sign-stats { display: flex; gap: 14px; font: 400 11.5px/1 var(--sans); color: rgba(20, 17, 14, 0.5); }
.sign-stats b { font-weight: 600; color: var(--ink); }

@media (prefers-reduced-motion: reduce) {
  .sign-card { transition: opacity 0.12s linear; transform: translate(-50%, 0); }
  a.signoff:hover .sign-card,
  a.signoff:focus-visible .sign-card { transform: translate(-50%, 0); }
}

/* the referral note, shown only when a creator sent the visitor */
.ref-note {
  margin: 0 auto 18px;
  padding: 12px 18px;
  width: fit-content;
  color: var(--mark);
}
.ref-note span { position: relative; font-family: var(--sans); font-size: 12.5px; color: rgba(20, 17, 14, 0.717); }

/* ------------------------------------------------------------------ mobile */

/* The framework's own small-screen padding, not a second set of numbers. */
@media (max-width: 480px) {
  main { padding: 36px 18px 44px; }
  .sec { padding: 44px 0; }
  .sec-tight { padding: 28px 0; }
  .beat { grid-template-columns: 40px 1fr; gap: 14px; }
  .beat-n { font-size: 24px; }
}

/* --------------------------------------------------------- reduced motion

   Nothing writes itself on. The page is simply already written, which is what
   somebody who asked for less motion is asking for. */
@media (prefers-reduced-motion: reduce) {
  /* The wipe goes. The tilt does not: it is typography rather than motion,
     and a level line of marker type is the thing this page is avoiding. */
  .write .w > span {
    opacity: 1;
    animation: none;
    clip-path: none !important;
    transform: rotate(var(--r, 0deg)) translate(var(--dx, 0), var(--dy, 0));
  }
  .rise,
  .mark { opacity: 1; transform: none; animation: none; }
  .draw-on > .sk path { stroke-dasharray: none; stroke-dashoffset: 0; animation: none; }
  .in { opacity: 1; transform: none; }
  .in.seen { transition: none; }
  .btn { transition: none; }
}
