/* ============================================================
   Alex McCarthy — portfolio v2
   One stylesheet for every page. Sections:
     1. Fonts        5. Nav
     2. Tokens       6. Home: hero, milestones, facts, pills
     3. Reset/base   7. Home: work grid
     4. Type scale   8. Ask band + footer
                     9. Motion + responsive
   Design rules that must hold everywhere:
     · five type sizes, no more        · no rounded corners except pills
     · hairline borders, never shadows · numerals always digits
     · one italic accent per page      · max 4 numbers at top weight
   ============================================================ */

/* 1 · FONTS ------------------------------------------------- */
@font-face{font-family:'Newsreader';font-style:normal;font-weight:400;font-display:swap;
  src:url('../fonts/newsreader-400.woff2') format('woff2')}
@font-face{font-family:'Newsreader';font-style:italic;font-weight:400;font-display:swap;
  src:url('../fonts/newsreader-400-italic.woff2') format('woff2')}
@font-face{font-family:'Geist';font-style:normal;font-weight:400;font-display:swap;
  src:url('../fonts/geist-400.woff2') format('woff2')}
@font-face{font-family:'Geist';font-style:normal;font-weight:500;font-display:swap;
  src:url('../fonts/geist-500.woff2') format('woff2')}
@font-face{font-family:'Geist Mono';font-style:normal;font-weight:400;font-display:swap;
  src:url('../fonts/geist-mono-400.woff2') format('woff2')}

/* 2 · TOKENS ------------------------------------------------ */
:root{
  --bg:#fafcfd;              /* page ground, faintly cool */
  --ink:#32404f;             /* headings, values, links */
  /* The text ramp clears WCAG AA on the page ground at every step. It used to
     run .58 and .35, which measured 3.19:1 and 1.90:1 against #fafcfd, so body
     copy and every mono label on the site failed. Hierarchy now comes from size
     and letter-spacing, which this design already leans on, rather than from
     contrast the eye cannot resolve. */
  --dim:rgba(50,64,79,.8);   /* body copy, labels, captions        5.7:1 */
  --faint:rgba(50,64,79,.715);/* secondary labels, placeholders    4.5:1 */
  /* Two accents. The bright one is a mark: rules, carets, plus signs, chart
     strokes, anything that is a graphic and only owes 3:1. The dark one is for
     accent text, which owes 4.5:1 and never got it at #e65f2e (3.38:1). */
  --acc:#e65f2e;             /* graphics                           3.38:1 */
  --acc-ink:#c4441a;         /* accent text                        4.87:1 */
  --hair:rgba(50,64,79,.1);  /* every border on the site */

  --serif:'Newsreader',Georgia,'Times New Roman',serif;
  --sans:'Geist',system-ui,-apple-system,sans-serif;
  --mono:'Geist Mono',ui-monospace,SFMono-Regular,Menlo,monospace;

  --gutter:clamp(28px,5vw,72px); /* page padding: scales, never cramps */
  --maxw:1180px;              /* the measure. 1800 made every card a billboard, and
                                 1180 lines the nav up with the case column. */
  --rhythm:80px;             /* between major sections */
  --ease:cubic-bezier(.16,1,.3,1);
}

/* 3 · RESET / BASE ------------------------------------------ */
*{margin:0;padding:0;box-sizing:border-box}
html{-webkit-text-size-adjust:100%}
body{
  background:var(--bg);color:var(--dim);
  font-family:var(--sans);font-size:15px;line-height:1.5;
  -webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;
}
/* height:auto belongs with max-width. Without it, an <img> carrying width/height
   attributes keeps its literal pixel height while the width scales down, and the
   picture stretches. Every rule below that wants a fixed height overrides this. */
img,svg,video{display:block;max-width:100%;height:auto}
a{color:inherit;text-decoration:none}
::selection{color:var(--acc-ink);background:rgba(230,95,46,.1)}
:focus-visible{outline:2px solid var(--acc);outline-offset:3px}

.wrap{max-width:var(--maxw);margin:0 auto;padding:0 var(--gutter)}
.sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}

/* 4 · TYPE SCALE -------------------------------------------- */
h1,h2,h3{font-family:var(--serif);font-weight:400;letter-spacing:-.02em;color:var(--ink)}
h1{font-size:clamp(38px,4.4vw,56px);line-height:1.08}
h2{font-size:32px;line-height:1.2}
h3{font-size:17px;line-height:1.35}
em{font-style:italic}
.mono{font-family:var(--mono);text-transform:uppercase;font-size:15px;color:var(--dim)}
.eyebrow{font-family:var(--mono);text-transform:uppercase;font-size:11px;
  letter-spacing:.16em;color:var(--acc-ink)}
