/* Verifying a versioned asset too soon after a deploy poisons the edge: the
   request is served by the previous deployment and cached under the NEW ?v=
   URL for the full immutable TTL. Wait for propagation before the first fetch,
   or the only cure is another content change and a new hash. */
/* doc.css - shared document styles for /resume/ and /projects/*
   Extracted from resume/index.html so the tokens and components
   live in one place. Cached 7 days by _headers (/css/*). */

/* ---- brand tokens · single source; mirrored in css/style.css ----
   qa-check fails the build if these two copies ever diverge, so this is
   duplication with a gate rather than duplication with hope. Inlined
   instead of @import because an @import is a blocking round trip on the
   critical path. */
:root{
  --bg:#0A0A0C; --surface:#121316; --raised:#17191D; --sunk:#070709;
  --line:#26292E;
  --tx:#F2F3F1; --tx2:#A9AEB4; --tx3:#7C8288;
  --gold:#D4B274;
  --green:#55AD8D;
  --on-accent:#0A0A0C;
  /* translucent chrome (sticky nav, drawers) follows the theme;
     the hero vignette and the lightbox stay dark on purpose, because
     both sit over photography. */
  --veil:rgba(10,10,12,.9); --veil-strong:rgba(10,10,12,.97);

  --disp:'Jost',ui-sans-serif,system-ui,sans-serif;
  --body:'Manrope',ui-sans-serif,system-ui,sans-serif;
  --mono:'JetBrains Mono',ui-monospace,monospace;
  --hand:'Silverman Hand',cursive;

  --gut:clamp(1.25rem,4vw,3.5rem);
}

[data-theme="light"]{
  --bg:#F7F7F5; --surface:#FFFFFF; --raised:#FFFFFF; --sunk:#EDEEEA;
  --line:#E1E2DE;
  --tx:#17191D; --tx2:#565B62; --tx3:#666B70;
  --gold:#846626;
  --green:#1B4A3A;
  --on-accent:#FFFFFF;
  --veil:rgba(247,247,245,.9); --veil-strong:rgba(247,247,245,.97);
}

/* accent selects which family drives --accent; both are AA on every ground */
:root,[data-accent="gold"]{--accent:var(--gold)}
[data-accent="green"]{--accent:var(--green)}

/* legacy aliases so existing rules keep working while the sheets migrate */
:root{--bg-2:var(--surface); --bg-3:var(--raised);
      --fg:var(--tx); --fg-dim:var(--tx2); --fg-faint:var(--tx3);}
:root{--max:1080px}
*,*::before,*::after{box-sizing:border-box}
html{scroll-behavior:smooth;-webkit-text-size-adjust:100%}
@media (prefers-reduced-motion:reduce){html{scroll-behavior:auto}
  *{animation-duration:.01ms!important;transition-duration:.01ms!important}}
body{margin:0;background:var(--bg);color:var(--fg);font-family:var(--body);
  font-weight:300;font-size:clamp(1rem,.5vw + .92rem,1.09rem);line-height:1.72;
  -webkit-font-smoothing:antialiased;overflow-x:hidden}
a{color:inherit}
:focus-visible{outline:2px solid var(--accent);outline-offset:3px;border-radius:2px}
.wrap{max-width:var(--max);margin-inline:auto;padding-inline:var(--gut)}
/* mirrors style.css — see the note there; #000 on the light themes' dark
   accents measured 3.91:1 and 2.09:1 on the skip link. */
.skip{position:absolute;left:-9999px;top:0;background:var(--accent);color:var(--on-accent);padding:.75rem 1.25rem;z-index:200}
.skip:focus{left:0}

/* nav */
.nav{position:sticky;top:0;z-index:100;background:var(--veil);backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px);
  border-bottom:1px solid var(--line)}
/* These metrics are load-bearing: they must match css/style.css, or the bar
   changes height as you move between the home page and a document page. It
   measured 78px on / and 71px here, which is the "formatting jumps" people
   notice without being able to name. Padding, brand and link sizes below are
   the home page's values. */
.nav .wrap{display:flex;align-items:center;justify-content:space-between;gap:1rem;padding-block:1.05rem}
/* The bar is chrome, not content: it keeps one width across the site while
   the pages below it keep their own deliberate measures (--max is 1240px for
   the home gallery, 1080px for documents). Without this the whole bar slid
   80px sideways as you moved between them. */
