/* ---- THE FACES, SELF-HOSTED (2 Sep 2026) ----
   Archivo (wdth 62..125, wght 100..900) and Caveat (wght 500..600), latin subset, shipped by
   sync.sh into <public>/fonts/ beside this file. No Google Fonts link anywhere any more: the
   customer PDF once printed body copy in Arial Narrow because the CDN file had not arrived inside
   the print budget, and a paying owner's paper must never depend on someone else's server at
   print time. font-display:block so nothing paints a fallback first. The URL is relative to this
   stylesheet; render.js rewrites it to /fonts/ when it inlines the CSS into a report. */
@font-face{font-family:'Archivo'; font-style:normal; font-weight:100 900; font-stretch:62% 125%; font-display:block; src:url(fonts/archivo-latin.woff2) format('woff2')}
@font-face{font-family:'Caveat'; font-style:normal; font-weight:500 600; font-display:block; src:url(fonts/caveat-latin.woff2) format('woff2')}

/* ============================================================================
   PAPER UI  ·  the estate's paper-craft design system
   Canonical source: ~/.claude/skills/paper-ui/paper.css
   Sync into a project with:  bash ~/.claude/skills/paper-ui/sync.sh <dir>
   Never edit the copy inside a project. Edit here, re-sync, re-verify.

   Every surface is a piece of real paper photographed off the Ellusion capture.
   Nothing in this file draws a texture: the canvas and the word plates are real
   assets built by ~/.claude/scripts/paper-plate. CSS only cuts, layers, lights
   and casts shadows.

   A brand overrides the four BRAND tokens below and nothing else.
   ========================================================================= */