.lede{font-family:var(--serif);font-size:19px;line-height:1.5;letter-spacing:-.01em;
  color:var(--ink);max-width:54ch}
/* The rule under Sovren draws itself once the fold has settled. The link is held
   on one line because an absolutely positioned ::after attaches to the first
   fragment of a wrapped inline only, which is why the rule was drawing into
   nothing. Thicker and darker than it was, so it reads as a link at rest. */
.lede a{position:relative;white-space:nowrap;transition:color .3s var(--ease)}
.lede a::after{content:"";position:absolute;left:0;right:0;bottom:-3px;height:1.5px;
  background:rgba(230,95,46,.7);transform:scaleX(0);transform-origin:left center;
  animation:draw .75s var(--ease) .55s forwards}
.lede a:hover{color:var(--acc-ink)}
.lede a:hover::after{background:var(--acc)}
@keyframes draw{to{transform:scaleX(1)}}

/* 5 · NAV --------------------------------------------------- */
.nav{border-bottom:1px solid var(--hair);padding:20px 0;background:var(--bg)}
.nav .wrap{display:flex;justify-content:space-between;align-items:center;gap:24px}
.nav .lock{display:flex;gap:16px;align-items:baseline}
.nav .lock b{color:var(--ink);font-weight:400}
.nav .links{display:flex;gap:32px}
.nav .links a:hover{color:var(--acc-ink)}
.nav .links a[aria-current]{color:var(--acc-ink);cursor:default}

/* 6 · HOME: HERO + MILESTONES + FACTS ----------------------- */
.hero{display:grid;grid-template-columns:1fr 1fr;gap:48px;padding-block:110px 56px;align-items:end}
.hero .lede{margin-top:20px}

.rail{display:flex;flex-direction:column;gap:7px}
.rail .row{display:flex;gap:10px;align-items:baseline}
.rail .y{font-family:var(--mono);font-size:13px;color:var(--acc-ink);width:64px;min-width:64px}
.rail .t{color:var(--ink)}
.rail-note{font-family:var(--mono);text-transform:uppercase;font-size:10px;
  letter-spacing:.12em;color:var(--faint);margin-bottom:14px}

.facts{border-top:1px solid var(--hair);border-bottom:1px solid var(--hair);
  padding:28px 0 22px;display:grid;grid-template-columns:repeat(4,1fr);gap:24px}
.facts .f b{display:block;font-family:var(--mono);font-weight:400;font-size:11px;
  letter-spacing:.16em;text-transform:uppercase;color:var(--acc-ink);margin-bottom:8px}
.facts .f .v{font-family:var(--mono);font-size:28px;line-height:1.15;color:var(--ink);
  font-variant-numeric:tabular-nums}
.facts .f .v .u{font-size:13px;color:var(--dim);margin-left:6px;letter-spacing:.04em}
.facts .f small{display:block;font-size:13px;line-height:1.45;color:var(--dim);margin-top:6px}

.pills{grid-column:1/-1;border-top:1px dashed var(--hair);padding-top:14px;
  display:flex;align-items:center;gap:10px;flex-wrap:wrap}
.pills .lab{font-family:var(--mono);font-size:11px;letter-spacing:.16em;
  text-transform:uppercase;color:var(--acc-ink);margin-right:6px}
.pill{font-family:var(--mono);font-size:11.5px;letter-spacing:.08em;text-transform:uppercase;
  color:var(--ink);border:1px solid var(--hair);border-radius:999px;padding:6px 14px;white-space:nowrap}

/* 7 · HOME: WORK GRID --------------------------------------- */
/* Two columns. Height variation comes from per-card aspect ratios, never from
   a multi-column span grid. Add a card by giving it one of the .a* classes. */
.grid{display:grid;grid-template-columns:1fr 1fr;gap:24px;padding:56px 0 24px;align-items:start}
.gcol{display:flex;flex-direction:column;gap:28px}
.card{display:flex;flex-direction:column}
.card .ph{position:relative;border:1px solid var(--hair);overflow:hidden;
  display:flex;align-items:center;justify-content:center;
  transition:border-color .3s var(--ease)}
.card:hover .ph{border-color:rgba(50,64,79,.22)}
.card .ph svg{width:52%;height:auto;max-height:80%}
/* a supplied image in a well: fitted, never cropped */
.card .ph img{max-width:100%;max-height:100%;width:auto;height:auto;display:block}

/* 12 · ABOUT ------------------------------------------------
   Two halves on one page. Above the seam, how I think about the work, in two
   columns that share rows so the hairlines run straight across. Below it, the
   contact sheet, which is the home page's card grid with a different subject
   inside it. Same tokens, same caption grammar, no new vocabulary.
   ------------------------------------------------------------ */

