/* ═══════════════════════════════════════════════════════════════════════
   Anshi Wang — personal site
   Beats: film → settle → card → unfold → resume.
   The card is the middle third of a letter sheet folded in thirds; the
   top third hinges up and the bottom third hinges down (§8).
   Zero dependencies. Native CSS transforms/transitions only.
   ═══════════════════════════════════════════════════════════════════ */

/* registered so the scroll-edge fades interpolate instead of snapping */
@property --fade-top { syntax: '<length>'; inherits: false; initial-value: 0px; }
@property --fade-bot { syntax: '<length>'; inherits: false; initial-value: 0px; }

:root {
  /* §4 — three values, and nothing else. Colour is the enemy here. */
  --paper:      #EFEADD;   /* warm bone */
  --ink:        #1C1C1A;   /* near-black charcoal */
  --ink-soft:   #6B665C;   /* muted grey */
  --hairline:   #DCD5C4;   /* slightly darker bone */

  /* §4 — pressed into the paper, not floating on it */
  --deboss: 0 1px 0 rgba(255,255,255,.65), 0 -1px 0 rgba(0,0,0,.12);

  --display: 'Cormorant Garamond', 'Palatino Linotype', Palatino, Constantia, serif;
  --body:    'EB Garamond', 'Palatino Linotype', Palatino, Constantia, Georgia, serif;

  /* Geometry. The card is one third of the sheet, so the sheet's height is
     locked to 3 × the card's — every size below follows from the card.

     These are the sizes of the OPEN sheet. The closed card is the same
     geometry scaled up by --card-boost, and that boost eases back to 1 as
     the paper unfolds. Sizing from the open state is what keeps the resume
     rendering at its true pixel size instead of being scaled down. */
  --card-boost: 1.45;                    /* the resting card reads ~38% larger */
  --card-w: min(calc(92vw / var(--card-boost)), 49vh, 515px);
  --card-h: calc(var(--card-w) / 1.75);  /* §4 — business-card proportions */
  --sheet-h: calc(var(--card-h) * 3);    /* 49vh keeps 3 × height ≤ 84vh */
  /* §5 — the card has an edge. Folded in thirds it is three sheets thick,
     which is exactly why the closed card reads as a slab and the opened
     sheet does not. */
  --thick: 6px;

  --unfold: 1350ms;
  --paper-ease: cubic-bezier(.19, 1, .22, 1);
  /* A hinge has to be watched, so the curve keeps real time in the middle
     of the travel instead of snapping open and creeping to a stop. */
  --hinge-ease: cubic-bezier(.62, .03, .21, 1);
}

*, *::before, *::after { box-sizing: border-box; }
html, body { height: 100%; }

