/* ============================================================
   Jack Verbel — portfolio
   Editorial gallery system.

   Built against how these portfolios are actually read: a reviewer
   gives 2–5 minutes and scans headline + image before any body copy.
   So the system is weighted toward exactly three things —

     · one idea per screen, with a lot of air between beats
     · imagery either full-bleed or on a strict column, never in between
     · type doing the hierarchy, colour doing none of it

   The page is achromatic on purpose. Both projects are red-accented
   products; if the site stays neutral, the work supplies all the colour.
   ============================================================ */

/* ---------- tokens ---------- */
:root{
  --page:   #ffffff;
  --plate:  #f1f1ed;   /* grey spacer bands between full-bleed beats */
  --ink:    #121212;
  --ink-2:  #6b6b66;   /* captions, secondary copy */
  --ink-3:  #a3a39d;   /* numerals, quiet meta */
  --line:   #e4e4de;

  --sans: 'Space Grotesk', -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
  --mono: 'Space Mono', ui-monospace, SFMono-Regular, Menlo, monospace;

  --gut:  clamp(20px, 5vw, 88px);   /* page gutter */
  --max:  1440px;

  /* vertical rhythm: beats are far apart, elements inside a beat are close */
  --beat: clamp(88px, 13vh, 168px);
  --step: clamp(26px, 4vh, 52px);
}

*, *::before, *::after{ box-sizing:border-box; }

html{ -webkit-text-size-adjust:100%; }

body{
  margin:0;
  background:var(--page);
  color:var(--ink);
  font-family:var(--sans);
  font-weight:400;
  font-size:17px;
  line-height:1.6;
  letter-spacing:-0.005em;
  -webkit-font-smoothing:antialiased;
  text-rendering:optimizeLegibility;
  overflow-x:hidden;
}

img, video{ max-width:100%; display:block; }

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

::selection{ background:var(--ink); color:var(--page); }

/* ---------- layout ---------- */
.wrap{ max-width:var(--max); margin:0 auto; padding-inline:var(--gut); }

/* a narrower measure for anything that has to be read as prose */
.measure{ max-width:62ch; }
.measure-tight{ max-width:48ch; }

.bleed{ width:100%; }
.bleed img, .bleed video{ width:100%; height:auto; }

.beat{ padding-block:var(--beat); }
.beat-tight{ padding-block:calc(var(--beat) * 0.55); }
.band{ background:var(--plate); }

/* Doubled class on purpose. At plain .stack specificity this loses to the
   later `.lede{margin:0}` rule and every stacked paragraph collapses together
   -- which is exactly what happened on the About page. */
.stack.stack > * + *{ margin-top:var(--step); }

.cols-2{ display:grid; grid-template-columns:repeat(2, minmax(0,1fr)); gap:clamp(18px,3vw,40px); }
.cols-3{ display:grid; grid-template-columns:repeat(3, minmax(0,1fr)); gap:clamp(16px,2.4vw,32px); }
.cols-4{ display:grid; grid-template-columns:repeat(4, minmax(0,1fr)); gap:clamp(14px,2vw,26px); }

/* the standard beat head: mono eyebrow on the left, prose on the right */
.beat-head{
  display:grid;
  grid-template-columns:minmax(120px, 1fr) minmax(0, 3fr);
  gap:clamp(20px,4vw,64px);
  align-items:start;
}

/* ---------- type ---------- */
.eyebrow{
  font-family:var(--mono);
  font-size:12px;
  font-weight:400;
  letter-spacing:0.14em;
  text-transform:uppercase;
  color:var(--ink-3);
  margin:0;
}

.display{
  font-family:var(--sans);
  font-weight:500;
  font-size:clamp(34px, 6.4vw, 82px);
  line-height:1.02;
  letter-spacing:-0.035em;
  margin:0;
  text-wrap:balance;
}

.h1{
  font-weight:500;
  font-size:clamp(38px, 7.6vw, 104px);
  line-height:0.96;
  letter-spacing:-0.04em;
  margin:0;
  text-wrap:balance;
}

.h2{
  font-weight:500;
  font-size:clamp(26px, 3.5vw, 44px);
  line-height:1.08;
  letter-spacing:-0.03em;
  margin:0;
  text-wrap:balance;
}

.h3{
  font-weight:500;
  font-size:clamp(19px, 1.7vw, 23px);
  line-height:1.25;
  letter-spacing:-0.02em;
  margin:0;
}

.lede{
  font-size:clamp(17px, 1.55vw, 21px);
  line-height:1.62;
  color:var(--ink-2);
  margin:0;
  text-wrap:pretty;
}

p{ margin:0; text-wrap:pretty; }
p + p{ margin-top:1em; }

