:root {
  /* dark is the default. murdered out, no colour anywhere. */
  --bg: #0a0a0b;
  --ink: #ecebe7;
  --muted: #75757a;
  --faint: rgba(236, 235, 231, 0.09);

  /* No accent colour. This is hers to pick. Set it in both themes and it
     reappears on the period, link hovers and focus rings at once. */
  --accent: #ecebe7;

  --panel: #0a0a0b;      /* the code panel field */
  --card: #f4f2ec;       /* the plate the code sits on */
  --card-edge: transparent;
  --code-ink: #0a0a0b;   /* the modules themselves */

  --sans: "Zen Kaku Gothic New", system-ui, sans-serif;
  --serif: "Shippori Mincho", Georgia, serif;

  --ease: cubic-bezier(0.22, 0.61, 0.36, 1);
}

/* Light is a considered palette, not an inversion. Warm bone rather than
   white, warm near-black rather than pure black, so it reads as paper
   instead of as a screen with the lights on. */
:root[data-theme="light"] {
  --bg: #f2f0ea;
  --ink: #16130f;
  --muted: #75706a;
  --faint: rgba(22, 19, 15, 0.13);
  --accent: #16130f;

  --panel: #f2f0ea;
  /* the plate flips to a dark mat in light mode. the code inside it does
     not, because it cannot. see the note on the card below. */
  --card: #16130f;
  --card-edge: rgba(22, 19, 15, 0.16);
  --code-ink: #0a0a0b;
}

/* Colour only animates after the first paint, so loading the page does
   not look like a transition. */
html.mode-ready,
html.mode-ready body,
html.mode-ready .lining {
  transition: background-color 0.35s var(--ease), color 0.35s var(--ease);
}

* { box-sizing: border-box; }

/* Labels a control for a screen reader without showing it. Clip rather
   than display:none, because display:none removes it from the
   accessibility tree too, which defeats the point. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}


html {
  scroll-behavior: smooth;
  /* keep anchor targets clear of the fixed header */
  scroll-padding-top: 7rem;
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--sans);
  font-weight: 400;
  line-height: 1.75;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

body.is-locked { overflow: hidden; }

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

/* the period is dimmed rather than coloured. still a detail, no hue. */
.dot { color: var(--muted); }

/* ---- header ---- */
.bar {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 40;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 1.6rem clamp(1.25rem, 5vw, 3.5rem);
  /* a scrim built from the theme token, so content scrolling underneath
     never collides with the header in either mode */
  background: linear-gradient(to bottom, var(--bg) 55%, transparent);
}

.wordmark {
  font-family: var(--serif);
  font-size: 0.85rem;
  font-weight: 400;
  letter-spacing: 0.08em;
  color: var(--ink);
  text-decoration: none;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.4s var(--ease);
}

.wordmark.is-shown {
  opacity: 1;
  pointer-events: auto;
}

.bar-right {
  display: flex;
  align-items: center;
  gap: clamp(0.85rem, 3vw, 1.35rem);
}

/* The visitor chooses the language. Never an automatic redirect based on
   their browser: Google's guidance is that redirecting between language
   versions can stop users and search engines seeing all of them.
   It points at the same page in the other language, not the home page,
   so switching never costs you your place. */
.lang-switch {
  color: var(--muted);
  text-decoration: none;
  font-family: var(--sans);
  font-size: 0.72rem;
  letter-spacing: 0.09em;
  padding: 0.5rem 0;
  transition: color 0.25s var(--ease);
}

.lang-switch:hover,
.lang-switch:focus-visible { color: var(--ink); }

/* A circle, half filled. No sun, no moon, no cartoon. It turns over
   rather than swapping to a different icon, so the switch reads as one
   object changing its mind. */
.mode-btn {
  display: flex;
  align-items: center;
  padding: 0.35rem;
  margin: -0.35rem;
  background: none;
  border: none;
  color: var(--ink);
  cursor: pointer;
  opacity: 0.55;
  transition: opacity 0.3s var(--ease);
}

.mode-btn:hover,
.mode-btn:focus-visible { opacity: 1; }

.mode-glyph {
  width: 15px;
  height: 15px;
  display: block;
  transition: transform 0.5s var(--ease);
}

:root[data-theme="light"] .mode-glyph { transform: rotate(180deg); }