/* the introduction. The portrait is a cutout, so it gets no well and no mount.
   It stands on the bottom edge of the row with air under it and the copy set
   level with its feet. Hairlines only elsewhere, and none at all here. */
.intro{display:grid;grid-template-columns:1.12fr .88fr;gap:0 48px;align-items:center;
  padding-block:68px 4px}
.intro .lede{margin-top:20px}
/* Height-capped rather than width-capped. The cutout is 1111px tall at source,
   and a width cap turns it into a column of dead space beside the copy. */
.intro .cut{justify-self:stretch;display:flex;justify-content:center}
.intro .cut img{width:auto;height:auto;max-height:min(72vh,624px);max-width:100%;
  display:block;filter:saturate(.97)}

/* the orienting line. About is the page people deep-link to, so it says what he
   is looking for before it says what he thinks. */
.orient{font-family:var(--mono);text-transform:uppercase;font-size:11px;
  letter-spacing:.14em;color:var(--dim);margin-top:22px}
/* the line a recruiter is actually looking for, so it carries full ink rather
   than the faint step it used to sit in */
.orient b{font-weight:400;color:var(--ink)}

/* the section head that used to be the h1 */
.sechead{border-top:1px solid var(--hair);margin-top:56px;padding-top:30px}
.sechead h2{max-width:22ch}
.sechead .dek{font-family:var(--serif);font-size:19px;line-height:1.5;letter-spacing:-.01em;
  color:var(--dim);max-width:58ch;margin-top:14px}

/* Two columns that share rows, so each belief sits level with the thing it
   replaced. DOM order is the mobile reading order, all of column one and then
   all of column two, which is why the placement map exists. */
.pairs{display:grid;grid-template-columns:1fr 1fr;gap:0 56px;padding-top:34px}
.pairs > *:nth-child(1){grid-column:1;grid-row:1}
.pairs > *:nth-child(2){grid-column:1;grid-row:2}
.pairs > *:nth-child(3){grid-column:1;grid-row:3}
.pairs > *:nth-child(4){grid-column:1;grid-row:4}
.pairs > *:nth-child(5){grid-column:2;grid-row:1}
.pairs > *:nth-child(6){grid-column:2;grid-row:2}
.pairs > *:nth-child(7){grid-column:2;grid-row:3}
.pairs > *:nth-child(8){grid-column:2;grid-row:4}
.pairs .ulab{margin:0 0 9px}
.cell{border-top:1px solid var(--hair);padding:12px 2px 14px;font-size:14.5px;
  line-height:1.62;color:var(--dim)}
.cell:nth-child(4),.cell:nth-child(8){border-bottom:1px solid var(--hair)}
.cell b{font-weight:500;color:var(--ink)}
.cell a{border-bottom:1px solid rgba(50,64,79,.3)}
.cell a:hover{color:var(--acc-ink);border-color:var(--acc-ink)}

/* the seam. one hairline, one label, one line. the air around it is doing as
   much work as the rule is, so don't tighten it. */
.seam{border-top:1px solid var(--hair);margin-top:72px;padding-top:28px}
.seam .eyebrow{display:block;margin-bottom:14px}
.turn{font-family:var(--serif);font-size:21px;line-height:1.4;letter-spacing:-.02em;
  color:var(--ink)}
.grid.tight{padding-top:36px}

/* a photo slot that carries its own brief until a real photograph replaces it.
   Delete the .plate and drop an <img> into the same .ph well, and nothing else
   about the page changes. */
.plate{position:absolute;inset:0;display:flex;flex-direction:column;
  justify-content:flex-end;gap:8px;padding:22px 24px;text-align:left;
  background:
    linear-gradient(to top right,transparent calc(50% - .5px),var(--hair) 50%,transparent calc(50% + .5px)),
    linear-gradient(to bottom right,transparent calc(50% - .5px),var(--hair) 50%,transparent calc(50% + .5px)),
    rgba(50,64,79,.015)}
.plate b{font-family:var(--mono);font-weight:400;font-size:10px;letter-spacing:.16em;
  text-transform:uppercase;color:var(--acc-ink)}
.plate p{font-size:12.5px;line-height:1.55;color:var(--dim);max-width:44ch;margin:0}
.plate em{font-style:normal;font-family:var(--mono);font-size:11px;color:var(--ink)}

/* photographs fill their wells edge to edge. Each file is already cut to 4:5,
   so cover crops nothing; it is here so a re-cut file can never leave a seam. */