.nav .wrap{max-width:1240px}
.brand{font-family:var(--disp);font-weight:400;letter-spacing:.22em;font-size:.82rem;
  text-transform:uppercase;text-decoration:none;white-space:nowrap}
.brand span{color:var(--accent)}
.nav ul{display:flex;gap:clamp(1rem,2.4vw,2.25rem);list-style:none;margin:0;padding:0;align-items:center}
.nav a{font-family:var(--disp);font-size:.74rem;letter-spacing:.17em;text-transform:uppercase;
  color:var(--fg-dim);text-decoration:none;position:relative;padding-block:.3rem;
  transition:color .25s;white-space:nowrap}
.nav a::after{content:'';position:absolute;left:0;bottom:0;height:1px;width:0;
  background:var(--accent);transition:width .3s}
.nav a:hover,.nav a[aria-current="true"]{color:var(--fg)}
.nav a:hover::after,.nav a[aria-current="true"]::after{width:100%}
.nav .dl{color:var(--accent);border:1px solid rgba(201,162,39,.4);padding:.45rem .85rem;border-radius:2px}
.nav .dl:hover{background:var(--accent);color:var(--on-accent)}
.nav .dl::after{display:none}
.burger{display:none;background:none;border:0;color:var(--fg);padding:.5rem;cursor:pointer}
@media (max-width:820px){
  .burger{display:block}
  .nav ul{position:fixed;inset:0;flex-direction:column;justify-content:center;align-items:center;gap:1.75rem;
    background:var(--veil-strong);backdrop-filter:blur(20px);transform:translateY(-100%);
    transition:transform .4s cubic-bezier(.4,0,.2,1)}
  .nav ul[data-open="true"]{transform:none;cursor:zoom-out}
  .nav a{font-size:1.05rem}
  /* backdrop-filter makes .nav a containing block for fixed descendants, so
     the overlay would size itself to the bar rather than the viewport */
  /* Same as css/style.css: no blur on a phone at all, so .nav can never be a
     containing block for the fixed overlay. See the note there. */
  .nav,.nav.menu-open{backdrop-filter:none;-webkit-backdrop-filter:none}
  .nav{background:var(--veil-strong)}
  .nav.menu-open{background:transparent;border-bottom-color:transparent}
  .nav ul[data-open="true"] a{color:var(--tx)}
  .nav ul[data-open="true"] a:hover{color:var(--accent)}
  .nav .brand{width:88px;height:34px}

  /* ---- tap targets ----
     WCAG 2.5.8 asks for 24px on both axes; these inline links measured 16-22px
     tall, and the theme controls 23-25px. They are text links inside prose, so
     the fix is padding around them rather than bigger type: the page looks the
     same and a thumb stops missing. qa-mobile.py holds the floor. */
  a.more,a.live,a.crumb,a.read,a.dl,.meta a,.contact .lines a,.doc-a a,
  .lines a,.foot a,footer a{
    display:inline-block;min-height:24px;padding-block:.28rem}
  .themebar button{min-height:40px;padding-inline:.75rem}
  .themebar .dot{width:.75rem;height:.75rem}
  .nav .brand{min-height:40px}

  /* The overlay's own links measured 34px and the accent dots 29px wide. In a
     full-screen menu there is no reason for either to be small. */
  .nav ul a{min-height:44px;display:flex;align-items:center;padding-inline:.6rem}
  .themebar button{min-width:40px}

  /* the bar hides the control on small screens, so surface it in the open menu
     or a phone cannot change theme at all */
  .nav.menu-open .themebar{display:flex;position:fixed;left:50%;bottom:3rem;
    transform:translateX(-50%);z-index:1}
}
@media print{.burger{display:none}}

/* hero */
.hero{padding-block:clamp(3.2rem,9vw,6rem) clamp(2rem,5vw,3rem);border-bottom:1px solid var(--line)}
.eyebrow{font-family:var(--disp);font-size:.68rem;letter-spacing:.4em;text-transform:uppercase;
  color:var(--accent);margin:0 0 1.2rem}
h1{font-family:var(--disp);font-weight:200;margin:0;line-height:.95;letter-spacing:.02em;
  font-size:clamp(2.4rem,8vw,5rem);text-transform:uppercase}