:root{
  /* ---- BRAND (override these, per project) ---- */
  --accent:#D97E06;          /* the one chromatic signal: price marks and buttons only */
  --accent-deep:#A2440A;     /* the same hue dark enough for text on cream */
  --paper:#F3EDE1;           /* the stock's own colour, sampled from the photograph */
  --ink:#16130F;             /* never pure black: the plates' ink measures 38,38,37 */

  /* ---- MATERIAL (shared, do not fork per project) ---- */
  --card:#F6F1E6;            /* a second sheet laid on the first */
  --muted:#5C554B;           /* ink at reading weight */
  --kraft:#C9A878;           /* the brown stock */
  --black:#16130F;           /* the black paper world */
  --hairline:#D6CDBB;
  --rule:rgba(22,19,15,.18);

  /* ---- TYPE ----
     Every face here is above the line in ~/.claude/skills/font-pairing/banned-fonts.json:
     it appears on 5 or more of the 1,290 real shipping sites in the refero corpus. That is a
     hard gate, not a preference. Run it before shipping any change to this block:
       node ~/.claude/skills/font-pairing/check-fonts.mjs <public-dir>

     Replaced 2026-08-31, Hayden's call. The whole stack was drawn from the barely-used bucket:
     Bricolage Grotesque was on 3 sites of 1,290, Hanken Grotesk on 1. Both looked considered and
     neither survives contact with the market. */
  /* Archivo (5 sites) carries display. It is the only free grotesque above the line with a real
     WIDTH axis (wdth 62..125), which is what this system needs: the display voice is condensed
     heavy caps, and condensing must come from the font, never from a transform. */
  --display:'Archivo','Arial Narrow',Arial,sans-serif;
  /* Inter is on 236 of 1,290 sites, more than the next eight faces together. */
  --body:'Archivo','Arial Narrow',Arial,sans-serif;   /* Inter until 2 Sep 2026: Hayden wanted the logo's tighter face on every word that is not a plate, so body is Archivo at a narrow regular width */
  /* --label is the small-caps label voice: kickers, part numbers, stat captions.
     The whole label layer was JetBrains Mono until 30 Aug 2026, which made every page in
     the system read as a terminal. A coding face is not a voice this paper system has.
     There is no --mono token any more, deliberately: the one block that kept it, a prompt
     the reader copies word for word, is English prose and reads better without it. Verbatim
     copying is protected by white-space, never by a fixed advance. Do not reintroduce it. */
  --label:'Archivo','Arial Narrow',Arial,sans-serif;
  /* Caveat is the ONE documented exception to the 5-site rule, and it is argued in
     banned-fonts.json: it is the most used handwriting face in the corpus, and every other
     script face appears exactly once, so no choice in the category can clear the line.
     One hand-written accent line per page. Never body, headings, labels or buttons. */
  --hand:'Caveat',cursive;
 /* The display WIDTH is one token, adopted 2 Sep 2026 on Hayden's call after the logo lane: the
    lockup the models drew was Archivo at the floor of its width axis, and he wanted that face for
    every heading, figure and wordmark on the site and the samples. The paper plates are photographs
    and do not read this. 66 for headings and figures; 62, the axis floor, for the wordmark and
    the lockup so they match logo-lockup.svg to the letter. Never type a width into a rule again:
    a heading that needs to be wider than the token is a heading that needs fewer words. */
 --wdth-display:62;
 --wdth-mark:62;
 --wdth-body:78;   /* the reading width: narrow enough to be the same family as the headings, wide enough to read at 15px on A4 */
  /* Seven steps. Every size on a paper-ui page is one of these. */
  --f1:12px; --f2:15px; --f3:17px; --f4:21px; --f5:26px; --f6:44px;
  --f7:clamp(40px,5.2vw,72px);

  /* ---- SPACE ---- */
  --s1:8px; --s2:14px; --s3:22px; --s4:36px; --s5:58px; --s6:94px;

  /* ---- DEPTH ---- */
  /* ---- THE LIGHT (3 Sep 2026, the depth lab) ----
     One sun, top-left. Every cast shadow in this file reads these two numbers, so the light moves
     for the whole estate in one edit and never per component. Measured: with the old symmetric
     "0 3px 4px" nothing on the page said where the light was, and the founder read the whole
     system as 2D beside the Ellusion film. A CONTACT shadow (a fifth of the offset, 1px blur)
     hugs the sheet; the CAST shadow (the full offset, wide blur) says how high the paper sits.
     A page never writes its own offsets: it uses the chains below. */
  --light-x:10px; --light-y:14px;
  --plane:translate3d(0,0,0);
  --lx-contact:calc(var(--light-x)*.2); --ly-contact:calc(var(--light-y)*.2);
  --sheet-shadow:0 1px 2px rgba(22,19,15,.10), var(--lx-contact) var(--ly-contact) 2px rgba(22,19,15,.14), var(--light-x) calc(var(--light-y)*1.4) 30px -14px rgba(22,19,15,.5);
  --sheet-shadow-lift:0 1px 2px rgba(22,19,15,.10), var(--lx-contact) var(--ly-contact) 2px rgba(22,19,15,.12), calc(var(--light-x)*1.6) calc(var(--light-y)*1.8) 38px -16px rgba(22,19,15,.6);
  /* a scrap or crumb stuck on top: the white rim, the contact, the cast */
  --scrap-shadow:drop-shadow(0 2px 0 rgba(255,253,248,.5)) drop-shadow(var(--lx-contact) var(--ly-contact) 1px rgba(22,19,15,.4)) drop-shadow(calc(var(--light-x)*.8) calc(var(--light-y)*.8) 10px rgba(22,19,15,.35));
  /* the same note laid on the black world sits in a deeper shadow */
  --tear-shadow-deep:drop-shadow(0 2px 0 rgba(255,253,248,.6)) drop-shadow(var(--lx-contact) var(--ly-contact) 1px rgba(0,0,0,.5)) drop-shadow(calc(var(--light-x)*1.6) calc(var(--light-y)*1.6) 24px rgba(0,0,0,.55));
  /* A clip-path clips box-shadow away. Put this on a WRAPPER around the clipped
     element: the drop-shadow follows the tear, and the light first shadow is the
     fibre lip standing proud of the sheet below. */
  --tear-shadow:drop-shadow(0 -1px 0 rgba(255,253,248,.95)) drop-shadow(var(--lx-contact) var(--ly-contact) 1px rgba(22,19,15,.35)) drop-shadow(calc(var(--light-x)*1.4) calc(var(--light-y)*1.4) 18px rgba(22,19,15,.30));

  /* ---- TEARS ----
     Irregular by construction: 36 points, amplitude 0 to 2.4 percent. A regular
     zigzag reads as a sawtooth, never as paper. --note and --scrapN tear on all
     four sides; --torn tears the top only; --torn-both tears top and bottom. */
  --torn:polygon(0% 0.78%,2.8% 0.36%,5.6% 1.56%,8.3% 0.17%,11.1% 1.29%,13.9% 0.88%,16.7% 0.14%,19.4% 1.22%,22.2% 0.09%,25% 1.04%,27.8% 0.17%,30.6% 0.22%,33.3% 1.02%,36.1% 1.98%,38.9% 0.3%,41.7% 0.54%,44.4% 1.51%,47.2% 2.27%,50% 1.39%,52.8% 0.95%,55.6% 2.34%,58.3% 0.11%,61.1% 2.06%,63.9% 0.7%,66.7% 0.35%,69.4% 0.28%,72.2% 0.74%,75% 1.96%,77.8% 0.43%,80.6% 1.4%,83.3% 1.53%,86.1% 0.89%,88.9% 1.31%,91.7% 0.15%,94.4% 0.14%,97.2% 0.49%,100% 1.63%,100% 100%,0% 100%);
  --torn-both:polygon(0% 0.78%,2.8% 0.36%,5.6% 1.56%,8.3% 0.17%,11.1% 1.29%,13.9% 0.88%,16.7% 0.14%,19.4% 1.22%,22.2% 0.09%,25% 1.04%,27.8% 0.17%,30.6% 0.22%,33.3% 1.02%,36.1% 1.98%,38.9% 0.3%,41.7% 0.54%,44.4% 1.51%,47.2% 2.27%,50% 1.39%,52.8% 0.95%,55.6% 2.34%,58.3% 0.11%,61.1% 2.06%,63.9% 0.7%,66.7% 0.35%,69.4% 0.28%,72.2% 0.74%,75% 1.96%,77.8% 0.43%,80.6% 1.4%,83.3% 1.53%,86.1% 0.89%,88.9% 1.31%,91.7% 0.15%,94.4% 0.14%,97.2% 0.49%,100% 1.63%,100% 97.62%,97.2% 98.45%,94.4% 98.32%,91.7% 99.85%,88.9% 98.41%,86.1% 98.86%,83.3% 97.73%,80.6% 97.98%,77.8% 98.91%,75% 98.61%,72.2% 98.57%,69.4% 98.33%,66.7% 99.25%,63.9% 97.9%,61.1% 98.62%,58.3% 98.17%,55.6% 98.4%,52.8% 99.91%,50% 98.83%,47.2% 99.64%,44.4% 98.18%,41.7% 99%,38.9% 99.72%,36.1% 97.65%,33.3% 99.31%,30.6% 98.25%,27.8% 97.9%,25% 98.74%,22.2% 98.62%,19.4% 99.41%,16.7% 98.32%,13.9% 98.09%,11.1% 99.28%,8.3% 98.91%,5.6% 98.59%,2.8% 99.25%,0% 98.97%);
  --scrap1:polygon(0.0% 0.7%,7.1% 3.1%,14.3% 2.9%,21.4% 2.2%,28.6% 1.0%,35.7% 3.6%,42.9% 3.6%,50.0% 2.3%,57.1% 2.3%,64.3% 1.1%,71.4% 0.0%,78.6% 1.7%,85.7% 2.6%,92.9% 0.3%,96.4% 0.0%,99.0% 7.1%,99.0% 14.3%,99.8% 21.4%,95.5% 28.6%,96.7% 35.7%,96.1% 42.9%,97.2% 50.0%,99.8% 57.1%,98.5% 64.3%,97.8% 71.4%,99.5% 78.6%,95.7% 85.7%,98.3% 92.9%,100.0% 99.3%,92.9% 96.3%,85.7% 99.4%,78.6% 95.8%,71.4% 98.0%,64.3% 97.5%,57.1% 98.5%,50.0% 97.8%,42.9% 99.2%,35.7% 99.8%,28.6% 96.1%,21.4% 98.9%,14.3% 96.5%,7.1% 99.1%,4.4% 100.0%,4.0% 92.9%,3.4% 85.7%,3.4% 78.6%,2.6% 71.4%,3.3% 64.3%,0.6% 57.1%,2.1% 50.0%,3.7% 42.9%,0.9% 35.7%,4.2% 28.6%,4.0% 21.4%,1.8% 14.3%,1.6% 7.1%);
  --scrap2:polygon(0.0% 2.0%,7.1% 1.7%,14.3% 4.5%,21.4% 1.7%,28.6% 0.1%,35.7% 0.4%,42.9% 3.2%,50.0% 4.5%,57.1% 2.2%,64.3% 2.2%,71.4% 2.5%,78.6% 3.4%,85.7% 1.0%,92.9% 2.7%,97.5% 0.0%,97.2% 7.1%,99.2% 14.3%,99.2% 21.4%,98.4% 28.6%,95.8% 35.7%,98.4% 42.9%,97.3% 50.0%,98.6% 57.1%,99.5% 64.3%,99.7% 71.4%,96.3% 78.6%,96.5% 85.7%,98.3% 92.9%,100.0% 96.9%,92.9% 97.0%,85.7% 96.0%,78.6% 95.7%,71.4% 98.2%,64.3% 95.5%,57.1% 95.7%,50.0% 97.3%,42.9% 96.0%,35.7% 97.8%,28.6% 98.3%,21.4% 98.9%,14.3% 96.1%,7.1% 99.3%,0.3% 100.0%,3.6% 92.9%,3.5% 85.7%,2.2% 78.6%,3.8% 71.4%,0.2% 64.3%,1.2% 57.1%,1.3% 50.0%,1.9% 42.9%,0.8% 35.7%,0.3% 28.6%,1.5% 21.4%,4.0% 14.3%,4.4% 7.1%);
  --scrap3:polygon(0.0% 3.4%,7.1% 2.2%,14.3% 1.1%,21.4% 4.3%,28.6% 2.4%,35.7% 3.3%,42.9% 0.1%,50.0% 1.0%,57.1% 3.8%,64.3% 3.6%,71.4% 4.0%,78.6% 2.7%,85.7% 1.9%,92.9% 2.6%,96.2% 0.0%,98.6% 7.1%,97.6% 14.3%,98.8% 21.4%,96.5% 28.6%,100.0% 35.7%,96.9% 42.9%,95.7% 50.0%,99.9% 57.1%,97.3% 64.3%,97.1% 71.4%,96.2% 78.6%,97.4% 85.7%,98.8% 92.9%,100.0% 98.2%,92.9% 95.7%,85.7% 95.9%,78.6% 99.4%,71.4% 99.6%,64.3% 99.6%,57.1% 99.9%,50.0% 98.0%,42.9% 99.4%,35.7% 96.1%,28.6% 97.1%,21.4% 99.4%,14.3% 97.6%,7.1% 98.5%,2.2% 100.0%,1.0% 92.9%,0.7% 85.7%,0.8% 78.6%,1.7% 71.4%,3.2% 64.3%,2.5% 57.1%,0.2% 50.0%,3.2% 42.9%,3.9% 35.7%,3.5% 28.6%,3.6% 21.4%,0.0% 14.3%,2.1% 7.1%);
  --note:polygon(0.0% 0.1%,4.5% 1.7%,9.1% 0.4%,13.6% 1.6%,18.2% 0.6%,22.7% 1.5%,27.3% 0.7%,31.8% 0.1%,36.4% 0.9%,40.9% 0.7%,45.5% 2.0%,50.0% 0.6%,54.5% 1.1%,59.1% 1.2%,63.6% 2.1%,68.2% 0.2%,72.7% 1.3%,77.3% 1.1%,81.8% 1.6%,86.4% 2.1%,90.9% 0.7%,95.5% 1.5%,98.8% 0.0%,98.8% 4.5%,99.9% 9.1%,98.2% 13.6%,97.9% 18.2%,99.2% 22.7%,99.3% 27.3%,97.8% 31.8%,99.8% 36.4%,99.8% 40.9%,99.8% 45.5%,98.2% 50.0%,98.3% 54.5%,99.0% 59.1%,98.6% 63.6%,100.0% 68.2%,98.6% 72.7%,99.0% 77.3%,98.9% 81.8%,99.1% 86.4%,99.1% 90.9%,99.5% 95.5%,100.0% 99.9%,95.5% 99.0%,90.9% 99.5%,86.4% 98.5%,81.8% 98.2%,77.3% 98.4%,72.7% 99.0%,68.2% 99.5%,63.6% 99.2%,59.1% 98.9%,54.5% 98.6%,50.0% 97.9%,45.5% 99.8%,40.9% 98.9%,36.4% 98.9%,31.8% 98.4%,27.3% 98.0%,22.7% 98.3%,18.2% 98.6%,13.6% 98.3%,9.1% 98.5%,4.5% 98.1%,1.5% 100.0%,1.5% 95.5%,0.5% 90.9%,2.0% 86.4%,0.8% 81.8%,1.4% 77.3%,1.8% 72.7%,2.1% 68.2%,2.2% 63.6%,0.0% 59.1%,0.7% 54.5%,0.7% 50.0%,0.9% 45.5%,0.3% 40.9%,1.2% 36.4%,0.2% 31.8%,0.7% 27.3%,2.1% 22.7%,1.6% 18.2%,1.0% 13.6%,0.2% 9.1%,2.1% 4.5%);

  /* fibre, laid over everything at 5 percent */
  --grain:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  /* where the real sheet lives, relative to the site root */
  --paper-canvas:url(/paper/crumple.webp);
}

