/* Stacy Dean Campbell — site styles
   Ground: the concrete of the jacket. Panels: paper laid on it.
   Display: Anton (stamped). Prose: Fraunces. Furniture: Inter. */

:root{
  /* THE NAV BREAKPOINT, published so the script does not keep its own copy.

     It did: the CSS said 825 and site.js said 826, two numbers that had to
     agree and nothing making them. Raising one silently left the other behind,
     so widening the window past the real breakpoint no longer closed the open
     panel. site.js now reads this value.

     The width itself is measured, not chosen -- see the note above the nav
     media query. */
  --bp-nav: 856px;

  /* One palette, and it is dark. The site used to follow the reader's OS, which
     meant the same page arrived as two quite different objects and only one of
     them had been looked at properly. This is the one that was designed.

     The layering rule still holds and is the whole reason it works: the ground
     is the floor and every surface steps UP from it, about 1.2:1 a step. Every
     text token clears 4.5:1 on every surface here.

     The one exception is /where-the-heathen-rage/first-chapter/, which pins the
     light values on body.reading in reader.css. A novel is printed on white
     stock, and a chapter is for reading rather than for looking at. */
  --concrete:#0B0A09; --concrete-2:#070706; --concrete-3:#141210;
  --paper:#2C2824; --paper-2:#3A3530;
  --ink:#EDEAE2; --ink-2:#CBC7BC; --ink-soft:#A8A296;
  --rule:#4A453C; --rule-soft:#332F2A;
  --oxide:#CE7157; --oxide-hi:#E08C71; --ochre:#C9A055;
  --on-oxide:#1B0E09; --oxide-txt:#E08C71;
  --slab:#1C1916;
  --plate:#0B0A09;
  --shadow:rgba(0,0,0,.6); --shadow-deep:rgba(0,0,0,.8);
  /* Text and accents that sit ON a dark surface — the plate, the banner, the
     capture panel. Dropped by accident when :root was replaced to make the site
     dark-only, which left 32 declarations referencing tokens that no longer
     existed. A var() on an undefined property is invalid at computed-value
     time, so the declaration resolves to `unset` rather than being ignored —
     and because `form.signup input:focus-visible` is MORE specific than the
     global :focus-visible, it did not fail to add a focus ring, it removed the
     one the site already had. On the primary signup field. */
  --ochre-slab:#C9A055;
  --on-dark:#F2F0EB; --on-dark-2:#C3BFB4; --on-dark-3:#8C8779;
  --dark-field:#131110;
  /* 3:1 minimum for a control boundary (WCAG 2.2 SC 1.4.11). #4A453C on
     #131110 measured 1.98:1 — the field had no discernible edge. */
  --dark-edge:#A8A296;
  --display:"Anton",Impact,"Arial Narrow",sans-serif;
  --prose:"Fraunces",Georgia,"Times New Roman",serif;
  --ui:"Inter",-apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif;
  --fs-3xs:.6875rem; --fs-2xs:.75rem; --fs-xs:.8125rem; --fs-sm:.875rem; --fs-base:1rem; --fs-md:1.0625rem; --fs-lg:1.15rem; --fs-xl:1.3rem;

  /* Display sizes. There was a scale for body text and none for headings, so
     every heading had been given its own hand-written clamp -- nine of them,
     two near-identical. These are the roles the design actually has. A heading
     picks a role; it does not invent a size. */
  /* The hero keeps a responsive step rather than one smooth curve. Collapsing
     its three clamps into a single line was tempting and wrong: it matched at
     375px and 1440px and drifted up to 20px in between, which is a redesign of
     the loudest thing on the page dressed up as a tidy-up. The steps move into
     the token, so the role is named in one place and what renders is unchanged. */
  --ds-hero:clamp(3.1rem, 10vw, 6.3rem);             /* the one title on a page */
  /* These are the curves the design already had, given names. Re-deriving them
     "properly" was the second temptation and also wrong: new coefficients mean
     new interpolated sizes at every width, so tidying the source made the page
     render MORE distinct sizes than before, not fewer. Naming a role is the
     work; inventing a size is a redesign. */
  --ds-title:clamp(2.4rem, 7vw, 4.4rem);      /* section-opening titles */
  --ds-section:clamp(2rem, 5vw, 3.2rem);      /* a thesis statement */
  --ds-head:clamp(1.55rem, 3.4vw, 2.2rem);    /* ordinary section heads */
  --ds-quote:clamp(1.3rem, 3.4vw, 2.05rem);   /* a pulled quotation */
  --ds-lead:clamp(1.05rem, 2.1vw, 1.3rem);    /* a standfirst */

  /* Spacing, on a 4px grid. 38 distinct values were rendering, 29 of them off
     any grid -- 0.15rem here, 0.45rem there, 0.78rem somewhere else. Each was
     defensible alone and together they are not a system. New work uses these. */
  --s-1:.25rem; --s-2:.5rem;  --s-3:.75rem; --s-4:1rem;
  --s-5:1.5rem; --s-6:2rem;   --s-7:3rem;   --s-8:4rem; --s-9:6rem;
  --wrap:1160px; --gut:clamp(1.25rem,4vw,3rem);
}

*{box-sizing:border-box}
/* The header is sticky and 63.6px tall: without this, tabbing backwards to a
   control scrolls it to top:0 and puts it entirely underneath the header. */
html{scroll-padding-top:5rem;scroll-behavior:smooth}
body{
  margin:0;background:var(--concrete);color:var(--ink);
  font-family:var(--ui);font-size:16px;line-height:1.6;-webkit-font-smoothing:antialiased;
  background-image:repeating-linear-gradient(101deg,rgba(0,0,0,.012) 0 1px,transparent 1px 4px),
                   repeating-linear-gradient(11deg,rgba(255,255,255,.014) 0 1px,transparent 1px 6px);
}
img{max-width:100%;height:auto;display:block}
/* the two places an image is deliberately sized by its box rather than its own
   proportions; both use object-fit so nothing distorts */
