/* Core Feature Lab
 *
 * Reference: Awsmd "Creative Portfolio Website". Warm paper ground, one
 * sculptural object carrying the page, an enormous grotesque headline with a
 * single italic serif word, mono micro-copy whispering at the edges, faint
 * column rules.
 *
 * Two rules from the taste-skill are broken here ON PURPOSE, because the
 * reference is the brief: mixed-family emphasis (serif italic inside a sans
 * headline) and decorative edge labels with column rules.
 *
 * Ground values are sampled from the hero render itself, so the object's own
 * background and the page meet with no visible seam.
 *
 * Shape lock: pills are full radius, everything else is square. No mid radii.
 */

@font-face {
  font-family: "Geist";
  src: url("assets/geist-sans.woff2") format("woff2");
  font-weight: 100 900;
  font-display: swap;
}
@font-face {
  font-family: "Geist Mono";
  src: url("assets/geist-mono.woff2") format("woff2");
  font-weight: 100 900;
  font-display: swap;
}
/* Playfair Display, SIL OFL. Italic only: it exists to set exactly one word. */
@font-face {
  font-family: "Playfair";
  src: url("assets/playfair-italic.woff2") format("woff2");
  font-weight: 400 900;
  font-style: italic;
  font-display: swap;
}

:root {
  /* Sampled from the reference itself, not invented. Its ground is a pale
     green-cream washing to a warmer near-white, far brighter than the tan a
     "warm cream" instruction drifts into on its own. */
  --paper-a: #f1f0de;      /* pale green-cream */
  --paper-b: #fbf6e2;      /* warm light cream */
  --ink: #14140f;          /* near-black, never #000 */
  --muted: #5f5d4e;        /* 7.4:1 on --paper-a */
  --faint: #7c7b6c;        /* micro-copy: 4.6:1 on --paper-a, passes AA */
  --rule: rgba(20, 20, 15, 0.08);
  --accent: #2340c8;       /* reserved for focus and hover, never resting text */

  --shell: 1440px;
  --gutter: clamp(20px, 4vw, 64px);
  --step: clamp(88px, 12vw, 190px);

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

*, *::before, *::after { 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;
  color: var(--ink);
  background: var(--paper-b);
  font-family: var(--sans);
  font-size: 17px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

img { max-width: 100%; display: block; height: auto; }

/* The hidden attribute is a user-agent rule, so any class that sets display
   silently beats it. Both the drum's buttons did exactly that and stayed on
   screen while reporting hidden. */
[hidden] { display: none !important; }
p { margin: 0; }
h1, h2, h3 { margin: 0; font-weight: 500; }

a { color: inherit; text-decoration: none; }

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 4px;
}

.shell {
  width: 100%;
  max-width: var(--shell);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.skip { position: absolute; left: -9999px; }
.skip:focus {
  left: 16px; top: 16px; z-index: 20;
  background: var(--ink); color: var(--paper-b);
  padding: 10px 16px; border-radius: 999px;
}

/* ---------- micro-copy: mono, uppercase, wide, quiet ---------- */

.micro {
  font-family: var(--mono);
  font-size: 10.5px;
  font-weight: 400;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--faint);
  line-height: 1.75;
}
.micro-ink { color: var(--muted); }
.arrow { color: var(--muted); font-style: normal; }

/* ---------- pill ---------- */

.pill {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 13px 22px;
  border-radius: 999px;
  border: 1px solid rgba(23, 21, 15, 0.14);
  background: rgba(255, 253, 246, 0.55);
  backdrop-filter: blur(6px);
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--ink);
  white-space: nowrap;
  transition: border-color 200ms ease, background-color 200ms ease, transform 140ms ease;
}
.pill:hover { border-color: var(--accent); background: rgba(255, 253, 246, 0.9); }
.pill:active { transform: translateY(1px); }
.pill-solid {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--paper-b);
}
.pill-solid:hover { background: #262218; border-color: #262218; }
.pill-solid .arrow { color: var(--paper-b); }

/* ---------- hero ---------- */

.hero {
  position: relative;
  min-height: 100dvh;
  /* The hero is a section but sets its own rhythm through its children.
     Without this it inherits --step twice over and the foot floats. */
  padding-block: 0;
  background: linear-gradient(155deg, var(--paper-a) 0%, var(--paper-b) 58%, var(--paper-a) 100%);
  overflow: hidden;
  display: flex;
  flex-direction: column;
}

/* Faint column rules. They organise the hero into a real 4 column measure
   rather than being drawn for texture. */
.rules {
  position: absolute;
  inset: 0;
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  pointer-events: none;
  z-index: 0;
}
.rules span { border-right: 1px solid var(--rule); }
.rules span:last-child { border-right: 0; }

.hero-object {
  position: absolute;
  z-index: 1;
  /* Video and poster share one box, so the swap from still to loop is
     invisible: same size, same position, same blend. */
  aspect-ratio: 1;
  width: clamp(320px, 37vw, 600px);
  left: 68%;
  top: 58%;
  transform: translate(-50%, -50%);
  pointer-events: none;
  /* Canvas draws its own transparency, so nothing to blend away. */
  animation: float 13s ease-in-out infinite;
}
@keyframes float {
  0%, 100% { transform: translate(-50%, -50%); }
  50%      { transform: translate(-50%, calc(-50% - 14px)); }
}
@media (prefers-reduced-motion: reduce) {
  .hero-object { animation: none; }
}

@keyframes float-sm {
  0%, 100% { transform: translate(-50%, -50%); }
  50%      { transform: translate(-50%, calc(-50% - 12px)); }
}

.hero-top {
  position: relative;
  z-index: 2;
  padding-top: clamp(26px, 3.4vw, 42px);
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 32px;
}
.wordmark {
  font-size: 14px;
  font-weight: 500;
  letter-spacing: -0.01em;
  white-space: nowrap;
  display: inline-flex;
  align-items: center;
  gap: 10px;
  color: var(--ink);
}
/* The mark is the same solid the hero renders, viewed down its 3-fold axis.
   It inherits currentColor, so it needs no light and dark variants. */
.wordmark .mark { width: 22px; height: 22px; flex: none; }
.wordmark .mark line { opacity: 0.42; }
.hero-top-right {
  display: flex;
  align-items: flex-start;
  gap: clamp(28px, 5vw, 76px);
}

h1 {
  position: relative;
  z-index: 2;
  font-size: clamp(2.6rem, 7.4vw, 6.4rem);
  line-height: 0.98;
  letter-spacing: -0.045em;
  max-width: 13ch;
  margin-top: clamp(36px, 5.5vw, 74px);
}
h1 em {
  font-family: var(--serif);
  font-style: italic;
  font-weight: 500;
  letter-spacing: -0.01em;
  /* Playfair's descenders clip against 0.98 leading; give them room. */
  line-height: 1.06;
  padding-right: 0.04em;
}

/* Edge micro-copy, level with the object's middle. */
.hero-edges {
  position: relative;
  z-index: 2;
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  pointer-events: none;
}
.hero-edges .micro { max-width: 22ch; }

.hero-foot {
  position: relative;
  z-index: 2;
  padding-bottom: clamp(28px, 4vw, 52px);
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 24px;
  flex-wrap: wrap;
}
.hero-cta { display: flex; gap: 10px; flex-wrap: wrap; }

@media (max-width: 900px) {
  .hero-object {
    width: min(92vw, 560px);
    /* left too, not just top and transform. Inheriting the desktop's left:68%
       pushed the object off-centre and 58px past the right edge on a phone. */
    left: 50%;
    top: 55%;
    transform: translate(-50%, -50%);
    opacity: 0.9;
  }
  /* The hero is 100dvh but its content only fills the top 350px, so the
     bottom third was empty. Pushing the foot down spreads the three parts
     over the screen: headline top, object middle, buttons within thumb
     reach at the bottom. */
  .hero-foot { margin-top: auto; }
  /* Its own name on purpose: redefining @keyframes float inside a media
     query is resolved inconsistently across engines. */
  .hero-object { animation-name: float-sm; }
  .hero-edges { display: none; }   /* edge copy has no edges to sit on */
  .rules { grid-template-columns: repeat(2, 1fr); }
  h1 { max-width: 12ch; }
}

/* ---------- sections ---------- */

section { padding-block: var(--step); }

.statement h2 {
  font-size: clamp(1.9rem, 4.6vw, 3.6rem);
  line-height: 1.03;
  letter-spacing: -0.04em;
  max-width: 17ch;
}
.statement h2 em {
  font-family: var(--serif);
  font-style: italic;
  line-height: 1.08;
}
.statement .body { margin-top: clamp(26px, 3vw, 40px); max-width: 56ch; }
.body p { color: var(--muted); font-size: clamp(1rem, 1.15vw, 1.12rem); }
.body p + p { margin-top: 1.1em; }

/* Principles: staggered, deliberately not three equal cards. */
.principles {
  margin-top: clamp(46px, 6vw, 82px);
  display: grid;
  gap: clamp(38px, 5vw, 64px);
}
.principle {
  display: grid;
  grid-template-columns: 3.6ch 1fr;
  gap: clamp(18px, 2.4vw, 34px);
  padding-top: 26px;
  border-top: 1px solid var(--rule);
  max-width: 52ch;
}
.principle:nth-child(2) { margin-left: clamp(0px, 12vw, 180px); }
.principle:nth-child(3) { margin-left: clamp(0px, 24vw, 360px); }
.principle .n {
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: 0.16em;
  color: var(--faint);
  padding-top: 6px;
}
.principle h3 {
  font-size: clamp(1.2rem, 1.9vw, 1.6rem);
  letter-spacing: -0.03em;
  line-height: 1.15;
}
.principle p { color: var(--muted); margin-top: 10px; font-size: 15.5px; }

@media (max-width: 860px) {
  .principle:nth-child(2), .principle:nth-child(3) { margin-left: 0; }
}

/* Contact band */
.contact {
  border-top: 1px solid var(--rule);
  padding-block: clamp(64px, 8vw, 120px) clamp(40px, 5vw, 64px);
}
.contact-grid {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 32px;
  flex-wrap: wrap;
}
.contact h2 {
  font-size: clamp(1.6rem, 3.4vw, 2.6rem);
  letter-spacing: -0.04em;
  line-height: 1.05;
  max-width: 16ch;
}
.contact a.mail {
  font-size: clamp(1rem, 1.3vw, 1.15rem);
  color: var(--muted);
  border-bottom: 1px solid rgba(23, 21, 15, 0.2);
  padding-bottom: 2px;
  transition: color 180ms ease, border-color 180ms ease;
}
.contact a.mail:hover { color: var(--accent); border-color: var(--accent); }

/* The home indicator sits over the last 34px of an iPhone's screen. */
footer { padding-bottom: calc(clamp(28px, 4vw, 44px) + env(safe-area-inset-bottom, 0px)); }
.foot { display: flex; justify-content: space-between; gap: 20px; flex-wrap: wrap; }

/* ---------- reveal ---------- */

.reveal {
  opacity: 0;
  transform: translateY(18px);
  transition: opacity 800ms cubic-bezier(0.22, 1, 0.36, 1),
              transform 800ms cubic-bezier(0.22, 1, 0.36, 1);
}
.reveal.shown { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) {
  .reveal { opacity: 1; transform: none; transition: none; }
}

/* ---------------------------------------------------------------------------
   Dark context.

   Same token names as the paper theme, redefined. Every component built for
   the light page (contact, footer, micro copy, rules) therefore works on dark
   with no second implementation, which is the same trick the product pages use
   with .app-ascend.

   The page makes exactly ONE switch, at Our Work, and stays dark to the end.
   Flipping back and forth would read as two websites stitched together.
--------------------------------------------------------------------------- */
.on-dark {
  --paper-a: #0d0d0b;
  --paper-b: #101010;
  --ink: #eceadb;
  --muted: #8b897c;
  --faint: #6f6d62;
  --rule: rgba(236, 234, 219, 0.09);
  --accent: #eceadb;
  background: #0d0d0b;
  color: var(--ink);
}
.on-dark .contact a.mail { border-bottom-color: rgba(236, 234, 219, 0.25); }

/* The seam between paper and dark is cut on a diagonal rather than straight
   across. A horizontal edge reads as two stacked pages; a slanted one reads as
   one page turning over.

   The block is pulled up by exactly the slant it cuts away, so the diagonal
   eats into the section above instead of leaving a wedge of empty ground. */
.on-dark.wipe {
  /* Exactly 18 degrees: tan(18) = 0.3249, so the rise is 32.49% of the width.
     Deliberately NOT clamped. A clamp caps the rise on wide screens while the
     width keeps growing, which quietly flattens the angle on the very monitors
     where the diagonal is most visible. */
  --slant: 32.49vw;
  clip-path: polygon(0 var(--slant), 100% 0, 100% 100%, 0 100%);
  /* Half the slant is reclaimed, but never more than 220px: at 18 degrees on a
     wide screen, half the rise is enough to drive the diagonal through the
     text of the section above. */
  margin-top: calc(-1 * min(var(--slant) * 0.5, 220px));
}

/* ---------------------------------------------------------------------------
   Our Work: a scroll-driven drum.

   The section is deliberately taller than the viewport. That surplus height IS
   the scroll distance the drum turns through, which is why nothing here has to
   capture the wheel.
--------------------------------------------------------------------------- */
.works {
  position: relative;
  height: calc(var(--slides, 6) * 62vh + 100vh);
  padding-block: 0;
}
.works .stage { position: sticky; top: 0; height: 100dvh; overflow: hidden; }

.works h2 {
  position: absolute;
  top: clamp(26px, 5vw, 60px);
  left: clamp(20px, 4vw, 64px);
  margin: 0;
  font-size: clamp(2.3rem, 5vw, 4.2rem);
  line-height: .95;
  letter-spacing: -.045em;
  z-index: 5;
  /* The heading is CLIPPED away during the wipe, not mis-coloured: the
     wrapper's clip-path removes everything above the diagonal. So it resolves
     into view as the wipe clears, which needs no code at all. Two earlier
     attempts here (difference blending, then a JS colour flip) were both
     fixing a symptom. */
}

/* Perspective lives on the parent so every card shares one vanishing point.
   That is what makes them read as one solid turning, not five loose planes. */
.drum {
  position: absolute; inset: 0;
  perspective: 1150px;
  perspective-origin: 50% 50%;
  transform-style: preserve-3d;
}
.wcard {
  position: absolute; left: 50%; top: 50%; margin: 0;
  width: clamp(180px, 17vw, 262px);
  transform-origin: 50% 50%;
  backface-visibility: visible;        /* the back of a card is part of the flip */
  border-radius: 12px;
  overflow: hidden;
  background: #15150f;
  box-shadow: 0 30px 70px rgba(0, 0, 0, .6);
  will-change: transform, opacity;
}
.wcard img { width: 100%; height: auto; pointer-events: none; }

/* An icon is square where a screenshot is portrait. Fixing the box keeps the
   drum's slot spacing even; without it a short card leaves a gap in the run. */
.wcard.is-icon {
  aspect-ratio: 1;
  display: grid;
  place-items: center;
  background: transparent;
  box-shadow: none;
}
.wcard.is-icon img {
  width: 74%;
  border-radius: 22%;
  box-shadow: 0 24px 56px rgba(0, 0, 0, .55);
}

.meta {
  position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: space-between;
  padding-inline: clamp(20px, 4vw, 64px);
  pointer-events: none;
  z-index: 6;
}
.meta-left { display: flex; align-items: flex-start; gap: clamp(16px, 3vw, 44px); max-width: 34ch; }
.meta-left > .micro { padding-top: 7px; }
.meta h3 { font-size: clamp(1.3rem, 2.1vw, 1.9rem); letter-spacing: -.03em; line-height: 1.05; }
.meta .line { margin-top: 10px; font-size: clamp(.92rem, 1.05vw, 1.02rem); line-height: 1.45; color: #b9b6a6; max-width: 30ch; }
.meta .note { margin-top: 14px; }

.works .btn {
  pointer-events: auto;
  display: inline-flex; align-items: center; gap: 10px;
  padding: 13px 22px;
  border-radius: var(--r-pill, 999px);
  border: 1px solid rgba(236, 234, 219, .22);
  color: var(--ink);
  font-family: var(--mono);
  font-size: 10.5px; letter-spacing: .2em; text-transform: uppercase;
  white-space: nowrap;
  transition: background-color 180ms ease, border-color 180ms ease, transform 120ms ease;
}
.works .btn:hover { background: var(--ink); color: #0d0d0b; border-color: var(--ink); text-decoration: none; }
.works .btn:active { transform: translateY(1px); }

/* Left, not centre. Centred put these under the card column, where the drum
   crosses them on every pass. */
.stepper {
  position: absolute;
  left: clamp(20px, 4vw, 64px);
  bottom: clamp(24px, 4vw, 48px);
  display: flex; gap: 8px; z-index: 7;
}
.stepper button {
  width: 38px; height: 38px;
  border-radius: 999px;
  background: transparent;
  border: 1px solid rgba(236, 234, 219, .18);
  color: var(--ink);
  font-size: 13px; line-height: 1;
  cursor: pointer;
  transition: background-color 180ms ease, border-color 180ms ease, opacity 180ms ease;
}
.stepper button:hover:not(:disabled) { background: rgba(236, 234, 219, .1); border-color: rgba(236, 234, 219, .4); }
.stepper button:disabled { opacity: .25; cursor: default; }
/* Wider than the arrows because it does a different job: they walk, it jumps. */
.stepper .skip-btn {
  width: auto; padding: 0 18px; margin-left: 6px;
  display: inline-flex; align-items: center; gap: 8px;
  font-family: var(--mono); font-size: 10px; letter-spacing: .18em; text-transform: uppercase;
}

/* ---------------------------------------------------------------------------
   Mobile. Most of the traffic, so this is not a fallback.

   The desktop layout floats the metadata and the controls OVER a centred card
   column, which only works when there is width to spare. On a phone they land
   squarely on top of the cards. So the stage becomes a real stack instead:
   heading, then the drum, then the text, then the controls, each with its own
   row and nothing overlapping anything.
--------------------------------------------------------------------------- */
@media (max-width: 860px) {
  .works {
    /* Shorter travel per slide. At desktop spacing a phone needs nine screens
       of thumb to get through the section. */
    height: calc(var(--slides, 6) * 46vh + 100vh);
  }
  .works .stage {
    display: grid;
    /* One column, stated. Two items sharing row 3 would otherwise auto-place
       into two implicit columns and squeeze the copy into half the width. */
    grid-template-columns: 1fr;
    grid-template-rows: auto minmax(0, 1fr) auto;
    gap: 10px;
    padding: clamp(18px, 5vw, 30px) clamp(18px, 5vw, 26px)
             calc(clamp(16px, 4vw, 26px) + env(safe-area-inset-bottom, 0px));
  }
  .works h2 {
    position: static;
    font-size: clamp(2rem, 9vw, 2.7rem);
  }
  .drum {
    position: relative; inset: auto; min-height: 0;
    /* Cards travel well beyond the centre, so without this they spill out of
       their row and land on the text below. Masked rather than hard-clipped so
       they fade at the band edge instead of being sliced. */
    overflow: hidden;
    -webkit-mask-image: linear-gradient(180deg, transparent 0, #000 14%, #000 86%, transparent 100%);
    mask-image: linear-gradient(180deg, transparent 0, #000 14%, #000 86%, transparent 100%);
  }

  .works .meta {
    position: static;
    inset: auto;
    display: block;
    padding-inline: 0;
    pointer-events: auto;
  }
  .meta-left { max-width: none; gap: 14px; }
  .meta .line { max-width: none; font-size: .95rem; }
  .meta .note { margin-top: 10px; }
  .works .btn { margin-top: 14px; min-height: 46px; }

  /* Three buttons over two rows read as clutter on a narrow screen. The
     arrows are a pointer affordance and a thumb already does that job by
     scrolling, so on a phone only the jump survives, on the same line as the
     product link. */
  #wprev, #wnext { display: none; }
  .works .meta { grid-row: 3; grid-column: 1; }
  .stepper {
    position: static;
    grid-row: 3;
    grid-column: 1;
    align-self: end;
    justify-self: end;
  }
  .stepper .skip-btn { margin-left: 0; }
}

/* ---------------------------------------------------------------------------
   iPad portrait, and any tablet narrow enough to fall under the phone rules.
   The stacked layout is the right shape here; two things break at this width.
   A 180px card is adrift in a 700px field of dark, and the two buttons end up
   550px apart, reading as unrelated controls rather than a pair. Capping the
   column fixes both, and the statement's measure with it.
--------------------------------------------------------------------------- */
@media (min-width: 600px) and (max-width: 860px) {
  .works .stage { max-width: 620px; margin-inline: auto; }
  .wcard { width: 250px; }
  .statement .body { max-width: 46ch; }
}

/* Very small phones: the arrows are enough, the wide skip button is not worth
   the row it costs. */
@media (max-width: 380px) {
  .stepper .skip-btn { padding: 0 12px; letter-spacing: .12em; }
}

/* Without motion a drum is just a stack of tilted rectangles, so it becomes a
   plain grid and the section stops being taller than the viewport. */
@media (prefers-reduced-motion: reduce) {
  .works { height: auto; }
  .works .stage { position: static; height: auto; padding-block: 80px; }
  .drum {
    position: static; perspective: none;
    display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
    gap: 20px; padding-inline: clamp(20px, 4vw, 64px);
  }
  .wcard { position: static; width: auto; transform: none !important; opacity: 1 !important; visibility: visible !important; filter: none !important; }
  .meta { position: static; padding-block: 30px; }
  .stepper { display: none; }
}

/* The title slot. Present so the drum's spacing stays even, drawn as nothing
   so the section opens on its heading rather than on a product. */
.wcard.is-intro {
  aspect-ratio: 1;
  background: transparent;
  box-shadow: none;
  pointer-events: none;
}
/* Deliberately NO transition on .meta opacity: paint() already drives it from
   the scroll position every frame. A CSS transition on top fights that and
   makes the fade lag the scroll it is supposed to track. */


/* ---------------------------------------------------------------------------
   Touch and legibility.
   Inline links are fine to click with a cursor and awkward to hit with a thumb,
   so they get real height without changing how they look.

   Keyed on the POINTER, not the width. An iPad in landscape is 1024px wide and
   still a touch device, so a width query would have left every target on it at
   the cursor sizes: an 18px Contact link and 38px arrows.
--------------------------------------------------------------------------- */
@media (pointer: coarse), (max-width: 860px) {
  .wordmark,
  .hero-top-right a.micro,
  .contact a.mail,
  .foot-links a,
  .nav-links a {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
  }
  .stepper button { width: 46px; height: 46px; }
  .stepper .skip-btn { width: auto; height: 46px; }
}

/* Narrow screens: legibility, and the parts that have nowhere to sit. */
@media (max-width: 860px) {
  .micro { font-size: 11px; }

  /* The hero's edge captions have no edges to sit on at this width. */
  .hero-edges { display: none; }

  /* 10.5px mono is a label size that survives a cursor and not a phone.
     One step up; the tracking already makes these read wider than they are. */
  .pill, .btn { font-size: 11px; }
  .stepper .skip-btn { font-size: 10.5px; }

  /* The tagline wraps to five lines up here and pushes Contact off the right
     edge. The statement section says the same thing a screen below, so on a
     phone the header keeps only the wordmark and the one link. */
  .hero-top-right p.micro { display: none; }
}

/* ===========================================================================
   /yash/ — the person page.
   Same tokens, same shapes, same rhythm as the studio page. The one new idea
   is the portrait, and the whole trick there is that it never ends: it is
   graded to the paper's temperature and feathered out on three edges, so it
   reads as part of the ground rather than a photograph pasted on top of it.
=========================================================================== */

.person-hero {
  min-height: auto;
  padding-bottom: clamp(56px, 8vw, 110px);
  display: block;
}

/* ---------- the portrait ---------- */

.portrait {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
  /* The hero's own gradient, repeated at the hero's own size. The photograph
     blends against THIS, so the two have to be the same surface or the blend
     shows a seam. isolation keeps the blending inside this box. */
  background: linear-gradient(155deg, var(--paper-a) 0%, var(--paper-b) 58%, var(--paper-a) 100%);
  isolation: isolate;
}

/* Light he stands in, rather than a flat field behind him. Warmer and
   brighter than the ground so the figure has somewhere to come out of. */
.portrait-wash {
  position: absolute;
  right: -8%;
  top: -6%;
  width: min(78vw, 900px);
  aspect-ratio: 1;
  background: radial-gradient(circle at 52% 46%,
              #fffdf4 0%, rgba(255, 253, 244, 0.72) 38%, rgba(255, 253, 244, 0) 72%);
}

.portrait picture { display: contents; }
.portrait img {
  position: absolute;
  right: 0;
  top: 0;
  height: 100%;
  width: min(52vw, 780px);
  max-width: none;
  object-fit: cover;
  object-position: 50% 50%;
  /* A real duotone rather than a filter's approximation of one. In luminosity
     the photograph contributes only its light and the paper underneath
     contributes the colour, so the image cannot be a different temperature
     from the page — it is painted in the page's own ink. Sepia guesses at
     this; the blend is exact and free. */
  mix-blend-mode: luminosity;
  filter: contrast(1.12) brightness(1.03);
  opacity: 0.92;
  /* Feathered left and bottom so the photograph has no edges where the page
     can see them: it becomes the ground on that side rather than a panel
     pasted onto it. */
  -webkit-mask-image:
    linear-gradient(to right, transparent 0, #000 42%),
    linear-gradient(to top, transparent 0, #000 30%);
  -webkit-mask-composite: source-in;
  mask-image:
    linear-gradient(to right, transparent 0, #000 42%),
    linear-gradient(to top, transparent 0, #000 30%);
  mask-composite: intersect;
}

/* Until a portrait exists the slot still has to look composed rather than
   broken: the wash stays, the image does not. */
.portrait.is-empty picture { display: none; }
.portrait-note {
  position: absolute;
  right: clamp(20px, 4vw, 64px);
  bottom: clamp(28px, 5vw, 56px);
  opacity: 0;
}
.portrait.is-empty .portrait-note { opacity: 1; }

/* ---------- hero type ---------- */

/* Both header links live on the paper side. Left to itself the row is
   space-between, which parked "back to the studio" in the middle of the
   photograph where nothing could be read. */
.person-hero .hero-top { justify-content: flex-start; gap: clamp(20px, 3vw, 44px); }
.person-hero .hero-top-right { align-items: center; padding-top: 3px; }

.person-head {
  position: relative;
  z-index: 2;
  padding-top: clamp(28px, 5vw, 64px);
}
.person-head h1 {
  margin-top: clamp(18px, 2.4vw, 30px);
  max-width: 12ch;
}
.person-head .lede {
  margin-top: clamp(20px, 2.4vw, 30px);
  max-width: 42ch;
  color: var(--muted);
  font-size: clamp(1rem, 1.15vw, 1.1rem);
  line-height: 1.5;
}
.person-head .hero-cta { margin-top: clamp(26px, 3.4vw, 42px); }

/* The address and number in plain sight, because the buttons above them depend
   on a mail client and a dialer that a desktop may not have. */
.reach {
  margin-top: 16px;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px 10px;
}
.reach a {
  color: var(--muted);
  border-bottom: 1px solid rgba(23, 21, 15, 0.18);
  padding-bottom: 1px;
  transition: color 180ms ease, border-color 180ms ease;
}
.reach a:hover { color: var(--accent); border-color: var(--accent); }
.contact a.mail.tel { margin-left: clamp(14px, 2vw, 28px); }

/* Name first, positioning beside it. One row so the headline still gets to be
   the biggest thing on the page. */
.person-id {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px clamp(12px, 1.6vw, 20px);
}
.person-name {
  /* Bigger than the chip beside it. A bordered pill pulls the eye harder than
     plain text at the same size, and the name has to win that row. */
  font-size: clamp(1.15rem, 1.6vw, 1.45rem);
  font-weight: 500;
  letter-spacing: -0.02em;
  color: var(--ink);
}

/* The reference's status chip, in our type rather than its. */
.status {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  padding: 7px 14px 7px 12px;
  border: 1px solid var(--rule);
  border-radius: 999px;
  background: rgba(255, 253, 246, 0.6);
  color: var(--muted);
}
/* Not a green availability light. That reads as "actively looking" to anyone
   who has seen one before, which is the one signal this page must not send.
   An ink dot is punctuation. */
.status .dot {
  width: 5px;
  height: 5px;
  border-radius: 999px;
  background: var(--muted);
}

/* ---------- what he has built ---------- */

.proof { padding-block: clamp(44px, 6vw, 80px); border-top: 1px solid var(--rule); }
/* These two are h2 for the document outline and micro for the eye, so the
   heading's own size, weight and margins have to be undone. */
h2.proof-label, h2.edu-label { font-weight: 400; margin: 0; }
.proof-label { color: var(--faint); }
.products {
  list-style: none;
  margin: clamp(26px, 3vw, 40px) 0 0;
  padding: 0;
  display: grid;
  /* Stated, not auto-fit. Six products in auto-fit columns landed as five
     across with one orphan underneath; three and three is a block. */
  grid-template-columns: repeat(3, 1fr);
  gap: clamp(24px, 2.6vw, 40px) clamp(24px, 3vw, 48px);
}
.products li { padding-top: 20px; border-top: 1px solid var(--rule); }
.products .where { color: var(--faint); }
.products h3 {
  margin-top: 12px;
  font-size: clamp(1.05rem, 1.5vw, 1.25rem);
  letter-spacing: -0.028em;
  line-height: 1.2;
}
.products .desc {
  margin-top: 8px;
  color: var(--muted);
  font-size: 14.5px;
  line-height: 1.45;
}

/* ---------- sections ---------- */

.sec-head { font-size: clamp(1.5rem, 2.6vw, 2.1rem); letter-spacing: -0.035em; }

/* Case notes are a list of four, so the staggered indent the studio page uses
   for its three principles would leave the fourth stranded. Flat here. */
.cases .principle { margin-left: 0 !important; max-width: 56ch; }
.principle .result { margin-top: 14px; color: var(--ink); letter-spacing: 0.14em; }

/* ---------- timeline ---------- */

.timeline {
  list-style: none;
  margin: clamp(40px, 5vw, 70px) 0 0;
  padding: 0;
  display: grid;
  gap: clamp(34px, 4vw, 54px);
}
.timeline li {
  display: grid;
  grid-template-columns: 17ch 1fr;
  gap: clamp(18px, 2.4vw, 34px);
  padding-top: 26px;
  border-top: 1px solid var(--rule);
  max-width: 74ch;
}
.timeline .when { padding-top: 6px; color: var(--faint); }
.timeline h3 {
  font-size: clamp(1.1rem, 1.7vw, 1.4rem);
  letter-spacing: -0.028em;
  line-height: 1.2;
}
.timeline .org { margin-top: 8px; }
.timeline li > div > p:not(.micro) {
  color: var(--muted);
  margin-top: 12px;
  font-size: 15.5px;
  max-width: 52ch;
}
/* Education used to be one uppercase caption holding two degrees. It is the
   same kind of thing as a role, so it gets the same shape. */
.edu-label {
  margin-top: clamp(48px, 6vw, 84px);
  color: var(--faint);
}
.edu-list { margin-top: clamp(18px, 2.2vw, 28px); gap: clamp(22px, 2.6vw, 34px); }
.edu-list h3 { font-size: clamp(1rem, 1.4vw, 1.15rem); }

/* ---------- contact ---------- */

/* The wipe's diagonal is var(--slant) tall down the left edge. On the studio
   page the works section fills that triangle; here the contact band is the
   only thing in the dark, so at 1920 it was 848px tall with content in the
   last 150. Two corrections, neither of which touches the 18 degrees.

   One: pull the band further up. The section above ends with left-aligned
   copy no wider than 52ch, so the top-right of the diagonal rises through
   blank paper at every width.

   Two: start the content from where the diagonal actually is at the text's
   left edge, not from the slant's full depth. The heading sits at 4-16% across
   depending on width, where the diagonal has already dropped to ~96% of the
   slant, so the last 4% plus breathing room is all the clearance it needs. */
.on-dark.wipe.person-wipe { margin-top: calc(-1 * min(var(--slant) * 0.62, 420px)); }
.person-contact { padding-top: calc(var(--slant, 0px) * 0.96 + clamp(30px, 3.4vw, 56px)); }

.contact-alt {
  margin-top: 16px;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px 2px;
}
.contact-alt a {
  color: var(--faint);
  border-bottom: 1px solid transparent;
  transition: color 180ms ease, border-color 180ms ease;
}
.contact-alt a:hover { color: var(--accent); border-bottom-color: var(--accent); }

@media (pointer: coarse), (max-width: 860px) {
  .reach a { min-height: 44px; display: inline-flex; align-items: center; }
  .contact-alt a {
    min-height: 44px;
    display: inline-flex;
    align-items: center;
    padding-inline: 2px;
  }
}

/* ---------- narrow screens ---------- */

@media (max-width: 860px) {
  /* Behind the type at phone width the photograph is neither readable as a
     picture nor quiet enough to be a texture. It gets its own band under the
     hero instead, full width, feathered top and bottom into the paper. */
  .person-hero {
    display: flex;
    flex-direction: column;
    padding-bottom: 0;
  }
  .person-hero .hero-top { order: 1; }
  .person-head { order: 2; }
  .portrait {
    order: 3;
    position: relative;
    inset: auto;
    height: clamp(340px, 56vh, 540px);
    margin-top: clamp(30px, 7vw, 52px);
  }
  .portrait img {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    right: auto;
    object-position: 50% 50%;
    opacity: 0.96;
    -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 14%, #000 84%, transparent 100%);
    -webkit-mask-composite: source-over;
    mask-image: linear-gradient(to bottom, transparent 0, #000 14%, #000 84%, transparent 100%);
    mask-composite: add;
  }
  .portrait-wash { display: none; }
  /* Below the hero the ground is flat, so the surface the photo blends
     against has to be flat too. */
  .portrait { background: var(--paper-b); }
  .person-head h1 { max-width: 13ch; }
  .person-head .lede { max-width: none; }

  .products { grid-template-columns: repeat(2, 1fr); }
  .timeline li { grid-template-columns: 1fr; gap: 10px; }
  .timeline .when { padding-top: 0; }
  .cases .principle { max-width: none; }
}

/* Two columns of product copy below 560 leaves three-word lines. */
@media (max-width: 560px) {
  .products { grid-template-columns: 1fr; }

  /* Wrapped, the address and number left the separator dangling at the end of
     the first line. Stacked, it is not needed at all. */
  .reach { flex-direction: column; align-items: flex-start; gap: 0; }
  .reach span { display: none; }
}

/* ===========================================================================
   /work/<app>/ — a product page for one app.
   Same tokens again. The only new shapes are the icon-plus-name lockup and the
   row of device screens, which every app page will want.
=========================================================================== */

/* Tall enough to hold the screen beside the type. The hero clips its overflow,
   so the phone runs off the bottom edge on purpose rather than colliding with
   the section below. */
.app-hero {
  min-height: min(94vh, 880px);
  padding-bottom: clamp(40px, 6vw, 80px);
  display: block;
}

/* Pushed down so the type sits against the middle of the screen beside it,
   rather than at the top with 400px of nothing underneath. */
.app-head { position: relative; z-index: 2; padding-top: clamp(30px, 11vh, 130px); }

.app-id { display: flex; align-items: center; gap: clamp(16px, 2vw, 24px); }
.app-icon {
  width: clamp(64px, 7vw, 96px);
  height: auto;
  border-radius: 22%;
  box-shadow: 0 18px 40px rgba(20, 20, 15, 0.14);
}
.app-head h1 { margin-top: 8px; font-size: clamp(2.4rem, 6vw, 4.6rem); }
.app-head .lede {
  margin-top: clamp(20px, 2.4vw, 30px);
  max-width: 40ch;
  color: var(--muted);
  font-size: clamp(1rem, 1.15vw, 1.1rem);
  line-height: 1.5;
}
.app-meta { margin-top: 14px; }

/* One real screen, whole, beside the type. Three cropped cards said less than
   one uncut screen does, and every crop was another chance to cut a label in
   half, which is exactly what the first set did. */
.app-screen {
  position: absolute;
  right: clamp(-40px, 2vw, 60px);
  top: clamp(96px, 13vw, 170px);
  width: min(38vw, 430px);
  z-index: 1;
  pointer-events: none;
}
.app-screen img {
  width: 100%;
  height: auto;
  border-radius: clamp(14px, 1.6vw, 22px);
  box-shadow: 0 40px 90px rgba(20, 20, 15, 0.18), 0 6px 18px rgba(20, 20, 15, 0.08);
}

/* The pills under the privacy paragraph sit inside a .shell already. */
.shell-reset { padding-inline: 0; margin-top: clamp(24px, 3vw, 36px); }

/* ---------- long-form legal and support pages ---------- */

.doc { padding-block: clamp(40px, 5vw, 72px) clamp(64px, 8vw, 110px); }
.doc h1 { font-size: clamp(2rem, 4.4vw, 3.2rem); max-width: 20ch; }
.doc .updated { margin-top: 18px; }
.doc h2 {
  margin-top: clamp(40px, 5vw, 64px);
  font-size: clamp(1.2rem, 1.9vw, 1.5rem);
  letter-spacing: -0.03em;
  padding-top: 22px;
  border-top: 1px solid var(--rule);
}
.doc h3 { margin-top: 26px; font-size: 1.02rem; letter-spacing: -0.02em; }
.doc p, .doc li { color: var(--muted); font-size: 16px; line-height: 1.6; max-width: 68ch; }
.doc p { margin-top: 14px; }
.doc ul { margin: 14px 0 0; padding-left: 1.1em; }
.doc li { margin-top: 8px; }
.doc a:not(.pill):not(.wordmark) {
  color: var(--muted);
  border-bottom: 1px solid rgba(23, 21, 15, 0.22);
  transition: color 180ms ease, border-color 180ms ease;
}
.doc a:not(.pill):not(.wordmark):hover { color: var(--accent); border-color: var(--accent); }
.doc .lead { font-size: 17px; color: var(--ink); max-width: 62ch; }

/* A draft has to say so on the page, not only in a commit message. */
.draft-flag {
  margin-top: clamp(26px, 3vw, 36px);
  padding: 16px 18px;
  border: 1px solid rgba(23, 21, 15, 0.16);
  border-radius: 10px;
  background: rgba(255, 253, 246, 0.7);
}
.draft-flag p { margin-top: 0; color: var(--ink); font-size: 14.5px; }

@media (max-width: 860px) {
  /* No room beside the type at this width, so the screen goes under it and
     keeps its own band, the same move the person page makes with the portrait. */
  .app-hero { min-height: auto; padding-bottom: 0; }
  .app-head { padding-top: clamp(28px, 5vw, 48px); }
  .app-head .lede { max-width: none; }
  .app-screen {
    position: relative;
    right: auto;
    top: auto;
    width: min(72vw, 320px);
    margin: clamp(30px, 7vw, 48px) auto 0;
  }
  .app-screen img { border-radius: 16px; }
}