body {
  margin: 0;
  background: #0d0b0a;
  font-family: var(--body);
  color: var(--ink);
  overflow: hidden;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

/* ══ BEAT 2 · backdrop ═══════════════════════════════════════════════ */

.backdrop { position: fixed; inset: 0; z-index: 0; }

.backdrop__image {
  position: absolute;
  inset: -6%;                       /* overscan so blur doesn't bleed edges */
  background: center / cover no-repeat;   /* src set by JS — §11 lazy-load */
  filter: blur(34px) saturate(.72) brightness(.66);
  transform: scale(1.12);
  opacity: 0;
  transition: filter 2000ms var(--paper-ease),
              transform 2400ms var(--paper-ease),
              opacity 1100ms ease;
}

body[data-phase='settle'] .backdrop__image,
body[data-phase='card']   .backdrop__image,
body[data-phase='page']   .backdrop__image {
  opacity: 1;
  /* the resting blur — soft enough not to compete with the paper, open
     enough that the room behind it still reads */
  filter: blur(13px) saturate(.8) brightness(.72);
  transform: scale(1.05);
}

body[data-phase='page'] .backdrop__image {
  filter: blur(17px) saturate(.74) brightness(.58);
}

.backdrop__vignette {
  position: absolute; inset: 0;
  background:
    radial-gradient(118% 88% at 50% 44%,
      rgba(24,16,10,0) 0%, rgba(20,14,9,.2) 54%, rgba(12,9,6,.64) 100%),
    linear-gradient(180deg, rgba(34,22,13,.1), rgba(12,9,6,.22));
  transition: background 1600ms ease;
}
body[data-phase='page'] .backdrop__vignette {
  background:
    radial-gradient(112% 84% at 50% 44%,
      rgba(24,16,10,.06) 0%, rgba(20,14,9,.34) 52%, rgba(10,7,5,.76) 100%),
    linear-gradient(180deg, rgba(34,22,13,.16), rgba(12,9,6,.3));
}

/* ══ BEAT 1 · the film ═══════════════════════════════════════════════ */

.film {
  position: fixed; inset: 0; z-index: 40;
  background: #000;
  transition: opacity 1500ms ease, filter 1600ms var(--paper-ease);
}

.film__video {
  width: 100%; height: 100%;
  object-fit: cover;
  display: block;
  transform: scale(1.085);   /* crop past the source's baked-in letterbox */
}

/* A 2:1 clip filled into a portrait viewport is a narrow vertical slice of
   itself. Show the whole frame instead and let the black do the framing. */
@media (max-aspect-ratio: 1/1) {
  .film__video { object-fit: contain; transform: none; }
}

.film__grain {
  position: absolute; inset: 0;
  pointer-events: none;
  opacity: .5;
  background: radial-gradient(105% 78% at 50% 50%,
    rgba(0,0,0,0) 42%, rgba(0,0,0,.55) 100%);
}

body[data-phase='dissolve'] .film {
  opacity: 0;
  filter: blur(30px) saturate(.6) brightness(.7);
  pointer-events: none;
}
body[data-phase='settle'] .film,
body[data-phase='card']   .film,
body[data-phase='page']   .film { opacity: 0; pointer-events: none; }
body:not([data-phase='intro']) .film__skip { opacity: 0; pointer-events: none; }

.film__skip {
  position: absolute;
  right: clamp(18px, 4vw, 44px);
  bottom: clamp(18px, 4vw, 40px);
  font-size: 13px;
  letter-spacing: .34em;
  text-indent: .34em;             /* offset the trailing letter-space */
  opacity: 0;
  animation: skipIn 900ms ease 2600ms forwards;
}
@keyframes skipIn { to { opacity: 1; } }

/* ══ the frosted pill shared by both controls ════════════════════════

   A translucent white ground is what lets muted grey stay legible over
   footage of any brightness, without lightening the ink itself.        */

.film__skip,
.sound {
  padding: .6em 1.25em .68em;
  border: 1px solid rgba(255,255,255,.14);
  border-radius: 999px;
  background: rgba(255,255,255,.14);
  -webkit-backdrop-filter: blur(8px) saturate(.92);
  backdrop-filter: blur(8px) saturate(.92);
  font-family: var(--display);
  text-transform: uppercase;
  color: var(--ink-soft);
  /* pressed into the frosted ground, same as ink into paper */
  text-shadow: 0 1px 0 rgba(255,255,255,.42);
  cursor: pointer;
  transition: background 320ms ease, border-color 320ms ease,
              color 320ms ease, opacity 320ms ease;
}
.film__skip:hover,
.sound:hover {
  background: rgba(255,255,255,.24);
  border-color: rgba(255,255,255,.22);
  color: #565148;
}

/* ══ the sound toggle ════════════════════════════════════════════════

   Fixed in the opposite corner from Skip, and outside .film so it
   survives every phase. Shares the frosted pill defined above, which is
   what carries the muted grey over footage of any brightness.           */

.sound {
  position: fixed;
  left: clamp(18px, 4vw, 44px);
  bottom: clamp(18px, 4vw, 40px);
  z-index: 60;
  font-size: 12px;
  letter-spacing: .3em;
  text-indent: .3em;              /* offset the trailing letter-space */
}

/* a hairline that fills in when sound is on — the state is legible
   without resorting to colour */
.sound__label { position: relative; }
.sound__label::after {
  content: '';
  position: absolute;
  left: 0; right: .3em; bottom: -.28em;
  height: 1px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 420ms var(--paper-ease);
}
.sound[aria-pressed='true'] .sound__label::after { transform: scaleX(1); }

.sound:focus { outline: none; }
.sound:focus-visible { outline: 1px solid currentColor; outline-offset: 4px; }

/* ══ BEAT 3 · stage, tilt, and the folded object ═════════════════════ */

.stage {
  position: fixed; inset: 0; z-index: 20;
  display: grid; place-items: center;
  perspective: 900px;                 /* §5 — deeper perspective */
  perspective-origin: 50% 45%;
  pointer-events: none;
}

/* JS writes the resting slant + cursor tilt here as one transform */
.tilt {
  -webkit-transform-style: preserve-3d;
  transform-style: preserve-3d;
  will-change: transform;
  pointer-events: none;
}

/* The closed card sits boosted; the boost eases back to 1 on exactly the
   hinge's duration and curve, so the shrink rides the fold rather than
   reading as a second, separate move. */
.scaler {
  -webkit-transform-style: preserve-3d;
  transform-style: preserve-3d;
  transform: scale(var(--card-boost));
  transition: transform var(--unfold) var(--hinge-ease);
  will-change: transform;
}
body[data-phase='page'] .scaler { transform: scale(1); }

/* card-sized box; the unfolded sheet extends above and below it */
.object {
  position: relative;
  width: var(--card-w);
  height: var(--card-h);
  -webkit-transform-style: preserve-3d;
  transform-style: preserve-3d;
  opacity: 0;
}

body[data-phase='card'] .object,
body[data-phase='page'] .object {
  animation: dropIn 1250ms var(--paper-ease) forwards;
}
@keyframes dropIn {
  0%   { opacity: 0; transform: translate3d(0, -40vh, 200px); }
  55%  { opacity: 1; }
  100% { opacity: 1; transform: translate3d(0, 0, 0); }
}

.fold {
  position: absolute; inset: 0;
  -webkit-transform-style: preserve-3d;
  transform-style: preserve-3d;
  pointer-events: auto;
  cursor: pointer;
  transition: opacity 420ms ease;
}
body[data-phase='page'] .fold { pointer-events: none; }

/* ══ the paper itself ════════════════════════════════════════════════

   §3/§4 — grain generated in CSS. Two turbulence passes: shadowed fibres
   on multiply, their lit side on screen. That pairing is what makes the
   surface read as felt cardstock rather than a flat grey wash.          */

.paper {
  position: absolute; inset: 0;
  background-color: var(--paper);
  border: 1px solid var(--hairline);     /* §4 — hairline in darker bone */
  border-radius: 2px;
  overflow: hidden;
  /* the two blend passes below are meant to blend with this element's own
     bone background and nothing else; isolating them keeps them from
     pulling sibling layers into the blend group on mobile compositors */
  isolation: isolate;
}

.paper::before,
.paper::after {
  content: '';
  position: absolute; inset: 0;
  pointer-events: none;
}

.paper::before {
  opacity: .58;
  mix-blend-mode: multiply;
  background-image:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='240' height='240'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9 .24' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3CfeComponentTransfer%3E%3CfeFuncA type='linear' slope='.6' intercept='-.04'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Crect width='240' height='240' filter='url(%23n)'/%3E%3C/svg%3E"),
    repeating-linear-gradient(90deg,
      rgba(120,112,94,.15) 0 1px,
      rgba(120,112,94,.05) 1px 2px,
      rgba(120,112,94,.09) 2px 3px,
      rgba(120,112,94,.02) 3px 5px);
  background-size: 240px 240px, auto;
}

.paper::after {
  opacity: .46;
  mix-blend-mode: screen;
  background-image:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='240' height='240'%3E%3Cfilter id='m'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9 .24' numOctaves='3' seed='7' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3CfeComponentTransfer%3E%3CfeFuncA type='linear' slope='.48' intercept='-.06'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Crect width='240' height='240' filter='url(%23m)'/%3E%3C/svg%3E"),
    repeating-linear-gradient(90deg,
      rgba(255,255,255,.15) 0 1px,
      rgba(255,255,255,0) 1px 3px);
  background-size: 240px 240px, auto;
  background-position: 1px 1px, 1px 0;
}

/* ══ the three panels ════════════════════════════════════════════════ */

.panel {
  position: absolute;
  left: 0;
  width: 100%;
  height: 100%;
  -webkit-transform-style: preserve-3d;
  transform-style: preserve-3d;
  /* §4 — soft, low, matte. JS nudges the offsets as the card tilts (§5). */
  box-shadow: var(--sx, 0px) var(--sy, 24px) 50px rgba(0,0,0,.35);
  transition: transform var(--unfold) var(--hinge-ease),
              box-shadow 600ms ease;
}

.panel--mid { top: 0; z-index: 2; }

/* Hinged at the card's top edge; folded, it lies behind the card.

   The trailing translateZ is load-bearing. A bare rotateX(180deg) lands the
   wing EXACTLY coplanar with .panel--mid — same rect, same depth, same cream
   — and then only z-index decides which paints on top. Inside preserve-3d,
   engines sort by depth and may ignore z-index for coplanar layers; iOS
   CoreAnimation does, and a wing wins. Because the wing is the same paper
   with the same grain, the card still looks perfect while its type vanishes.
   After a 180° X-rotation local +Z points away from the viewer, so this
   pushes each wing a definite 2px behind the face. */
.panel--top {
  bottom: 100%;
  -webkit-transform-origin: 50% 100%;
          transform-origin: 50% 100%;
  -webkit-transform: rotateX(180deg) translateZ(2px);
          transform: rotateX(180deg) translateZ(2px);
}

/* hinged at the card's bottom edge */
.panel--bot {
  top: 100%;
  -webkit-transform-origin: 50% 0%;
          transform-origin: 50% 0%;
  -webkit-transform: rotateX(-180deg) translateZ(2px);
          transform: rotateX(-180deg) translateZ(2px);
}

/* §8 — the unfold. Both wings swing out to flat and the three panels
   become one continuous sheet. The lower wing trails the upper one, so it
   reads as paper opening rather than two doors on one motor. */
/* flat, and coplanar on purpose — the three panels become one sheet.
   Same function list as the folded state, so each term interpolates
   cleanly instead of going through matrix decomposition. */
body[data-phase='page'] .panel--top,
body[data-phase='page'] .panel--bot {
  -webkit-transform: rotateX(0deg) translateZ(0px);
          transform: rotateX(0deg) translateZ(0px);
}
.panel--bot { transition-delay: 110ms; }

/* §8 — then the folded assembly cross-fades into the real scrollable sheet.
   Same paper, same footprint, so only the ink appears to change. */
body[data-phase='page'] .fold {
  opacity: 0;
  /* The type layer rides 3px proud of the panels, which puts it in front of
     .page as well. Opacity alone would leave it technically in front of the
     resume; taking it out of visibility once the cross-fade lands removes
     any chance of it ghosting over the document. */
  visibility: hidden;
  transition: opacity 460ms ease 980ms, visibility 0s linear 1440ms;
}

/* §8 — no flip. This needs no `backface-visibility`: the middle panel never
   rotates, so the card is always front-facing, and the only panels that do
   turn are the wings, which carry nothing but blank cream.

   Do not add it back. Inside this five-deep preserve-3d chain (.tilt >
   .scaler > .object > .fold > .panel--mid), `backface-visibility: hidden`
   promotes the text to its own compositing layer, and WebKit misjudges the
   accumulated matrix as back-facing and drops the layer — the card renders
   with texture, edges and slant intact but no type at all. */

/* ══ §5 · the card's extruded edge, so it reads as a solid object ═════ */

/* Seen nearly edge-on, only a sliver of these ever projects — so the
   value contrast has to carry the read, not the width. */
.edge {
  position: absolute;
  transition: opacity 420ms ease;
  background-image: linear-gradient(var(--edge-angle, 90deg),
    #DCD5C3 0%, #C6BDA6 38%, #ADA48C 72%, #C2B9A2 100%);
}
/* 89.6° rather than a flat 90°: an exactly perpendicular plane projects to
   zero area, and degenerate planes are what upset 3D depth sorting. The
   0.4° is invisible and keeps every plane non-degenerate. */
.edge--r { top: 0; right: 0; width: var(--thick); height: 100%;
           -webkit-transform-origin: 100% 50%; transform-origin: 100% 50%;
           -webkit-transform: rotateY(-89.6deg); transform: rotateY(-89.6deg); }
.edge--l { top: 0; left: 0;  width: var(--thick); height: 100%;
           -webkit-transform-origin: 0 50%; transform-origin: 0 50%;
           -webkit-transform: rotateY(89.6deg); transform: rotateY(89.6deg); }
.edge--t { top: 0; left: 0;  height: var(--thick); width: 100%;
           -webkit-transform-origin: 50% 0; transform-origin: 50% 0;
           -webkit-transform: rotateX(-89.6deg); transform: rotateX(-89.6deg);
           --edge-angle: 180deg; }
.edge--b { bottom: 0; left: 0; height: var(--thick); width: 100%;
           -webkit-transform-origin: 50% 100%; transform-origin: 50% 100%;
           -webkit-transform: rotateX(89.6deg); transform: rotateX(89.6deg);
           --edge-angle: 0deg; }

/* the wings are thin leaves, not slabs — no extrusion on them.
   As the sheet opens the stack becomes one leaf, so the slab goes away. */
body[data-phase='page'] .edge { opacity: 0; }

/* ══ §5 · directional specular. Paper catches light, it never shines ══ */

.gloss {
  position: absolute; inset: 0;
  pointer-events: none;
  border-radius: 2px;
  opacity: .34;
  background: radial-gradient(58% 58% at var(--gx, 26%) var(--gy, 16%),
    rgba(255,255,255,.62), rgba(255,255,255,0) 68%);
  transition: opacity 500ms ease;
}

/* ══ §9 · card face ══════════════════════════════════════════════════ */

/* The type's own layer: a sibling of the panels, not a child of one, held a
   definite 3px in front of the whole stack (the folded wings sit 2px behind
   the face, so nothing can sort above this). It is deliberately FLAT — no
   preserve-3d — so its text is never entered into 3D sorting, and explicitly
   backface-visible so no engine may cull it. */
.card {
  position: absolute;
  top: 0; left: 0;
  width: 100%; height: 100%;
  z-index: 3;
  -webkit-transform: translateZ(3px);
          transform: translateZ(3px);
  -webkit-transform-style: flat;
          transform-style: flat;
  -webkit-backface-visibility: visible;
          backface-visibility: visible;
  pointer-events: none;                 /* the panel underneath takes the click */
  font-size: clamp(9px, calc(var(--card-w) / 46), 12px);  /* scales as a unit */
  transition: opacity 420ms ease;
}

/* Riding proud of the panels also puts the type in front of .page, so it
   has to leave before the resume becomes legible — otherwise the two sets
   of type briefly double-expose. The type goes first, then blank cream
   dissolves into blank cream, then the document's own type arrives. */
body[data-phase='page'] .card {
  opacity: 0;
  transition: opacity 300ms ease 820ms;
}

/* nothing inside the type layer may be culled either */
.card * {
  -webkit-backface-visibility: visible;
          backface-visibility: visible;
}

.card__phone,
.card__monogram {
  position: absolute;
  top: 10.5%;
  font-family: var(--display);
  text-transform: uppercase;
  color: var(--ink-soft);
  text-shadow: var(--deboss);
}
.card__phone    { left: 9.5%;  font-size: 1.14em; letter-spacing: .17em; }
/* §9 — the two letters set close together, nothing beneath */
.card__monogram { right: 9.5%; font-size: 1.6em;  letter-spacing: .04em; color: var(--ink); }

.card__center {
  position: absolute;
  left: 0; right: 0; top: 45%;
  transform: translateY(-50%);
  text-align: center;
}

.card__name {
  margin: 0;
  font-family: var(--display);
  font-size: 2.85em;
  font-weight: 400;
  text-transform: uppercase;
  letter-spacing: .22em;            /* §4 — 0.18em–0.25em */
  text-indent: .22em;
  line-height: 1.08;
  color: var(--ink);
  text-shadow: var(--deboss);
}

.card__title {
  margin: .62em 0 0;
  font-family: var(--display);
  font-size: 1.02em;
  text-transform: uppercase;
  letter-spacing: .3em;
  text-indent: .3em;
  color: var(--ink-soft);
  text-shadow: var(--deboss);
}

.card__foot {
  position: absolute;
  left: 0; right: 0; bottom: 13.5%;
  text-align: center;
  font-family: var(--display);
  text-transform: uppercase;
  color: var(--ink-soft);
  text-shadow: var(--deboss);
}
.card__foot p { margin: 0; font-size: .98em; letter-spacing: .14em; }
.card__mail { margin-top: .42em !important; }

.card__hint {
  position: absolute;
  left: 0; right: 0; bottom: 5%;
  margin: 0;
  text-align: center;
  font-family: var(--display);
  font-size: .84em;
  text-transform: uppercase;
  letter-spacing: .42em;
  text-indent: .42em;
  color: var(--ink-soft);
  text-shadow: var(--deboss);
  opacity: 0;
  transition: opacity 700ms ease;
}
body[data-phase='card'] .card__hint { opacity: .62; transition-delay: 1900ms; }
.fold:hover .card__hint { opacity: .95; }

/* ══ §8 · the resume sheet · same footprint as the unfolded assembly ══ */

.page {
  position: absolute;
  left: 0;
  top: calc(-1 * var(--card-h));
  width: 100%;
  height: var(--sheet-h);
  opacity: 0;
  pointer-events: none;
  box-shadow: var(--sx, 0px) var(--sy, 24px) 50px rgba(0,0,0,.35);
  transition: opacity 520ms ease;
}
body[data-phase='page'] .page {
  opacity: 1;
  pointer-events: auto;
  transition-delay: 980ms;          /* hand off once the wings are near flat */
}

.page__paper { border-radius: 2px; }

/* the creases the sheet keeps, because paper remembers being folded */
.crease {
  position: absolute;
  left: 0; right: 0;
  height: 1px;
  pointer-events: none;
  background: linear-gradient(90deg,
    rgba(120,110,88,0), rgba(120,110,88,.22) 14%,
    rgba(120,110,88,.22) 86%, rgba(120,110,88,0));
  box-shadow: 0 1px 0 rgba(255,255,255,.5);
}
.crease--a { top: calc(var(--card-h) - 1px); }
.crease--b { top: calc(var(--card-h) * 2 - 1px); }

.page__scroll {
  position: absolute; inset: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  border-radius: 2px;

  --fade-top: 0px;
  --fade-bot: 0px;
  -webkit-mask-image: linear-gradient(180deg,
    transparent 0, #000 var(--fade-top),
    #000 calc(100% - var(--fade-bot)), transparent 100%);
  mask-image: linear-gradient(180deg,
    transparent 0, #000 var(--fade-top),
    #000 calc(100% - var(--fade-bot)), transparent 100%);
  transition: --fade-top 260ms ease, --fade-bot 260ms ease;

  scrollbar-width: thin;
  scrollbar-color: rgba(107,102,92,.28) transparent;
}
.page__scroll::-webkit-scrollbar { width: 7px; }
.page__scroll::-webkit-scrollbar-track { background: transparent; }
.page__scroll::-webkit-scrollbar-thumb {
  background: rgba(107,102,92,.24); border-radius: 4px;
}
.page__scroll::-webkit-scrollbar-thumb:hover { background: rgba(107,102,92,.4); }

/* ══ §10 · the document ══════════════════════════════════════════════ */

.doc {
  /* §4 — a narrow column, so wide screens never stretch the measure */
  max-width: 30em;
  margin: 0 auto;
  padding: 3.1em 2.6em 3.4em;
  /* keyed to the open sheet's true width, since the page renders unscaled */
  font-size: clamp(11.5px, calc(var(--card-w) / 34), 14px);
  line-height: 1.66;
  color: var(--ink);
}

.doc__head { text-align: center; margin-bottom: 2.9em; }

.doc__name {
  margin: 0;
  font-family: var(--display);
  font-size: 2.5em;
  font-weight: 400;
  text-transform: uppercase;
  letter-spacing: .24em;
  text-indent: .24em;
  color: var(--ink);
  text-shadow: var(--deboss);
}

.doc__contact {
  margin: .95em 0 0;
  font-size: .88em;
  letter-spacing: .045em;
  color: var(--ink-soft);
  text-shadow: var(--deboss);
}
.doc__contact + .doc__contact { margin-top: .3em; }

/* The separator is glued to the item before it with a non-breaking space,
   so a wrap can only ever happen after a dot — never stranding one at the
   start of the next line. */
.doc__contact > *:not(:last-child)::after {
  content: '\00a0\00b7';
  color: var(--ink-soft);
  border-bottom: 0;
}

.doc__contact a {
  color: inherit;
  text-decoration: none;
  border-bottom: 1px solid rgba(107,102,92,.3);
  transition: border-color 280ms ease, color 280ms ease;
}
.doc__contact a:hover { color: var(--ink); border-bottom-color: var(--ink); }

.dot { color: var(--ink-soft); padding: 0 .18em; }

/* §10 — spaced caps, centred, with a hairline rule beneath */
.sec { margin-bottom: 2.6em; }
.sec:last-of-type { margin-bottom: 1.4em; }

.sec__title {
  margin: 0 0 1.5em;
  padding-bottom: .85em;
  border-bottom: 1px solid var(--hairline);
  text-align: center;
  font-family: var(--display);
  font-size: .84em;
  font-weight: 400;
  text-transform: uppercase;
  letter-spacing: .3em;
  text-indent: .3em;
  color: var(--ink-soft);
  text-shadow: var(--deboss);
}

.item + .item { margin-top: 1.9em; }

.item__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1.2em;
}

.item__name {
  margin: 0;
  font-family: var(--body);
  font-size: 1.02em;
  font-weight: 500;
  letter-spacing: .012em;
  color: var(--ink);
  text-shadow: var(--deboss);
}

/* §10 — dates in muted grey, right-aligned within the column */
.item__date {
  flex: none;
  font-family: var(--display);
  font-size: .78em;
  text-transform: uppercase;
  letter-spacing: .13em;
  white-space: nowrap;
  color: var(--ink-soft);
  text-shadow: var(--deboss);
}

.item__meta {
  margin: .18em 0 0;
  font-size: .88em;
  font-style: italic;
  color: var(--ink-soft);
  text-shadow: var(--deboss);
}

.points { margin: .6em 0 0; padding: 0; list-style: none; }
.points li {
  position: relative;
  padding-left: 1em;
  margin-bottom: .34em;
  font-size: .94em;
  line-height: 1.6;
  text-shadow: var(--deboss);
}
.points li::before {
  content: '';
  position: absolute;
  left: 0; top: .78em;
  width: 4px; height: 1px;
  background: var(--ink-soft);
}
.points li:last-child { margin-bottom: 0; }

.traits { margin: 0; }
.traits dt {
  font-family: var(--display);
  font-size: .78em;
  text-transform: uppercase;
  letter-spacing: .22em;
  color: var(--ink-soft);
  text-shadow: var(--deboss);
}
.traits dd {
  margin: .3em 0 1.15em;
  font-size: .94em;
  line-height: 1.62;
  text-shadow: var(--deboss);
}
.traits dd:last-of-type { margin-bottom: 0; }

.colophon {
  margin: 2.4em 0 0;
  text-align: center;
  color: var(--ink-soft);
  letter-spacing: .3em;
}

/* ══ fold back ═══════════════════════════════════════════════════════ */

.foldback {
  position: absolute;
  top: .5em; right: .7em;
  width: 2em; height: 2em;
  display: grid; place-items: center;
  border: 0; background: none;
  font-family: var(--display);
  font-size: 1.15em;
  line-height: 1;
  color: var(--ink-soft);
  cursor: pointer;
  opacity: 0;
  pointer-events: none;
  transition: opacity 400ms ease 900ms, color 260ms ease;
}
.foldback:hover { color: var(--ink); }
body[data-phase='page'] .foldback { opacity: .65; pointer-events: auto; }

/* ══ focus, without breaking the object ══════════════════════════════ */

.fold:focus { outline: none; }
.fold:focus-visible { outline: 1px solid rgba(239,234,221,.75); outline-offset: 8px; }
.film__skip:focus-visible,
.foldback:focus-visible,
.doc__contact a:focus-visible { outline: 1px solid currentColor; outline-offset: 3px; }

/* ══ narrow screens ══════════════════════════════════════════════════ */

@media (max-width: 640px) {
  /* the card already runs nearly edge to edge here, so there is no room to
     boost it much — and shrinking the open sheet to make room would cost
     more in resume legibility than the larger card would buy */
  :root { --card-boost: 1.06; }
}

/* Phone widths: state the card's type size as a plain length. The clamp on
   .card routes a nested min() through calc() via a custom property, which
   older mobile WebKit resolves unreliably; at every phone width that
   expression bottoms out on its 9px floor regardless, so this is the same
   number with none of the resolution risk. */
@media (max-width: 520px) {
  .card { font-size: 9px; }
}

@media (max-width: 640px) {

  .doc { padding: 2.4em 1.6em 2.8em; }
  .item__head { flex-direction: column; gap: .1em; }
  .doc__contact { line-height: 1.9; }
}

/* ══ §11 · reduced motion — the object stays, the theatre goes ════════ */

@media (prefers-reduced-motion: reduce) {
  .backdrop__image {
    transition: opacity 300ms ease;
    filter: blur(19px) saturate(.74) brightness(.6);
    transform: scale(1.05);
  }
  body[data-phase='card'] .object,
  body[data-phase='page'] .object { animation: rmFadeIn 380ms ease forwards; }
  @keyframes rmFadeIn { to { opacity: 1; transform: none; } }

  /* skip the unfold entirely and cross-fade straight to the resume */
  .scaler { transition: transform 320ms ease; }
  .panel { transition: opacity 300ms ease; }
  .panel--top, .panel--bot { display: none; }
  body[data-phase='page'] .panel--mid { opacity: 0; }
  .page { transition: opacity 320ms ease; }
  body[data-phase='page'] .page { transition-delay: 0ms; }
  .film { transition: opacity 320ms ease; }
  .film__skip { animation: none; opacity: 1; }
}