.cap{
  font-family:var(--mono);
  font-size:11.5px;
  line-height:1.55;
  letter-spacing:0.02em;
  color:var(--ink-2);
  margin:12px 0 0 0;
}

.num{
  font-family:var(--mono);
  font-size:12px;
  letter-spacing:0.1em;
  color:var(--ink-3);
}

figure{ margin:0; }

/* ---------- nav ---------- */
.nav{
  position:sticky; top:0; z-index:50;
  display:flex; align-items:center; justify-content:space-between;
  gap:24px;
  padding:20px var(--gut);
  background:color-mix(in srgb, var(--page) 88%, transparent);
  backdrop-filter:saturate(1.4) blur(14px);
  -webkit-backdrop-filter:saturate(1.4) blur(14px);
  border-bottom:1px solid transparent;
  transition:border-color .3s ease;
}
.nav.is-stuck{ border-bottom-color:var(--line); }

/* the wordmark is the identity: the name, set plainly. no monogram. */
.nav-name{
  font-weight:700;
  font-size:16px;
  letter-spacing:-0.03em;
  white-space:nowrap;
}

.nav-links{ display:flex; align-items:center; gap:clamp(16px,2.4vw,34px); }
.nav-links a{
  font-family:var(--mono);
  font-size:12px;
  letter-spacing:0.1em;
  text-transform:uppercase;
  color:var(--ink-2);
  padding-block:4px;
  position:relative;
}
.nav-links a::after{
  content:''; position:absolute; left:0; right:0; bottom:0;
  height:1px; background:currentColor;
  transform:scaleX(0); transform-origin:left;
  transition:transform .32s cubic-bezier(.2,.7,.3,1);
}
.nav-links a:hover{ color:var(--ink); }
.nav-links a:hover::after{ transform:scaleX(1); }
.nav-links a[aria-current="page"]{ color:var(--ink); }
.nav-links a[aria-current="page"]::after{ transform:scaleX(1); }

/* ---------- home ---------- */
.intro{
  padding-block:clamp(56px,11vh,132px) clamp(48px,8vh,96px);
}
.intro-statement{
  font-weight:400;
  font-size:clamp(28px, 4.6vw, 62px);
  line-height:1.14;
  letter-spacing:-0.035em;
  margin:0;
  max-width:19ch;
  text-wrap:balance;
}
.intro-statement em{
  font-style:normal;
  color:var(--ink-3);
}
.intro-meta{
  display:flex; flex-wrap:wrap; gap:10px 14px;
  margin-top:clamp(28px,4vh,48px);
}
/* these two carry whole sentences, unlike the short discipline tags, so they
   have to be allowed to break rather than forcing the page wider than a phone */
.intro-meta .tag{ white-space:normal; }

/* project tiles: full-bleed image, everything else underneath in small type */
/* three across, then straight to one column; a 2-up step would strand the third tile */
.tiles-3{ display:grid; grid-template-columns:repeat(3, minmax(0,1fr)); }
@media (max-width: 760px){ .tiles-3{ grid-template-columns:1fr; } }
.tile{ display:block; }
/* White, not a grey plate. Most of the render JPGs have a white background
   baked in, so a tinted plate behind them shows as a hard white rectangle
   inside a grey box. Grey is reserved for text-only bands for that reason. */
.tile-media{
  position:relative;
  background:var(--page);
  aspect-ratio:1/1;          /* both hero shots are portrait; a square box
                                seats them without cropping or dwarfing them */
  overflow:hidden;
}
.tile-media img{
  width:100%; height:100%;
  object-fit:contain;
  padding:clamp(18px,3vw,52px);
  transition:transform .7s cubic-bezier(.2,.7,.3,1);
}
.tile:hover .tile-media img{ transform:scale(1.032); }
/* landscape subjects (a vehicle) would shrink to a sliver inside the same padding */
.tile-media-wide img{ padding:clamp(4px,1vw,14px); }
.tile-head{
  display:flex; align-items:baseline; justify-content:space-between;
  gap:20px; margin-top:20px;
}
.tile-title{
  font-weight:500;
  font-size:clamp(21px, 2.1vw, 29px);
  letter-spacing:-0.03em;
  line-height:1.1;
  margin:0;
}
.tile-sub{ color:var(--ink-2); margin-top:8px; max-width:44ch; font-size:16px; }
.tile-tags{ display:flex; flex-wrap:wrap; gap:8px; margin-top:16px; }

.tag{
  font-family:var(--mono);
  font-size:11px;
  letter-spacing:0.08em;
  text-transform:uppercase;
  color:var(--ink-2);
  border:1px solid var(--line);
  border-radius:999px;
  padding:5px 11px;
  white-space:nowrap;
}

/* ---------- case study ---------- */
.case-head{ padding-block:clamp(40px,7vh,88px) clamp(28px,4vh,52px); }

