/* ============================================================
   chrisschubert.me — one page

   Stripped back from the five-page draft. Everything the old stylesheet
   carried for pages that no longer exist (ledger, exhibits, positions,
   confirm gate, onward links) is gone. What is left is the room, the type,
   and five blocks.

   The system, in one line each:
     ground   warm near-black, never neutral grey
     accent   chartreuse for the live things, amber for the warm ones
     light    a single warm pool, high left, fixed so it reads as a room
     type     Newsreader carries voice, Archivo carries information
     grain    generated, not an image, and doing more work than it looks
   ============================================================ */

/* Four variable faces, latin only, self-hosted so the page makes no external
   request. No unicode-range descriptors on purpose: Google's latin subset
   excludes U+2192 and this page is full of arrows. */
@font-face{
  font-family:"Newsreader";font-style:normal;font-weight:200 800;font-display:swap;
  src:url("assets/fonts/newsreader-latin.woff2") format("woff2");
}
@font-face{
  font-family:"Newsreader";font-style:italic;font-weight:200 800;font-display:swap;
  src:url("assets/fonts/newsreader-italic-latin.woff2") format("woff2");
}
@font-face{
  font-family:"Archivo";font-style:normal;font-weight:100 900;font-display:swap;
  src:url("assets/fonts/archivo-latin.woff2") format("woff2");
}
@font-face{
  font-family:"JetBrains Mono";font-style:normal;font-weight:100 800;font-display:swap;
  src:url("assets/fonts/jetbrains-mono-latin.woff2") format("woff2");
}

:root{
  --bg:#100D08;
  --panel:#1A150E;
  --panel-2:#151009;
  --ink:#F4EDDF;
  --dim:#A2937D;
  --faint:#7B6E5B;
  --line:#2E271C;
  --line-2:#453A28;
  --lit:#4A3F2B;              /* the edge facing the light */
  --key:#DCF25A;              /* chartreuse: things that are running */
  --warm:#E8934A;             /* amber: the human side. also the Guild mark */
  --halo:#dcf25a33;
  --halo-soft:#dcf25a14;

  --disp:"Newsreader",Georgia,serif;
  --body:"Archivo",ui-sans-serif,system-ui,sans-serif;
  --mono:"JetBrains Mono",ui-monospace,"SF Mono",Menlo,monospace;

  --shell:1140px;
  --measure:64ch;
  --pad:clamp(20px,5vw,48px);

  --grain:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='200' height='200'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='4' stitchTiles='stitch'/><feColorMatrix type='saturate' values='0'/></filter><rect width='200' height='200' filter='url(%23n)' opacity='0.55'/></svg>");
}

*{box-sizing:border-box;}
html{-webkit-text-size-adjust:100%;scroll-behavior:smooth;}
@media (prefers-reduced-motion:reduce){html{scroll-behavior:auto;}}
body{
  margin:0;background:var(--bg);color:var(--ink);
  font-family:var(--body);font-size:16.5px;line-height:1.62;
  -webkit-font-smoothing:antialiased;
}
/* The room: a warm pool high left, a soft vignette, grain over everything.
   All fixed, so the light stays put while the page moves through it. */