/* ---- hero ---- */
.hero {
  position: relative;
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 6rem clamp(1.25rem, 5vw, 3.5rem) clamp(2.5rem, 7vh, 4rem);
}

/* The monogram. b and s sharing one bowl, with the period that runs
   through the whole site as the terminal. Constructed from geometry
   rather than set in a typeface, so it is ownable, and it holds from a
   16px favicon to a garment tag. Same mark on every icon and the social
   card, drawn once and scaled. */
.mark {
  width: clamp(74px, 15vw, 104px);
  height: auto;
  display: block;
  margin: 0 auto clamp(1.6rem, 4vh, 2.4rem);
  color: var(--ink);
}

/* the wordmark stays small on purpose. understatement is the whole
   argument, and an 11rem logo was arguing the opposite. */
.hero h1 {
  margin: 0;
  font-family: var(--serif);
  font-weight: 400;
  font-size: clamp(1.5rem, 3.4vw, 2rem);
  letter-spacing: 0.14em;
  text-indent: 0.14em;
  line-height: 1;
}

.hero-line {
  margin: 1.6rem 0 0;
  font-size: 0.78rem;
  letter-spacing: 0.1em;
  color: var(--muted);
}

.ghost {
  position: absolute;
  bottom: clamp(2.5rem, 7vh, 4rem);
  left: 50%;
  transform: translateX(-50%);
  padding: 0 0 0.3rem;
  background: none;
  border: none;
  border-bottom: 1px solid var(--faint);
  color: var(--muted);
  font-family: var(--sans);
  font-size: 0.82rem;
  letter-spacing: 0.05em;
  cursor: pointer;
  transition: color 0.3s var(--ease), border-color 0.3s var(--ease);
}

.ghost:hover {
  color: var(--ink);
  border-bottom-color: var(--ink);
}

.ghost:active { transform: translateX(-50%) scale(0.98); }

/* ---- blocks ---- */
.block {
  max-width: 46rem;
  /* spacing lives in margin, not padding, so an anchor jump lands on the
     content instead of on a screenful of empty padding */
  margin: clamp(4.5rem, 14vh, 8rem) auto 0;
  padding: 0 clamp(1.25rem, 5vw, 3.5rem);
}

/* The reveal only exists when scripting does. If script.js never runs,
   the content is simply there, which is how it should have been built
   the first time. */
html.js .block,
html.js .plate {
  opacity: 0;
  transform: translateY(14px);
  transition: opacity 0.45s var(--ease), transform 0.45s var(--ease);
}

html.js .block.is-visible,
html.js .plate.is-visible {
  opacity: 1;
  transform: translateY(0);
}

.block:last-of-type { margin-bottom: clamp(4.5rem, 14vh, 8rem); }

/* tail space so the final section can still scroll up to the anchor
   position, otherwise the last nav link lands lower than the others */
main { padding-bottom: 26vh; }

.block h2 {
  margin: 0 0 1.5rem;
  font-family: var(--serif);
  font-weight: 400;
  font-size: 1.05rem;
  letter-spacing: 0.02em;
  color: var(--muted);
}

.block p {
  margin: 0 0 1.25rem;
  max-width: 40ch;
  font-size: 1rem;
  color: var(--ink);
}

.block p:last-child { margin-bottom: 0; }

.lede {
  font-family: var(--serif);
  font-size: clamp(1.35rem, 4vw, 1.9rem) !important;
  line-height: 1.5;
  max-width: 26ch !important;
}

/* The turn gets its own air. The list is setup, this is the line, and
   putting it in the same paragraph lets a reader scroll past it. */
.turn {
  margin-top: 2.25rem !important;
  font-family: var(--serif);
  font-size: clamp(1.35rem, 4vw, 1.9rem) !important;
  line-height: 1.5;
  max-width: 26ch !important;
}

/* the machine readable sentence. kept because it is the only plain
   statement of what this place actually is. */
.plain {
  margin-top: 3.5rem !important;
  color: var(--muted) !important;
  font-size: 0.95rem !important;
}

/* ---- channels ---- */
.channels { list-style: none; margin: 0; padding: 0; }

.channel {
  display: flex;
  align-items: center;
  gap: 1rem;
  padding: 1.05rem 0;
  border-bottom: 1px solid var(--faint);
  color: var(--ink);
  text-decoration: none;
  transition: color 0.3s var(--ease), padding-left 0.35s var(--ease);
}