/* ---------------------------------------------------------------- base ---- */
*{box-sizing:border-box; margin:0; padding:0}
em,i,cite,address,blockquote{font-style:normal}   /* no italics, ever */
/* html clips too: body's overflow-x alone propagates to the viewport, and
   documentElement.scrollWidth keeps counting a rotated scrap. */
html{background:var(--paper); overflow-x:clip}
body{
  background:var(--paper); color:var(--ink);
  font-family:var(--body); font-size:var(--f3); line-height:1.55; min-height:100vh;
  font-variation-settings:"wdth" var(--wdth-body);   /* inherited by every label and paragraph; headings set the display width themselves */
  -webkit-font-smoothing:antialiased; overflow-x:clip;
}
/* THE CANVAS: the real photographed sheet, fixed so it sits still while paper
   scrolls over it. Build it with make-canvas.py; never draw a substitute. */
body::before{content:""; position:fixed; inset:0; z-index:0; pointer-events:none;
  background:var(--paper) var(--paper-canvas) center/cover no-repeat}
body::after{content:""; position:fixed; inset:0; z-index:0; pointer-events:none;
  opacity:.05; background-image:var(--grain); background-size:200px 200px}
a{color:inherit}
.frame{position:relative; z-index:1; max-width:1080px; margin:0 auto; padding:var(--s3) var(--s3) var(--s5)}
.sr-only{position:absolute; width:1px; height:1px; padding:0; margin:-1px; overflow:hidden; clip:rect(0 0 0 0); white-space:nowrap; border:0}