.role{font-family:var(--disp);font-weight:300;letter-spacing:.18em;text-transform:uppercase;
  color:var(--fg-dim);font-size:clamp(.78rem,1.5vw,.95rem);margin:1.1rem 0 0}
.lede{max-width:60ch;color:var(--fg-dim);margin:1.8rem 0 0;font-size:clamp(1.02rem,1.3vw,1.16rem)}
.lede strong{color:var(--fg);font-weight:400}
.meta{display:flex;flex-wrap:wrap;gap:.5rem 1.4rem;margin:2rem 0 0;font-size:.86rem;color:var(--fg-faint)}
.meta a{color:var(--fg-dim);text-decoration:none;border-bottom:1px solid rgba(201,162,39,.35)}
.meta a:hover{color:var(--accent)}

/* sections */
section{padding-block:clamp(2.6rem,6vw,4.2rem)}
section+section{border-top:1px solid var(--line)}
/* These section labels are <h2>. They used to be <p>, which left the résumé's
   outline as an <h1> followed by a flat run of <h3> job titles — a screen
   reader navigating by heading got no sections at all, on the one page this
   site exists to be read. font-weight is pinned because an h2 would otherwise
   inherit bold and the label would change shape. */
.sec-head{font-family:var(--disp);font-size:.7rem;font-weight:400;letter-spacing:.36em;
  text-transform:uppercase;color:var(--accent);margin:0 0 .5rem}
.sec-sub{color:var(--fg-faint);margin:0 0 2.4rem;font-size:.94rem;max-width:58ch}

/* case studies */
.case{border-left:2px solid var(--line);padding-left:clamp(1rem,2.5vw,1.8rem);margin-bottom:3.2rem}
.case:last-child{margin-bottom:0}
.case:hover{border-left-color:rgba(201,162,39,.45)}
.case h3{font-family:var(--disp);font-weight:400;text-transform:uppercase;letter-spacing:.06em;
  font-size:clamp(1.15rem,2.4vw,1.5rem);margin:0 0 .35rem;line-height:1.2}
.case .live{display:inline-block;font-family:var(--disp);font-size:.68rem;letter-spacing:.16em;
  text-transform:uppercase;color:var(--accent);text-decoration:none;margin:0 0 .1rem}
.case .live::after{content:' ↗';opacity:.7}
.case .live:hover{border-bottom:1px solid var(--accent)}
.case .gated{display:inline-block;font-family:var(--disp);font-size:.68rem;letter-spacing:.16em;
  text-transform:uppercase;color:var(--fg-faint)}
.case .what{color:var(--fg);margin:.9rem 0 1.1rem;max-width:62ch}
.tags{display:flex;flex-wrap:wrap;gap:.4rem;margin:0 0 1.2rem;padding:0;list-style:none}
.tags li{font-family:var(--disp);font-size:.63rem;letter-spacing:.13em;text-transform:uppercase;
  color:var(--fg-dim);border:1px solid var(--line);border-radius:2px;padding:.28rem .6rem;background:var(--bg-2)}
/* A filmography reads as a strip of titles, not as a second row of tech chips:
   same rhythm as .tags, no box. Unlike .tags it survives into the PDF — the
   film work is half of what the resume is claiming. */
.tags.films{gap:.35rem 1.5rem;margin-bottom:1rem}
.tags.films li{border:0;background:none;padding:.1rem 0;color:var(--fg-dim)}
/* runtime rides with its title; a separator between items would strand a rule
   at the head of every wrapped row */
.tags.films span{color:var(--fg-faint);margin-left:.35em}

.case ul.pts{margin:0;padding:0;list-style:none;max-width:66ch}
.case ul.pts li{position:relative;padding-left:1.05rem;margin-bottom:.7rem;color:var(--fg-dim)}
.case ul.pts li::before{content:'';position:absolute;left:0;top:.72em;width:5px;height:1px;background:var(--accent)}
.case ul.pts li strong{color:var(--fg);font-weight:400}

/* experience */
.job{display:grid;grid-template-columns:9.5rem 1fr;gap:0 2rem;padding-block:1.4rem;
  border-top:1px solid var(--line)}
.job:first-of-type{border-top:0;padding-top:0}
.when{font-family:var(--disp);font-size:.7rem;letter-spacing:.13em;text-transform:uppercase;
  color:var(--fg-faint);padding-top:.35rem}