body::before,body::after{content:"";position:fixed;inset:0;pointer-events:none;}
body::before{
  z-index:0;
  background:
    radial-gradient(52% 60% at 16% 2%,#f7d98a1c,transparent 58%),
    radial-gradient(34% 42% at 2% -6%,#dcf25a12,transparent 62%),
    radial-gradient(120% 100% at 26% 8%,transparent 42%,#00000094 100%);
}
body::after{z-index:60;background-image:var(--grain);background-size:200px 200px;opacity:.085;}

h1,h2,h3,.card .t{font-family:var(--disp);font-optical-sizing:auto;}

p{margin:0 0 1.05em;max-width:var(--measure);text-wrap:pretty;}
a{color:var(--key);text-underline-offset:.2em;text-decoration-thickness:1px;}
a:hover{text-decoration-thickness:2px;}
:focus-visible{outline:2px solid var(--key);outline-offset:3px;border-radius:2px;}
img{max-width:100%;display:block;}
strong{font-weight:600;color:var(--ink);}

.shell{position:relative;z-index:1;max-width:var(--shell);margin:0 auto;padding:0 var(--pad);}
section{padding-block:clamp(2.8rem,6.5vw,5rem);border-top:1px solid var(--line);}

.slab{font-family:var(--mono);font-size:10.5px;letter-spacing:.17em;text-transform:uppercase;
  color:var(--faint);display:flex;align-items:center;gap:1rem;margin:0 0 1.6rem;}
.slab::after{content:"";flex:1;height:1px;background:var(--line);}

/* 30ch, not 19: the headline now carries a full thought instead of a slogan,
   and 19ch broke it into five lines. 600 rather than 700 because light type on
   a dark ground already gains optical weight, and three lines of 700 at this
   size reads as a wall. */
h1{font-size:clamp(34px,2rem + 3.3vw,66px);line-height:1.04;letter-spacing:-.024em;font-weight:600;
  margin:0 0 1.6rem;max-width:30ch;text-wrap:balance;}
/* Colour and glow, but upright. The italic was the part that announced "this
   is my slogan"; the chartreuse is just the palette arriving at the top of the
   page. h2 keeps its slant, where a shorter line carries it fine. */
h1 em{font-style:normal;color:var(--key);text-shadow:0 0 30px var(--halo),0 0 72px var(--halo-soft);}
h2{font-size:clamp(24px,1.2rem + 1.3vw,35px);line-height:1.12;letter-spacing:-.02em;font-weight:700;
  margin:0 0 1.1rem;max-width:24ch;text-wrap:balance;}
h2 em{font-style:italic;color:var(--warm);}
.lede{font-size:clamp(17px,1rem + .4vw,20.5px);color:var(--dim);max-width:54ch;line-height:1.55;}
.note{font-family:var(--mono);font-size:12px;line-height:1.75;color:var(--faint);max-width:62ch;margin:0;}

/* ---------- masthead ---------- */
.masthead{
  position:relative;z-index:2;max-width:var(--shell);margin:0 auto;
  padding:1.5rem var(--pad) 1.4rem;display:flex;align-items:baseline;
  justify-content:space-between;gap:.9rem 2rem;flex-wrap:wrap;
}
.masthead .who{font-family:var(--mono);font-size:12px;font-weight:700;letter-spacing:.15em;
  text-transform:uppercase;color:var(--ink);}
/* 56ch clamped the longer spine into two lines with "of AI." orphaned on the
   second, which reads as an accident rather than a line break. It fits on one
   line from about 720px up; below that, balance splits it evenly instead of
   leaving a two-word widow. */
.masthead .spine{font-size:13px;color:var(--faint);line-height:1.45;
  max-width:78ch;text-wrap:balance;}

/* ---------- 1. hello ----------
   No border on the first section and extra room above it, so the page opens
   on the sentence rather than on a rule. */
.hello{border-top:none;padding-block:clamp(3rem,8vw,6.5rem) clamp(2rem,4vw,3rem);}
.hello .lede{font-size:clamp(17.5px,1rem + .55vw,21px);line-height:1.5;}
.hello p:last-child{margin-bottom:0;}
/* No font-size here. It would outrank .lede (0,1,1 against 0,1,0) and kill the
   step down from the opening paragraph, while .lede's narrower measure still
   won, leaving paragraph one the same size as the rest in a shorter column. */
.hello p{color:var(--dim);}
.hello p strong{color:var(--ink);font-weight:500;}

/* ---------- 2. the trawl: many sources in, a few files out ----------
   The wedge is a filled taper, not one connector per row. The rows either
   side are HTML text that reflows, so stroked connectors would line up at
   one width and drift at every other. Chartreuse is the machine reading,
   amber is the setup a person does. */
.art .trawl{display:grid;gap:clamp(.8rem,1.8vw,1.3rem);}

.trawl .bl{font-family:var(--mono);font-size:9.5px;letter-spacing:.16em;
  text-transform:uppercase;color:var(--faint);display:block;padding-bottom:.9rem;}
.trawl .bl.warm{color:var(--warm);}
.trawl .bl.lit{color:var(--key);}

/* the setup bar. grid rather than flex so a long note wraps inside its own
   cell instead of stretching the row. */
.trawl .give{display:grid;grid-template-columns:1fr 1fr;
  gap:0 clamp(1rem,2.6vw,2.1rem);
  background:var(--panel);border:1px solid var(--line);border-top-color:var(--warm);
  box-shadow:0 26px 50px -34px #000;padding:clamp(1.1rem,2.2vw,1.6rem);}
.trawl .give .bl{grid-column:1/-1;}
.trawl .gv{font-size:14px;line-height:1.5;color:var(--dim);
  border-top:1px solid var(--line);padding-top:.75rem;}
.trawl .gv b{color:var(--ink);font-weight:600;display:block;}

/* three tracks: sources, the wedge, files. The wedge track is the only one
   that is allowed to be narrow, so it never wins space from the text. */
.trawl .mid{display:grid;grid-template-columns:minmax(0,1fr) clamp(38px,7vw,86px) minmax(0,1fr);
  align-items:stretch;
  background:var(--panel);border:1px solid var(--line);border-top-color:var(--lit);
  box-shadow:0 26px 50px -34px #000;padding:clamp(1.1rem,2.2vw,1.6rem);}
.trawl .col{display:grid;align-content:start;}
/* the row rules stop short of the wedge, so the taper reads as sitting beside
   the list rather than cutting across it */
.trawl .mid > .col:first-of-type{padding-right:clamp(.6rem,1.6vw,1.2rem);}
.trawl .s{font-size:14px;line-height:1.45;color:var(--dim);padding:.62rem 0;
  border-top:1px solid var(--line);}
.trawl .files{padding-left:clamp(.9rem,2.4vw,1.9rem);}
.trawl .s.fn{font-family:var(--mono);font-size:12.5px;color:var(--ink);}

/* align-self would beat the grid's stretch and let the intrinsic 1:1 viewBox
   decide the height, which leaves a square wedge in a much taller row */
.trawl .wedge{width:100%;height:100%;display:block;}

@media (max-width:620px){
  /* the wedge only means anything pointing left to right, so on a phone the
     two columns stack and it drops out rather than turning on its side. */
  .trawl .mid{grid-template-columns:1fr;gap:1.4rem;}
  .trawl .wedge{display:none;}
  .trawl .files{padding-left:0;}
  .trawl .give{grid-template-columns:1fr;}
}

/* ---------- 3. cards ----------
   Image-led. Each card is a screenshot or a mark, then a name, then two or
   three sentences. One heading links out, to the playable game. Everything else
   stays on the page.*/
/* Each card opens on a rule and a number. Three of them are full-width blocks
   that run diagram, prose, diagram, prose down the page, and without a visible
   start the reader cannot tell where one item ends and the next begins. */
.cards{display:flex;flex-direction:column;margin-top:2.4rem;}
.card{display:grid;grid-template-columns:minmax(0,1.15fr) minmax(0,1fr);
  gap:clamp(1.6rem,3.5vw,3rem);align-items:center;
  padding-block:clamp(2rem,4vw,3.2rem);border-top:1px solid var(--line-2);}
.card:first-child{padding-top:0;border-top:none;}
.card.flip .art{order:2;}
/* A dense landscape screenshot squeezed into the narrow column is a picture of
   an interface rather than a readable one. The wide shape takes the whole
   shell, with its heading above the art and its prose below. */
.card.full{grid-template-columns:1fr;gap:clamp(1.3rem,2.5vw,1.9rem);align-items:start;}
.card.full p{max-width:62ch;}
@media (max-width:880px){
  .card{grid-template-columns:1fr;gap:1.5rem;align-items:start;}
  .card.flip .art{order:0;}
}
.card .head .idx{font-family:var(--mono);font-size:11px;letter-spacing:.16em;
  color:var(--key);margin-right:.9rem;}
.card .t{font-size:clamp(21px,1.1rem + .9vw,27px);font-weight:600;letter-spacing:-.016em;
  line-height:1.15;margin:.85rem 0 0;}

/* The one outbound link on the page, sitting inside a display-face heading.
   The global a rule would paint it chartreuse and underline it at full weight,
   which on 27px cream type reads as a warning rather than an invitation. Keep
   the heading's own colour, dim the rule underneath, and let hover carry it.
   text-decoration rather than border-bottom so it survives wrapping. */
.card .t a{color:inherit;text-decoration-color:var(--lit);
  text-decoration-thickness:1px;text-underline-offset:.18em;
  transition:color .18s,text-decoration-color .18s;}
.card .t a:hover{color:var(--key);text-decoration-color:var(--key);}
/* the only affordance telling you this heading is different from the other
   five, so it is worth the two characters */
.card .t a::after{content:"\2197";font-size:.62em;color:var(--key);
  margin-left:.28em;vertical-align:.14em;}
.card .body .head{margin-bottom:.9rem;}
.card p{font-size:15.5px;color:var(--dim);margin:0 0 .8rem;max-width:48ch;}
.card p:last-of-type{margin-bottom:0;}
.card .tag{display:inline-flex;align-items:center;gap:.5rem;font-family:var(--mono);
  font-size:9.5px;letter-spacing:.15em;text-transform:uppercase;color:var(--dim);
  border:1px solid var(--line-2);padding:4px 9px;}
.card .tag::before{content:"";width:5px;height:5px;flex:none;background:var(--key);
  box-shadow:0 0 8px var(--halo);}
.card .tag.warm::before{background:var(--warm);box-shadow:0 0 8px #e8934a44;}
.card .cap{font-family:var(--mono);font-size:11px;line-height:1.65;color:var(--faint);
  margin:.75rem 0 0;max-width:46ch;}

/* A dark app screenshot can be absorbed into the palette. A light one cannot:
   grading a white phone screen into warm near-black destroys the detail that
   makes it worth showing. So dark art is graded, light art is framed. */
.art .wide{position:relative;overflow:hidden;border:1px solid var(--line);
  border-top-color:var(--lit);box-shadow:0 26px 50px -34px #000;}
/* height:auto is load-bearing on every img on this page. They all carry
   width/height attributes for layout stability, and width:100% alone leaves
   the height pinned at the attribute value, which stretches the picture. */
.art .wide img{width:100%;height:auto;filter:contrast(1.04) saturate(.92);}
.art .wide::after{content:"";position:absolute;inset:0;pointer-events:none;
  background:linear-gradient(160deg,#dcf25a0a,transparent 45%),
             linear-gradient(0deg,#100d0866,transparent 40%);}

/* Two cards have no screenshot. Both were parked at the tail of the split
   block, which is why deleting that block took them with it the first time. */
.art .mark{background:var(--panel);border:1px solid var(--line);border-top-color:var(--lit);
  box-shadow:0 26px 50px -34px #000;padding:clamp(1.6rem,4vw,2.8rem);
  display:flex;align-items:center;justify-content:center;}
.art .mark svg{width:100%;height:auto;max-width:380px;}

.art .emblem{background:radial-gradient(70% 70% at 50% 30%,#e8934a14,transparent 70%),var(--panel);
  border:1px solid var(--line);border-top-color:var(--lit);box-shadow:0 26px 50px -34px #000;
  padding:clamp(2rem,5vw,3.4rem);display:flex;flex-direction:column;align-items:center;gap:1.4rem;}
.art .emblem img{width:clamp(96px,18vw,140px);height:auto;}
.art .emblem .motto{font-family:var(--mono);font-size:10.5px;letter-spacing:.22em;
  text-transform:uppercase;color:var(--warm);}

/* ---------- the pipeline: one input, four outputs ----------
   Two explicit rows: pictures on row 1, captions on row 2, arrows centred in
   their own auto-width tracks on row 1. Every picture crops to the same 4:5
   box from the top, which is what makes the paper form and the screen that
   replaced it line their fields up against each other.

   Track positions arrive as --c and --r inline, computed by build.py from the
   number of stages, so adding a fourth needs no CSS. Reading them through
   var() rather than setting grid-column inline is what lets the phone rule
   below flatten the whole thing without !important. */
.art .pipe{background:var(--panel);border:1px solid var(--line);border-top-color:var(--lit);
  box-shadow:0 26px 50px -34px #000;padding:clamp(1.1rem,2.4vw,1.8rem);}
.pipe .flow{display:grid;align-items:start;gap:clamp(.6rem,1.4vw,1.1rem);
  grid-template-columns:var(--cols);}
.pipe .flow > *{grid-column:var(--c);grid-row:var(--r);}

.pipe .shot{aspect-ratio:4/5;overflow:hidden;background:var(--panel-2);
  border:1px solid var(--line);border-top-color:var(--lit);}
.pipe .shot img{width:100%;height:100%;object-fit:cover;object-position:top center;}
.pipe .shot svg{width:100%;height:100%;display:block;}
.pipe .n{font-family:var(--mono);font-size:9.5px;letter-spacing:.16em;text-transform:uppercase;
  color:var(--key);display:block;margin:0 0 .45rem;}
.pipe .lab{padding-top:.85rem;border-top:1px solid var(--line);margin-top:.85rem;}
.pipe .lab .t{display:block;font-size:14.5px;font-weight:600;color:var(--ink);
  line-height:1.3;letter-spacing:-.008em;margin-bottom:.35rem;}
.pipe .lab p{font-size:12.5px;line-height:1.5;color:var(--dim);margin:0;max-width:none;}
.pipe .arrow{align-self:center;font-family:var(--mono);font-size:15px;color:var(--line-2);}

.pipe .outs{border-left:1px solid var(--line-2);padding-left:clamp(.9rem,1.8vw,1.3rem);}
.pipe .outs .n{color:var(--warm);margin-bottom:.7rem;}
.pipe .out{display:grid;grid-template-columns:22px minmax(0,1fr);gap:.7rem;align-items:start;
  padding:.55rem 0;border-top:1px solid var(--line);}
.pipe .out:first-of-type{border-top:none;padding-top:0;}
.pipe .out span{font-size:13.5px;line-height:1.45;color:var(--dim);}
.pipe .ico{width:22px;height:22px;color:var(--warm);opacity:.85;margin-top:-1px;}
/* the brand mark carries its own colours, so it must not inherit the amber or
   the icon set's slight fade */
.pipe .ico.brand{color:inherit;opacity:1;}
@media (max-width:900px){
  .pipe .flow{grid-template-columns:1fr;gap:.4rem;}
  .pipe .flow > *{grid-column:1;grid-row:auto;}
  .pipe .arrow{justify-self:center;transform:rotate(90deg);padding:.35rem 0;}
  .pipe .shot{aspect-ratio:16/10;}
  .pipe .lab{padding-top:.7rem;}
  .pipe .outs{border-left:none;border-top:1px solid var(--line-2);
    padding:1.1rem 0 0;margin-top:.6rem;}
}

/* ---------- 5. contact ---------- */
/* Words left, face right. This was the emptiest block on the page with the
   widest unused gutter, so the portrait fills space that was already there
   instead of pushing anything down. */
/* auto rather than 1fr on the text track, with justify-content:start. A 1fr
   track eats all the free width and parks the face on the outer margin, 246px
   away from the longest line, which reads as a stray element rather than a
   composition. */
.hail{display:grid;grid-template-columns:minmax(0,auto) auto;
  justify-content:start;gap:clamp(1.6rem,4vw,3.5rem);align-items:start;}
.hail .say{min-width:0;}
.hail .me{width:clamp(112px,13vw,164px);}
/* A daylight photo on a near-black ground pops hard. Saturation back, a touch
   darker, so it belongs to the palette. Deliberately under-graded compared to
   the screenshots: the face is the one thing here that has to read clearly. */
.hail .me img{width:100%;height:auto;display:block;border:1px solid var(--line-2);
  filter:saturate(.86) contrast(1.03) brightness(.94);
  box-shadow:0 26px 50px -34px #000;}

@media (max-width:620px){
  /* stacks under the address rather than above it, so the reading order and the
     DOM order stay the same */
  .hail{grid-template-columns:1fr;}
  .hail .me{width:124px;}
}

.mail{font-family:var(--disp);font-size:clamp(24px,1.3rem + 1.6vw,40px);font-weight:600;
  letter-spacing:-.02em;color:var(--key);text-decoration:none;display:inline-block;
  border-bottom:1px solid var(--line-2);padding-bottom:.15rem;transition:border-color .2s;}
.mail:hover{border-bottom-color:var(--key);}

footer{position:relative;z-index:1;max-width:var(--shell);margin:0 auto;
  padding:1.7rem var(--pad) clamp(3.5rem,7vw,6rem);border-top:1px solid var(--line);
  display:flex;flex-wrap:wrap;gap:.9rem 2rem;align-items:baseline;justify-content:space-between;
  font-family:var(--mono);font-size:11.5px;letter-spacing:.05em;color:var(--faint);}
footer a{color:var(--key);text-decoration:none;}
footer a:hover{text-decoration:underline;}

/* ---------- one entrance, then nothing moves ----------
   A single staggered reveal on load. No scroll-triggered animation: on a page
   this short it fires all at once anyway and only costs a listener. */
@media (prefers-reduced-motion:no-preference){
  .rise{animation:rise .7s cubic-bezier(.2,.75,.3,1) both;}
  .rise:nth-child(2){animation-delay:.06s;}
  .rise:nth-child(3){animation-delay:.12s;}
  .rise:nth-child(4){animation-delay:.18s;}
  @keyframes rise{from{opacity:0;transform:translateY(11px);}to{opacity:1;transform:none;}}
}