.grid.tight .ph img{width:100%;height:100%;max-width:none;max-height:none;
  object-fit:cover;display:block}
/* the portraits stay portrait on a phone. Without this the global narrow-screen
   rule flattens every well to 16:9 and cuts the heads off. */
.grid.tight .a45 .ph{aspect-ratio:4/5}

/* the unit with no photograph. Same well, same 4:5, same caption grammar, only
   the contents changed, so the sheet stays a regular grid of four. */
.grid .card.txt .ph{padding:34px 32px;justify-content:flex-start;align-items:center}
.quiet{font-family:var(--serif);font-size:23px;line-height:1.4;letter-spacing:-.02em;
  color:var(--ink)}

.sheet-note{border-top:1px solid var(--hair);margin-top:26px;padding-top:14px;
  font-family:var(--mono);text-transform:uppercase;font-size:10px;letter-spacing:.14em;
  color:var(--faint)}

@media(max-width:860px){
  .intro{grid-template-columns:1fr;gap:28px 0;padding-block:44px 0;align-items:start}
  .intro .cut img{max-height:min(52vh,420px)}
  .sechead{margin-top:44px}
  .pairs{grid-template-columns:1fr}
  .pairs > *{grid-column:1!important;grid-row:auto!important}
  .pairs > *:nth-child(5){margin-top:32px}
  .seam{margin-top:52px}
}
/* .live wells hold a running document, edge to edge. pointer-events stay off so
   the card's own link keeps the whole surface. */
.card .ph.live{padding:0;background:none}
.card .ph.live iframe{width:100%;height:100%;border:0;display:block;pointer-events:none}
.card .ph .tag{position:absolute;top:10px;right:12px;font-family:var(--mono);font-size:9px;
  letter-spacing:.2em;text-transform:uppercase;color:var(--faint)}
.card .cap{display:flex;justify-content:space-between;gap:8px;margin-top:10px;align-items:baseline}
.card .cap .m{font-family:var(--mono);text-transform:uppercase;font-size:12.5px;
  color:var(--dim);text-align:right;white-space:nowrap}
.card .deck{font-size:14px;margin-top:3px;max-width:60ch}
.card .deck b{color:var(--ink);font-weight:400;font-variant-numeric:tabular-nums}
.a169 .ph{aspect-ratio:16/9}
.a107 .ph{aspect-ratio:10/7}
.a85  .ph{aspect-ratio:8/5}
.a45  .ph{aspect-ratio:4/5}
.a43  .ph{aspect-ratio:4/3}
.a11  .ph{aspect-ratio:1/1}

/* 8 · ASK BAND + FOOTER ------------------------------------- */
.ask{border-top:1px solid var(--hair);margin-top:56px;padding:44px 0 8px}
.ask .in{display:flex;justify-content:space-between;align-items:baseline;gap:24px;flex-wrap:wrap}
.ask h2{max-width:30ch}
.ask .links{display:flex;gap:28px;align-items:baseline}
.ask .links a{font-family:var(--mono);text-transform:uppercase;font-size:13px;color:var(--ink);
  border-bottom:1px solid var(--hair);padding-bottom:3px;transition:border-color .3s var(--ease)}
.ask .links a:hover{border-bottom-color:var(--ink)}
.ask .links a.hot{color:var(--acc-ink);border-bottom-color:rgba(230,95,46,.4)}

.foot{border-top:1px solid var(--hair);padding:20px 0;margin-top:48px}
.foot .wrap{display:flex;justify-content:space-between;gap:24px;flex-wrap:wrap}
.foot a:hover{color:var(--acc-ink)}

/* 9 · MOTION + RESPONSIVE ----------------------------------- */
.rv{opacity:0;transform:translateY(8px);animation:rise .5s var(--ease) forwards}
.rv:nth-child(2){animation-delay:.08s}
.rv:nth-child(3){animation-delay:.16s}
@keyframes rise{to{opacity:1;transform:none}}

/* Each thing collapses when IT runs out of room, not all at once.
   The work grid is the last to give up its second column. */