a.channel:hover,
a.channel:focus-visible {
  color: var(--accent);
  padding-left: 0.55rem;
}

.glyph {
  width: 19px;
  height: 19px;
  flex-shrink: 0;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.3;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.channel-name { font-size: 0.95rem; }

.channel-note {
  margin-left: auto;
  font-size: 0.78rem;
  color: var(--muted);
  letter-spacing: 0.03em;
}

.channel.is-quiet { color: var(--muted); cursor: default; }
.channel.is-quiet .glyph { opacity: 0.4; }

/* ---- the lining ---- */
.lining {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 1.5rem;
  background: var(--panel);
  transform: translateY(100%);
  visibility: hidden;
  transition: transform 0.6s var(--ease), visibility 0.6s;
}

.lining.is-open { transform: translateY(0); visibility: visible; }

.lining-close {
  position: absolute;
  top: 1.6rem;
  right: clamp(1.25rem, 5vw, 3.5rem);
  background: none;
  border: none;
  color: var(--muted);
  font-family: var(--sans);
  font-size: 0.8rem;
  letter-spacing: 0.06em;
  cursor: pointer;
  padding: 0.4rem;
}

.lining-close:hover { color: var(--ink); }

.lining-inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  opacity: 0;
  transition: opacity 0.4s var(--ease) 0.25s;
}

.lining.is-open .lining-inner { opacity: 1; }

/* The plate flips with the theme. The code on it does not.
   A QR with light modules on a dark field is outside the format, which
   assumes dark on light. Tested against a real decoder at three sizes:
   standard passed every time, inverted failed every time. So in light
   mode the mat goes dark and the code keeps its own white ground inside
   it. The plate flips, the code stays scannable. */
.code-card {
  width: min(74vw, 320px);
  padding: 1.1rem;
  background: var(--card);
  border: 1px solid var(--card-edge);
  transition: background-color 0.35s var(--ease);
}

/* in light mode the code needs its own light ground inside the dark mat */
:root[data-theme="light"] .code-art {
  background: #ffffff;
  padding: 0.75rem;
}

/* With the panel matching the page, the slide up and the card are the
   whole transition. Nothing else marks the change, and nothing needs to. */

.code-art { line-height: 0; }

.qr { display: block; width: 100%; height: auto; }

.code-hint {
  margin-top: 1.75rem;
  font-family: var(--serif);
  font-size: 0.85rem;
  letter-spacing: 0.04em;
  color: var(--muted);
}

/* ---- footer ---- */
.foot {
  padding: 2.5rem clamp(1.25rem, 5vw, 3.5rem) 3.5rem;
  font-size: 0.78rem;
  color: var(--muted);
}

.foot-row {
  display: flex;
  justify-content: space-between;
}

/* Static on purpose. A copyright year marks first publication, so it
   should not walk forward every January off the system clock. */
.notice {
  letter-spacing: 0.04em;
}

/* left for whoever actually looks. no hover required, so it reads the
   same on a phone as it does on a laptop. */
.whisper {
  margin: 2.5rem 0 0;
  font-family: var(--serif);
  font-size: 0.8rem;
  letter-spacing: 0.04em;
  color: color-mix(in srgb, var(--ink) 17%, transparent);
}

.whisper--fainter {
  margin-top: 0.35rem;
  color: color-mix(in srgb, var(--ink) 9%, transparent);
}

/* ---- quality floor ---- */
:focus-visible {
  outline: 1px solid var(--accent);
  outline-offset: 4px;
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .block { opacity: 1; transform: none; transition: none; }
  .lining, .lining-inner, .channel, .ghost { transition: none; }
  .lining { transform: translateY(0); }
  .lining:not(.is-open) { display: none; }
}


/* ---- art plate ----
   Medium agnostic on purpose. She works in watercolor, oils, pencil,
   chalk, crayon, clay, digital, photography, animation and video, so
   this holds a still or a moving piece without changing shape.

   Nothing is cropped. Native aspect ratio is preserved and the height is
   capped so a tall piece still fits one screen. Photographed analog work
   keeps its paper edge and uneven color, which is the good part. */

.plate {
  margin: clamp(5rem, 16vh, 9rem) auto 0;
  padding: 0 clamp(1.25rem, 5vw, 3.5rem);
  max-width: 72rem;
}