.plate picture.bgplate img,.banner img{height:100%}
a{color:inherit}
:focus-visible{outline:2px solid var(--oxide);outline-offset:3px}
@media (prefers-reduced-motion:reduce){*{animation:none!important;transition:none!important}html{scroll-behavior:auto}}
.wrap{max-width:var(--wrap);margin-inline:auto;padding-inline:var(--gut)}
hr.rule{height:1px;background:var(--rule);border:0;margin:0}
.skip{position:absolute;left:-9999px}
/* A label that is read but not seen. */
.vh{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
.skip:focus{left:1rem;top:1rem;z-index:99;background:var(--ink);color:var(--concrete);padding:var(--s-3) 1rem}

.stamp{font-family:var(--display);font-weight:400;text-transform:uppercase;letter-spacing:.005em;line-height:.86;text-wrap:balance}
.eyebrow{font-family:var(--ui);font-size:var(--fs-2xs);font-weight:600;letter-spacing:.16em;text-transform:uppercase;color:var(--ochre);margin:0}
.prose{font-family:var(--prose);font-size:var(--fs-md);line-height:1.72;color:var(--ink-2);max-width:64ch}
.prose p{margin:0 0 1.05em}.prose p:last-child{margin-bottom:0}
.prose strong{color:var(--ink);font-weight:600}

/* masthead */
.epigraph{background:var(--slab);color:var(--on-dark-3);border-bottom:1px solid var(--dark-edge)}
.epigraph p{margin:0;padding-block:.6rem;font-family:var(--prose);font-style:italic;font-size:var(--fs-sm);text-align:center;line-height:1.4}
.epigraph b{color:var(--on-dark-2);font-style:normal;font-weight:500;font-family:var(--ui);font-size:var(--fs-3xs);letter-spacing:.12em;text-transform:uppercase}
header.site{position:sticky;top:0;z-index:60;background:color-mix(in srgb,var(--concrete) 90%,transparent);backdrop-filter:saturate(1.15) blur(12px);border-bottom:1px solid var(--rule)}
.bar{display:flex;align-items:center;gap:1.5rem;padding-block:.85rem;justify-content:space-between;flex-wrap:wrap}
.mark{font-family:var(--display);text-transform:uppercase;font-size:var(--fs-lg);letter-spacing:.015em;text-decoration:none;white-space:nowrap;line-height:1;display:inline-flex;align-items:center;min-height:30px}
.mark i{color:var(--oxide-txt);font-style:normal}
nav.main{display:flex;gap:1.35rem;flex-wrap:wrap}
nav.main a{font-family:var(--ui);font-size:var(--fs-2xs);font-weight:500;letter-spacing:.11em;text-transform:uppercase;text-decoration:none;border-bottom:1px solid transparent;padding-bottom:3px;color:var(--ink-soft)}
nav.main a[aria-current="page"]{color:var(--ink);border-bottom-color:var(--ink);font-weight:600}
/* ---- the menu on a phone -------------------------------------------------
   Seven links need 540px. A 390px phone has about 184px once the wordmark and
   the gutters are paid for, so they wrapped onto four rows and the header ate
   the top of every page before a word of it was read. They fit on one row only
   at 856px, which is where the toggle hands over.

   THE NUMBER IS MEASURED, NOT CHOSEN. On 2026-09-05 the desktop nav was forced
   visible at every width from 1100px down and the row's line count watched:
   seven links plus the wordmark fit on one row down to 837px and wrap at 836.
   The breakpoint was 826, so from 826 to 836 the site showed a desktop nav
   that did not fit -- it wrapped to a second row and the header silently went
   from 64px to 118px. 856 gives the measured requirement 19px of slack for
   font-loading variance and for a link whose label gets longer later.

   Re-measure with scratchpad/edge2.mjs if the nav's contents change. A
   breakpoint that is not derived from the content is a guess that goes stale
   the first time somebody adds a link.

   The button is markup, not script: with JS off the nav is simply visible,
   which is why the panel is hidden by a class on the header rather than by
   `display:none` on the nav itself. */
.navtoggle{display:none}
@media (max-width:855px){
  .bar{gap:var(--s-3);flex-wrap:nowrap}
  .navtoggle{
    display:flex;flex-direction:column;justify-content:center;gap:5px;
    /* flex:none, or the wordmark beside it compresses the button below the
       24x24 minimum — measured at 23x44 on the reader page before this. A
       width alone is a suggestion inside a flex row. */
    flex:0 0 44px;
    width:44px;height:44px;padding:0 10px;margin-inline-start:auto;
    background:none;border:0;cursor:pointer;
  }
  .navtoggle span{display:block;height:2px;background:var(--ink);transition:transform .18s ease,opacity .18s ease}
  .navtoggle[aria-expanded="true"] span:nth-child(1){transform:translateY(7px) rotate(45deg)}
  .navtoggle[aria-expanded="true"] span:nth-child(2){opacity:0}
  .navtoggle[aria-expanded="true"] span:nth-child(3){transform:translateY(-7px) rotate(-45deg)}
  /* Only hidden once the script is there to reveal it again. */
  html.js nav.main{display:none}
  html.js .bar.open nav.main{
    display:flex;flex-direction:column;gap:0;
    position:absolute;left:0;right:0;top:100%;
    background:var(--concrete);border-bottom:1px solid var(--rule);
    padding:.35rem var(--gut) 1rem;
  }
  html.js .bar.open nav.main a{
    padding:.9rem 0;border-bottom:1px solid var(--rule-soft);
    font-size:var(--fs-xs);min-height:44px;display:flex;align-items:center;
  }
  html.js .bar.open nav.main a:last-child{border-bottom:0}
  header.site{position:sticky}
  .bar{position:relative}
}
@media (prefers-reduced-motion:reduce){.navtoggle span{transition:none}}


.sec{padding-block:clamp(2.75rem,6vw,4.75rem)}
.sec-tight{padding-block:0 clamp(2.75rem,6vw,4.75rem)}
.sec-head{display:flex;align-items:baseline;gap:1.15rem;margin-bottom:1.9rem;flex-wrap:wrap}
.sec-head h2{font-family:var(--prose);font-weight:600;font-size:var(--ds-head);margin:0;line-height:1.12;letter-spacing:-.015em}
.sec-head hr{flex:1;align-self:center;min-width:2rem}
.panel{background:var(--paper);border:1px solid var(--rule-soft);box-shadow:0 10px 26px -16px var(--shadow);padding:clamp(1.4rem,3.4vw,2.4rem)}

.btn{display:inline-flex;align-items:center;gap:var(--s-2);font-family:var(--ui);font-size:var(--fs-xs);font-weight:600;letter-spacing:.09em;text-transform:uppercase;padding:.82rem 1.35rem;text-decoration:none;border:1px solid var(--ink);cursor:pointer;background:none;color:var(--ink);transition:transform .12s ease,background .12s ease,color .12s ease}
.btn-solid{background:var(--oxide);border-color:var(--oxide);color:var(--on-oxide)}
.cta-row{display:flex;flex-wrap:wrap;gap:var(--s-3);margin-top:1.8rem}

/* hero on an archive plate */
.plate{position:relative;background:var(--plate);border-bottom:1px solid var(--dark-edge);overflow:hidden;isolation:isolate}
/* Only the right 1336px of the source survived the old composite, so the photograph is given the
   right of the band at close to native resolution rather than stretched across the full width and
   hidden under the copy panel. Magnification drops from 2.16x to about 1.3x. */
.plate picture.bgplate{position:absolute;top:0;right:0;height:100%;width:68%;z-index:0;display:block;
  -webkit-mask-image:linear-gradient(90deg,transparent 0,rgba(0,0,0,.55) 10%,#000 30%);
          mask-image:linear-gradient(90deg,transparent 0,rgba(0,0,0,.55) 10%,#000 30%)}
.plate picture.bgplate img{width:100%;height:100%;object-fit:cover;object-position:62% 50%;filter:contrast(1.03) saturate(1.04)}
/* The tablet branch is a RAMP that meets the desktop curve, not a cap.
   It was clamp(2.6rem, 12vw, 4rem), which hit its 64px ceiling at 534px and
   held it all the way to 880 -- a 346px stretch with no growth -- and then the
   desktop curve took over at 881 with 88.1px. A 37.7% jump across one pixel,
   on the title of the page.
   calc(1.05rem + 8.02vw) is the line through (421, 50.6px) and (880, 87.4px):
   it leaves the phone branch where it was and arrives within 0.7px of the
   desktop curve. Worst step across any boundary is now 1.7%. */
@media (max-width:880px){ :root{ --ds-hero:clamp(2.6rem, calc(1.05rem + 8.02vw), 5.5rem); } }
@media (max-width:420px){ :root{ --ds-hero:clamp(2.3rem, 13vw, 3.2rem); } }
@media (max-width:880px){.plate picture.bgplate{width:100%;-webkit-mask-image:none;mask-image:none}}
.plate::after{content:"";position:absolute;inset:0;z-index:1;background:
  linear-gradient(95deg,rgba(9,8,7,.97) 0%,rgba(9,8,7,.93) 26%,rgba(9,8,7,.72) 42%,rgba(9,8,7,.34) 58%,rgba(9,8,7,.12) 76%,rgba(9,8,7,.06) 100%),
  linear-gradient(to top,rgba(9,8,7,.72) 0%,rgba(9,8,7,.28) 16%,rgba(9,8,7,0) 40%)}
/* on a phone the copy stacks at the TOP of the band, so the shading has to run the
   other way - dark where the type is, easing off over the jacket below. */
@media (max-width:880px){.plate::after{background:
  linear-gradient(to bottom,rgba(9,8,7,.96) 0%,rgba(9,8,7,.94) 55%,rgba(9,8,7,.88) 78%,rgba(9,8,7,.72) 100%)}}
.plate .wrap{position:relative;z-index:2}
.hero-grid{display:grid;grid-template-columns:1.08fr .92fr;gap:clamp(1.75rem,5vw,4.25rem);align-items:center}
@media (max-width:880px){.hero-grid{grid-template-columns:1fr;gap:2.25rem}}
.plate .hero-grid{padding-block:clamp(2.75rem,6.5vw,5.25rem) clamp(3.25rem,7vw,5.5rem)}
.plate .hero-grid>div:first-child{position:relative;padding-block:.25rem}
.plate h1,.plate .hook,.plate .pubmeta,.plate .eyebrow{text-shadow:0 1px 14px rgba(9,8,7,.55)}
.plate h1{color:var(--on-dark);margin:.9rem 0 0;font-size:var(--ds-hero)}
.plate .hook{color:var(--on-dark);border-left-color:var(--ochre-slab)}
.plate .pubmeta{color:var(--on-dark-2);border-top-color:rgba(242,240,235,.26)}
.plate .pubmeta b{color:var(--on-dark)}
.plate .pubmeta s{color:var(--on-dark-3)}
.plate .eyebrow{color:var(--ochre-slab)}
.plate .btn-ghost{color:var(--on-dark);border-color:var(--on-dark)}
.credit{position:relative;z-index:2;margin:0;padding-bottom:1.1rem;font-size:var(--fs-3xs);line-height:1.5;letter-spacing:.04em;color:#B7B1A5;max-width:64ch;text-shadow:0 1px 3px rgba(9,8,7,.9)}
.credit em{font-style:italic}
.hook{font-family:var(--prose);font-size:var(--ds-lead);line-height:1.5;color:var(--ink);margin:1.5rem 0 0;max-width:40ch;border-left:3px solid var(--oxide);padding-left:1.1rem}
.pubmeta{display:flex;flex-wrap:wrap;gap:.4rem .85rem;align-items:center;margin-top:1.5rem;padding-top:1rem;border-top:1px solid var(--rule);font-size:var(--fs-2xs);letter-spacing:.09em;text-transform:uppercase;color:var(--ink-soft);font-weight:500}
.pubmeta b{color:var(--ink);font-weight:600}
.pubmeta s{color:var(--rule);text-decoration:none}
.cover-stage{display:flex;justify-content:center}
/* align-self, not the default stretch. The cover sits inside a <picture>
   that is a row flex container; a stretched flex item takes its cross size
   from the line, and an image with an aspect-ratio then derives its WIDTH
   from that height — which is zero until something gives the line a height.
   The base rule sets an explicit width so it never showed; the narrow
   override switches to width:auto and the cover collapses to nothing. */
/* object-fit, because a max-height and a definite width together describe a
   box that is not the cover's shape: 240 wide against a 42vh cap gives 240x312
   for a jacket that is 700x1066, and without this the artwork is squashed to
   fit it. Contain keeps the proportions and gives back the difference. */
.cover-stage img{align-self:center;object-fit:contain;width:min(100%,360px);filter:drop-shadow(0 24px 36px var(--shadow-deep)) drop-shadow(0 4px 10px var(--shadow))}

/* page banner for interior pages */
.banner{position:relative;background:var(--plate);overflow:hidden;border-bottom:1px solid var(--dark-edge)}
.banner img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;object-position:50% 55%;filter:contrast(1.04) brightness(1);z-index:0}
.banner::after{content:"";position:absolute;inset:0;z-index:1;background:linear-gradient(97deg,rgba(10,9,8,.90) 0%,rgba(10,9,8,.70) 46%,rgba(10,9,8,.28) 100%)}
.banner .wrap{position:relative;z-index:2;padding-block:clamp(2.25rem,5.5vw,3.75rem)}
.banner h1{color:var(--on-dark);margin:var(--s-3) 0 0;font-size:var(--ds-title)}
.banner .eyebrow{color:var(--ochre-slab)}
.banner .lede{font-family:var(--prose);color:var(--on-dark-2);font-size:var(--fs-md);max-width:56ch;margin:1rem 0 0;line-height:1.6}

/* quotes */
.band{background:var(--concrete-2);border-block:1px solid var(--rule)}
.quotes{display:grid;grid-template-columns:repeat(3,1fr);padding-block:clamp(1.9rem,4vw,2.9rem);gap:0}
@media (max-width:880px){.quotes{grid-template-columns:1fr}}
.q{padding:0 clamp(1rem,2.4vw,2rem);border-left:1px solid var(--rule);display:flex;flex-direction:column}
.q:first-child{border-left:0;padding-left:0}
@media (max-width:880px){.q{border-left:0;padding-left:0;padding-block:1.3rem;border-top:1px solid var(--rule)}.q:first-child{border-top:0;padding-top:0}}
.q blockquote{margin:0;font-family:var(--prose);font-size:var(--fs-base);line-height:1.52;color:var(--ink);text-wrap:pretty}
.q cite{display:block;margin-top:auto;padding-top:var(--s-3);font-family:var(--ui);font-style:normal;font-size:var(--fs-3xs);font-weight:600;letter-spacing:.14em;text-transform:uppercase;color:var(--ink-soft)}

/* thesis */
.thesis{background:var(--slab);color:var(--on-dark)}
.thesis-in{display:grid;grid-template-columns:1fr 1fr;gap:clamp(1.5rem,4vw,3.5rem);align-items:center;padding-block:clamp(2.5rem,5.5vw,4rem)}
@media (max-width:880px){.thesis-in{grid-template-columns:1fr}}
.thesis .eyebrow{color:var(--ochre-slab)}
.thesis h2{font-family:var(--display);text-transform:uppercase;font-size:var(--ds-section);line-height:.92;margin:var(--s-3) 0 0;color:var(--on-dark)}
.thesis p{font-family:var(--prose);font-size:var(--fs-md);line-height:1.68;color:var(--on-dark-2);margin:1.1rem 0 0;max-width:46ch}
.mediums{display:grid;grid-template-columns:repeat(2,1fr);gap:1px;background:var(--dark-edge);border:1px solid var(--dark-edge)}
.med{background:var(--slab);padding:1.15rem 1.1rem}
.med .n{font-family:var(--display);text-transform:uppercase;font-size:var(--fs-lg);color:var(--on-dark);line-height:1}
.med .d{font-size:var(--fs-2xs);color:var(--on-dark-3);margin-top:.4rem;line-height:1.45}
.med .d b{color:var(--ochre-slab);font-weight:600}

/* work index */
.filters{display:flex;gap:.5rem;flex-wrap:wrap;margin-bottom:1.4rem}
.filters button{font-family:var(--ui);font-size:var(--fs-3xs);font-weight:600;letter-spacing:.11em;text-transform:uppercase;padding:.5rem .85rem;border:1px solid var(--rule);background:none;color:var(--ink-soft);cursor:pointer;transition:background .12s ease,color .12s ease,border-color .12s ease}
.filters button[aria-pressed="true"]{background:var(--ink);border-color:var(--ink);color:var(--concrete)}
.work{border-top:1px solid var(--rule)}
.row{display:grid;grid-template-columns:5.5rem 1fr 9rem 10rem;gap:1rem 1.25rem;align-items:baseline;padding:1rem 0;border-bottom:1px solid var(--rule-soft)}
@media (max-width:820px){.row{grid-template-columns:4.5rem 1fr;gap:var(--s-1) .9rem}.row .med-col,.row .note{grid-column:2}}
.row .yr{font-family:var(--ui);font-size:var(--fs-xs);font-weight:600;color:var(--ink-soft);letter-spacing:.04em;font-variant-numeric:tabular-nums}
.row .ttl{font-family:var(--prose);font-size:var(--fs-lg);font-weight:600;color:var(--ink);line-height:1.25}
.row .ttl small{display:block;font-family:var(--ui);font-weight:400;font-size:var(--fs-2xs);color:var(--ink-soft);margin-top:var(--s-1)}
.row .med-col{font-size:var(--fs-3xs);font-weight:600;letter-spacing:.13em;text-transform:uppercase;color:var(--ochre)}
.row .note{font-size:var(--fs-xs);color:var(--ink-2);line-height:1.45}
.row.is-hidden{display:none}

/* capture, with the signature */
.capture{background:var(--slab);border:0;color:var(--on-dark-2)}
.capture .eyebrow{color:var(--ochre-slab)}
.capture-grid{display:grid;grid-template-columns:1fr 1fr;gap:clamp(1.4rem,4vw,3.25rem);align-items:center}
@media (max-width:880px){.capture-grid{grid-template-columns:1fr}}
.capture h2{font-family:var(--prose);font-weight:600;font-size:var(--ds-head);margin:.6rem 0 0;line-height:1.15;color:var(--on-dark)}
.capture p{font-family:var(--prose);font-size:var(--fs-base);line-height:1.65;color:var(--on-dark-2);margin:.9rem 0 0;max-width:42ch}
.sig{width:min(100%,300px);height:64px;margin-top:1.6rem;background-color:var(--ochre-slab);
  -webkit-mask:url(/assets/img/signature.png) left center/contain no-repeat;
          mask:url(/assets/img/signature.png) left center/contain no-repeat}
.sig-ink{background-color:var(--ink)}
.sig-lg{height:96px;width:min(100%,400px);opacity:.95}
form.signup{display:flex;flex-direction:column;gap:.65rem}
.field-row{display:flex;gap:var(--s-2);flex-wrap:wrap}
form.signup input[type=email]{flex:1 1 14rem;min-width:0;font-family:var(--ui);font-size:var(--fs-base);padding:.85rem 1rem;background:var(--dark-field);border:1px solid var(--dark-edge);color:var(--on-dark)}
form.signup input[type=email]::placeholder{color:var(--on-dark-3)}
form.signup input[type=email]:focus-visible{border-color:var(--ochre-slab);outline:2px solid var(--ochre-slab);outline-offset:2px}
form.signup input[type=email]:focus{border-color:var(--ochre-slab)}
form.signup button{font-family:var(--ui);font-size:var(--fs-xs);font-weight:600;letter-spacing:.09em;text-transform:uppercase;padding:.85rem 1.4rem;background:var(--oxide);color:var(--on-oxide);border:1px solid var(--oxide);cursor:pointer}
.consent{font-size:var(--fs-2xs);color:var(--on-dark-3);line-height:1.55;margin:.1rem 0 0}
.hp{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}
.said{font-family:var(--prose);font-size:var(--fs-base);color:var(--ochre-slab);margin:var(--s-3) 0 0;min-height:1.4em}

/* buy */
.buy{display:flex;flex-direction:column;gap:var(--s-2)}
.buy a{display:flex;align-items:center;justify-content:space-between;gap:1rem;padding:var(--s-3) 1rem;text-decoration:none;background:var(--paper-2);border:1px solid var(--rule-soft);font-size:var(--fs-xs);font-weight:600;letter-spacing:.05em;text-transform:uppercase;transition:background .12s ease,border-color .12s ease}
.buy a span{color:var(--oxide-txt);font-weight:400}
.note-sm{font-size:var(--fs-2xs);color:var(--ink-soft);margin:.85rem 0 0;line-height:1.55}

/* media */
.bw{filter:grayscale(1) contrast(1.05);border:1px solid var(--rule);box-shadow:0 14px 32px -20px var(--shadow-deep)}
.split{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:clamp(1.5rem,4vw,3.25rem);align-items:center}
.split-narrow{grid-template-columns:minmax(0,360px) 1fr;align-items:start}
/* The biography runs far longer than the portrait beside it, so the left column
   was ending in mid-air and the two stopped reading as one block. Holding it in
   view keeps the face with the words it belongs to. */
@media (min-width:821px){
  .split-narrow>div:first-child{position:sticky;top:5.5rem}
}
@media (max-width:820px){.split,.split-narrow{grid-template-columns:1fr}}
.creds{list-style:none;margin:1.15rem 0 0;padding:0;border-top:1px solid var(--rule-soft)}
.creds li{display:flex;gap:1rem;align-items:baseline;padding:.65rem 0;border-bottom:1px solid var(--rule-soft);font-size:var(--fs-sm);color:var(--ink-2)}
.creds .k{font-family:var(--display);text-transform:uppercase;font-size:var(--fs-sm);color:var(--ink);flex:0 0 5.75rem;line-height:1.2}
.cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(230px,1fr));gap:1.15rem}
.card{background:var(--paper);border:1px solid var(--rule-soft);padding:1.4rem;display:flex;flex-direction:column;gap:.5rem}
.card h3{font-family:var(--prose);font-weight:600;font-size:var(--fs-lg);margin:0;line-height:1.22}
.card .kind{font-size:var(--fs-3xs);font-weight:600;letter-spacing:.14em;text-transform:uppercase;color:var(--ochre)}
.card p{margin:0;font-size:var(--fs-sm);color:var(--ink-soft);line-height:1.58}
.card .foot{margin-top:auto;padding-top:.85rem;border-top:1px solid var(--rule-soft);font-size:var(--fs-2xs);color:var(--ink-2);font-weight:500}
.duo{display:grid;grid-template-columns:1fr 1fr;gap:1.15rem}
@media (max-width:820px){.duo{grid-template-columns:1fr}}
.duo .panel{display:flex;flex-direction:column;gap:var(--s-3)}
.duo h3{font-family:var(--prose);font-weight:600;font-size:var(--fs-xl);margin:var(--s-1) 0 0;line-height:1.2}
.duo p{margin:0;font-family:var(--prose);font-size:var(--fs-base);color:var(--ink-2);line-height:1.62}
.link-arrow{margin-top:auto;padding-top:.9rem;font-size:var(--fs-2xs);font-weight:600;letter-spacing:.1em;text-transform:uppercase;color:var(--oxide-txt);text-decoration:none;display:inline-flex;gap:var(--s-2);align-items:center;font-family:var(--ui)}

.shelf{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:clamp(1.1rem,2.5vw,2rem);align-items:end}
.shelf figure{margin:0;display:flex;flex-direction:column;gap:.85rem;align-items:flex-start}
/* NOT display:contents. A <picture> holds a <source> as well as the <img>, and
   display:contents promotes both to grid items — the invisible <source> then
   silently occupies a grid cell and shunts the picture into the next column. */
.shelf picture,.cover-stage picture,.split picture,.split-narrow picture{display:block;min-width:0}
.split>picture,.split-narrow>picture{align-self:center}
.cover-stage>picture{display:flex;justify-content:center;width:100%}
/* one height for every jacket so they stand on a common baseline; widths follow each book's real
   proportions and nothing is cropped. */
.shelf img{height:clamp(178px,20vw,272px);width:auto;max-width:100%;object-fit:contain;
  border:1px solid var(--rule);box-shadow:0 14px 28px -14px var(--shadow-deep)}
.nocover{aspect-ratio:2/3;border:1px dashed var(--rule);background:repeating-linear-gradient(45deg,transparent 0 7px,color-mix(in srgb,var(--rule) 22%,transparent) 7px 8px);display:grid;place-items:center;text-align:center}
.nocover span{font-family:var(--ui);font-size:var(--fs-3xs);font-weight:600;letter-spacing:.13em;text-transform:uppercase;color:var(--ink-soft);line-height:1.5}
.shelf figcaption{font-size:var(--fs-2xs);line-height:1.4;color:var(--ink-soft)}
.shelf figure{padding-top:var(--s-1)}
.shelf figcaption b{display:block;font-family:var(--prose);font-size:var(--fs-base);font-weight:600;color:var(--ink);margin-bottom:.15rem}

.pull-big{font-family:var(--prose);font-size:var(--ds-quote);line-height:1.34;color:var(--ink);margin:0;max-width:24ch;text-wrap:balance}
.pull-big cite{display:block;margin-top:1rem;font-family:var(--ui);font-style:normal;font-size:var(--fs-3xs);font-weight:600;letter-spacing:.14em;text-transform:uppercase;color:var(--ink-soft)}

/* embeds */
.embed{border:1px solid var(--rule-soft);background:var(--paper-2);padding:.4rem;box-shadow:0 10px 26px -18px var(--shadow)}
.embed iframe{display:block;width:100%;border:0;border-radius:10px}
.embed-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(330px,1fr));gap:1.1rem}
.rec{display:flex;flex-direction:column;gap:.6rem;min-width:0}
.rec h3{margin:0;font-family:var(--prose);font-size:var(--fs-md);font-weight:600;color:var(--ink)}
.rec h3 span{display:block;font-family:var(--ui);font-size:var(--fs-3xs);font-weight:500;letter-spacing:.11em;text-transform:uppercase;color:var(--ink-soft);margin-top:var(--s-1)}