@media(max-width:1040px){
  .facts{grid-template-columns:1fr 1fr}
  .nav .links{gap:20px}
}
@media(max-width:860px){
  .hero{grid-template-columns:1fr;padding-block:48px 32px;gap:32px}
}
@media(max-width:760px){
  .grid{grid-template-columns:1fr}
  .card .ph{aspect-ratio:16/9}
  /* the square well holds a square drawing. Letting it go 16:9 here would put
     the excess space back, just along the other axis. */
  .a11 .ph{aspect-ratio:1/1}
}
@media(max-width:620px){
  .nav .lock span{display:none}

  /* Two by two rather than a single column. Four numbers stacked was most of a
     phone screen before the work even started, and the point of these is that
     they are scanned together. */
  .facts{grid-template-columns:1fr 1fr;gap:22px 20px;padding:22px 0}
  .facts .f b{font-size:10px;letter-spacing:.14em}
  .facts .f .v{font-size:24px}
  .facts .f .v .u{font-size:12px;margin-left:4px}
  .facts .f small{font-size:12px;line-height:1.4;margin-top:5px}

  /* The year stays beside its line instead of sitting on top of it. Stacking
     turned six milestones into eighteen lines of vertical scroll. */
  .rail{gap:11px}
  .rail .row{flex-direction:row;gap:11px;align-items:baseline}
  .rail .y{width:38px;min-width:38px;font-size:12px}
  .rail .t{font-size:14px;line-height:1.45}
}
@media(prefers-reduced-motion:reduce){
  .rv{animation:none;opacity:1;transform:none}
  .lede a::after{animation:none;transform:none}
  *{transition:none!important}
}

/* ============================================================
   10 · CASE STUDY
   Sidebar TOC + a narrow reading column. Same tokens, same
   rhythm. Figures are line drawings, never screenshots.
   ============================================================ */
.case{display:grid;grid-template-columns:200px minmax(0,1fr);gap:0 56px;
  max-width:1180px;margin:0 auto;padding:0 var(--gutter)}
.side{position:sticky;top:0;height:fit-content;padding-top:56px;align-self:start}
.side .back{font-family:var(--mono);text-transform:uppercase;font-size:11px;
  letter-spacing:.14em;color:var(--dim)}
.side .back:hover{color:var(--acc-ink)}
.toc{list-style:none;margin-top:32px;display:flex;flex-direction:column;gap:9px}
.toc a{display:block;font-size:13.5px;line-height:1.35;color:var(--dim);
  border-left:1px solid transparent;padding-left:12px;margin-left:-13px;
  transition:color .25s var(--ease),border-color .25s var(--ease)}
.toc a:hover{color:var(--ink)}
.toc a.on{color:var(--ink);border-left-color:var(--acc-ink)}
.body{padding:56px 0 0;max-width:66ch;min-width:0}

.chead{padding-bottom:8px}
.chead .kick{font-family:var(--mono);text-transform:uppercase;font-size:11px;
  letter-spacing:.16em;color:var(--acc-ink)}
.chead h1{font-size:clamp(34px,3.6vw,46px);margin-top:14px}
.chead .sub{font-family:var(--serif);font-size:20px;line-height:1.45;color:var(--ink);
  margin-top:20px;letter-spacing:-.01em}
.meta{display:grid;grid-template-columns:repeat(4,1fr);gap:22px;margin-top:34px;
  padding:22px 0;border-top:1px solid var(--hair);border-bottom:1px solid var(--hair)}
.meta b{display:block;font-family:var(--mono);font-weight:400;font-size:10px;
  letter-spacing:.16em;text-transform:uppercase;color:var(--acc-ink);margin-bottom:7px}
.meta span{display:block;font-size:13.5px;line-height:1.45;color:var(--ink)}

.sec{padding-top:56px}
.sec .eyebrow{display:block;margin-bottom:12px}
.sec h2{font-size:27px;margin-bottom:14px}
.sec p{margin-bottom:14px;color:rgba(50,64,79,.78)}
.sec p b{font-weight:500;color:var(--ink)}
.sec p em{font-style:italic}
/* a link inside body prose. the global a{color:inherit;text-decoration:none} makes
   cross-case links invisible, so they get a hairline under them and the accent on hover. */
.sec p a{border-bottom:1px solid rgba(50,64,79,.3)}
.sec p a:hover{color:var(--acc-ink);border-color:var(--acc-ink)}

/* the one-sentence pull, used sparingly */
.pull{font-family:var(--serif);font-size:21px;line-height:1.4;letter-spacing:-.02em;
  color:var(--ink);border-left:2px solid var(--acc);padding:2px 0 2px 20px;margin:26px 0}

/* decision block: situation, call, cost */
.dec{border:1px solid var(--hair);padding:22px 24px;margin:24px 0}
.dec dl{display:grid;grid-template-columns:76px 1fr;gap:10px 16px}
.dec dt{font-family:var(--mono);font-size:10px;letter-spacing:.14em;text-transform:uppercase;
  color:var(--acc-ink);padding-top:3px}
.dec dd{font-size:14px;line-height:1.5;color:rgba(50,64,79,.82)}
.dec dd b{font-weight:500;color:var(--ink)}

/* a function grid: one change, read across four parts of the org. Two up, not
   four, because the case column is 66ch and four columns would be 140px each.
   Cells sit on a hairline field, so the dividers are the gap, not borders. */