.plate figure {
  margin: 0;
}

.plate img,
.plate video {
  display: block;
  margin: 0 auto;
  max-width: 100%;
  max-height: 82svh;
  width: auto;
  height: auto;
}

/* a piece that wants the whole screen, for clay, sculpture, anything
   that reads better big */
.plate--full {
  max-width: none;
  padding: 0;
}

/* full still means one screen. a piece you cannot see all of at once is
   a piece nobody sees. this lets a wide panorama reach both edges while
   a square simply sits taller and centered. */
.plate--full img,
.plate--full video {
  max-height: 92svh;
  max-width: 100%;
  width: auto;
}

.plate figcaption {
  margin: 1.1rem auto 0;
  max-width: 40ch;
  text-align: center;
  font-family: var(--serif);
  font-size: 0.78rem;
  line-height: 1.7;
  letter-spacing: 0.04em;
  color: var(--muted);
}

.plate--full figcaption {
  padding: 0 clamp(1.25rem, 5vw, 3.5rem);
}

@media (prefers-reduced-motion: reduce) {
  .plate { opacity: 1; transform: none; transition: none; }
}


/* ---- 404 ---- */
.lost {
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 6rem clamp(1.25rem, 5vw, 3.5rem) 3rem;
}

.lost-code {
  margin: 0 0 1.75rem;
  font-family: var(--serif);
  font-size: 0.8rem;
  letter-spacing: 0.35em;
  text-indent: 0.35em;
  color: var(--muted);
}

.lost h1 {
  margin: 0;
  font-family: var(--serif);
  font-weight: 400;
  font-size: clamp(1.5rem, 3.4vw, 2rem);
  letter-spacing: 0.1em;
  text-indent: 0.1em;
}

.lost-note {
  margin: 1.5rem 0 0;
  max-width: 34ch;
  text-wrap: balance;
  font-size: 0.9rem;
  color: var(--muted);
}

.lost-back {
  margin-top: 3.25rem;
  padding-bottom: 0.3rem;
  border-bottom: 1px solid var(--faint);
  color: var(--muted);
  text-decoration: none;
  font-size: 0.82rem;
  letter-spacing: 0.05em;
  transition: color 0.3s var(--ease), border-color 0.3s var(--ease);
}

.lost-back:hover {
  color: var(--ink);
  border-bottom-color: var(--ink);
}

/* the 404 has no hero to scroll past, so the mark is present immediately */
.page-404 .wordmark.is-shown { opacity: 1; pointer-events: auto; }


/* ---- the header ----------------------------------------------------
   There is no navigation. Three sections on one page is a scroll, and
   anything persistent sitting on top of the content is the visitor
   feeling the site instead of reading it.

   The header hides the moment you scroll down, because scrolling down
   means you are reading. It returns the moment you scroll up, because
   scrolling up means you are looking for something. Chrome on intent
   rather than chrome on position.

   When there is a shop worth navigating, it goes here, and it will
   already behave correctly. */

.bar {
  transform: translateY(0);
  /* NN/G's guidance on partially persistent headers is that the reveal
     should feel like the visitor scrolled it into view themselves, so
     it comes back faster than it leaves. */
  transition: transform 0.26s cubic-bezier(0.32, 0.72, 0, 1);
}

.bar.is-tucked {
  transition: transform 0.42s cubic-bezier(0.32, 0.72, 0, 1);
}

.bar.is-tucked {
  transform: translateY(-100%);
}

@media (prefers-reduced-motion: reduce) {
  .bar { transition: none; }
}


/* ---- the menu ------------------------------------------------------
   It drops from under the header, because that is where it was pressed.
   It never appears on its own, it covers only what it needs, and it is
   gone the instant you look away.

   The structure is a display case, not a table of contents. When there
   is work to sell, the pieces sit at the top of the panel and the
   section links sit beneath them. A shop's menu should show the thing,
   not describe the category it lives in.

   Every link carries a second line saying what it is. That is the one
   pattern worth keeping from fastpitch.dev and origin.club. */

[hidden] { display: none !important; }

/* Three dots, not three lines. The overflow mark is what this audience
   actually taps a hundred times a day inside the apps they live in; a
   hamburger is a website convention. It also belongs to the family
   already on this site, which is circles and single stroke weights, and
   it rhymes with the period in the wordmark.

   Open state resolves into the cross that already exists in the glyph
   sprite, so both states speak the same language. */