.job h3{font-family:var(--disp);font-weight:500;text-transform:uppercase;letter-spacing:.05em;
  font-size:1rem;margin:0 0 .15rem}
.job .org{color:var(--accent);font-size:.9rem;margin:0 0 .7rem}
.job ul{margin:0;padding:0;list-style:none}
.job ul li{position:relative;padding-left:1.05rem;margin-bottom:.5rem;color:var(--fg-dim);font-size:.97rem}
.job ul li::before{content:'';position:absolute;left:0;top:.72em;width:5px;height:1px;background:rgba(201,162,39,.55)}
@media(max-width:700px){.job{grid-template-columns:1fr;gap:.5rem}.when{padding-top:0}}

/* skill + client rows */
.rows{display:grid;gap:1.15rem}
.row{display:grid;grid-template-columns:9.5rem 1fr;gap:0 2rem;align-items:start}
.row dt{font-family:var(--disp);font-size:.7rem;letter-spacing:.15em;text-transform:uppercase;
  color:var(--accent);padding-top:.28rem}
.row dd{margin:0;color:var(--fg-dim);font-size:.97rem}
@media(max-width:700px){.row{grid-template-columns:1fr;gap:.25rem}.row dt{padding-top:0}}

/* contact */
.contact{background:var(--bg-2)}
.contact .wrap{display:flex;flex-wrap:wrap;gap:1.6rem 3rem;align-items:baseline;justify-content:space-between}
.contact h2{font-family:var(--disp);font-weight:300;text-transform:uppercase;letter-spacing:.05em;
  font-size:clamp(1.3rem,3vw,2rem);margin:0}
.cta{display:inline-block;font-family:var(--disp);font-size:.75rem;letter-spacing:.18em;
  text-transform:uppercase;color:var(--on-accent);background:var(--accent);padding:.85rem 1.6rem;
  text-decoration:none;border-radius:2px;transition:opacity .25s}
.cta:hover{opacity:.85}
.contact .lines{display:grid;gap:.35rem;font-size:.95rem}
.contact .lines a{color:var(--fg-dim);text-decoration:none;border-bottom:1px solid rgba(201,162,39,.3)}
.contact .lines a:hover{color:var(--accent)}
footer{border-top:1px solid var(--line);padding-block:2rem;color:var(--fg-faint);font-size:.82rem}


/* ============================================================
   Portfolio components (/projects/ and case studies)
   Added when the résumé style block was extracted to this file.
   ============================================================ */

/* generic versions of selectors the résumé scopes inside .case,
   so case-study pages can use them outside a .case block */
.live{display:inline-block;font-family:var(--disp);font-size:.68rem;letter-spacing:.16em;
  text-transform:uppercase;color:var(--accent);text-decoration:none}
.live::after{content:' ↗';opacity:.7}
.live:hover{border-bottom:1px solid var(--accent)}
ul.pts{margin:0;padding:0;list-style:none;max-width:66ch}
ul.pts li{position:relative;padding-left:1.05rem;margin-bottom:.7rem;color:var(--fg-dim)}
ul.pts li::before{content:'';position:absolute;left:0;top:.72em;width:5px;height:1px;background:var(--accent)}
ul.pts li strong{color:var(--fg);font-weight:400}

/* internal link: same shape as .live but no arrow.
   The arrow means "leaves the site" and should keep meaning that. */
.more{display:inline-block;font-family:var(--disp);font-size:.68rem;letter-spacing:.16em;
  text-transform:uppercase;color:var(--accent);text-decoration:none;margin-top:.9rem}
.more::after{content:' →';opacity:.7;transition:transform .25s}
.more:hover{border-bottom:1px solid var(--accent)}

/* breadcrumb back to the index */
.crumb{display:inline-block;font-family:var(--disp);font-size:.68rem;letter-spacing:.18em;
  text-transform:uppercase;color:var(--fg-faint);text-decoration:none;margin-bottom:1.6rem}
.crumb::before{content:'← ';opacity:.7}
.crumb:hover{color:var(--accent)}

/* metrics row */
.metrics{display:grid;grid-template-columns:repeat(auto-fit,minmax(8rem,1fr));
  gap:1.4rem 1.8rem;margin:2.4rem 0 0;padding:0;list-style:none}
