/* abeyk.com: minimal. Name, role, bio, writing, links. Nothing else. */

@font-face {
  font-family: 'Geist';
  src: url('../fonts/geist-var.woff2') format('woff2');
  font-weight: 100 900;
  font-display: swap;
}
/* Fraunces, for the name and essay titles only. Subset to latin and
   pinned to one point in its design space — display optical size, a
   little softness, and WONK on, which is the axis that swaps in the
   eccentric letterforms and is the whole reason to use this face rather
   than another tasteful serif. 23KB of the original 120. */
@font-face {
  font-family: 'Fraunces';
  src: url('../fonts/fraunces-display.woff2') format('woff2');
  font-weight: 100 900;
  font-display: swap;
}
@font-face {
  font-family: 'JetBrains Mono';
  src: url('../fonts/jetbrains-mono-var.woff2') format('woff2');
  font-weight: 100 800;
  font-display: swap;
}
@font-face {
  font-family: 'Charter';
  src: url('../fonts/charter-regular.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Charter';
  src: url('../fonts/charter-italic.woff2') format('woff2');
  font-weight: 400;
  font-style: italic;
  font-display: swap;
}
/* The real bold, because font-synthesis is off below: without this face a
   <strong> in the prose would come out identical to the text around it. */
@font-face {
  font-family: 'Charter';
  src: url('../fonts/charter-bold.woff2') format('woff2');
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

:root {
  --bg: #0B0A08;
  --ink: #EAE3D6;    /* 15.5:1 */
  --dim: #8A8274;    /* 5.2:1 */
  --rule: #221F19;
}

* { box-sizing: border-box; margin: 0; padding: 0; }

html {
  background: var(--bg);
  color-scheme: dark;
  /* iOS Safari inflates text in landscape unless told not to */
  -webkit-text-size-adjust: 100%;
}

/* the reticle: a 20px hairline crosshair with an open center, dark halo
   under light strokes, hotspot dead center. It is the resting cursor and
   nothing else — anything you can act on hands back to the system, so
   the affordance is always the one the OS taught you. Only drawn for a
   real mouse; a trackpad-less phone never loads it. */
@media (hover: hover) and (pointer: fine) {
  html {
    cursor: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20'%3E%3Cpath d='M10 1v5M10 14v5M1 10h5M14 10h5' fill='none' stroke='%230B0A08' stroke-width='2.6' stroke-opacity='.7'/%3E%3Cpath d='M10 1v5M10 14v5M1 10h5M14 10h5' fill='none' stroke='%23EAE3D6' stroke-width='1'/%3E%3C/svg%3E") 10 10, crosshair;
  }

  /* links and buttons: the native hand, in every state. Nothing else
     ever overrides the reticle — no I-beam over headings or labels.
     The open center is what keeps it off the words. */
  a, button { cursor: pointer; }
}

body {
  font-family: 'Geist', system-ui, sans-serif;
  font-weight: 300;
  font-size: 16.5px;
  line-height: 1.6;
  color: var(--ink);
  -webkit-font-smoothing: antialiased;
  font-synthesis: none;
  /* long unbroken tokens (urls, an email) never widen the viewport */
  overflow-wrap: break-word;
  /* Nothing here is meant to be dragged across. Every click on this page
     is aimed at something — a link, or the bee — and a half-selected
     heading left behind by a missed swipe is the page looking broken
     rather than the page being used. */
  -webkit-user-select: none;
  user-select: none;
}

.page {
  max-width: 38rem;
  margin: 0 auto;
  /* rem, not vh: a phone's address bar resizing the viewport must not
     re-flow the top of the page mid-scroll */
  padding: clamp(3.5rem, 14vh, 9rem) 1.5rem 6rem;
  padding-left: max(1.5rem, env(safe-area-inset-left));
  padding-right: max(1.5rem, env(safe-area-inset-right));
}

/* The home page is one screen. There is nothing below the fold, so there
   is nothing to scroll to, and a page that drags a few pixels and
   springs back reads as broken — hence the lock rather than merely
   fitting. dvh, so the height is the space actually left over once a
   phone's address bar has taken its cut. The gate matters: on a viewport
   too short to hold the page, locking would put the links somewhere you
   could never reach, so below that it scrolls like anything else. */
@media (min-height: 600px) {
  html.home, html.home body {
    height: 100dvh;
    overflow: hidden;
    overscroll-behavior: none;
  }
}
html.home .page { padding-bottom: 2rem; }

/* Everything on this page is a fixed size except the writing list, which
   gains a row every essay. So the list is what gives when the screen runs
   out: .page becomes a column the height of the locked screen, the fixed
   parts hold their size, and the list takes whatever is left and scrolls
   inside itself instead of pushing the links off the bottom. At four
   entries there is room to spare and none of this shows — same rules in
   the same places, no scrollbar. Behind the same gate as the lock, for
   the same reason: on a viewport short enough that the page itself
   scrolls, a second scroller inside it is a trap. */
@media (min-height: 600px) {
  html.home .page {
    height: 100%;
    display: flex;
    flex-direction: column;
  }
  /* a flex column stops adjacent margins collapsing, and this 0.25rem
     would then stack on top of the label's 0.75rem rather than sit
     inside it — 4px of drift for nothing. The label owns that gap. */
  html.home .links { margin-top: 0; }
  /* the name, bio, labels and links keep their height; only the list
     gives */
  html.home .page > :not(.writing) { flex: none; }
  html.home .writing {
    /* min-height: 0 is the whole trick: a flex item won't shrink below
       its content without it, and the list would go on pushing */
    min-height: 0;
    overflow-y: auto;
    /* a flick that runs out of list stops there, rather than handing the
       rest of itself to the page behind it */
    overscroll-behavior: contain;
    -webkit-overflow-scrolling: touch;
    /* the gutter the scrollbar and a focus ring live in; the negative
       margin hands it straight back, so the rules still begin and end
       exactly under the text above them */
    padding-inline: 4px;
    margin-inline: -4px;
    /* thin, in the dim, over no track at all — a default scrollbar is a
       piece of furniture heavier than anything else on the page. The two
       standard properties and no ::-webkit- fallback on purpose: where
       these aren't understood the browser draws its own overlay bar,
       which is already thin, already dark under color-scheme, and costs
       no width. Forcing a styled one there would be a downgrade. */
    scrollbar-width: thin;
    scrollbar-color: rgba(138, 130, 116, 0.45) transparent;
  }
}

/* The one place the page raises its voice, and it does it in the same
   face as the quietest type here: the labels, the dates and the links
   are all JetBrains Mono, so the title rhymes with them rather than
   arriving from somewhere else. A monospace at display size needs two
   things doing to it — the tracking pulled in hard, because the face is
   spaced for code at 13px and reads loose at 40, and the size held down,
   because every glyph is as wide as the widest one. Medium rather than
   bold: at this size the extra weight closes the counters. */
h1 {
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-weight: 400;
  font-size: clamp(1.85rem, 5.5vw, 2.2rem);
  line-height: 1.15;
  letter-spacing: -0.035em;
}

/* The name is thirteen glyphs where an essay title can be twenty, so it
   holds its full size all the way down to a phone rather than shrinking
   to a width it never needed. The vw term only bites below about 350px,
   where 2.2rem of monospace would finally be wider than the column. */
html.home h1 { font-size: clamp(1.95rem, 8.6vw, 2.2rem); }

.role {
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: 13px;
  font-weight: 400;
  color: var(--dim);
  margin-top: 0.5rem;
}

/* The one sentence that says what I do, and the only serif on this page.
   Three faces sound like a lot for four elements, but each is doing a
   different job: the mono is the machine's voice, this is mine, and the
   list below is neither. Italic and a tone under the ink so it reads as
   an aside to the name rather than as a second headline.

   It holds one line on a desktop, which is the whole point of the size:
   64 characters of Charter italic at 17.5px measure 515px in a 560px
   column, and the 45px of slack is there so the fallback Georgia, which
   sets about five per cent wider, still fits. The old 34rem max-width was
   what actually broke the line, at 544px, before the type ever ran out of
   room. On a phone it wraps, as it must. */
.bio {
  margin-top: 1.45rem;
  max-width: none;
  font-family: 'Charter', Georgia, serif;
  font-style: italic;
  font-size: 17.5px;
  line-height: 1.55;
  color: #B8B0A1;
}

.label {
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: 12px;
  font-weight: 400;
  letter-spacing: 0.08em;
  color: var(--dim);
  margin: 3.5rem 0 0.75rem;
}

.writing { list-style: none; }
.writing li {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 1.5rem;
  padding: 0.55rem 0;
  border-top: 1px solid var(--rule);
}
.writing li:last-child { border-bottom: 1px solid var(--rule); }
/* phosphor hover: 70ms attack, 600ms cooling decay. Underline and
   color only; no background boxes over text, ever. Gated on a real
   hovering pointer, or a tap on iOS leaves the amber stuck lit. */
.writing a {
  /* the list is the reason anyone is on this page, so it carries a size
     of its own rather than the body's */
  font-size: 18.5px;
  color: var(--ink);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-decoration-color: rgba(226, 177, 60, 0.5);
  text-underline-offset: 4px;
  transition: text-decoration-color 600ms cubic-bezier(.19, 1, .22, 1);
}
@media (hover: hover) {
  .writing a:hover {
    transition-duration: 70ms;
    text-decoration-color: #E2B13C;
  }
}
.writing a:active, .links a:active, .back a:active { transform: translateY(1px); }
.writing a, .links a { display: inline-block; }
/* the tap flash is the only feedback touch gets; keep it, lose the
   grey box Safari and Chrome paint over it */
a { -webkit-tap-highlight-color: rgba(226, 177, 60, 0.18); }
.date {
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: 12.5px;
  font-weight: 400;
  color: var(--dim);
  white-space: nowrap;
}

.links {
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: 14.5px;
  font-weight: 400;
  color: var(--dim);
  margin-top: 0.25rem;
}
/* same phosphor as the writing list: the rule lights amber, the word
   comes up to full ink. It has to be its own decoration-color or the
   underline just inherits whatever the text is doing. */
.links a {
  color: var(--dim);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-decoration-color: rgba(226, 177, 60, 0.45);
  text-underline-offset: 4px;
  transition: color 600ms cubic-bezier(.19, 1, .22, 1),
              text-decoration-color 600ms cubic-bezier(.19, 1, .22, 1);
}
@media (hover: hover) {
  .links a:hover {
    transition-duration: 70ms;
    color: var(--ink);
    text-decoration-color: #E2B13C;
  }
}

/* the bee's field: a decorative overlay across the document, never in
   the way of a click and never able to widen the page */
.bee-field {
  position: absolute;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
  z-index: 0;
}
.bee-svg { display: block; width: 100%; height: 100%; }

/* The handle. A mouse never gets one — the bee runs from a mouse, so
   catching it would be a promise the physics can't keep. A finger gets a
   thumb-sized catch.

   `all` rather than `auto`: the handle is a transparent circle, and only
   `all` makes an unpainted shape hit-testable in every engine.
   `touch-action: none` tells the browser a touch starting here is not a
   scroll — bee.js cancels the touch stream as well, because Safari has
   never reliably honoured touch-action on SVG children. */
.bee-grab { pointer-events: none; }
@media (hover: none) and (pointer: coarse) {
  .bee-grab { pointer-events: all; touch-action: none; }
}

/* A mouse gets no handle, but it does get the lines. `stroke` is the
   whole point: the target is the ink of the drawing and nothing else, so
   the hollow of the body and the air between the wings are both misses.

   Not behind a media query. `(hover: hover) and (pointer: fine)` is about
   a device's primary input, and gating on it would lock out a touchscreen
   laptop with a mouse plugged into it. A finger landing here is turned
   away in the handler instead, by the pointer type of the actual event. */
.bee-line, .bee-hit { pointer-events: stroke; }
/* A press that came for the bee and missed. The ring opens where your
   hand came down, so the page has visibly answered even though the bee
   is already gone: without it, a miss and a page that ignored you look
   exactly the same. */
.bee-miss {
  fill: none;
  stroke: #8A8274;
  stroke-width: 1.2;
  transform-box: fill-box;
  transform-origin: center;
  animation: bee-miss 700ms cubic-bezier(.19, 1, .22, 1) forwards;
}
@keyframes bee-miss {
  from { transform: scale(0.32); opacity: 0.85; }
  to   { transform: scale(2.4); opacity: 0; }
}

/* and what it says when you actually get a hand on it */
.bee-buzz {
  fill: #E2B13C;
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: 13px;
  letter-spacing: 0.06em;
  animation: bee-buzz 2.2s ease-out forwards;
}
@keyframes bee-buzz {
  0%   { opacity: 0; transform: translateY(5px); }
  12%  { opacity: 1; transform: translateY(0); }
  62%  { opacity: 1; transform: translateY(-3px); }
  100% { opacity: 0; transform: translateY(-16px); }
}
/* no drift and no scale where motion is off; they simply appear and go */
@media (prefers-reduced-motion: reduce) {
  .bee-miss { animation: bee-fade 700ms linear forwards; }
  .bee-buzz { animation: bee-fade 2.2s linear forwards; }
  @keyframes bee-fade { from { opacity: 0.85; } to { opacity: 0; } }
}

/* no text selection or long-press callout while you're carrying it */
.bee-field {
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
}

@media (prefers-reduced-motion: reduce) {
  * { transition-duration: 0s !important; }
}

::selection { background: #E2B13C; color: var(--bg); }

:focus-visible { outline: 1px solid #E2B13C; outline-offset: 3px; }

/* essay pages */
.back {
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: 13px;
  font-weight: 400;
}
/* same phosphor as every other link on the site: dim mono text, the rule
   lights amber, the word comes up to full ink. The arrow is inline-block,
   which keeps the underline off it; a decorated arrow reads as a typo. */
.back a {
  color: var(--dim);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-decoration-color: rgba(226, 177, 60, 0.45);
  text-underline-offset: 4px;
  display: inline-block;
  padding: 0.4rem 0;
  transition: color 600ms cubic-bezier(.19, 1, .22, 1),
              text-decoration-color 600ms cubic-bezier(.19, 1, .22, 1);
}
.back .arr { display: inline-block; transition: transform 0.18s ease; }
@media (hover: hover) {
  .back a:hover {
    transition-duration: 70ms;
    color: var(--ink);
    text-decoration-color: #E2B13C;
  }
  .back a:hover .arr { transform: translateX(-3px); }
}
/* The name and an essay title are the same object in different places, so
   they are the same size, weight and tracking. Everything h1 needs is on
   the h1 rule; there is nothing left to override here. */
.essay-head { margin-top: 3rem; }
.essay-meta {
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: 13px;
  font-weight: 400;
  color: var(--dim);
  margin-top: 0.5rem;
}
.prose {
  margin-top: 1.75rem;
  font-family: 'Charter', Georgia, serif;
  font-size: 18px;
  line-height: 1.55;
}
.prose p { margin-bottom: 1rem; max-width: 62ch; }
/* Section headings are the essay title one step down: same face, same
   tightened tracking, ink rather than dim. They used to be set as small
   lowercase labels, which read as furniture rather than as structure. */
.prose h2 {
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: 19px;
  font-weight: 500;
  color: var(--ink);
  letter-spacing: -0.02em;
  line-height: 1.32;
  margin: 3.25rem 0 1rem;
}
.prose .fn { font-size: 15px; color: var(--dim); }

/* An explicit weight, not the browser's. Its default for strong is
   `bolder`, which is relative — and relative to this page's 300 it
   resolves to 400, so emphasis came out identical to the text around it.
   Charter's real bold is loaded above for the same reason. */
.prose strong, .prose b { font-weight: 700; }

/* A short list of principles, numbered in the mono the labels are in, so
   it reads as a set of rules rather than as more prose with bullets. The
   number sits in the margin and the rules between items are the same
   hairline as the writing list on the home page. */
.prose ol.rules {
  list-style: none;
  margin: 1.75rem 0 2rem;
  border-top: 1px solid var(--rule);
  counter-reset: rule;
}
.prose ol.rules li {
  counter-increment: rule;
  position: relative;
  padding: 0.9rem 0 0.9rem 2.6rem;
  border-bottom: 1px solid var(--rule);
}
.prose ol.rules li::before {
  content: counter(rule, decimal-leading-zero);
  position: absolute;
  left: 0;
  top: 1.15rem;
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: 12px;
  letter-spacing: 0.08em;
  color: #E2B13C;
}

/* A link in an essay gets the same phosphor as the ones on the home
   page: ink text over a dim rule, 70ms to amber on hover against 600ms
   to cool back down. Unlike those, these sit inside running text, so
   they skip the inline-block and the press nudge -- both would break a
   link that wraps across a line. */
.prose a {
  color: var(--ink);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-decoration-color: rgba(226, 177, 60, 0.5);
  text-underline-offset: 4px;
  transition: text-decoration-color 600ms cubic-bezier(.19, 1, .22, 1);
}
@media (hover: hover) {
  .prose a:hover { transition-duration: 70ms; text-decoration-color: #E2B13C; }
}
.prose code {
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: 0.82em;
  font-weight: 400;
  color: var(--ink);
  background: rgba(226, 177, 60, 0.1);
  padding: 0.1em 0.3em;
}

/* Figures in essays run wider than the text column, because a chart
   needs the room a paragraph doesn't. Never wider than the viewport. On
   a phone the figure keeps its width and scrolls sideways inside its own
   box — shrinking a 1060-unit drawing into 340px would leave the labels
   unreadable, and an unreadable chart is worse than a scrollbar. */
.figure-wrap {
  width: min(60rem, calc(100vw - 3rem));
  margin: 3rem 50% 3.25rem;
  transform: translateX(-50%);
}
.figure-wrap > figure { margin: 0; }
.figure-wrap svg, .figure-wrap canvas { max-width: 100%; height: auto; display: block; }
.figure-wrap figcaption {
  margin-top: 0.8rem;
  border-top: 1px solid var(--rule);
  padding-top: 0.65rem;
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: 12px;
  font-weight: 400;
  line-height: 1.6;
  color: var(--dim);
}
@media (max-width: 880px) {
  /* Fit the drawing to the screen rather than parking it in a sideways
     scroller. Nothing on a phone should scroll except the page itself. */
  .figure-wrap { width: calc(100vw - 2rem); }
  .figure-wrap > figure { min-width: 0; }

  /* A 1060-unit drawing squeezed into 350px renders its labels at about
     a third of their size, which is no size at all. These grow the type
     inside the viewBox to compensate and drop whichever annotations
     would then collide — the figure loses its footnotes on a phone and
     keeps its argument. The .figure-wrap prefix is there to outrank the
     per-figure styles, which come later in the document. */
  .figure-wrap svg text { letter-spacing: 0.02em; }

  /* Sized backwards from the render, not guessed: a 1060-unit drawing in
     a 324px box is scaled to 0.31, so a label needs about 32 units in the
     viewBox to come out at ten real pixels. Anything that then has no
     room to sit is dropped rather than allowed to overlap. */
  .figure-wrap #fig-hero text { font-size: 34px; }

  .figure-wrap #fig-waterfall .rowlbl { font-size: 33px; }
  .figure-wrap #fig-waterfall .total { font-size: 34px; }
  .figure-wrap #fig-waterfall .num { font-size: 28px; }
  .figure-wrap #fig-waterfall .seglbl { display: none; }

  .figure-wrap #fig-coldstart .num { font-size: 29px; }
  .figure-wrap #fig-coldstart .ann { font-size: 31px; }
  .figure-wrap #fig-coldstart .alarm { font-size: 31px; }
  .figure-wrap #fig-coldstart .code { font-size: 31px; }

  .figure-wrap #fig-retention .num { font-size: 29px; }
  .figure-wrap #fig-retention .ann { font-size: 31px; }

  /* The pipeline is the one figure that cannot be rescued by type. Ten
     boxes across a landscape canvas, and "ON-DEVICE ASR" needs 260 units
     to be legible inside a box 108 wide. It carries a second, portrait
     drawing of the same turn instead, and the two swap here. */
  .figure-wrap #fig-turn .wide { display: none; }
  .figure-wrap #fig-turn .tall { display: block; }
}
/* The game, running in the essay rather than pictured in it.

   It sits in the text column and is exactly as wide as the writing,
   rather than breaking out the way a chart does. A chart earns its extra
   width by being unreadable without it; this is a thing you play, and it
   draws itself in a fixed 447px column anyway, so any width past the
   column is margin.

   It arrives as a poster: the title screen, cropped, with nothing live
   about it and a play button across the bottom. Press play and the frame
   grows into something you can actually play in. */
.prose figure.play { margin: 2.5rem 0 2.75rem; }
.play-hd {
  display: flex;
  justify-content: space-between;
  gap: 1rem;

  padding-bottom: 0.6rem;
  border-bottom: 1px solid var(--rule);
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--dim);
}
.play-hd span:first-child { color: #E2B13C; }
/* It is a <p>, so `.prose p`'s 62ch measure was clamping it — 409px in
   11px mono, inside a 560px frame, which stranded the link 151px short of
   the right edge. Two classes to outrank one class and a type. */
.prose .play-hd { max-width: none; }
/* The way out lives in the chrome, not in the sentence under the frame.
   It is an action, and the caption is a description; a link buried in the
   middle of a caption is neither easy to find nor easy to hit. */
.play-hd a {
  color: var(--dim);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-decoration-color: rgba(226, 177, 60, 0.45);
  text-underline-offset: 4px;
  transition: color 600ms cubic-bezier(.19, 1, .22, 1),
              text-decoration-color 600ms cubic-bezier(.19, 1, .22, 1);
}
@media (hover: hover) {
  .play-hd a:hover {
    transition-duration: 70ms;
    color: var(--ink);
    text-decoration-color: #E2B13C;
  }
}

.play-box {
  position: relative;
  margin-top: 0.75rem;
  height: 400px;
  border: 1px solid var(--rule);
  background: var(--bg);
  transition: height 480ms cubic-bezier(.19, 1, .22, 1);
}
.play-box.is-live { height: 760px; }

.play-clip { position: absolute; inset: 0; overflow: hidden; }
.play-clip iframe { display: block; width: 100%; border: 0; }

/* Before you press play the iframe is given more height than the title
   screen needs, and the clip shows the top of it. Nothing inside it
   overflows, so there is no scrollbar to hide and nothing to scroll: the
   poster cannot be scrolled because there is nowhere for it to go. That
   is also why it takes no pointer events — a wheel travelling past it
   belongs to the page. */
.play-box:not(.is-live) .play-clip { pointer-events: none; }
.play-box:not(.is-live) .play-clip iframe { height: 920px; }

/* Once it is live the iframe is the height of the frame and the game
   scrolls inside it. A desktop scrollbar there would be the only piece of
   system furniture on this page, so the iframe is drawn one bar wider
   than its box: the bar hangs off the right and the clip cuts it, and
   since a scrollbar takes its width out of the content box, what is left
   inside is exactly the box's width, still centred. */
.play-box.is-live .play-clip iframe { height: 100%; }
/* In both states, because the poster's own document turns out to be
   taller than the room it is given no matter how much it is given, and a
   bar it can never use is the worst of both. */
@media (hover: hover) and (pointer: fine) {
  .play-clip iframe { width: calc(100% + 17px); }
}

/* the invitation: the same button as the bee's card, laid over the crop */
.play-go {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  padding: 4rem 0 1.1rem;
  border: 0;
  background: linear-gradient(to bottom, rgba(11, 10, 8, 0) 0%, rgba(11, 10, 8, 0.75) 45%, var(--bg) 100%);
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: 12px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink);
  cursor: pointer;
  transition: color 600ms cubic-bezier(.19, 1, .22, 1);
}
.play-go span {
  border: 1px solid rgba(226, 177, 60, 0.45);
  padding: 0.6rem 1.6rem;
  transition: border-color 600ms cubic-bezier(.19, 1, .22, 1);
}
@media (hover: hover) {
  .play-go:hover { transition-duration: 70ms; color: #E2B13C; }
  .play-go:hover span { transition-duration: 70ms; border-color: #E2B13C; }
}

@media (max-width: 640px) {
  /* a phone has less height to give away, and the game's own move bar is
     stuck to the bottom of the frame either way */
  .play-box.is-live { height: 620px; }
}

.n { font-family: 'JetBrains Mono', ui-monospace, monospace; font-size: 0.86em; font-weight: 400; }

/* phones: the layout already fits, so this is about the thumb.
   Every link gets ~44px of vertical reach. The row stays a row here —
   title left, date right, same as the desktop — because the date pinned
   to the right margin is what makes the list read as a list. */
@media (max-width: 640px) {
  .page { padding-top: clamp(3rem, 11vh, 6rem); }
  /* the home page has to clear a phone's small viewport — the one left
     after the address bar — so it buys that back from the padding, but
     not so much that the name sits under the top of the glass */
  html.home .page { padding-top: clamp(2.5rem, 9vh, 4.5rem); padding-bottom: 1.5rem; }
  html.home .label { margin-top: 2.5rem; }
  .essay-head { margin-top: 2rem; }
  .writing li {
    gap: 1rem;
    padding: 0.25rem 0;
  }
  /* the date holds its width and its edge; a long title wraps against it
     rather than pushing it off the right margin */
  .writing .date { flex: none; }
  /* the padding lives on the anchor, not the row, so the 44px belongs
     to the tap target rather than to dead space beside it */
  .writing a { padding: 0.55rem 0; }
  .links { line-height: 2.5; }
  .links a { padding: 0.35rem 0; }
  .prose { font-size: 17.5px; line-height: 1.65; }
  .prose h2 { margin-top: 2.5rem; }
}