.menu-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  margin: -0.4rem -0.5rem -0.4rem 0;   /* 44px target, no visual bulk */
  padding: 0;
  background: none;
  border: none;
  color: var(--ink);
  cursor: pointer;
  opacity: 0.75;
  transition: opacity 0.25s var(--ease);
}

.menu-btn:hover,
.menu-btn:focus-visible { opacity: 1; }

.menu-glyph {
  width: 20px;
  height: 20px;
  display: block;
  overflow: visible;
}

.glyph-dots,
.glyph-cross {
  transform-box: fill-box;
  transform-origin: center;
  transition: opacity 0.24s var(--ease), transform 0.34s cubic-bezier(0.32, 0.72, 0, 1);
}

.glyph-cross {
  opacity: 0;
  transform: rotate(-40deg) scale(0.65);
}

.menu-btn[aria-expanded="true"] .glyph-dots {
  opacity: 0;
  transform: scale(0.4);
}

.menu-btn[aria-expanded="true"] .glyph-cross {
  opacity: 1;
  transform: rotate(0deg) scale(1);
}

@media (prefers-reduced-motion: reduce) {
  .glyph-dots, .glyph-cross, .menu-btn { transition: none; }
}

.menu-scrim {
  position: fixed;
  inset: 0;
  z-index: 36;
  background: color-mix(in srgb, var(--bg) 55%, transparent);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
  opacity: 0;
  /* Both of these matter. Opacity alone leaves a full screen invisible
     sheet that still swallows every click on the page underneath. */
  visibility: hidden;
  pointer-events: none;
  transition: opacity 0.4s var(--ease), visibility 0.4s;
}

.menu-scrim.is-open {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
}

.menu {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 38;   /* under .bar, which is 40. it drops from beneath the header */
  max-height: 86svh;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: calc(4.6rem + env(safe-area-inset-top, 0px)) clamp(1.25rem, 5vw, 3.5rem) 2.25rem;
  background: var(--bg);
  border-bottom: 1px solid var(--faint);
  transform: translateY(-101%);
  transition: transform 0.46s cubic-bezier(0.32, 0.72, 0, 1);
}

.menu.is-open { transform: translateY(0); }

.menu-inner {
  max-width: 62rem;
  margin: 0 auto;
}

/* the work rail. horizontal on a phone so a thumb can flick through it. */
.menu-work {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(150px, 1fr);
  gap: 0.9rem;
  margin-bottom: 2.5rem;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;
}

.menu-work::-webkit-scrollbar { display: none; }

.work {
  scroll-snap-align: start;
  text-decoration: none;
  color: var(--ink);
}

.work-frame {
  display: block;
  aspect-ratio: 4 / 5;
  overflow: hidden;
  background: color-mix(in srgb, var(--ink) 5%, transparent);
}

.work-frame img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.6s var(--ease);
}

.work:hover .work-frame img { transform: scale(1.03); }

.work-name {
  display: block;
  margin-top: 0.6rem;
  font-size: 0.82rem;
  letter-spacing: 0.03em;
}

.work-meta {
  display: block;
  margin-top: 0.15rem;
  font-family: var(--serif);
  font-size: 0.75rem;
  color: var(--muted);
}

.work.is-gone .work-frame img { opacity: 0.4; }

/* the links. label plus one line saying what it is. */
.menu-links {
  display: grid;
  gap: 0;
}

.menu-links a,
.menu-code {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  padding: 1.05rem 0;
  border-top: 1px solid var(--faint);
  background: none;
  border-left: none;
  border-right: none;
  border-bottom: none;
  text-align: left;
  text-decoration: none;
  color: var(--ink);
  cursor: pointer;
  transition: padding-left 0.3s var(--ease);
}

.menu-links > *:last-child { border-bottom: 1px solid var(--faint); }

.menu-links a:hover,
.menu-links a:focus-visible,
.menu-code:hover,
.menu-code:focus-visible { padding-left: 0.6rem; }

.menu-label {
  font-family: var(--serif);
  font-size: 1.35rem;
  letter-spacing: 0.02em;
  line-height: 1.2;
}

.menu-note {
  font-family: var(--sans);
  font-size: 0.78rem;
  letter-spacing: 0.03em;
  color: var(--muted);
}