/* contact */
.cform{display:grid;gap:var(--s-3);max-width:34rem}
.cform label{font-size:var(--fs-3xs);font-weight:600;letter-spacing:.13em;text-transform:uppercase;color:var(--ink-soft)}
.cform input,.cform textarea{font-family:var(--ui);font-size:var(--fs-base);padding:.75rem .9rem;background:var(--paper);border:1px solid var(--rule);color:var(--ink);width:100%}
.cform textarea{min-height:8rem;resize:vertical}
.cform input:focus-visible,.cform textarea:focus-visible{border-color:var(--oxide);outline:2px solid var(--oxide);outline-offset:2px}
.cform input:focus,.cform textarea:focus{border-color:var(--oxide)}

/* footer */
footer.site{background:var(--concrete-2);border-top:1px solid var(--rule);padding-block:clamp(2.25rem,5vw,3.25rem);margin-top:clamp(2rem,5vw,3rem)}
.foot-grid{display:grid;grid-template-columns:1.5fr 1fr 1fr 1fr;gap:1.75rem}
@media (max-width:820px){.foot-grid{grid-template-columns:1fr 1fr;gap:1.5rem}}
@media (max-width:520px){.foot-grid{grid-template-columns:1fr}}
.foot-grid h3{font-size:var(--fs-3xs);font-weight:600;letter-spacing:.14em;text-transform:uppercase;color:var(--ink-soft);margin:0 0 var(--s-3)}
.foot-links{list-style:none;margin:0;padding:0;display:grid;gap:var(--s-2)}
/* A list of links is not "inline in a sentence", so WCAG 2.2 SC 2.5.8's
   exemption does not cover it: each one needs 24px of its own, at every width
   rather than only under the phone breakpoint. */