.metric b{display:block;font-family:var(--disp);font-weight:300;color:var(--accent);
  font-size:clamp(1.6rem,4vw,2.4rem);line-height:1;letter-spacing:.01em}
.metric span{display:block;margin-top:.45rem;font-size:.7rem;letter-spacing:.14em;
  text-transform:uppercase;color:var(--fg-faint);line-height:1.4}
.metrics.compact{gap:.9rem 1.4rem;margin-top:1.2rem}
.metrics.compact .metric b{font-size:1.15rem}
.metrics.compact .metric span{font-size:.62rem;margin-top:.25rem}

/* index project cards */
.pgrid-work{display:grid;gap:clamp(1.4rem,3vw,2rem);grid-template-columns:1fr;
  margin:0;padding:0;list-style:none}
@media(min-width:640px){.pgrid-work{grid-template-columns:repeat(2,1fr)}}
@media(min-width:940px){.pgrid-work{grid-template-columns:repeat(3,1fr)}}
.pcard{border:1px solid var(--line);border-radius:2px;background:var(--bg-2);overflow:hidden;
  display:flex;flex-direction:column;transition:border-color .25s}
.pcard:hover{border-color:rgba(201,162,39,.45)}
.pcard picture,.pcard img{display:block;width:100%;height:auto}
.pcard img{aspect-ratio:16/10;object-fit:cover}
.pcard .pbody{padding:clamp(1.1rem,2.5vw,1.6rem);display:flex;flex-direction:column;flex:1}
.pcard h3{font-family:var(--disp);font-weight:400;text-transform:uppercase;letter-spacing:.06em;
  font-size:clamp(1.05rem,2.2vw,1.3rem);margin:0 0 .5rem;line-height:1.2}
.pcard h3 a{text-decoration:none}
.pcard .what{color:var(--fg-dim);margin:0 0 1.1rem;font-size:.95rem}
.pcard .metrics.compact{margin-bottom:1.4rem}
.pcard .more{margin-top:auto;padding-top:.2rem}

/* screenshots */
.shots{display:grid;gap:clamp(1.2rem,3vw,2rem);margin:2rem 0 0;padding:0;list-style:none}
.shots.two{grid-template-columns:repeat(auto-fit,minmax(min(100%,18rem),1fr))}
.shot{margin:0}
.shot picture{display:block}
.shot img{display:block;width:100%;height:auto;border:1px solid var(--line);border-radius:2px}
.shot figcaption{margin-top:.7rem;font-size:.9rem;color:var(--fg-faint);line-height:1.55;max-width:62ch}
.shot a{display:block;text-decoration:none}

/* before / after pair */
.ba{display:grid;gap:1rem;grid-template-columns:repeat(auto-fit,minmax(min(100%,17rem),1fr));margin:0}
.ba .shot{position:relative}
.ba-label{position:absolute;top:.6rem;left:.6rem;font-family:var(--disp);font-size:.6rem;
  letter-spacing:.16em;text-transform:uppercase;color:var(--fg-dim);background:var(--veil);
  border:1px solid var(--line);border-radius:2px;padding:.25rem .55rem}
.ba + figcaption{margin-top:.7rem;font-size:.9rem;color:var(--fg-faint);max-width:62ch}

/* pull block */
.callout{background:var(--bg-2);border-left:2px solid var(--accent);
  padding:clamp(1.1rem,3vw,1.8rem) clamp(1.2rem,3vw,2rem);margin:0 0 2.6rem;border-radius:0 2px 2px 0}
.callout h2,.callout h3{font-family:var(--disp);font-weight:400;text-transform:uppercase;letter-spacing:.06em;
  font-size:1rem;margin:0 0 .7rem;color:var(--fg)}
.callout p{margin:0 0 .8rem;color:var(--fg-dim);max-width:66ch}
.callout p:last-child{margin-bottom:0}
.callout strong{color:var(--fg);font-weight:400}
.callout code{font-size:.86em;color:var(--fg);background:var(--bg-3);
  border:1px solid var(--line);border-radius:2px;padding:.08em .35em}

/* The theme control on a page with no nav to sit in. build-projects.py has
   floated it "top-right" since it was written, but the class it emits was
   never styled, so on /cover-letter/ it stacked in the top-left corner
   against the page edge. */