@media (min-width: 46rem) {
  .menu-work {
    grid-auto-flow: row;
    grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
    overflow: visible;
  }
}

@media (prefers-reduced-motion: reduce) {
  .menu, .menu-scrim, .menu-links a, .menu-code, .work-frame img { transition: none; }
}


/* ---- the trust line ------------------------------------------------
   Research on unfamiliar stores is consistent: questionable legitimacy
   stops more sales than any other concern, and the two things that most
   raise willingness to buy are knowing the data is private and knowing
   the seller's contact details are real. A site with no company name, no
   contact and no policy reads as a risk, however good it looks.

   So it is here, and it is quiet. One line of small type. Restraint and
   legitimacy are not in conflict; hiding who you are and being elegant
   are different things. */

.foot-legal {
  margin: 1.1rem 0 0;
  font-size: 0.72rem;
  letter-spacing: 0.03em;
  color: var(--muted);
}

.foot-legal a {
  color: var(--muted);
  text-decoration: none;
  border-bottom: 1px solid var(--faint);
  transition: color 0.25s var(--ease), border-color 0.25s var(--ease);
}

.foot-legal a:hover,
.foot-legal a:focus-visible {
  color: var(--ink);
  border-bottom-color: var(--ink);
}

.foot-sep {
  padding: 0 0.4rem;
  opacity: 0.5;
}

/* ---- document pages ---- */
.doc {
  max-width: 40rem;
  margin: 0 auto;
  padding: clamp(7rem, 18vh, 10rem) clamp(1.25rem, 5vw, 3.5rem) 4rem;
}

.doc h1 {
  margin: 0 0 2rem;
  font-family: var(--serif);
  font-weight: 400;
  font-size: clamp(1.5rem, 3.4vw, 2rem);
  letter-spacing: 0.1em;
  text-indent: 0.1em;
}

.doc h2 {
  margin: 2.75rem 0 0.85rem;
  font-family: var(--serif);
  font-weight: 400;
  font-size: 1.05rem;
  color: var(--muted);
}

.doc p {
  margin: 0 0 1.1rem;
  max-width: 62ch;
  font-size: 0.98rem;
  line-height: 1.75;
}

.doc-lede {
  font-family: var(--serif);
  font-size: clamp(1.2rem, 3.2vw, 1.5rem) !important;
  line-height: 1.55;
}

.doc a {
  color: var(--ink);
  text-decoration: none;
  border-bottom: 1px solid var(--faint);
}

.doc a:hover { border-bottom-color: var(--ink); }

.doc-updated {
  margin-top: 3rem;
  font-size: 0.78rem !important;
  color: var(--muted);
}

.page-doc .wordmark.is-shown { opacity: 1; pointer-events: auto; }


/* ---- the list ------------------------------------------------------
   One field. Baymard's ranked abandonment causes are all friction, and
   the cheapest friction to remove is a field nobody needs. No name, no
   preferences, no account. */

.join {
  display: flex;
  flex-wrap: wrap;
  gap: 0.7rem;
  margin-top: 1.6rem;
  max-width: 30rem;
}

.join-field {
  flex: 1 1 13rem;
  min-width: 0;
  padding: 0.85rem 0;
  background: none;
  border: none;
  border-bottom: 1px solid var(--faint);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 0.95rem;
  transition: border-color 0.25s var(--ease);
}

.join-field::placeholder { color: var(--muted); }

.join-field:focus {
  outline: none;
  border-bottom-color: var(--ink);
}

.join-btn {
  padding: 0.85rem 1.4rem;
  min-height: 44px;
  background: none;
  border: 1px solid var(--faint);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 0.82rem;
  letter-spacing: 0.05em;
  cursor: pointer;
  transition: border-color 0.25s var(--ease), background 0.25s var(--ease);
}

.join-btn:hover:not(:disabled) {
  border-color: var(--ink);
  background: color-mix(in srgb, var(--ink) 6%, transparent);
}

.join-btn:disabled { opacity: 0.5; cursor: default; }

/* the honeypot. off screen rather than display:none, because some bots
   skip anything the CSS hides outright. */
.hp {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  opacity: 0;
}

.join-msg {
  margin-top: 0.9rem !important;
  font-size: 0.85rem !important;
  min-height: 1.2em;
  color: var(--muted) !important;
}