.foot-links a{display:inline-flex;align-items:center;min-height:24px;
  font-size:var(--fs-sm);text-decoration:none;color:var(--ink-2)}
.colophon{font-size:var(--fs-2xs);color:var(--ink-soft);line-height:1.6;margin:2.25rem 0 0;padding-top:1.15rem;border-top:1px solid var(--rule)}

/* direct-answer block and FAQ */
.answer{background:var(--concrete-3);border-bottom:1px solid var(--rule)}
.answer p{margin:0;padding-block:clamp(1.5rem,3vw,2.1rem);font-family:var(--prose);font-size:var(--fs-md);line-height:1.68;color:var(--ink-2);max-width:78ch}
.answer b{color:var(--ink);font-weight:600}
.faq{display:grid;gap:0;border-top:1px solid var(--rule)}
.fq{padding:1.1rem 0;border-bottom:1px solid var(--rule-soft)}
.fq h3{margin:0 0 .35rem;font-family:var(--prose);font-size:var(--fs-md);font-weight:600;color:var(--ink);line-height:1.3}
.fq p{margin:0;font-family:var(--prose);font-size:var(--fs-base);line-height:1.62;color:var(--ink-2);max-width:72ch}

/* WCAG 2.2 target size: give every tap target at least 24px of height, and more on
   touch screens. This readership is 50-70 and largely on phones. */