/* ---------------------------------------------------------------- type ---- */
/* No "opsz" here any more: Archivo has no optical-size axis. A font-variation-settings naming an
   axis the font does not carry is not an error, it is silently dropped, so it would have sat in
   the file forever reading as intent. */
h1{font-family:var(--display); font-weight:800; text-transform:uppercase; line-height:.95; letter-spacing:0; font-size:var(--f7); max-width:15ch; font-variation-settings:"wdth" var(--wdth-display)}
h2{font-family:var(--display); font-weight:800; text-transform:uppercase; font-size:clamp(26px,3vw,var(--f6)); line-height:1; letter-spacing:0; font-variation-settings:"wdth" var(--wdth-display)}
h3{font-family:var(--display); font-weight:700; font-size:var(--f4); line-height:1.15; letter-spacing:-0.01em; font-variation-settings:"wdth" 94}
p.lede{font-size:clamp(17px,1.6vw,20px); color:var(--muted); max-width:52ch; line-height:1.5}
.kicker{font-family:var(--label); font-size:var(--f1); letter-spacing:.08em; text-transform:uppercase; color:var(--muted); margin-bottom:var(--s2)}
.kicker b{color:var(--accent-deep); font-weight:500}
.hand{font-family:var(--hand); font-weight:600; color:var(--accent-deep); font-size:clamp(24px,2.6vw,34px); line-height:1.1; max-width:22ch}
.muted{color:var(--muted)}
.small{font-size:14.5px}
.fine{font-family:var(--label); font-size:var(--f1); line-height:1.6; color:var(--muted); max-width:52ch}
hr.rule{border:0; border-top:1px solid var(--rule); margin:var(--s5) 0}

/* -------------------------------------------------------------- plates ----
   Real torn word plates. Build with:
     python3 ~/.claude/scripts/paper-plate/plate.py WORD --height 260 --no-shadow
   THREE RULES, each one paid for:
   1. --no-shadow. A baked shadow fails the tool's ALPHA check on short words
      and cannot suit both a cream and a black ground. The page casts it here.
   2. Never under about 200px tall, or the tool's GRAIN row cannot measure it.
   3. Put the real width and height on the <img>. Without them a lazy plate is
      1px wide until it loads, so the row wraps at the wrong word and shifts. */
.plates{display:flex; flex-wrap:wrap; align-items:flex-end; gap:.12em .1em; line-height:0}
/* Earlier rows lie ON TOP of later ones, so a row's cast shadow falls onto the plates beneath
   it the way stacked paper does. Without this a later row painted over the shadow above it and
   the light read as missing on every plate but the last row (the light gate caught it, 3 Sep). */