.join-msg[data-state="ok"] { color: var(--ink) !important; }
.join-msg[data-state="error"] { color: var(--ink) !important; }

/* The widget draws itself inside a cross origin frame, so its own
   border and surface cannot be reached from here. What can be done is
   stop it looking pasted on: give it the full width of the form, and
   round the frame element itself so the hard corners go. The washed
   out edge you get otherwise is almost always a theme mismatch, which
   is why the mode is copied onto the widget before the API loads and
   the widget is rebuilt when the mode changes. */
.cf-turnstile {
  flex-basis: 100%;
  margin-top: 0.6rem;
  min-height: 0;
}

.cf-turnstile iframe {
  display: block;
  border-radius: 6px;
  max-width: 100%;
}

.cf-turnstile:empty { margin: 0; }

.doc--list .join {
  max-width: 26rem;
  margin-top: 2rem;
}

.ghost--inline {
  position: static;
  transform: none;
  display: inline-block;
  margin-top: 1.6rem;
  text-decoration: none;
}

.ghost--inline:active { transform: none; }

.doc--list .lost-back { display: inline-block; margin-top: 3rem; }


/* ---- fixes -------------------------------------------------------- */

/* Chrome paints its own pale background over an autofilled field, which
   is where that loud white box came from. The inset shadow is the only
   way to override it, since background-color is ignored. */
.join-field:-webkit-autofill,
.join-field:-webkit-autofill:hover,
.join-field:-webkit-autofill:focus {
  -webkit-text-fill-color: var(--ink);
  -webkit-box-shadow: 0 0 0 1000px var(--bg) inset;
  box-shadow: 0 0 0 1000px var(--bg) inset;
  caret-color: var(--ink);
  transition: background-color 9999s ease-out;
}

/* the list form stacks, so every gap is the same gap */
.doc--list .join {
  display: block;
  max-width: 26rem;
  margin-top: 2.25rem;
}

.doc--list .join-field {
  display: block;
  width: 100%;
  margin: 0;
}

.doc--list .cf-turnstile { margin: 1.5rem 0 0; }
.doc--list .join-btn { display: block; margin-top: 1.5rem; }
.doc--list .join-msg { margin-top: 1.25rem !important; }

/* footer: the list and the socials live here, where people look */
.foot-links {
  margin: 1.1rem 0 0;
  font-size: 0.78rem;
  letter-spacing: 0.03em;
}

.foot-links a {
  display: inline-flex;
  align-items: center;
  gap: 0.42rem;
  color: var(--ink);
  text-decoration: none;
  border-bottom: 1px solid var(--faint);
  transition: border-color 0.25s var(--ease);
}

.foot-links a:hover,
.foot-links a:focus-visible { border-bottom-color: var(--ink); }

.glyph--foot {
  width: 15px;
  height: 15px;
  stroke-width: 1.5;
}

.foot-legal { margin-top: 0.7rem; }


/* ---- marked actions ------------------------------------------------
   A word alone does not say what it opens. "show the code" could be a
   discount code; the mark says it is a scannable one before the word is
   read. Same reason the list action carries an envelope. */

.glyph--act {
  width: 17px;
  height: 17px;
  stroke-width: 1.4;
  flex-shrink: 0;
}

.ghost--marked {
  display: inline-flex;
  align-items: center;
  gap: 0.55rem;
}

/* the action sits beside the promise, not only in the footer */
.act {
  display: inline-flex;
  align-items: center;
  gap: 0.55rem;
  margin-top: 1.5rem;
  padding-bottom: 0.32rem;
  border-bottom: 1px solid var(--faint);
  color: var(--ink);
  text-decoration: none;
  font-size: 0.92rem;
  letter-spacing: 0.02em;
  transition: border-color 0.25s var(--ease), gap 0.25s var(--ease);
}

.act:hover,
.act:focus-visible {
  border-bottom-color: var(--ink);
  gap: 0.8rem;
}

.act + .plain { margin-top: 1rem !important; }

/* Mark and notice sit together. Apart, they read as two unrelated
   fragments; together they read as what they actually are, a copyright
   notice: the symbol, the year and the owner in one line. */
.foot-mark {
  margin: 0;
  font-size: 0.78rem;
  color: var(--muted);
}

.foot-mark .notice {
  margin-left: 0.55rem;
  letter-spacing: 0.04em;
}