nav.main a{padding:var(--s-2) 0 .5rem;display:inline-flex;align-items:center;min-height:26px}
.foot-links a{display:inline-flex;align-items:center;min-height:26px;padding-block:.15rem}
.buy a{min-height:44px}
.filters button{min-height:34px}
.link-arrow{min-height:26px}
@media (hover:none) and (pointer:coarse){
  nav.main{gap:1.1rem .95rem}
  nav.main a{min-height:34px;padding:.6rem 0}
  .foot-links{gap:.15rem}
  .foot-links a{min-height:38px}
  .link-arrow{min-height:38px}
}

/* ---- phones ---- */
@media (max-width:880px){
  /* the jacket was filling the screen and running off the bottom */
  .cover-stage{padding-top:.5rem}
  /* A definite width, never `width:auto`. The cover is a flex item inside the
     <picture>, and a LAZY image has no natural size until it loads; with auto
     on both axes the box resolves to 0x0, which then means it never intersects
     the viewport, so it never loads, so it never gets a size. The base rule set
     an explicit width, which is the only reason this never showed until a page
     put a cover below the fold. */
  /* Capped by width alone. A height cap alongside a definite width describes a
     box that is not the jacket's shape, and the difference shows as dead space
     around the art. 240px wide is about 365 tall for these covers, which fits a
     phone; the height cap was for the 360px hero, which no longer applies here. */
  .cover-stage img{width:min(100%,240px);height:auto}
  .plate .hero-grid{padding-block:1.75rem 2.25rem}
}
@media (max-width:640px){
  /* the epigraph was taking two lines and a whole band before anything else */
  .epigraph p{font-size:var(--fs-2xs);padding-block:var(--s-2);line-height:1.35}
  .bar{padding-block:.65rem}
  .mark{font-size:var(--fs-base)}
  nav.main{gap:.85rem}
  nav.main a{font-size:var(--fs-3xs);letter-spacing:.08em}
  .hook{font-size:var(--fs-base);padding-left:.9rem}
  .pubmeta{font-size:var(--fs-3xs);margin-top:1.15rem;padding-top:.85rem}
  .cta-row{gap:.5rem;margin-top:1.35rem}
  .btn{padding:var(--s-3) 1.05rem;font-size:var(--fs-2xs)}
  .quotes{padding-block:1.5rem}
  .sec,.sec-tight{padding-block:2.25rem}
  .sec-tight{padding-top:0}
}
@media (max-width:420px){
  .cover-stage img{width:min(100%,200px);height:auto}
}