.themefloat{position:fixed;top:.9rem;right:.9rem;z-index:100;
  background:var(--veil);backdrop-filter:blur(14px);
  border-radius:999px;padding:.25rem .4rem}
.themefloat .themebar{margin-left:0}
@media print{.themefloat{display:none}}

/* prose */
.prose{max-width:62ch;color:var(--fg-dim)}
/* .prose caps the measure. On an element that is also a .wrap it capped the
   wrap itself, and .wrap centres what it constrains — so the letter's body sat
   centred under a left-aligned letterhead. Constrain the children instead and
   the column starts where the name does. Screen only: print sets its own
   narrower measure and already reads correctly. */
@media screen{
  .wrap.prose{max-width:var(--max)}
  .wrap.prose > *{max-width:62ch}
}
.prose p{margin:0 0 1.1rem}
.prose p:last-child{margin-bottom:0}
.prose strong{color:var(--fg);font-weight:400}
code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.86em;
  color:var(--fg-dim);word-break:break-word}

@media print{
  body{background:#fff;color:#111;font-size:10.5pt;line-height:1.45}
  .nav,.cta,.skip,footer{display:none}
  /* A link to the PDF, inside the PDF. build-pdf.sh renders these pages from
     http://localhost:8823, so the annotation Chrome baked in pointed at
     localhost — a dead link in the résumé every reader was downloading. It is
     also nonsense to offer a download from within the download. Hiding every
     /pdf/ link in print kills both, and keeps the PDF byte-stable: the URL
     carries a content hash, so embedding it made each render depend on the
     hash of the previous render and the file could never settle. */
  a[href^="/pdf/"]{display:none}
  /* Same reasoning for the plain-text version: a reader holding the printed
     résumé does not need a link to a plainer copy of what is in their hand.
     It also baked a localhost annotation during the PDF build, which is what
     build-pdf.sh refused on. */
  a[href="/resume/plain/"]{display:none}
  section{padding-block:.6rem;border:0}
  .case{border-left:0;padding-left:0;margin-bottom:1rem;break-inside:avoid}
  .hero{padding-block:0 .6rem}
  h1{color:#111;font-size:24pt}
  .sec-head,.eyebrow,.case .live,.row dt,.job .org{color:#5a4a12}
  .lede,.case .what,.case ul.pts li,.job ul li,.row dd,.when,.sec-sub{color:#333}
  .tags li{color:#444;background:none;border-color:#ccc}
  .contact{background:none}
}


@media print{
  /* Every other work entry's link text is its own URL. One is labelled, so on
     paper it has to spell out where it goes. This overrides the ↗ glyph, which
     means nothing in a printed document anyway. */
  .live.named::after{content:" (" attr(href) ")";font-size:8pt;color:#555;opacity:1;
    /* .live uppercases its text; a path typed back off the page in caps 404s */
    text-transform:none;letter-spacing:.02em}
}

@media print{
  .crumb,.more,.pcard img,.shot,.ba{display:none}
  .callout{background:none;border-left:2px solid #5a4a12;padding:.4rem 0 .4rem .8rem;margin-bottom:.8rem}
  .callout h2,.callout h3{color:#111}
  .callout p,.prose,.prose p{color:#333}
  .callout code,code{color:#111;background:none;border:0}
  .metric b{color:#5a4a12;font-size:14pt}
  .metric span{color:#444}
  .pcard{border:0;background:none;break-inside:avoid}
  .pcard .pbody{padding:0}
  ul.pts li{color:#333}
  .live{color:#5a4a12}
}

/* Headlines wrap on meaning rather than leaving a single word stranded; the
   property is ignored where unsupported, so there is nothing to fall back to. */
h1,h2,h3,.claim{text-wrap:balance}
p,figcaption,.what,.sec-sub{text-wrap:pretty}

/* A feature card's image opens it full size. The lift is the affordance; the
   global reduced-motion rule already flattens the transition. */
.pcard .shotlink{display:block;overflow:hidden}
.pcard .shotlink img{transition:transform .4s ease}
.pcard .shotlink:hover img,.pcard .shotlink:focus-visible img{transform:scale(1.03)}

/* Tall phone screenshots in a 16:10 card: centre crop hides the part that
   matters, so each one names its own focal point. */
.pcard-focus img{object-position:var(--focus,50% 50%)}

/* ---- print: the resume has to land on two pages ----
   The screen layout pads generously with --gut and section padding; on paper
   that was pushing 1,400 words onto six sheets. Collapse the page furniture,
   keep the type readable, and never break a job across a sheet. */
@page{margin:9mm 11mm}
@media print{
  :root{--gut:0}
  html,body{font-size:9.3pt;line-height:1.32}
  .wrap{max-width:none;padding:0;margin:0}
  section{padding-block:.35rem}
  .hero{padding-block:0 .35rem}
  h1{font-size:19pt;line-height:1.02;margin:0 0 .15rem}
  .role{font-size:10.5pt;margin:0 0 .3rem}
  .lede{font-size:10pt;margin:0 0 .45rem}
  .meta{margin:.35rem 0 0;gap:.2rem .9rem;font-size:9pt}
  .sec-head{font-size:9pt;margin:.55rem 0 .25rem}
  .sec-sub{display:none}
  h2{font-size:12.5pt;margin:.5rem 0 .3rem}
  h3{font-size:10.5pt;margin:.3rem 0 .15rem}
  .job{margin-bottom:.5rem;break-inside:avoid;page-break-inside:avoid}
  .job ul{margin:.15rem 0 0;padding-left:1rem}
  .job ul li{margin:0 0 .12rem}
  .tags{margin:.2rem 0 0;gap:.2rem .35rem}
  .tags li{font-size:8pt;padding:.05rem .3rem}
  .metrics{gap:.5rem 1.2rem;margin:.35rem 0}
  .metric b{font-size:12pt}
  .row{margin:.15rem 0}
  p{margin:0 0 .3rem}
  a{text-decoration:none}
  /* the PDF is a leave-behind: spell out where the links actually go */
  .meta a[href^="http"]::after{content:" (" attr(href) ")";font-size:8pt;color:#555}
}

@media print{
  /* Five work write-ups with bullet lists, six jobs and four detail blocks do
     not fit two sheets. On paper keep the one-line summary and the metrics —
     the bullets exist in full on the case-study pages the URL points to. */
  .case ul.pts{display:none}
  .case .tags{display:none}
  .case .tags.films{display:flex;color:#333}
  .contact{display:none}          /* email and phone are already in the header */
  #work .case{margin-bottom:.45rem}
  .job:last-child{margin-bottom:0}
}

@media print{
  /* A section label alone at the foot of a sheet reads as a mistake: adding the
     film strip pushed the first job over the break and stranded "History" on
     page one. Keep every heading with what it introduces. */
  .sec-head,h2,h3{break-after:avoid;page-break-after:avoid}
  h1{font-size:17pt}
  .job{margin-bottom:.38rem}
  section{padding-block:.25rem}
  .sec-head{margin:.4rem 0 .18rem}
  .metrics{margin:.25rem 0}
  .job ul li{margin:0 0 .08rem}
}

/* ---- print: repoint the palette, do not patch it ----
   Colour comes from tokens, and print never redefined them, so every rule that
   said color:var(--fg) printed near-white on white. The strongest sentence on
   the resume was invisible in the PDF. Redefine the tokens once and every
   descendant inherits correctly. */
@media print{
  :root{
    --bg:#fff; --bg-2:#fff; --bg-3:#fff;
    --fg:#111; --fg-dim:#333; --fg-faint:#555;
    --accent:#6b5410; --line:#c9c9c9;
  }
  a{color:inherit}
  .meta a{color:#444}
  /* a download link inside the download is noise */
  a[href$=".pdf"],.dl,.cta{display:none}
}

@media print{
  /* Removing --gut for print also removed the measure, so lines ran the full
     width of the sheet at about 110 characters. Prose sets its own. */
  .prose,.prose p,.lede{max-width:34em}
  .job ul li,.case .what,ul.pts li{max-width:36em}
  .prose p{margin:0 0 .55rem}
  .prose ul{margin:.35rem 0 .6rem}
  .prose ul li{margin:0 0 .35rem;max-width:34em}
}

/* brand guidelines swatches */
.swatches{display:grid;gap:.9rem;margin:0 0 2rem}
@media(min-width:820px){.swatches{grid-template-columns:1fr 1fr}}
.sw{display:grid;grid-template-columns:2.6rem 1fr auto;align-items:center;column-gap:.8rem;row-gap:.3rem;
  padding:.75rem .85rem;border:1px solid var(--line);border-radius:2px;background:var(--bg-2)}
.sw .chip{width:2.6rem;height:2.6rem;border-radius:2px;border:1px solid var(--line);grid-row:span 2}
.sw b{font-family:var(--disp);font-weight:400;text-transform:uppercase;letter-spacing:.06em;font-size:.8rem}
.sw code{font-size:.78rem;color:var(--fg-dim);justify-self:end}
.sw-c{grid-column:3;grid-row:2;font-size:.75rem;color:var(--accent);justify-self:end}
.sw-u{grid-column:2;grid-row:2;font-size:.8rem;color:var(--fg-faint)}
/* ---- theme + accent control ---- */
.themebar{display:flex;align-items:center;gap:.35rem;margin-left:1rem}
.themebar .grp{display:flex;border:1px solid var(--line);border-radius:999px;overflow:hidden}
.themebar button{appearance:none;background:none;border:0;cursor:pointer;
  font-family:var(--body);font-size:.62rem;letter-spacing:.14em;text-transform:uppercase;
  color:var(--tx3);padding:.42rem .6rem;line-height:1;transition:color .25s,background .25s}
.themebar button:hover{color:var(--tx)}
.themebar button[aria-pressed="true"]{color:var(--on-accent);background:var(--accent)}
.themebar .dot{width:.6rem;height:.6rem;border-radius:50%;display:inline-block}
.themebar .dot.gold{background:#D4B274}
.themebar .dot.green{background:#55AD8D}
@media(max-width:860px){.themebar{display:none}}
/* the wordmark is the real lockup; light and dark ink swap with the theme */
.brand{display:inline-block;width:104px;height:40px;background-repeat:no-repeat;
  background-position:left center;background-size:contain;
  background-image:url("/img/brand/sps-horiz.svg")}
[data-theme="light"] .brand{background-image:url("/img/brand/sps-horiz-light.svg")}
.vh{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
.logos{display:grid;gap:1rem;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));margin:0 0 2rem}
.logos figure{margin:0;padding:1.4rem 1rem;border:1px solid var(--line);border-radius:2px;
  background:var(--surface);display:grid;gap:.9rem;place-items:center}
.logos img{width:100%;max-width:130px;height:64px;object-fit:contain}
.logos figcaption{font-family:var(--disp);font-size:.66rem;letter-spacing:.16em;text-transform:uppercase;color:var(--tx3)}
[data-theme="light"] .logos img{filter:invert(1)}

/* ---- high contrast and reduced transparency ----
   forced-colors: Windows high contrast replaces the palette wholesale, so stop
   painting over its choices and let borders carry structure.
   prefers-reduced-transparency: the veils and the blur are decoration; solid
   surfaces read the same and cost less. */
@media (prefers-reduced-transparency: reduce){
  .nav,.nav.solid{background:var(--bg);backdrop-filter:none}
  .lb{background:#060608;backdrop-filter:none}
}
@media (forced-colors: active){
  *{forced-color-adjust:auto}
  .nav,.nav.solid,.lb{background:Canvas;backdrop-filter:none}
  .cta,.cta.ghost{border:1px solid ButtonText;background:ButtonFace;color:ButtonText}
  .themebar button[aria-pressed="true"]{background:Highlight;color:HighlightText}
  .pcard,.scard,.doc,.card{border:1px solid CanvasText}
  :focus-visible{outline:3px solid Highlight;outline-offset:2px}
  .hero::after{display:none}
}
/* the signature closes the page, in the hand it was drawn in */
.sign{display:grid;gap:.55rem;margin:1.9rem 0 0;justify-items:start}
.sign img{width:180px;height:auto;opacity:.9}
[data-theme="light"] .sign img{filter:invert(1)}
.sign span{font-family:var(--hand);font-size:1.05rem;color:var(--tx3);line-height:1.3}
@media print{
  /* The signature is drawn in light ink for the dark theme and inverted for
     the light one. Print renders from whichever theme the page loaded in, so
     force the dark ink here or the cover letter's PDF closes on a blank line. */
  .sign img,[data-theme="light"] .sign img{filter:invert(1);width:150px}
  .sign span{color:#555}
  .sign{margin:1rem 0 0;gap:.3rem;break-inside:avoid}
}