/* role / timeline / tools, set as a spec strip under the title */
.spec{
  display:grid;
  grid-template-columns:repeat(auto-fit, minmax(150px, 1fr));
  gap:clamp(18px,3vw,40px);
  border-top:1px solid var(--line);
  padding-top:22px;
  margin-top:clamp(28px,4vh,48px);
}
.spec dt{
  font-family:var(--mono); font-size:11px; letter-spacing:0.1em;
  text-transform:uppercase; color:var(--ink-3); margin:0 0 7px 0;
}
.spec dd{ margin:0; font-size:15px; line-height:1.5; }

/* a keyed fact block that sits beside prose */
.facts{ border-top:1px solid var(--line); padding-top:18px; }
.facts dl{ margin:0; display:grid; gap:9px; }
.facts .row{ display:flex; justify-content:space-between; gap:16px; font-size:14px; }
.facts .row dt{ font-family:var(--mono); font-size:11.5px; letter-spacing:0.06em;
  text-transform:uppercase; color:var(--ink-3); margin:0; }
.facts .row dd{ margin:0; text-align:right; }

/* the numbered beat marker running down the case study */
.beat-no{
  font-family:var(--mono); font-size:12px; letter-spacing:0.12em;
  color:var(--ink-3); display:block; margin-bottom:14px;
}

/* requirement / feature lists */
.ticks{ list-style:none; margin:0; padding:0; display:grid; gap:11px; }
.ticks li{
  font-size:15px; line-height:1.5; color:var(--ink-2);
  padding-left:22px; position:relative;
}
.ticks li::before{
  content:''; position:absolute; left:0; top:.62em;
  width:9px; height:1px; background:var(--ink-3);
}

.feature{ border-top:1px solid var(--line); padding-top:20px; }
.feature h3{ margin:0 0 9px 0; }
.feature p{ color:var(--ink-2); font-size:15.5px; }

/* process strip */
.steps{
  display:grid; grid-template-columns:repeat(5, minmax(0,1fr));
  gap:clamp(12px,2vw,28px);
  border-top:1px solid var(--line); padding-top:24px;
}
.steps div{ font-size:14.5px; line-height:1.4; }
.steps .num{ display:block; margin-bottom:9px; }

/* next-project pager */
.pager{
  display:flex; align-items:baseline; justify-content:space-between;
  gap:20px; border-top:1px solid var(--line); padding-top:26px;
}
.pager .h2{ transition:opacity .3s ease; }
.pager a:hover .h2{ opacity:.55; }

/* ---------- about ---------- */
.entry{
  display:grid;
  grid-template-columns:minmax(120px,1fr) minmax(0,3fr);
  gap:clamp(16px,4vw,64px);
  padding-block:26px;
  border-top:1px solid var(--line);
}
.entry:last-child{ border-bottom:1px solid var(--line); }
.entry .when{ font-family:var(--mono); font-size:11.5px; letter-spacing:0.08em;
  text-transform:uppercase; color:var(--ink-3); }
.entry .what{ font-weight:500; font-size:18px; letter-spacing:-0.02em; margin:0; }
.entry .where{ color:var(--ink-2); font-size:14.5px; margin-top:4px; }
.entry .detail{ color:var(--ink-2); font-size:15px; margin-top:12px; max-width:62ch; }

.skills{ display:flex; flex-wrap:wrap; gap:9px; }

/* ---------- contact ---------- */
.contact-row{
  display:grid; grid-template-columns:minmax(120px,1fr) minmax(0,3fr);
  gap:clamp(16px,4vw,64px);
  padding-block:22px; border-top:1px solid var(--line);
  align-items:baseline;
}
.contact-row:last-of-type{ border-bottom:1px solid var(--line); }
.contact-row .val{ font-size:clamp(19px,2.2vw,28px); letter-spacing:-0.025em; }
.contact-row a.val{ position:relative; }
.contact-row a.val::after{
  content:''; position:absolute; left:0; right:0; bottom:-2px; height:1px;
  background:currentColor; transform:scaleX(0); transform-origin:left;
  transition:transform .34s cubic-bezier(.2,.7,.3,1);
}
.contact-row a.val:hover::after{ transform:scaleX(1); }

/* ---------- footer ---------- */
.foot{
  border-top:1px solid var(--line);
  padding:clamp(36px,6vh,64px) var(--gut) clamp(28px,4vh,44px);
  display:flex; flex-wrap:wrap; gap:16px 40px;
  justify-content:space-between; align-items:baseline;
  font-family:var(--mono); font-size:12px; letter-spacing:0.04em;
  color:var(--ink-2);
}
.foot a:hover{ color:var(--ink); }

/* ---------- scroll reveal ----------
   Deliberately small: a short rise and fade, once, never on the first
   screen. Anything more elaborate reads as a template. */