/* Interior banners crop his own frame differently so the pages do not repeat
   the same picture, and no image on this site belongs to anyone else. */
.banner img.bnr-work{object-position:78% 62%}
.banner img.bnr-about{object-position:26% 48%}
.banner img.bnr-contact{object-position:52% 70%}

/* The publisher's own mark, as drawn. Big enough that the words read, which is
   the whole point of putting a logo on a page. */
.enp-logo{width:88px;height:auto;margin:.5rem 0 var(--s-1)}

/* The music section carries an embed only once the playlist exists. Without it,
   the prose should not sit in a half-empty two-column grid. */
.wrap-prose{display:block}
.wrap-prose .prose{max-width:70ch}

/* The cross-list opt-in. Unticked, and it stays unticked: a pre-ticked box is
   not consent under UK/EU GDPR, and both sites record those jurisdictions.
   The label wraps the input so the whole line is the target — a 13px checkbox
   on its own is under the 24px minimum and miserable on a thumb. */
.also{display:flex;gap:.6rem;align-items:flex-start;margin:.15rem 0 0;
  font-family:var(--ui);font-size:var(--fs-2xs);line-height:1.5;cursor:pointer;min-height:32px;padding:.35rem 0}
/* 24x24 is the WCAG 2.2 SC 2.5.8 minimum, and the box itself has to meet it.
   The wrapping label is already 32px tall, so the target was fine in practice —
   but a control that measures 18px is a control that fails every audit, and
   arguing with the scanner costs more than four pixels. */