.fgrid{display:grid;grid-template-columns:1fr 1fr;gap:1px;
  background:var(--hair);border:1px solid var(--hair);margin:26px 0}
.fcell{background:var(--bg);padding:18px 20px 20px}
.fcell h4{font-family:var(--mono);font-size:10px;font-weight:400;letter-spacing:.14em;
  text-transform:uppercase;color:var(--acc-ink);margin-bottom:11px}
.fcell ul{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:10px}
.fcell li{font-size:13.5px;line-height:1.5;color:rgba(50,64,79,.82);
  padding-left:14px;position:relative}
.fcell li::before{content:"";position:absolute;left:0;top:9px;width:6px;height:1px;
  background:var(--acc)}
.fcell li b{font-weight:500;color:var(--ink)}
.todo{color:var(--acc-ink);font-family:var(--mono);font-size:11px;letter-spacing:.1em;
  text-transform:uppercase;line-height:1.6}
@media(max-width:620px){ .fgrid{grid-template-columns:1fr} }

/* a screenshot well. Product captures are near-white and so is the page ground,
   so one hairline is not enough to see where the image stops. Three edges: the
   outer frame, the tinted mat, and a stronger hairline on the image itself. The
   label bar says what you are looking at without spending a figcaption on it. */
.shot{border:1px solid var(--hair);background:rgba(50,64,79,.025);margin:26px 0}
.shot .lab{display:block;font-family:var(--mono);font-size:10px;letter-spacing:.14em;
  text-transform:uppercase;color:var(--acc-ink);padding:11px 16px;
  border-bottom:1px solid var(--hair)}
.shot .frame{padding:16px}
.shot img{display:block;width:100%;height:auto;border:1px solid rgba(50,64,79,.16)}
@media(max-width:620px){ .shot .frame{padding:10px} }

figure{margin:28px 0}
figure .art{border:1px solid var(--hair);padding:26px 22px;background:rgba(50,64,79,.02)}
figure figcaption{font-size:12.5px;line-height:1.5;color:var(--dim);margin-top:10px}
figure figcaption b{font-family:var(--mono);font-size:10px;letter-spacing:.14em;
  text-transform:uppercase;color:var(--acc-ink);font-weight:400;margin-right:8px}
figure figcaption strong{font-family:var(--sans);font-size:inherit;font-weight:500;
  letter-spacing:0;text-transform:none;color:var(--ink);margin:0}
/* a live artifact — the real prototype, running as itself. An iframe rather
   than a redraw or a recording: exact fidelity, total style isolation, and it
   stays interactive. No well and no border; each artifact carries its own. */
figure .embed{display:block;width:100%;height:624px;border:0;margin-inline:auto;color-scheme:light}
figure .embed.short{height:600px}
figure .embed.portrait{max-width:420px;height:548px}   /* 640x820 artifacts */

/* two short parallel lists, where AI goes and where it stops. no bullet glyphs;
   the hairline does the separating and the label carries the beat. */
.ulab{font-family:var(--mono);font-size:10px;letter-spacing:.16em;text-transform:uppercase;
  color:var(--faint);margin:26px 0 0}
.ulab.hot{color:var(--acc-ink)}
.uses{list-style:none;margin:9px 0 0;padding:0;border-top:1px solid var(--hair)}
.uses li{padding:10px 2px;border-bottom:1px solid var(--hair);font-size:14.5px;
  line-height:1.62;color:var(--dim)}
.uses li b{font-weight:500;color:var(--ink)}
.uses.tight li{color:var(--ink)}
.uses + p{margin-top:21px}

/* a small table, hairlines only, no fills. wrapped so a narrow screen scrolls the
   table instead of the page. */
.tblwrap{overflow-x:auto;margin:18px 0 4px}
.tbl{width:100%;border-collapse:collapse;min-width:560px;text-align:left}
.tbl th{font-family:var(--mono);font-weight:400;font-size:10px;letter-spacing:.14em;
  text-transform:uppercase;color:var(--acc-ink);padding:0 18px 9px 0;vertical-align:bottom;
  border-bottom:1px solid var(--hair);white-space:nowrap}
.tbl td{font-size:13.5px;line-height:1.55;color:var(--dim);padding:11px 18px 11px 0;
  border-bottom:1px solid var(--hair);vertical-align:top}
.tbl td:first-child{color:var(--ink);font-weight:500}
.tbl th:last-child,.tbl td:last-child{padding-right:0}

/* a rule inside a section: a short subhead carrying its own claim */
.sec h3{font-size:17.5px;margin:26px 0 8px}
.sec h3:first-of-type{margin-top:18px}