.rv{ opacity:0; transform:translateY(16px); }
.rv-in{ opacity:1; transform:none;
  transition:opacity .7s cubic-bezier(.2,.7,.3,1), transform .7s cubic-bezier(.2,.7,.3,1); }

/* ---------- responsive ---------- */
@media (max-width: 900px){
  .beat-head{ grid-template-columns:1fr; gap:14px; }
  .cols-3{ grid-template-columns:repeat(2, minmax(0,1fr)); }
  .cols-4{ grid-template-columns:repeat(2, minmax(0,1fr)); }
  .steps{ grid-template-columns:repeat(2, minmax(0,1fr)); gap:20px; }
  .entry, .contact-row{ grid-template-columns:1fr; gap:8px; }
  .intro-statement{ max-width:none; }
}

@media (max-width: 620px){
  body{ font-size:16px; }
  .cols-2{ grid-template-columns:1fr; }
  .cols-3, .cols-4{ grid-template-columns:1fr; }
  .steps{ grid-template-columns:1fr; }
  .nav{ padding-block:15px; }
  .nav-links{ gap:15px; }
  .nav-links a{ font-size:11px; letter-spacing:0.07em; }
  .tile-head{ flex-direction:column; gap:6px; }
}

@media (prefers-reduced-motion: reduce){
  .rv{ opacity:1; transform:none; }
  *, *::before, *::after{
    animation-duration:.001ms !important;
    transition-duration:.001ms !important;
  }
}

@media print{
  .nav{ position:static; }
  .rv{ opacity:1; transform:none; }
}

/* ================= interactive exploded view =================
   Full-bleed and white, matching the rest of the page. Edge to edge means the
   assembly can fill a screen instead of sitting in a 760px box. */
.xv-track{
  position:relative;
  height:900vh;                 /* long: each stop dwells before it moves */
  background:var(--page);
  /* break out of .wrap and any max-width above it */
  width:100vw;
  margin-left:calc(50% - 50vw);
}
.xv-stage{
  position:sticky; top:0;
  height:100svh; min-height:520px;
  overflow:hidden;
  background:var(--page);
}
/* Input-transparent on purpose. This is a full-screen sticky element, so any
   touch handling here competes with the gesture people use to scroll past it —
   which is exactly how a phone user got stranded on this section. Scroll
   position is read from the document; the stage never receives the gesture. */
.xv{
  position:absolute; inset:0;
  pointer-events:none;
  -webkit-tap-highlight-color:transparent;
}
.xv canvas{ width:100%; height:100%; display:block; }

/* --- overlay type, dark on white --- */
.xv-label{
  position:absolute; left:clamp(20px,5vw,72px); bottom:clamp(56px,9vh,104px);
  opacity:0; transform:translateY(10px);
  transition:opacity .5s cubic-bezier(.2,.7,.3,1), transform .5s cubic-bezier(.2,.7,.3,1);
  pointer-events:none; z-index:2;
}
.xv-label.is-on{ opacity:1; transform:none; }
.xv-name{
  display:block; font-size:clamp(26px,4vw,58px); font-weight:500;
  letter-spacing:-0.035em; line-height:1.0; color:var(--ink);
}
.xv-note{
  display:block; font-family:var(--mono); font-size:11px;
  letter-spacing:0.16em; text-transform:uppercase;
  color:var(--ink-3); margin-top:12px;
}
.xv-hint{
  position:absolute; left:clamp(20px,5vw,72px); top:clamp(20px,5vh,56px);
  font-family:var(--mono); font-size:11px; letter-spacing:0.16em;
  text-transform:uppercase; color:var(--ink-3);
  transition:opacity .4s ease; z-index:2; margin:0;
}
.xv.is-loading .xv-hint::after{ content:' — loading'; }

.xv-bar{
  position:absolute; left:0; right:0; bottom:0; height:2px;
  background:var(--line); z-index:2;
}
.xv-bar span{
  display:block; height:100%; background:var(--ink);
  transform-origin:left; transform:scaleX(0);
}

/* The rendered animation carries the section until the viewer reports ready,
   so blocked WebGL, a failed fetch or data-saver mode all degrade to video. */
.xv-fb{
  position:absolute; top:50%; left:50%; transform:translate(-50%,-50%);
  width:min(88vw,760px); z-index:1;
}
.xv.is-ready ~ .xv-fb{ display:none; }
.xv:not(.is-ready) canvas{ visibility:hidden; }
.xv.xv-fallback{ display:none; }

@media (max-width: 900px){
  .xv-track{ height:720vh; }
}
@media (prefers-reduced-motion: reduce){
  .xv-track{ height:auto; width:auto; margin-left:0; }
  .xv-stage{ position:static; height:auto; aspect-ratio:1/1; }
  .xv-hint{ display:none; }
}