.also input{margin:.1rem 0 0;width:24px;height:24px;flex:0 0 24px;cursor:pointer}
.also b{font-weight:600}

/* ---------------------------------------------------------------- notice
   The pages a reader reaches from an email: confirm, unsubscribe.

   They used to carry their own inline <style> block, which the site's own CSP
   (style-src 'self') does not authorise -- an inline stylesheet is not "self".
   The page still worked, so nobody noticed: the address was confirmed either
   way, and the reader simply got unstyled Times on white. That is the first
   page a new subscriber ever sees, and on 2026-09-05 Brian confirmed his own
   signup and got exactly that.

   Serving it from here rather than inline fixes the policy problem and the
   design problem at once: these pages now inherit the real palette, the real
   type and the real components, so they belong to the site instead of
   approximating it. */
.notice{min-height:100svh;display:grid;grid-template-rows:auto 1fr auto;background:var(--concrete)}
.notice-body{display:grid;place-items:center;padding:var(--s-7) var(--gut)}
.notice-card{max-width:36rem;text-align:center}
.notice-card .eyebrow{margin-bottom:var(--s-4)}
.notice-card h1{
  font-family:var(--display);font-weight:400;
  font-size:var(--ds-title);line-height:1.06;letter-spacing:.005em;
  text-transform:uppercase;color:var(--ink);margin:0 0 var(--s-3);text-wrap:balance}