.prow{position:relative} .prow:nth-child(1){z-index:6} .prow:nth-child(2){z-index:5} .prow:nth-child(3){z-index:4} .prow:nth-child(4){z-index:3} .prow:nth-child(5){z-index:2}
.plates img{display:block; width:auto; filter:drop-shadow(var(--lx-contact) var(--ly-contact) 1px rgba(22,19,15,.55)) drop-shadow(var(--light-x) var(--light-y) 12px rgba(22,19,15,.34)); transform-origin:50% 90%}
.plates img:nth-child(3n+1){transform:rotate(-1.1deg)}
.plates img:nth-child(3n+2){transform:rotate(.8deg) translateY(-2px)}
.plates img:nth-child(3n){transform:rotate(-.4deg) translateY(1px)}
.on-dark .plates img, .world .plates img{filter:drop-shadow(var(--lx-contact) var(--ly-contact) 1px rgba(0,0,0,.6)) drop-shadow(var(--light-x) var(--light-y) 12px rgba(0,0,0,.55))}
/* A multi-line statement: one .prow per line, so the break is chosen, not left
   to wrapping. Never call this class "line": that collides with body copy. */
.plate-say{display:grid; gap:.05em; justify-items:start}
.plate-say .prow{gap:0 .02em}
.plate-say.centred{justify-items:center}
.plate-say.centred .prow{justify-content:center}

/* ---------------------------------------------------------------- note ----
   A torn cream card. The wrapper carries the shadow because the clip would
   cut it away; the wrapper also carries the rotation. */
.note-wrap{filter:var(--tear-shadow)}
.note{background:var(--paper); color:var(--ink); clip-path:var(--note); padding:var(--s4)}
.note.card{background:var(--card)}

/* --------------------------------------------------------------- world ----
   A section of black paper torn in over the canvas. Give it .on-dark so plates
   and hairlines switch to their dark-ground values. */