/* the two-beat lead-in that replaces a figure caption. beat one is a claim in
   first person, beat two is the link. never explain the artifact twice. */
.leadin{margin-top:22px;font-size:15.5px;line-height:1.72;color:var(--ink)}
.leadin a{border-bottom:1px solid rgba(50,64,79,.3)}
.leadin a:hover{color:var(--acc-ink);border-color:var(--acc-ink)}
.leadin + figure{margin-top:14px}

/* a working note to Alex, deliberately loud. these come off before the page ships. */
.sec-flag{margin-top:18px;padding:12px 14px;border:1px dashed var(--acc);
  background:rgba(230,95,46,.05);font-family:var(--mono);font-size:12px;line-height:1.6;
  color:var(--acc-ink)}

/* an image Alex still has to make: the brief, in the slot the picture will fill */
.imgslot{border:1px dashed rgba(50,64,79,.28);background:rgba(50,64,79,.02);
  padding:22px 24px;display:flex;flex-direction:column;justify-content:center;gap:9px}
.imgslot b{font-family:var(--mono);font-weight:400;font-size:10px;letter-spacing:.16em;
  text-transform:uppercase;color:var(--acc-ink)}
.imgslot p{font-size:12.5px;line-height:1.55;color:var(--dim);max-width:62ch;margin:0}
.imgslot p b{display:inline;font-family:var(--sans);font-size:12.5px;letter-spacing:0;
  text-transform:none;color:var(--ink)}
.imgslot em{font-style:normal;font-family:var(--mono);font-size:11.5px;color:var(--ink)}

/* two artifact cards side by side */
.strip{display:grid;grid-template-columns:1fr 1fr;gap:16px;margin-top:20px}
.nxt.sm{padding:20px 20px 18px}
.nxt.sm .t{font-size:19px}
.nxt.sm .d{font-size:13px;margin-top:6px}
.nxt.sm .go{margin-top:13px;font-size:10px}
@media(max-width:700px){ .strip{grid-template-columns:1fr} }
@media(max-width:520px){
  figure .embed{height:560px}
  figure .embed.short{height:580px}
}


/* next case: a full-width target, not a text link */
.cnext{margin-top:72px}
.nxt{display:block;border:1px solid var(--hair);padding:26px 28px 24px;
  transition:border-color .3s var(--ease),background-color .3s var(--ease)}
.nxt:hover{border-color:rgba(50,64,79,.28);background:rgba(50,64,79,.02)}
.nxt .k{display:block;font-family:var(--mono);font-size:10px;letter-spacing:.16em;
  text-transform:uppercase;color:var(--acc-ink);margin-bottom:10px}
.nxt .t{display:block;font-family:var(--serif);font-size:26px;line-height:1.2;
  letter-spacing:-.02em;color:var(--ink)}
.nxt .d{display:block;font-size:14px;line-height:1.5;color:var(--dim);margin-top:8px;max-width:56ch}
.nxt .go{display:inline-block;font-family:var(--mono);font-size:11px;letter-spacing:.16em;
  text-transform:uppercase;color:var(--ink);margin-top:16px;
  border-bottom:1px solid var(--hair);padding-bottom:4px}
.nxt .go i{font-style:normal;display:inline-block;margin-left:6px;
  transition:transform .3s var(--ease)}
.nxt:hover .go{border-bottom-color:var(--acc-ink)}
.nxt:hover .go i{transform:translateX(4px)}
.cfoot{display:flex;gap:28px;flex-wrap:wrap;margin-top:22px;
  font-family:var(--mono);text-transform:uppercase;font-size:11px;letter-spacing:.1em}
.cfoot a{color:var(--dim)}
.cfoot a:hover{color:var(--acc-ink)}

@media(max-width:900px){
  .case{grid-template-columns:1fr;gap:0}
  .side{position:static;padding-top:32px}
  .toc{display:none}
  .body{padding-top:28px}
  .meta{grid-template-columns:1fr 1fr;gap:18px}
  .dec dl{grid-template-columns:1fr;gap:4px 0}
  .dec dt{padding-top:10px}
}

/* ============================================================
   13 · LOCK
   The ground is a material: a 4x4 Bayer-ordered dither over three
   octaves of value noise, quantised to exactly two inks. No greys,
   because the pattern is the grey. Ink coverage sits near 10%, which
   reads as tone at arm's length and only resolves into a pattern
   when you lean in.

   A halftone is a printing process, so it belongs to the same world
   as the hairlines on the rest of the site, and it cannot turn into
   the glow this page started as: no colour, no centre, no source.

   MOBILE POST-MORTEM. The first version punched the mark and the
   field out of the canvas by measuring getBoundingClientRect every
   frame. On iOS the keyboard moves the visual viewport under a fixed
   canvas, so those holes drifted away from the things they were
   meant to be protecting and the type ended up sitting on raw noise.
   The holes are now real DOM elements painted in the page ground.
   No coordinate maths, nothing to keep in sync, correct on every
   device by construction.
   ============================================================ */