.notice-card p{
  font-family:var(--prose);font-size:var(--fs-md);line-height:1.7;
  color:var(--ink-2);margin:0 auto var(--s-3);max-width:34rem}
.notice-card p:last-of-type{margin-bottom:0}
.notice-card .cta-row{justify-content:center}
/* The reassurance under the action: smaller, quieter, still 4.5:1 on the ground
   it sits on.

   Selector is `.notice-card p.notice-fine`, not `.notice-fine`, because
   `.notice-card p` above is (0,1,1) and a lone class is (0,1,0) -- the general
   rule beat the specific one and this rendered as prose serif at body size, so
   it read as a third paragraph rather than as fine print. Exactly the shape of
   the focus-ring bug: the more specific selector silently winning. */
.notice-card p.notice-fine{
  font-family:var(--ui);font-size:var(--fs-2xs);line-height:1.65;
  color:var(--ink-soft);max-width:30rem;margin:2.4rem auto 0}
.notice-card p.notice-fine a{color:var(--oxide-txt)}
.notice-card p{text-wrap:pretty}
.notice-head,.notice-foot{padding:var(--s-4) var(--gut);border-color:var(--rule-soft)}
.notice-head{border-bottom:1px solid var(--rule-soft)}
.notice-foot{border-top:1px solid var(--rule-soft);
  font-family:var(--ui);font-size:var(--fs-2xs);color:var(--ink-soft);text-align:center}
.notice-foot a{color:var(--ink-soft)}
.notice-mark{font-family:var(--display);font-size:var(--fs-lg);letter-spacing:.02em;
  text-transform:uppercase;color:var(--ink);text-decoration:none;display:inline-block}
.notice-mark i{color:var(--oxide);font-style:normal}

/* ---- hover is for pointers that can hover ---------------------------------
   Every hover rule on this site lived unguarded. On a touch screen there is no
   hover state to leave: tapping a link applies :hover and it STAYS applied
   until the reader taps something else, so a tapped button holds its pressed
   colour and a tapped nav item keeps its underline while the next page loads.
   It reads as a stuck UI, and it is the single most common mobile CSS defect.

   `@media (hover: hover)` asks the DEVICE, not the width -- so a laptop with a
   touchscreen still gets hover, and an iPad with a trackpad attached gets it
   too. Focus and active states are deliberately NOT in here: those must work
   everywhere. */
@media (hover:hover){
  nav.main a:hover{color:var(--ink);border-bottom-color:var(--oxide-txt)}
  .btn:hover{transform:translateY(-1px)}
  .btn-solid:hover{background:var(--oxide-hi);border-color:var(--oxide-hi)}
  .btn-ghost:hover{background:var(--ink);color:var(--concrete)}
  .plate .btn-ghost:hover{background:var(--on-dark);color:var(--plate)}
  .filters button:hover{border-color:var(--ink);color:var(--ink)}
  form.signup button:hover{background:var(--oxide-hi)}
  .buy a:hover{background:var(--paper);border-color:var(--ink)}
  .link-arrow:hover{text-decoration:underline;text-underline-offset:4px}
  .foot-links a:hover{color:var(--oxide-txt);text-decoration:underline;text-underline-offset:3px}
}

/* ---- notched screens ------------------------------------------------------
   An iPhone in LANDSCAPE puts the notch and the home indicator inside the
   viewport, and the browser does not inset the page for you unless asked. A
   full-bleed row -- the masthead, the footer, any sticky bar -- runs its first
   characters under the notch, and a bottom-anchored control sits under the
   home indicator where a tap is intercepted by the system gesture.

   env() resolves to 0px everywhere it does not apply, so this costs nothing on
   any other device and needs no media query. max() keeps the design's own
   gutter as the floor, so the inset only ever ADDS. */
.wrap{
  padding-inline:
    max(var(--gut), env(safe-area-inset-left))
    max(var(--gut), env(safe-area-inset-right));
}

/* ---- coarse pointers ------------------------------------------------------
   Raised from 34/38px to 44. WCAG 2.2 SC 2.5.8 asks for 24x24 and these
   already passed it; 44x44 is Apple's guidance and 48dp is Material's, and
   both exist because a fingertip is about 9mm and people miss small targets on
   a moving bus. The AA minimum is a floor, not a target. */
@media (hover:none) and (pointer:coarse){
  /* The masthead link and the consent box are the two remaining sub-44px
     targets a finger can actually reach. The skip link is keyboard-only and
     stays as it is. */
  .mark{min-height:44px}
  .also input[type=checkbox]{width:28px;height:28px}
  .also{min-height:44px;align-items:center}
  nav.main a{min-height:44px;padding:.75rem 0}
  .foot-links a{min-height:44px;display:inline-flex;align-items:center}
  .link-arrow{min-height:44px;display:inline-flex;align-items:center}
  .filters button{min-height:44px}
  form.signup button,form.signup input[type=email]{min-height:48px}
}