/* ---- the code, in the footer ---------------------------------------
   Shown outright rather than hidden behind a control. It is one small
   image; making someone find it costs more than the space it takes.

   It is an SVG, so it is resolution independent and cannot go grainy or
   soften at any size, which matters for something a camera has to read.
   One file, fetched once, cached across every page. "full size" opens
   the same file rather than a page built to display it. */

/* The code sits opposite the text rather than inside it. Splitting a
   link hierarchy with a picture is what made the footer feel jumbled:
   the reading order kept getting interrupted by an object.

   Order in the text column is identity, then what you can do, then what
   you can check. Legal last is the convention people arrive expecting.

   No caption. 83% of this audience has used a QR code in the last year
   and half of them scan weekly, so "point a camera at it" explains a
   thing they do without thinking. The file already carries its own
   address, which is disclosure rather than instruction. */

.foot-main {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 2rem;
  flex-wrap: wrap;
}

.foot-text { flex: 1 1 18rem; }

.code-block {
  flex: 0 0 auto;
  margin-top: 0.2rem;
}

/* Tap the plate and the code comes up big; tap anywhere else and it
   goes away. The Popover API does that part.

   The thumbnail down here does not need to scan, because tapping it
   opens the version that does. That frees it from the dark-on-light
   rule, so instead of a white card glaring out of a dark page it is
   drawn in the page's own ink like everything else.

   It is a CSS mask over a transparent file: one small cached asset,
   painted by the stylesheet, so it takes the theme automatically and
   there is no second copy of the code to keep in sync. */

.code-plate {
  display: block;
  padding: 0;
  border: none;
  background: none;
  line-height: 0;
  cursor: pointer;
}

.code-mark {
  display: block;
  width: 78px;
  height: 78px;
  background-color: var(--muted);
  -webkit-mask-image: url(/qr-mark.svg);
  mask-image: url(/qr-mark.svg);
  -webkit-mask-size: contain;
  mask-size: contain;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  transition: background-color 0.3s var(--ease);
}

.code-plate:hover .code-mark,
.code-plate:focus-visible .code-mark { background-color: var(--ink); }

/* ---- the code, enlarged ---------------------------------------------
   Full screen and white, not a card on a dim backdrop.

   This is the Venmo case, not the boarding-gate case: a code shown to
   another person's camera rather than to a fixed scanner. The apps that
   do this well go full screen and push screen brightness to maximum,
   because a dim screen and glare cause more failed scans than anything
   else. The web cannot set brightness, but a full bleed white panel is
   the closest thing: under auto brightness a mostly white screen reads
   far brighter than a small card on a dark page.

   It also removes the sizing problem entirely. A fit-content popover
   was landing at different sizes on desktop and phone and drifting off
   centre; a panel pinned to all four edges cannot. */

.code-pop {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  max-width: none;
  max-height: none;
  margin: 0;
  padding: 0;
  border: none;
  overflow: hidden;
  background: #ffffff;
}

/* the whole surface closes it. tap anywhere. */
.code-pop-surface {
  display: grid;
  place-items: center;
  width: 100%;
  height: 100%;
  padding: 6vmin;
  margin: 0;
  border: none;
  background: none;
  cursor: pointer;
}

.code-pop-surface img {
  display: block;
  width: min(74vmin, 460px);
  height: auto;
}

.code-pop::backdrop { background: #ffffff; }

.code-pop, .code-pop::backdrop {
  transition: opacity 0.28s var(--ease), display 0.28s allow-discrete,
              overlay 0.28s allow-discrete;
  opacity: 0;
}

.code-pop:popover-open,
.code-pop:popover-open::backdrop { opacity: 1; }

@starting-style {
  .code-pop:popover-open,
  .code-pop:popover-open::backdrop { opacity: 0; }
}

.code-fallback { display: none; }

@supports not selector(:popover-open) {
  [popover] { display: none; }
  .code-plate { cursor: default; }
  .code-fallback {
    display: inline-block;
    margin-top: 0.5rem;
    font-family: var(--serif);
    font-size: 0.72rem;
    color: var(--muted);
    text-decoration: none;
    border-bottom: 1px solid var(--faint);
  }
}

@media (prefers-reduced-motion: reduce) {
  .code-pop, .code-pop::backdrop { transition: none; }
}
