/* Self-hosted so the store makes no third-party request. The stylesheet used to
   @import these from fonts.googleapis.com, which sent every visitor's IP to Google
   on every page load — on a site whose principles page says no tracking and no
   third parties. Both families are SIL Open Font License 1.1, which permits this.

   unicode-range is kept, so a browser fetches the latin-ext file only if the copy
   actually contains a character in it. Today none does. A future copy edit with an
   accent in it will work without anybody remembering this comment.

   Files come from Google's CDN via storefront-source/fonts.sh. Re-run it to refresh. */
@font-face {
  font-family: 'Archivo Black';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/assets/fonts/archivo-black-400-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Archivo Black';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/assets/fonts/archivo-black-400-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/assets/fonts/ibm-plex-mono-400-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/assets/fonts/ibm-plex-mono-400-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('/assets/fonts/ibm-plex-mono-500-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('/assets/fonts/ibm-plex-mono-500-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('/assets/fonts/ibm-plex-mono-600-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('/assets/fonts/ibm-plex-mono-600-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('/assets/fonts/ibm-plex-mono-700-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('/assets/fonts/ibm-plex-mono-700-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

:root {
  --orange: #ea4a1c;
  --orange-hot: #fb542b;
  --white: #ffffff;
  --white-dim: rgba(255, 255, 255, 0.62);
  --hairline: rgba(255, 255, 255, 0.28);
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--orange);
  color: var(--white);
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
  position: relative;
  overflow-x: hidden;
  -webkit-font-smoothing: antialiased;
}

/* Real per-pixel grain, generated on a canvas at load.
   These two opacities are the volume dial — turn them, not the density in
   grain.js, which sets the texture of the noise rather than how loud it is.

   The canvas is drawn larger than the viewport and jittered by transform each
   frame, so the grain reshuffles like projected film without re-generating a
   megapixel of noise. Sized in JS — no `inset`, or CSS would stretch it. */
#grain,
#grain-light {
  position: fixed;
  top: 0;
  left: 0;
  pointer-events: none;
  will-change: transform;
}
#grain {
  z-index: 90;
  opacity: 0.17;
  mix-blend-mode: multiply;
}
#grain-light {
  z-index: 91;
  opacity: 0.05;
  mix-blend-mode: screen;
}

.wrap {
  max-width: 940px;
  margin: 0 auto;
  padding: 0 28px;
  position: relative;
  z-index: 2;
}

/* masthead */
header {
  padding: 56px 0 8px;
}
/* The wordmark is outlines, not text. The letters are fitted at a clearance the font's
   own spacing does not produce, and a re-spaced word set as live text is hinted letter by
   letter at this size and goes soft. The drawing comes from ../../wordmark/, which is the
   single source; build.py inlines it.

   font-size still drives it. The viewBox is 712 units tall against a 688 unit cap, so a
   height of 0.712em puts the cap at exactly 0.688em: the same optical size the text had. */
.wordmark {
  font-size: clamp(56px, 14vw, 132px);
  margin: 0;
  color: var(--white);
}
.wordmark svg,
.wordmark-sm svg {
  display: block;
  height: 0.712em;
  width: auto;
  fill: currentColor;
}