.lockbody{background:var(--bg);margin:0;overflow:hidden;
  min-height:100vh;min-height:100dvh}

/* the canvas fills its fixed box in CSS and measures itself in JS, so the bitmap
   and the box can never disagree about how tall the screen is */
#dic{position:fixed;inset:0;width:100%;height:100%;z-index:0;display:block;
  image-rendering:pixelated;
  transition:opacity .7s cubic-bezier(.22,1,.36,1)}

.di-wrap{position:fixed;inset:0;display:grid;place-items:center;z-index:2;
  padding:24px}
.di-stack{display:flex;flex-direction:column;align-items:center}

/* Each of these carries its own clear stock. That is what keeps the halftone
   from running under anything that has to be read.

   The clear space is a disc on a wrapper rather than a radius on the image:
   rounding the mark itself cropped the corners off the plate and quietly
   changed the logo. */
.di-hole{position:relative;z-index:2;display:grid;place-items:center;
  width:calc(var(--di-mark) + var(--di-ring) * 2);
  height:calc(var(--di-mark) + var(--di-ring) * 2);
  border-radius:50%;background:var(--bg)}
.di-mark{display:block;width:var(--di-mark);height:var(--di-mark)}

.di-plate{position:relative;margin-top:4px;
  padding:26px 40px 20px;background:var(--bg);
  transition:padding .3s cubic-bezier(.22,1,.36,1)}
.di-plate.lit{padding:26px 48px 20px}
.di-lab{display:block;text-align:center;font-family:var(--mono);font-size:10.5px;
  letter-spacing:.2em;text-transform:uppercase;color:var(--dim);
  margin-bottom:13px;transition:color .3s}
.di-plate.bad .di-lab{color:var(--acc-ink)}
.di-plate input{display:block;width:288px;max-width:100%;border:0;background:none;
  outline:none;padding:0 0 11px;text-align:center;font-family:var(--mono);
  font-size:18px;letter-spacing:.34em;text-indent:.34em;color:var(--ink);
  caret-color:var(--acc);border-bottom:1px solid rgba(50,64,79,.35);
  transition:border-color .35s}
.di-plate.lit input{border-bottom-color:rgba(50,64,79,.85)}
.di-plate input::selection{background:rgba(50,64,79,.12)}
/* THE ACCENT. one inked cell on the plate edge. nine square pixels. */
.di-dot{position:absolute;right:12px;bottom:8px;width:3px;height:3px;background:var(--acc);
  transition:right .22s cubic-bezier(.22,1,.36,1),left .22s cubic-bezier(.22,1,.36,1)}
.di-plate.lit .di-dot{right:auto;left:12px}

.di-ask{position:fixed;left:50%;transform:translateX(-50%);bottom:0;z-index:3;
  padding:14px 18px calc(14px + env(safe-area-inset-bottom));background:var(--bg);
  white-space:nowrap;font-family:var(--mono);font-size:10.5px;letter-spacing:.16em;
  text-transform:uppercase;color:var(--dim)}
.di-ask a{color:var(--ink);border-bottom:1px solid rgba(50,64,79,.28);padding-bottom:3px}
.di-ask a:hover{color:var(--acc-ink);border-bottom-color:var(--acc)}

.di-wrap.out{opacity:0;transition:opacity .45s ease .2s}

:root{ --di-mark:128px; --di-ring:42px; }
@media(max-width:620px){
  :root{ --di-mark:84px; --di-ring:26px }
  .di-plate{padding:22px 24px 18px;margin-top:0}
  .di-plate.lit{padding:22px 26px 18px}
  .di-plate input{width:240px;font-size:17px;letter-spacing:.26em;text-indent:.26em}
  .di-ask{font-size:10px;letter-spacing:.1em}
}
/* The bottom line goes while the field has focus. A max-height media query cannot
   detect the iOS keyboard, because CSS measures the layout viewport, which does not
   shrink. Focus is the signal that actually fires. */
.lockbody.typing .di-ask{opacity:0;pointer-events:none;transition:opacity .2s}
@media(max-height:560px){
  :root{ --di-mark:64px; --di-ring:20px }
  .di-ask{display:none}
}
@media(prefers-reduced-motion:reduce){ *{transition:none!important} }