.world-wrap{position:relative; filter:drop-shadow(0 -2px 0 rgba(255,253,248,.85)) drop-shadow(0 -10px 16px rgba(22,19,15,.25))}
.world{position:relative; background:var(--black); color:var(--paper); clip-path:var(--torn-both); padding:var(--s6) var(--s3); overflow:hidden}
.world::after{content:""; position:absolute; inset:0; pointer-events:none; opacity:.09; background-image:var(--grain); background-size:160px 160px}
.world .inner{position:relative; z-index:2; max-width:1080px; margin:0 auto}
/* torn layers scattered on the world. Position each one per page. */
.layer{position:absolute; pointer-events:none}
.layer.kraft{background:var(--kraft) var(--paper-kraft, none) center/cover; clip-path:var(--note)}
.layer.orange{background:var(--accent); clip-path:var(--note)}
.layer.orange::after{content:""; position:absolute; inset:0; opacity:.18; background-image:var(--grain); background-size:120px 120px}
.layer.cream{background:var(--paper); clip-path:var(--note)}
/* halftone, the film's own dot field. Mask it radially or the patch is a box. */
.layer.dots{background-image:radial-gradient(circle, var(--accent) 1.5px, transparent 1.8px); background-size:9px 9px;
  -webkit-mask-image:radial-gradient(ellipse at 30% 40%, #000 10%, transparent 68%);
  mask-image:radial-gradient(ellipse at 30% 40%, #000 10%, transparent 68%)}
.layer.dots.pale{background-image:radial-gradient(circle, var(--paper) 1.4px, transparent 1.7px); background-size:10px 10px; opacity:.22}

/* ---------------------------------------------------------------- card ----
   A cream sheet laid on any ground. Rotation is per-instance and deliberate:
   below about one degree it reads as a bug, not as hand-placed. */
.sheet{background:var(--card); color:var(--ink); padding:var(--s3); border-radius:3px;
  box-shadow:var(--sheet-shadow); display:flex; flex-direction:column; gap:10px; min-width:0}
.on-dark .sheet, .world .sheet{box-shadow:0 2px 0 rgba(255,253,248,.5), var(--lx-contact) var(--ly-contact) 2px rgba(0,0,0,.35), calc(var(--light-x)*1.4) calc(var(--light-y)*1.6) 34px -14px rgba(0,0,0,.8)}
.sheet .n{font-family:var(--label); font-size:var(--f1); letter-spacing:.12em; text-transform:uppercase; color:var(--muted)}
.sheet .n b{color:var(--accent-deep); font-weight:500}
.sheet h3{font-size:var(--f5); line-height:1.02; text-transform:uppercase; font-weight:800; font-variation-settings:"wdth" var(--wdth-display)}
.sheet ul{list-style:none; margin:4px 0 0; padding:0; display:grid}
.sheet li{padding:8px 0; border-top:1px dashed var(--hairline); font-size:var(--f2); line-height:1.4; display:flex; gap:10px}
.sheet li::before{content:"+"; color:var(--accent-deep); font-weight:700; flex:0 0 auto}
.sheet .foot{margin-top:auto; padding-top:var(--s2); border-top:1px solid var(--rule)}

/* -------------------------------------------------------------- scraps ----
   A torn colour scrap, stuck on. The wrapper rotates and casts; the span is
   the paper. Use .scrap for a price mark, a badge, a stamped aside. */
.scrap{display:inline-block; filter:var(--scrap-shadow); transform:rotate(7deg)}
.scrap > span{display:block; background:var(--accent); color:var(--ink); clip-path:var(--scrap2); padding:14px 22px 12px; text-align:center}
.scrap.cream > span{background:var(--paper)}
.scrap b{display:block; font-family:var(--display); font-weight:800; font-size:clamp(40px,4.4vw,60px); line-height:.95; letter-spacing:-0.02em; font-variation-settings:"wdth" var(--wdth-display)}
.scrap small{display:block; font-family:var(--label); font-size:var(--f1); letter-spacing:.12em; text-transform:uppercase; margin-top:4px}
.stamp-mark{display:inline-block; font-family:var(--label); font-weight:500; font-size:var(--f1); letter-spacing:.12em; text-transform:uppercase; color:var(--accent-deep); border:2px solid var(--accent-deep); border-radius:4px; padding:5px 9px; line-height:1}

/* ------------------------------------------------------------- actions ----
   One paper-cut pill. The accent belongs to this and to .scrap, nothing else. */
.btn, .cta{position:relative; display:inline-flex; align-items:center; gap:14px; background:var(--accent); color:var(--ink); text-decoration:none; font-family:var(--display); font-weight:700; font-size:var(--f3); padding:16px 26px; border-radius:999px; font-variation-settings:"wdth" 94;
  box-shadow:0 2px 0 rgba(255,253,248,.35) inset, var(--lx-contact) var(--ly-contact) 0 rgba(22,19,15,.28), calc(var(--light-x)*.9) calc(var(--light-y)*.9) 18px -8px rgba(22,19,15,.45);
  transition:transform .18s, box-shadow .18s}
.btn::after, .cta::after{content:""; position:absolute; inset:0; border-radius:inherit; opacity:.12; background-image:var(--grain); background-size:120px 120px; pointer-events:none}
.btn:hover, .cta:hover{transform:translateY(-2px) rotate(-.6deg); box-shadow:0 2px 0 rgba(255,253,248,.35) inset, var(--lx-contact) var(--ly-contact) 0 rgba(22,19,15,.24), calc(var(--light-x)*1.3) calc(var(--light-y)*1.3) 26px -10px rgba(22,19,15,.55)}
.btn:active, .cta:active{transform:translateY(0)}
.cta small{font-family:var(--label); font-weight:500; font-size:var(--f1); letter-spacing:.06em; padding-left:14px; border-left:1px solid rgba(22,19,15,.3)}
a:focus-visible, .btn:focus-visible, .cta:focus-visible, button:focus-visible{outline:2px solid var(--accent-deep); outline-offset:3px; border-radius:2px}

/* ------------------------------------------------------------ chrome ------ */
/* Sticky from 2026-08-31, Hayden's call: the nav is always there. Rebuilt 2 Sep 2026 on his
   dictated note: a matte near-black strip of the same black paper as the world section, torn
   along its bottom edge, off-white words, the accent kept for "Fixes". Full-bleed on purpose:
   the old cream bar floated inset inside the frame and read as unfinished. */
header.top{position:sticky; top:0; z-index:40; margin:0 calc(50% - 50vw); padding:14px clamp(20px,4vw,48px) 12px;
  background:#211C16; color:var(--paper); display:flex; justify-content:space-between; align-items:baseline; flex-wrap:wrap; gap:4px var(--s3);
  filter:drop-shadow(0 6px 10px rgba(22,19,15,.28))}
header.top::before{content:""; position:absolute; inset:0 0 auto 0; height:100%; opacity:.14; background-image:var(--grain); background-size:120px 120px; pointer-events:none}
/* the torn bottom edge, cut from the same sheet */
header.top::after{content:""; position:absolute; left:0; right:0; bottom:-7px; height:8px; background:#211C16;
  clip-path:polygon(0 0,100% 0,100% 22%,96% 68%,91% 30%,86% 82%,80% 38%,74% 90%,68% 45%,62% 78%,56% 26%,50% 88%,44% 40%,38% 72%,32% 30%,26% 92%,20% 48%,14% 74%,9% 28%,4% 80%,0 34%)}
.wordmark{font-family:var(--display); font-weight:800; font-size:24px; letter-spacing:-0.01em; text-transform:uppercase; text-decoration:none; white-space:nowrap; font-variation-settings:"wdth" var(--wdth-mark); color:var(--paper); position:relative}
.wordmark b{color:var(--accent); font-weight:700}
nav.links{display:flex; gap:var(--s3); font-family:var(--label); font-size:var(--f1); letter-spacing:.06em; text-transform:lowercase; position:relative}
nav.links a{text-decoration:none; color:rgba(243,237,225,.72); padding-bottom:2px; border-bottom:1px solid transparent; transition:color .18s, border-color .18s}
nav.links a:hover, nav.links a[aria-current]{color:var(--paper); border-bottom-color:var(--accent)}
@media (max-width:700px){
  header.top{padding:12px 16px 10px}
  nav.links{width:100%; justify-content:space-between; gap:8px}
  nav.links a{white-space:nowrap; font-size:12px}
}
footer.foot{margin-top:var(--s6); padding-top:var(--s2); border-top:1px solid var(--rule); display:flex; justify-content:space-between; gap:var(--s3); flex-wrap:wrap; font-family:var(--label); font-size:var(--f1); color:var(--muted)}
footer.foot a{color:var(--muted); text-decoration:none}
footer.foot a:hover{color:var(--ink)}
.on-dark footer.foot, .world footer.foot{border-top-color:rgba(243,237,225,.25); color:rgba(243,237,225,.7)}
.on-dark footer.foot a, .world footer.foot a{color:rgba(243,237,225,.7)}

/* --------------------------------------------------------------- prose ---- */
.prose{max-width:66ch}
.prose p{margin-bottom:var(--s2)}
.prose h2{margin:var(--s4) 0 var(--s2)}
.prose ul, .prose ol{padding-left:1.2em; margin-bottom:var(--s2)}
.prose li{margin-bottom:6px}
table.sheet-table{width:100%; border-collapse:collapse; font-size:15.5px; margin:var(--s2) 0 var(--s3)}
table.sheet-table th, table.sheet-table td{text-align:left; vertical-align:top; padding:10px 12px 10px 0; border-bottom:1px solid var(--rule)}
table.sheet-table th{font-family:var(--label); font-size:var(--f1); letter-spacing:.1em; text-transform:uppercase; color:var(--muted); font-weight:500}
table.sheet-table td.num{white-space:nowrap; font-weight:600}
.sheetwrap{overflow-x:auto}

/* ------------------------------------------------------------ annotation ----
   THE MARK RULE, and it decides what may be drawn on a paper page at all.
   Pencil line is honest as ANNOTATION and dishonest as ICONOGRAPHY. A tick, an
   arrow, an underline, a circle or a bracket claims only "somebody marked this
   sheet", which is true of every real sheet that was ever useful. A drawn clock or
   spanner claims "here is a thing, rendered", which stands illustration beside
   letters photographed off real stock: the same category error as a synthesised
   paper texture, one level up. Never depict an object. Mark the page instead.

   And COUNT THE MARKS. Two read as intent, five read as clip art. Three is the
   working ceiling for one view, and adding a fourth means removing one. */
.tick{position:relative; padding-left:30px}
.tick::before{content:""; position:absolute; left:0; top:.42em; width:15px; height:9px;
  border-left:2px solid var(--accent); border-bottom:2px solid var(--accent);
  transform:rotate(-45deg); border-radius:1px}

/* A countable list. Three things inside one sentence read as one thing with commas;
   the same three as items have a count that survives a single pass, three left edges
   and a rule each. Use it wherever a page has to say what someone gets. */
.ticks{list-style:none; margin:0; padding:0; display:grid}
.ticks li{position:relative; padding:12px 0 12px 30px; line-height:1.28;
  border-top:1px solid var(--rule)}
.ticks li:first-child{border-top:0; padding-top:0}
.ticks li::before{content:""; position:absolute; left:0; top:19px; width:15px; height:9px;
  border-left:2px solid var(--accent); border-bottom:2px solid var(--accent);
  transform:rotate(-45deg); border-radius:1px}
.ticks li:first-child::before{top:7px}

/* --------------------------------------------------------------- crumb ------
   A small torn KRAFT note that states one fact and gets out of the way: what lands,
   how many, how long. It exists because a fold can carry a proposition in words and
   still not carry the OBJECT, and a reader who cannot picture the thing does not buy
   it. Kraft on purpose: cream on a cream canvas measures barely better than the
   paragraph it replaced. Hang it in a gutter, never in the reading column. */
.crumb{display:block; width:min(200px,16vw); text-align:left;
  filter:var(--scrap-shadow);
  transform:rotate(-5deg)}
.crumb > span{display:block; background:var(--kraft) var(--paper-kraft, none) center/cover;
  color:var(--ink); clip-path:var(--scrap1); padding:16px 20px 14px}
.crumb.cream > span{background:var(--paper)}
.crumb b{display:block; font-family:var(--display); font-weight:800;
  font-size:clamp(19px,1.62vw,22px); line-height:1.05; letter-spacing:-0.01em;
  font-variation-settings:"wdth" var(--wdth-display)}
.crumb small{display:block; font-family:var(--label); font-size:var(--f1); letter-spacing:.1em;
  text-transform:uppercase; margin-top:7px; color:rgba(22,19,15,.72)}
@media (max-width: 860px){
  .crumb{width:auto; max-width:300px; margin-inline:auto; transform:rotate(-2deg)}
}

/* ---------------------------------------------------------- plate line ------
   A headline whose rows sit SIDE BY SIDE while the column allows it and stack on the
   chosen break when it does not. One wide line of torn paper reads far stronger than
   two ragged ones, and this gets that at 1440 without ever leaving the break to the
   browser. Plates are 260px native: a row taller than that is upscaled and soft, so
   size the clamp off the real widths, never off a guess. */
.plate-line{display:flex; flex-wrap:wrap; align-items:flex-end; justify-content:center;
  gap:.02em .14em; margin:0}
.plate-line .prow{gap:0 .1em}
.plate-line.left{justify-content:flex-start}

/* -------------------------------------------------------------- motion ----
   Transform and opacity only. Staged once on entry, never looping. */
/* `to` names no transform on purpose: a filled animation whose last frame says transform:none
   would pin a .plane (below) flat for ever. Ending on the element's own transform lets an
   entrance glide into its plane pose. */
@keyframes paper-rise{from{opacity:0; transform:translateY(10px)} to{opacity:1}}
@keyframes paper-press{from{opacity:0; transform:rotate(7deg) scale(1.5)} to{opacity:1; transform:rotate(7deg) scale(1)}}
.rv{animation:paper-rise .55s cubic-bezier(.22,1,.36,1) both}
.rv.d1{animation-delay:.06s} .rv.d2{animation-delay:.14s} .rv.d3{animation-delay:.22s}
.rv.d4{animation-delay:.30s} .rv.d5{animation-delay:.38s} .rv.d6{animation-delay:.46s}
.press{animation:paper-press .5s cubic-bezier(.22,1,.36,1) .9s both}

/* ---- DEPTH (3 Sep 2026, the depth lab) ----
   Opt in per view. The container carries data-stage and ONE direct child .stage-inner; each
   layer inside is a .plane with --d, its height above the sheet in px (4 for a caption, 16 for
   the headline plates, 30 for a scrap stuck on top). The pointer moves every plane by its
   height (paper.js sets --px/--py in -1..1 on the stage) and the stage tilts through the view
   as it scrolls, so the planes separate the way a desk does under a moving camera. Proven in
   the lab: distinct displacement per plane, transform that differs at two scroll positions,
   55fps in software Chrome. TRAP: any ancestor with overflow:hidden is a scroll container and
   view() binds to it, frozen, with no error. Clip with overflow:clip. */
[data-stage]{perspective:1400px; perspective-origin:50% 40%}
[data-stage] > .stage-inner{transform-style:preserve-3d; animation:paper-camera linear both; animation-timeline:view(); animation-range:entry 0% exit 100%}
@keyframes paper-camera{from{transform:rotateX(7deg) rotateY(-5deg)} to{transform:rotateX(-4deg) rotateY(4deg)}}
/* The pose is a token, --plane, so an element with a rotation of its own composes it:
   transform:var(--plane) rotate(7deg). Defined on :root as the identity so that rule is valid
   on an element that is not a plane. */
.plane{--plane:translate3d(calc(var(--px,0)*var(--d,0)*1.2px), calc(var(--py,0)*var(--d,0)*1.2px), calc(var(--d,0)*2px)); transform:var(--plane); will-change:transform}
/* the plates land on the sheet from above, one row after the next */
.land img{animation:paper-land .7s cubic-bezier(.22,1,.36,1) both}
.land .prow:nth-child(2) img{animation-delay:.14s} .land .prow:nth-child(3) img{animation-delay:.28s} .land .prow:nth-child(4) img{animation-delay:.42s}
@keyframes paper-land{from{transform:translateZ(160px) rotate(-3deg); opacity:0} 60%{opacity:1} to{transform:translateZ(0) rotate(0); opacity:1}}

@media (prefers-reduced-motion: reduce){
  .rv, .press, .land img, [data-stage] > .stage-inner{animation:none}
  .plane{transform:none}
  .btn:hover, .cta:hover{transform:none}
}

/* ------------------------------------------------------------ responsive -- */
@media (max-width: 860px){
  .cta{width:100%; justify-content:center}
  .world{padding:var(--s5) var(--s2)}
  .note{padding:var(--s3)}
}
@media (max-width: 560px){
  .frame{padding:var(--s2) var(--s2) var(--s4)}
  h1{max-width:none}
}

/* ── print ─────────────────────────────────────────────────────────────────────
   Found 30 Aug 2026 on Three Fixes' questions page: the headline printed as a blank
   two inch band. `.rv` is an entrance that starts at opacity 0, and Chrome takes the
   print snapshot before it finishes, so anything staged prints invisible. Every paper
   surface that a person might print or save as a PDF needs this, so it belongs here
   rather than in one project's stylesheet.

   The canvas is position:fixed, which Chrome repeats on every printed sheet. That is
   wanted: each page lands on paper. */
@media print{
  .rv, .press, .rv.d1, .rv.d2, .rv.d3, .rv.d4, .rv.d5, .rv.d6, .land img, .plane, [data-stage] > .stage-inner, .logomark .bar{
    animation:none !important; opacity:1 !important; transform:none !important;
  }
  /* On a printed sheet the paper is already paper: a plate keeps its contact shadow and drops
     the cast, so a report's PDF does not grow a soft bitmap under every headline word. */
  .plates img{filter:drop-shadow(var(--lx-contact) var(--ly-contact) 1px rgba(22,19,15,.45))}
  /* The canvas does NOT print. It is position:fixed, so Chrome embeds the whole photograph
     again on every sheet: a twelve page Three Fixes report came out at 40MB, which no owner
     can be emailed and nobody wants to download. The paper colour carries the look, and on
     an actual sheet the paper is already paper. Measured 30 Aug 2026: 40MB down to 300KB. */
  body::before, body::after{display:none !important}
}

/* ---- THE LOGO MARK (added 2 Sep 2026, the Three Fixes logo lane; colours re-cut 6 Sep 2026) ----
   Three rules, the top two in the accent, the third shorter and in the ground's opposite: ink on
   paper, paper on black (Hayden, 6 Sep 2026: "the top two yellow, then the white or black third
   shorter line"). Brand-generic because it is built from the four tokens; a brand that wants
   a different mark overrides .logomark's children, never the animation. Inline the SVG:
     <svg class="logomark" viewBox="0 0 64 64"><rect class="bar" .../><rect class="bar" .../><rect class="bar" .../></svg>
   and the rules draw in one, two, three. Respects reduced motion. Static copies (a favicon, a
   PDF header) use the same geometry from ~/.claude/skills/paper-ui/brand/<brand>/. */
.logomark{width:34px; height:34px; display:inline-block; vertical-align:middle}
.logomark .bar{fill:var(--accent); transform-origin:left center}
.logomark .bar:nth-child(3){fill:var(--ink)}
.on-dark .logomark .bar, .world .logomark .bar{fill:var(--accent)}
.on-dark .logomark .bar:nth-child(3), .world .logomark .bar:nth-child(3){fill:var(--paper)}
@media (prefers-reduced-motion:no-preference){
  .logomark .bar{transform:scaleX(0); animation:mark-draw .55s cubic-bezier(.2,.8,.2,1) forwards}
  .logomark .bar:nth-child(1){animation-delay:.1s} .logomark .bar:nth-child(2){animation-delay:.4s} .logomark .bar:nth-child(3){animation-delay:.75s}
}
@keyframes mark-draw{to{transform:scaleX(1)}}