/* The name itself stays in the markup: it is the h1 and it is what search reads. */
.vh {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* inner-page masthead: the wordmark shrinks and becomes the way home */
header.inner { padding: 44px 0 8px; }
.wordmark-sm {
  font-size: clamp(30px, 6vw, 44px);
  margin: 0;
  color: var(--white);
  text-decoration: none;
  display: inline-block;
}
.wordmark-sm:hover { color: #ffe9e2; }
.wordmark-sm:focus-visible { outline: 2px solid var(--white); outline-offset: 6px; }

.rule-row {
  display: flex;
  align-items: center;
  gap: 16px;
  margin-top: 18px;
}
.rule-row hr {
  flex: 1;
  border: 0;
  border-top: 2px solid var(--white);
  margin: 0;
}
.rule-row span {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  white-space: nowrap;
}

/* section heading: label first, rule runs out to the right */
.section-head { margin-top: 56px; }
.section-head:first-child { margin-top: 0; }

.subline {
  font-size: 14px;
  font-weight: 500;
  margin: 22px 0 0;
  color: var(--white);
}

/* shelf */
main { padding: 60px 0 0; }

.shelf {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
  gap: 44px 30px;
}

/* below two auto-fill columns the shelf would drop to one scrap per row,
   which stops reading as a shelf. Force two up. */
@media (max-width: 600px) {
  .shelf {
    grid-template-columns: repeat(2, 1fr);
    gap: 40px 24px;
  }
}

.item {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  text-decoration: none;
  color: inherit;
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
  text-align: left;
  font: inherit;
}
.item svg {
  width: 100%;
  height: auto;
  display: block;
  margin-bottom: 16px;
  transform: rotate(var(--rest, 0deg));
}
.item .scrap { fill: var(--white); }

/* the lot number, inked on the paper rather than printed under it */
.lot {
  fill: var(--orange);
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.1em;
}

/* nothing on a shelf sits square to the world */
.shelf .item:nth-child(1) svg { --rest: -0.9deg; }
.shelf .item:nth-child(2) svg { --rest:  0.7deg; }
.shelf .item:nth-child(3) svg { --rest: -0.5deg; }
.shelf .item:nth-child(4) svg { --rest:  1.1deg; }
.shelf .item:nth-child(5) svg { --rest: -1.2deg; }
.shelf .item:nth-child(6) svg { --rest:  0.4deg; }
.shelf .item:nth-child(7) svg { --rest: -0.6deg; }
/* Past seven, repeat the sequence rather than settling every later scrap at one angle:
   three tiles sharing a tilt read as a grid again, which is the thing the tilt exists to
   break. Products are added rarely enough that this only has to not be wrong. */
.shelf .item:nth-child(n+8) svg { --rest:  0.9deg; }
.item:hover .scrap { fill: #ffe9e2; }
.item:focus-visible { outline: 2px solid var(--white); outline-offset: 8px; }

.item-name {
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  margin: 0 0 4px;
}
.item-price {
  font-size: 13px;
  font-weight: 500;
  margin: 0;
  color: var(--white-dim);
}

.item.empty { cursor: default; }
.item.empty svg { opacity: 0.22; }
.item.empty .item-name { color: var(--white-dim); font-weight: 500; }

/* Movement. All of it is opt-out — a reader who has asked the OS for less
   motion gets the page exactly as it was before any of this was added. */
@media (prefers-reduced-motion: no-preference) {

  /* picking a scrap off the shelf: a short lift and a tilt, like paper.
     Only the real products move; the reserved ones are not `a.item`. */
  .item svg { transition: transform 150ms cubic-bezier(0.2, 0.7, 0.3, 1); }
  a.item:hover svg,
  a.item:focus-visible svg { transform: translateY(-5px) rotate(calc(var(--rest, 0deg) * 2.6)); }
  a.item:active svg { transform: translateY(-1px) rotate(calc(var(--rest, 0deg) * 1.5)); }

  /* the shelf being laid out, once, on arrival */
  .shelf .item { animation: settle 420ms cubic-bezier(0.2, 0.75, 0.3, 1) backwards; }
  .shelf .item:nth-child(2) { animation-delay: 70ms; }
  .shelf .item:nth-child(3) { animation-delay: 140ms; }
  .shelf .item:nth-child(4) { animation-delay: 210ms; }
  .shelf .item:nth-child(5) { animation-delay: 280ms; }
  .shelf .item:nth-child(6) { animation-delay: 350ms; }
  .shelf .item:nth-child(7) { animation-delay: 420ms; }
  /* The stagger stops here on purpose. Past about half a second the shelf stops reading as
     being laid out and starts reading as slow. */
  .shelf .item:nth-child(n+8) { animation-delay: 490ms; }
}

@keyframes settle {
  from { opacity: 0; transform: translateY(14px) rotate(-2.5deg); }
  to   { opacity: 1; transform: none; }
}

/* product page: scrap and price on a left rail, description beside it */
.product {
  display: grid;
  grid-template-columns: 1fr;
  gap: 44px;
  padding-top: 8px;
}
@media (min-width: 760px) {
  .product {
    grid-template-columns: 216px 1fr;
    gap: 56px;
    /* 8px, not 40. main already gives 60 and the header 8, so 40 here put 108px of
       empty orange between the rule and the first thing drawn on every product page,
       against 76 on the same page one pixel narrower. The shelf gets away with 60
       because the subline sits in it; a product page has nothing there. */
    padding-top: 8px;
    align-items: start;
  }
}
.product-rail svg {
  width: 100%;
  max-width: 216px;
  height: auto;
  display: block;
  transform: rotate(-0.9deg);   /* same lean it has on the shelf */
}
.product-rail .scrap { fill: var(--white); }
.product-title {
  font-size: 15px;
  font-weight: 700;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  margin: 20px 0 4px;
}
.product-price {
  font-size: 15px;
  font-weight: 500;
  margin: 0 0 20px;
  color: var(--white-dim);
}

.buy {
  font: inherit;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 12px 20px;
  border: 2px solid var(--white);
  background: var(--white);
  color: var(--orange);
  cursor: pointer;
  text-decoration: none;
  display: inline-block;
}
.buy:hover { background: #ffe9e2; }
.buy:focus-visible { outline: 2px solid var(--white); outline-offset: 4px; }
.buy:active { transform: translateY(1px); }

.buy-note {
  font-size: 11px;
  line-height: 1.6;
  color: var(--white-dim);
  margin: 14px 0 0;
  max-width: 216px;
}

/* running text */
.prose { max-width: 62ch; }
.prose p {
  font-size: 14px;
  line-height: 1.75;
  margin: 0 0 18px;
}
.prose p:last-child { margin-bottom: 0; }
.prose strong { font-weight: 700; }
.prose a { color: var(--white); text-decoration: none; border-bottom: 2px solid var(--white); }
.prose a:hover { color: #ffe9e2; border-bottom-color: #ffe9e2; }

.sub-head {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  margin: 32px 0 12px;
}

.spec {
  list-style: none;
  padding: 0;
  margin: 0;
  max-width: 62ch;
}
.spec li {
  font-size: 14px;
  line-height: 1.75;
  padding: 10px 0;
  border-bottom: 2px solid var(--hairline);
  display: flex;
  gap: 18px;
  flex-wrap: wrap;
}
.spec li:first-child { border-top: 2px solid var(--hairline); }
.spec .k {
  font-weight: 700;
  font-size: 11px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  min-width: 132px;
  padding-top: 3px;
}
.spec .v { flex: 1; min-width: 200px; color: var(--white); }

/* numbered principles */
.principles { counter-reset: principle; margin: 0; padding: 0; list-style: none; max-width: 62ch; }
.principles li { counter-increment: principle; margin: 0 0 34px; }
.principles li::before {
  content: counter(principle, decimal-leading-zero);
  font-family: 'Archivo Black', 'IBM Plex Mono', sans-serif;
  font-size: 30px;
  line-height: 1;
  letter-spacing: -0.03em;
  display: block;
  margin-bottom: 10px;
}
.principles h3 {
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  margin: 0 0 10px;
}
.principles p { font-size: 14px; line-height: 1.75; margin: 0; }
/* Principle 01 grew from one paragraph to three when the email list made
   "no record of you anywhere" false. Zero margin ran them into one block. */
.principles p + p { margin-top: 13px; }

/* the small print that has to be there and does not want to be loud */
.fine {
  font-size: 11px;
  line-height: 1.7;
  color: var(--white-dim);
  max-width: 62ch;
  margin: 0 0 14px;
}
.fine:last-child { margin-bottom: 0; }

/* footer */
footer {
  margin-top: 92px;
  border-top: 2px solid var(--white);
  padding: 18px 0 56px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: 18px;
  font-size: 12px;
}
.footer-links {
  display: flex;
  gap: 22px;
  flex-wrap: wrap;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  font-weight: 500;
}
.footer-links a { color: var(--white); text-decoration: none; border-bottom: 2px solid var(--white); padding-bottom: 1px; }
.footer-links a:hover { color: #ffe9e2; border-bottom-color: #ffe9e2; }
.footer-links span { color: var(--white-dim); }

.email-form { display: flex; gap: 0; }
.email-form input {
  font: inherit;
  font-size: 12px;
  padding: 10px 12px;
  border: 2px solid var(--white);
  border-right: 0;
  background: transparent;
  color: var(--white);
  min-width: 165px;
}
.email-form input::placeholder { color: var(--white-dim); text-transform: uppercase; letter-spacing: 0.08em; }
.email-form button {
  font: inherit;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 10px 16px;
  border: 2px solid var(--white);
  background: var(--white);
  color: var(--orange);
  cursor: pointer;
}
.email-form button:hover { background: #ffe9e2; }
.email-form button:active { transform: translateY(1px); }

@media (max-width: 520px) {
  footer { flex-direction: column; align-items: flex-start; }
  .email-form { width: 100%; }
  .email-form input { flex: 1; min-width: 0; }
}

/* ---- sets ---------------------------------------------------------------
   A second shelf below the lots. The divider is the same rule-row the header
   uses, so the page has one device for "a new section starts here" rather
   than two. Singles are never taken off the shelf above it: pure bundling —
   where the set is the only way to buy — tests worse than selling separately. */
.rule-row.shelf-divider { margin: 68px 0 14px; }

/* The line introducing the sets needs air beneath it before the first scrap.
   At 14px it read as a caption belonging to SET 01 rather than as a sentence
   about the row. */
.rule-row.shelf-divider + .subline { margin-bottom: 34px; }

/* The struck price is the sum of the parts. It sits after the set price, not
   before it, so the number a buyer reads first is the one they pay. */
.item-price s,
.product-price s {
  opacity: 0.5;
  margin-left: 6px;
  text-decoration-thickness: 1px;
}

/* A set names its apps as links in the spec list; the singles pages do not, so
   this only applies here. Underlined rather than coloured — there is one accent
   in this shop and it is the ground. */
.spec .k a {
  color: inherit;
  text-decoration: underline;
  text-underline-offset: 2px;
  text-decoration-thickness: 1px;
}
.spec .k a:hover { text-decoration-thickness: 2px; }

/* ---- product screenshots ------------------------------------------------
   Rendered offscreen from the real views by each product's own harness, so
   the picture cannot show an interface the app does not have.

   Each one sits on a torn white sheet — the same device the shelf uses, from
   the same generator — rather than in a hairline box. A light interface on
   this orange is a hard collision, and a thin rule around it reads as an
   apology for the join. Paper is what this shop puts things on. */
.shot { margin: 30px 0 34px; }

/* A grid item's default min-width is auto, which is its CONTENT width — so a
   1400px screenshot widened its own column to 1480px and scrolled the whole
   page sideways. min-width: 0 lets the track shrink to its share of the row. */
.product > * { min-width: 0; }

.shot .sheet {
  position: relative;
  padding: 3.1% 2.8%;
  /* Nothing in this shop sits square. Sub-degree, per product, same rule as
     the scraps on the shelf. */
  transform: rotate(var(--rest, -0.5deg));
}
.shot .sheet svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
}
.shot .sheet svg path { fill: var(--white); }
.shot .sheet img {
  position: relative;
  display: block;
  width: 100%;
  max-width: 100%;
  height: auto;
}
.shot figcaption {
  margin-top: 15px;
  font-size: 12px;
  line-height: 1.55;
  color: var(--white-dim);
  max-width: 62ch;
}

/* ---- the mark on the paper ----------------------------------------------
   Each scrap carries its tool's mark in orange ink: the same drawing as the
   app icon, at one weight of line. The lot number says which lot; the mark
   says what is in it. Orange on white is the only ink this shop has. */
/* Not scoped to .item: the same scrap appears on the product page inside
   .product-rail, and scoping these to the shelf left every mark there with
   SVG's default black fill. Black is not a colour this shop has. */
.mark {
  fill: none;
  stroke: var(--orange);
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.mark .ink { fill: var(--orange); stroke: none; }
/* The lit row. `ink` fills its block; this thickens the speech lines beside it, so the
   row reads as lit on the scrap the way it does in the app icon. */
.mark .inkline { stroke-width: 3.4; }
.mark .paper { fill: var(--white); stroke: none; }
.mark .glyph {
  fill: var(--orange);
  stroke: none;
  font-family: Georgia, "Times New Roman", serif;
  font-size: 42px;
  text-anchor: middle;
}
/* A lot not yet filled carries no mark, because there is nothing to draw yet. */
.item.empty .item-price { opacity: 0.55; }

/* Onionskin's mark is a watermarked page, so it carries the watermark: the
   app's own name struck across the sheet, knocked out of the orange the way a
   real mark sits in the paper rather than on it. The store scrap can hold a
   word; the app icon cannot, because at 32 points in a Dock a word is mush and
   only the diagonal survives. Same drawing, two levels of detail — the icons
   already drop their text lines below 64pt for the same reason.
   6.6px is measured, not chosen: nine monospace characters at 0.66em of advance
   come to 39 of the band's 54 units, which leaves the word air at both ends. It
   was 9px against a band it filled edge to edge, and read as texture rather than
   as a word. The scrap renders at 162px on the shelf and 220px on a product
   page, so those are the only two sizes this has to be right at. */
.mark .stamp {
  fill: var(--white);
  stroke: none;
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
  font-size: 6.6px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-anchor: middle;
  dominant-baseline: central;
}

/* ---- not on sale yet ----------------------------------------------------
   The Buy control is a <span>, not a link, while no processor is connected.
   It has to LOOK unavailable as well as be unavailable: it was previously an
   anchor pointing at a placeholder, which is a relative URL and resolved to a
   404 on a real host — a button that looked alive and was not. */
.buy.notyet {
  display: inline-block;
  background: none;
  color: var(--white);
  border: 1px dashed currentColor;
  opacity: 0.75;
  cursor: default;
}
.buy.notyet:hover { background: none; }

/* The preview banner, on every page so a tester landing deep in the site
   still sees it. */
.preview-note {
  margin: 0 0 26px;
  padding: 10px 14px;
  border: 1px dashed var(--white);
  font-size: 12px;
  line-height: 1.5;
  letter-spacing: 0.02em;
  opacity: 0.9;
}

/* ---- the manual, over the page ----------------------------------------- */
/* Standard 15: the control sits with the price, where somebody deciding whether to buy
   is already looking, rather than in a navigation bar they would have to go find. */

.manual-open {
  display: inline-block;
  margin-top: 18px;
  font-size: 12px;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--white);
  text-decoration: none;
  border-bottom: 1px dashed var(--hairline);
  padding-bottom: 3px;
}
.manual-open:hover,
.manual-open:focus-visible { border-bottom-color: var(--white); }

.manual-box[hidden] { display: none; }
.manual-box {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: center;
}
.manual-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(24, 8, 3, .94);
  /* No blur. The grain behind it is per-pixel noise and a backdrop-filter turns it
     into porridge on the two engines that support it. */
}
.manual-frame {
  position: relative;
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
}
.manual-bar {
  position: relative;
  z-index: 2;
  display: flex;
  align-items: baseline;
  gap: 16px;
  padding: 16px 20px;
  font-size: 11px;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--white-dim);
}
.manual-name { color: var(--white); }
.manual-hint { flex: 1; opacity: .6; }
@media (max-width: 620px) { .manual-hint { display: none; } }

.manual-close {
  appearance: none;
  background: none;
  border: 1px dashed var(--hairline);
  color: var(--white);
  font: inherit;
  font-size: 20px;
  line-height: 1;
  width: 34px;
  height: 34px;
  cursor: pointer;
  flex: 0 0 auto;
}
.manual-close:hover,
.manual-close:focus-visible { border-style: solid; }

.manual-doc {
  flex: 1;
  width: 100%;
  border: 0;
  display: block;
  /* The manual paints its own grounds panel by panel and its page is transparent, so
     the backdrop shows through the gaps at either end of the run. */
  background: transparent;
}
