/* =========================================================
   RIYA TULSIANI — PORTFOLIO
   Colours live here. Change these 4 lines to restyle the site.
   ========================================================= */
:root{
  --oxblood:#7C0C14;   /* deep red background */
  --red:#C8102E;       /* bright accent red   */
  --cream:#EDE7DE;     /* off-white           */
  --ink:#141210;       /* near-black          */

  --serif:'Instrument Serif', Georgia, serif;
  --grotesk:'Archivo', 'Helvetica Neue', Arial, sans-serif;
  --mono:'JetBrains Mono', ui-monospace, Menlo, monospace;

  --pad:clamp(18px, 4vw, 56px);
}

/* INVERT MODE — the [ INVERT ] button in the nav swaps these */
body.inverted{
  --oxblood:#EDE7DE;
  --cream:#7C0C14;
  --ink:#EDE7DE;
}

*,*::before,*::after{box-sizing:border-box;}
html{scroll-behavior:smooth;}
body{
  margin:0;
  background:var(--oxblood);
  color:var(--cream);
  font-family:var(--grotesk);
  -webkit-font-smoothing:antialiased;
  overflow-x:hidden;
}
img{display:block;width:100%;height:100%;object-fit:cover;}
a{color:inherit;text-decoration:none;}
ul{margin:0;padding:0;list-style:none;}
button{font:inherit;color:inherit;background:none;border:0;padding:0;}
::selection{background:var(--red);color:#fff;}

.mono{
  font-family:var(--mono);
  font-size:clamp(8.5px,1vw,11px);
  font-weight:400;
  letter-spacing:.14em;
  text-transform:uppercase;
}
em{font-style:italic;}
.line{display:block;}

.reveal{
  opacity:0; transform:translateY(26px);
  transition:opacity .9s cubic-bezier(.22,1,.36,1), transform .9s cubic-bezier(.22,1,.36,1);
}
.reveal.in{opacity:1;transform:none;}

/* ============ INTRO LOADER ============ */
.loader{
  position:fixed; inset:0; z-index:200;
  background:var(--oxblood);
  display:grid; place-items:center;
  transition:opacity .7s ease, visibility .7s;
}
/* pointer-events goes first: visibility only flips at the END of the
   transition, so without this the fading overlay eats clicks for .7s */
.loader.done{opacity:0;visibility:hidden;pointer-events:none;}
.loader__inner{text-align:center;display:grid;gap:18px;}
.loader__meta{opacity:.6;}
.loader__word{
  font-family:var(--serif);
  font-size:clamp(46px,9vw,110px);
  font-weight:400; margin:0; line-height:.95;
}
.loader__count{font-size:11px;letter-spacing:.3em;}

/* ============ NAV ============ */
.nav{
  position:fixed; top:0; left:0; right:0; z-index:100;
  display:flex; justify-content:space-between; align-items:center;
  padding:16px var(--pad);
  color:var(--cream);
  transition:transform .4s ease;
}
/* when the bar is sitting over the About/Work panels, flip its colour */
.nav.on-panel{color:var(--ink);}
.nav.hidden{transform:translateY(-110%);}
.nav__links{display:flex;gap:clamp(10px,2vw,26px);align-items:center;}
.nav a, .nav button{opacity:.75;transition:opacity .25s;}
.nav a:hover, .nav button:hover{opacity:1;}
/* without this each label breaks mid-bracket: "[ .ABOUT" / "]" */
.nav a, .nav button{white-space:nowrap;}
/* on a phone the links alone fill the bar, so the name has to go */
@media(max-width:600px){
  .nav{justify-content:flex-end;}
  .nav__brand{display:none;}
}

/* ============ SHARED RULES ============ */
.hero__rule, .about__rule, .work__rule, .lab__rule, .contact__rule{
  display:flex; justify-content:space-between; gap:12px;
  opacity:.55;
}

/* ============ 01 — HERO ============ */
.hero{
  position:relative;
  min-height:100svh;
  padding:72px var(--pad) 48px;
  display:flex; flex-direction:column;
  overflow:hidden;
}
.hero__title{margin:auto 0;position:relative;z-index:3;text-align:center;}
.hero__title h1{
  font-family:var(--serif); font-weight:400;
  font-size:clamp(52px,12.5vw,170px);
  line-height:.88; margin:0; letter-spacing:-.015em;
}
.line--indent{padding-left:clamp(0px,7vw,120px);}
.hero__scroll{
  display:inline-block; margin-top:clamp(20px,4vh,44px);
  opacity:.6; animation:bob 2.4s ease-in-out infinite;
}
@keyframes bob{0%,100%{transform:translateY(0)}50%{transform:translateY(6px)}}

.scatter{
  position:absolute; z-index:2;
  font-weight:500; font-size:clamp(13px,1.6vw,20px); opacity:.9;
}
.scatter--1{top:16%;  left:var(--pad);}
.scatter--2{top:11%;  right:var(--pad);}
.scatter--3{bottom:30%;left:var(--pad);}
.scatter--4{bottom:16%;right:var(--pad);}

/* photo frames (shared by the carousel and the project windows) */
.snap__frame{
  display:block; position:relative;
  width:100%; aspect-ratio:3/4;
  overflow:hidden;
  background:
    repeating-linear-gradient(45deg,
      color-mix(in srgb, var(--cream) 8%, transparent) 0 6px,
      transparent 6px 12px),
    color-mix(in srgb, var(--cream) 11%, transparent);
}
.snap__frame::after{
  content:'ADD PHOTO';
  position:absolute; inset:0;
  display:grid; place-items:center;
  font-family:var(--mono); font-size:8px; letter-spacing:.2em;
  color:color-mix(in srgb, var(--cream) 50%, transparent);
}
.snap__frame img{position:relative;z-index:1;}
.snap__frame img.missing{display:none;}
/* A photo that isn't in images/ yet takes its whole slot off the page,
   rather than sitting there as an empty box. script.js sets [hidden];
   this makes sure the slot's own rules don't fight it. */
.deck[hidden], .win__shot[hidden], .about__deckwrap[hidden]{display:none !important;}

@media(max-width:860px){
  .hero{min-height:88vh;}
  .scatter{display:none;}
}

/* ============ KINETIC NAME WALL ============
   Rows of the name sliding opposite ways at different speeds.
   The "smear" is stacked text-shadows trailing behind each row —
   same look as fast motion blur, but cheap for the browser. */
.kinetic{
  background:var(--oxblood); color:var(--cream);
  padding:clamp(34px,6vw,76px) 0;
  overflow:hidden;
  display:flex; flex-direction:column;
  gap:clamp(1px,.35vw,7px);
  user-select:none;
  border-top:1px solid color-mix(in srgb, var(--cream) 14%, transparent);
  border-bottom:1px solid color-mix(in srgb, var(--cream) 14%, transparent);
}
.kinetic__line{
  display:flex; width:max-content;
  animation:kslide var(--dur,20s) linear infinite;
  animation-direction:var(--dirn,normal);
  will-change:transform;
}
.kinetic__line span{
  font-family:var(--grotesk);
  font-weight:700;
  font-size:clamp(26px,5.6vw,86px);
  letter-spacing:-.035em;
  line-height:.94;
  white-space:pre;
}
@keyframes kslide{to{transform:translateX(-50%);}}

/* Smear trails. --sm is set per row in script.js from that row's speed,
   so fast rows drag a long ghost and slow rows stay almost sharp.
   The trail points backwards along the direction of travel. */
.kinetic__line--fwd span{
  text-shadow:
    calc(var(--sm,1) *  .030em) 0 color-mix(in srgb, var(--cream) 62%, transparent),
    calc(var(--sm,1) *  .062em) 0 color-mix(in srgb, var(--cream) 44%, transparent),
    calc(var(--sm,1) *  .100em) 0 color-mix(in srgb, var(--cream) 30%, transparent),
    calc(var(--sm,1) *  .145em) 0 color-mix(in srgb, var(--cream) 19%, transparent),
    calc(var(--sm,1) *  .196em) 0 color-mix(in srgb, var(--cream) 11%, transparent),
    calc(var(--sm,1) *  .254em) 0 color-mix(in srgb, var(--cream)  6%, transparent);
}
.kinetic__line--back span{
  text-shadow:
    calc(var(--sm,1) * -.030em) 0 color-mix(in srgb, var(--cream) 62%, transparent),
    calc(var(--sm,1) * -.062em) 0 color-mix(in srgb, var(--cream) 44%, transparent),
    calc(var(--sm,1) * -.100em) 0 color-mix(in srgb, var(--cream) 30%, transparent),
    calc(var(--sm,1) * -.145em) 0 color-mix(in srgb, var(--cream) 19%, transparent),
    calc(var(--sm,1) * -.196em) 0 color-mix(in srgb, var(--cream) 11%, transparent),
    calc(var(--sm,1) * -.254em) 0 color-mix(in srgb, var(--cream)  6%, transparent);
}

@media (prefers-reduced-motion:reduce){
  .kinetic__line{animation:none;}
  .kinetic__line span{text-shadow:none;}
}

/* ============ 02 — ABOUT ============ */
.about{
  background:var(--cream); color:var(--ink);
  padding:clamp(48px,8vw,110px) var(--pad);
}
.about__statement{
  font-weight:600;
  font-size:clamp(30px,7.4vw,104px);
  line-height:.96; letter-spacing:-.035em;
  margin:clamp(30px,6vw,70px) 0 clamp(40px,7vw,90px);
}
.about__statement em{font-family:var(--serif);font-weight:400;color:var(--red);}

/* The staircase. Each line steps right by one notch; the steps shrink
   with the viewport so the last line can't run off a phone screen. */
.stair{padding-left:calc(var(--step, 0) * clamp(10px, 3.4vw, 64px));}
.stair--2{--step:1;}
.stair--3{--step:2;}
.stair--4{--step:3;}
.about__statement .arr{
  font-style:normal; color:var(--red);
  margin-left:.22em;
  display:inline-block;
  /* a touch smaller than the words, so it points rather than shouts */
  font-size:.78em;
  transform:translateY(-.08em);
}

.about__grid{display:grid;gap:clamp(28px,5vw,64px);grid-template-columns:1fr;}
@media(min-width:880px){
  .about__grid{grid-template-columns:1.1fr .9fr;align-items:center;}
  /* No photos in the deck yet → one column, so the text isn't
     squeezed into half the page next to an empty space. */
  .about__grid--solo{grid-template-columns:minmax(0,46em);}
}
.bracket{
  position:relative; padding:22px;
  border:1px solid color-mix(in srgb, var(--ink) 25%, transparent);
}
.bracket p{margin:0 0 14px;line-height:1.95;text-transform:none;letter-spacing:.04em;}
.bracket p:last-child{margin-bottom:0;}
/* ---- the "short on time" shortcut line ----
   Sits between the bio bracket and [ SEE MY WORK ]. The three words
   inside it are underlined so they read as links; the brackets and the
   words between them are not. */
.about__start{
  margin:18px 0 0;
  font-size:10px; letter-spacing:.14em; line-height:2;
  opacity:.75;
}
.about__start-link{
  font:inherit; letter-spacing:inherit;
  color:var(--red);
  text-decoration:underline; text-underline-offset:3px;
  text-decoration-thickness:1px;
  transition:opacity .2s;
}
.about__start-link:hover{opacity:.6;}

.about__cta{display:inline-block;margin-top:20px;color:var(--red);transition:transform .25s;}
.about__cta:hover{transform:translateX(6px);}

/* ---- the photo carousel ----
   A pile of photos sitting beside the bio. The arrows move through the
   pile, and the photo on top opens full-screen when clicked. */
.deck{
  position:relative;
  width:min(100%, 420px);
  margin:0 auto;
  aspect-ratio:3/4;
  user-select:none;
}
.deck__card{
  position:absolute; inset:0; margin:0;
  transition:transform .5s cubic-bezier(.22,1,.36,1), opacity .4s ease;
  will-change:transform;
}
/* The whole photo is the button. Only the one on top takes the click —
   the ones behind it are decoration, and two of them are invisible. */
.deck__open{display:block;width:100%;height:100%;cursor:pointer;}
.deck__card:not(.is-top) .deck__open{pointer-events:none;}
.deck__card .snap__frame{
  height:100%; aspect-ratio:auto;
  box-shadow:0 12px 34px rgba(0,0,0,.22);
  background:
    repeating-linear-gradient(45deg,
      color-mix(in srgb, var(--ink) 7%, transparent) 0 6px,
      transparent 6px 12px),
    color-mix(in srgb, var(--ink) 9%, transparent);
}
.deck__card .snap__frame::after{
  color:color-mix(in srgb, var(--ink) 45%, transparent);
}
/* only the top card shows the "add photo" hint, so the pile stays clean */
.deck__card:not(.is-top) .snap__frame::after{opacity:0;}
.deck__card.is-top .snap__frame{transition:filter .3s, box-shadow .3s;}
.deck__card.is-top .deck__open:hover .snap__frame{
  filter:brightness(1.06);
  box-shadow:0 18px 46px rgba(0,0,0,.3);
}

.deck__caption{
  position:absolute; left:0; right:0; bottom:-26px;
  font-family:var(--mono); font-size:9px; letter-spacing:.14em;
  text-transform:uppercase;
  opacity:0; transition:opacity .3s ease;   /* only the top card shows its caption */
}
.deck__card.is-top .deck__caption{opacity:.65;}

/* the counter and the arrows, on a hairline under the pile */
.deck__bar{
  display:flex; align-items:center; justify-content:space-between; gap:16px;
  width:min(100%, 420px);
  margin:46px auto 0;
  padding-top:12px;
  border-top:1px solid color-mix(in srgb, var(--ink) 22%, transparent);
  font-size:10px; letter-spacing:.16em;
}
.deck__nav{display:flex;gap:14px;}
.deck__bar button{
  font-family:var(--mono); font-size:10px; letter-spacing:.16em;
  opacity:.5; transition:opacity .2s, color .2s, transform .2s;
}
.deck__bar button:hover{opacity:1;color:var(--red);}
#deckPrev:hover{transform:translateX(-3px);}
#deckNext:hover{transform:translateX(3px);}
.deck__hint{margin-top:14px;text-align:center;opacity:.4;}

/* ============ 03 — WORK / FOLDERS ============ */
.work{
  background:var(--cream); color:var(--ink);
  padding:clamp(40px,6vw,80px) var(--pad) clamp(60px,9vw,120px);
}
.work__rule{opacity:.55;}
.work__title{
  font-weight:600; font-size:clamp(34px,8vw,116px);
  line-height:.92; letter-spacing:-.04em;
  margin:clamp(24px,4vw,48px) 0 clamp(36px,6vw,72px);
}
.work__title em{font-family:var(--serif);font-weight:400;color:var(--red);}

/* ---- scattered folders ----
   Still a grid underneath, so nothing ever overlaps or falls off the
   page — but each folder is nudged and tilted off its cell by --fx/--fy/--fr,
   which script.js sets per folder. That's the "pinned to a corkboard" look. */
/* Flex rather than grid so a part-full last row centres itself instead
   of leaving a hole on the right. Folders get a fixed basis and don't
   grow, so they stay the same size however many there are. */
.folders{
  display:flex; flex-wrap:wrap; justify-content:center;
  gap:clamp(58px,7vw,96px) clamp(26px,3vw,46px);
  max-width:1200px; margin-inline:auto;
  padding-bottom:40px;   /* room for the folders nudged downwards */
}
.folder{
  display:block; text-align:center;
  flex:0 0 clamp(150px,17vw,210px);
}

/* ---- the big three ---- */
.folders--big{
  gap:clamp(64px,7vw,96px) clamp(34px,5vw,80px);
  max-width:1000px;
}
.folder--big{flex:0 0 clamp(210px,25vw,290px);}
/* Two classes deep on purpose: the plain `.folder svg` rule further down
   has the same weight and would otherwise win on source order. */
.folders--big .folder svg{width:clamp(140px,17vw,206px);}
.folder--big .folder__label{font-size:13px;margin-top:16px;}
.folder__blurb{
  display:block; margin-top:5px;
  font-family:var(--serif); font-style:italic;
  font-size:clamp(15px,1.5vw,19px); line-height:1.3;
  color:var(--red);
}

/* ---- the drawer: everything else, smaller so it reads as the
       supporting cast rather than competing with the big three ---- */
.folders__divider{
  display:flex; align-items:center; gap:clamp(14px,2vw,24px);
  max-width:1000px; margin:clamp(10px,2vw,26px) auto clamp(34px,5vw,58px);
  font-size:9px; letter-spacing:.2em; text-transform:uppercase;
  opacity:.45;
}
.folders__divider::before,
.folders__divider::after{
  content:''; flex:1 1 0;
  border-top:1px solid color-mix(in srgb, var(--ink) 22%, transparent);
}
.folders--drawer{gap:clamp(46px,5vw,68px) clamp(20px,2.4vw,36px);}
.folders--drawer .folder{flex:0 0 clamp(116px,13vw,154px);}
.folders--drawer .folder svg{width:clamp(74px,8.5vw,106px);}
.folders--drawer .folder__label{font-size:10px;margin-top:10px;}

/* These beat .reveal's plain translateY because .folders .folder is two
   classes deep. Each state repeats the full transform — a transform can
   only be declared once, so every variant has to spell all of it out. */
.folders .folder{
  transition:opacity .7s cubic-bezier(.22,1,.36,1),
             transform .45s cubic-bezier(.22,1,.36,1);
  transform:translate(var(--fx,0px), calc(var(--fy,0px) + 26px)) rotate(var(--fr,0deg));
}
.folders .folder.in{
  transform:translate(var(--fx,0px), var(--fy,0px)) rotate(var(--fr,0deg));
}
.folders .folder.in:hover{
  transform:translate(var(--fx,0px), calc(var(--fy,0px) - 11px)) rotate(0deg) scale(1.06);
}

.folder svg{width:clamp(96px,11vw,142px);height:auto;margin:0 auto;display:block;}
.folder__flap{
  transform-origin:center bottom;
  transition:transform .35s cubic-bezier(.22,1,.36,1);
}
.folder:hover .folder__flap{transform:rotate(-9deg) translateY(-2px);}
.folder__label{
  display:block; margin-top:12px;
  font-family:var(--mono); font-size:11px;
  letter-spacing:.1em; text-transform:lowercase; line-height:1.5;
}
/* One line, two things stacked on top of each other: the year normally,
   OPEN ↗ on hover. Stacking them means the row never changes height, so
   a scattered folder can't nudge its neighbours when you point at it. */
.folder__slot{
  position:relative; display:block; margin-top:4px;
  font-size:9px; height:1.3em; line-height:1.3em;
}
.folder__year, .folder__meta{
  position:absolute; inset:0;
  font-family:var(--mono);
  letter-spacing:.14em; text-transform:uppercase;
  transition:opacity .3s, transform .3s;
}
.folder__year{opacity:.42;}
.folder__meta{
  color:var(--red);
  opacity:0; transform:translateY(-3px);
}
.folder:hover .folder__year{opacity:0;transform:translateY(3px);}
.folder:hover .folder__meta{opacity:1;transform:none;}

/* ============ PROJECT WINDOW ============ */
.win{
  position:fixed; inset:0; z-index:150;
  display:grid; place-items:center;
  padding:clamp(14px,3vw,40px);
  background:color-mix(in srgb, var(--ink) 55%, transparent);
  opacity:0; visibility:hidden;
  transition:opacity .3s ease, visibility .3s;
}
.win.open{opacity:1;visibility:visible;}
.win__panel{
  position:relative;
  width:min(100%, 880px);
  max-height:88svh;
  display:flex; flex-direction:column;
  background:var(--cream); color:var(--ink);
  border:1px solid var(--ink);
  box-shadow:12px 12px 0 color-mix(in srgb, var(--ink) 85%, transparent);
  transform:translateY(16px) scale(.97);
  transition:transform .4s cubic-bezier(.22,1,.36,1);
}
.win.open .win__panel{transform:none;}
.win__bar{
  display:flex; align-items:center; justify-content:space-between; gap:12px;
  padding:9px 12px;
  background:var(--red); color:#fff;
  cursor:grab;
}
.win__bar:active{cursor:grabbing;}
.win__bar-title{font-family:var(--mono);font-size:10px;letter-spacing:.14em;text-transform:uppercase;}
.win__dots{display:flex;gap:6px;}
.win__dots span{width:11px;height:11px;border-radius:50%;background:rgba(255,255,255,.45);}
.win__close{
  width:22px;height:22px;display:grid;place-items:center;
  border:1px solid rgba(255,255,255,.6);
  font-family:var(--mono);font-size:11px;line-height:1;
  transition:background .2s;
}
.win__close:hover{background:rgba(255,255,255,.22);}

.win__body{
  display:grid; gap:clamp(18px,3vw,34px);
  padding:clamp(20px,3.4vw,38px);
  overflow-y:auto;
}
@media(min-width:720px){
  .win__body{grid-template-columns:.85fr 1.15fr;}
  /* Most of the small folders have no photo yet. Without this the text
     stayed squeezed into its narrow column with a blank half beside it. */
  .win__body:has(.win__shot[hidden]){grid-template-columns:1fr;}
  .win__body:has(.win__shot[hidden]) .win__text{max-width:64ch;}
}

.win__shot .snap__frame{
  aspect-ratio:4/5;
  background:
    repeating-linear-gradient(45deg,
      color-mix(in srgb, var(--ink) 7%, transparent) 0 6px,
      transparent 6px 12px),
    color-mix(in srgb, var(--ink) 9%, transparent);
}
.win__shot .snap__frame::after{color:color-mix(in srgb, var(--ink) 45%, transparent);}

.win__tag{display:block;color:var(--red);margin-bottom:12px;}
.win__title{
  font-weight:600; font-size:clamp(26px,4.4vw,52px);
  line-height:.98; letter-spacing:-.03em; margin:0 0 16px;
}
.win__desc{
  font-family:var(--mono); font-size:11px; line-height:2;
  letter-spacing:.04em; margin:0 0 24px;
  opacity:.85;
}
/* ---- the dated list, for a project that has a `timeline` ----
   Same mono as the paragraph above it, with the date held in a narrow
   red column on the left so the steps read as a spine. Only projects
   with a timeline get one; everything else hides it. */
.win__time{
  list-style:none; margin:0 0 24px; padding:0;
  border-top:1px solid color-mix(in srgb, var(--ink) 18%, transparent);
}
.win__time[hidden]{display:none;}
.win__time li{
  display:grid; gap:2px 16px;
  grid-template-columns:1fr;
  padding:12px 0;
  border-bottom:1px solid color-mix(in srgb, var(--ink) 18%, transparent);
}
@media(min-width:560px){
  .win__time li{grid-template-columns:88px 1fr;}
}
.win__when{
  font-size:9px; letter-spacing:.16em; text-transform:uppercase;
  color:var(--red); padding-top:.25em;
}
.win__what{
  font-family:var(--mono); font-size:11px; line-height:1.95;
  letter-spacing:.04em; opacity:.85;
}

.win__link{
  display:inline-block; padding:11px 20px;
  background:var(--red); color:#fff;
  font-family:var(--mono); font-size:10px; letter-spacing:.16em; text-transform:uppercase;
  transition:transform .25s;
}
.win__link:hover{transform:translateX(5px);}
.win__soon{
  display:inline-block;
  font-family:var(--mono); font-size:10px; letter-spacing:.16em; text-transform:uppercase;
  opacity:.5;
}

/* ============ LIGHTBOX ============ */
.lb{
  position:fixed; inset:0; z-index:160;
  display:grid; grid-template-rows:auto 1fr auto;
  padding:clamp(14px,3vw,34px);
  background:color-mix(in srgb, var(--ink) 97%, transparent);
  opacity:0; visibility:hidden;
  transition:opacity .3s ease, visibility .3s;
}
.lb.open{opacity:1;visibility:visible;}
.lb__top{display:flex;justify-content:space-between;align-items:center;color:#EDE7DE;}
.lb__stage{display:grid;place-items:center;min-height:0;padding:14px 0;}
.lb__stage img{
  width:auto; height:auto;
  max-width:100%; max-height:72svh;
  object-fit:contain;
  box-shadow:0 20px 60px rgba(0,0,0,.5);
}
.lb__empty{
  width:min(60vw,360px); aspect-ratio:3/4;
  display:grid; place-items:center;
  border:1px dashed rgba(237,231,222,.35);
  font-family:var(--mono);font-size:10px;letter-spacing:.2em;color:rgba(237,231,222,.6);
}
.lb__bottom{display:flex;justify-content:space-between;align-items:center;gap:16px;color:#EDE7DE;}
.lb__nav{display:flex;gap:18px;}
.lb__nav button{opacity:.65;transition:opacity .2s;}
.lb__nav button:hover{opacity:1;}
.lb__close{opacity:.65;transition:opacity .2s;}
.lb__close:hover{opacity:1;}

/* ============ BY THE NUMBERS ============ */
/* Sits at the bottom of the Work section, on the same cream. */
.nums{
  display:grid; gap:clamp(18px,2.6vw,34px);
  grid-template-columns:repeat(2,1fr);
  margin-top:clamp(48px,7vw,96px);
  padding-top:clamp(28px,4vw,48px);
  border-top:1px solid color-mix(in srgb, var(--ink) 20%, transparent);
}
@media(min-width:760px){.nums{grid-template-columns:repeat(5,1fr);}}
.nums__item{display:flex; flex-direction:column; gap:8px;}
.nums__fig{
  font-weight:600; font-size:clamp(28px,4.4vw,60px);
  line-height:.9; letter-spacing:-.045em;
  color:var(--red);
}
.nums__label{opacity:.6; line-height:1.7;}

/* ============ 03 — THE LAB ============ */
/* Cream, like Work directly above it — the hairline is the only divider,
   so the two read as one chapter about the work. */
.lab{
  background:var(--cream); color:var(--ink);
  padding:clamp(48px,7vw,100px) var(--pad) clamp(60px,9vw,120px);
  border-top:1px solid color-mix(in srgb, var(--ink) 16%, transparent);
}
.lab__title{
  font-weight:600; font-size:clamp(32px,7.4vw,104px);
  line-height:.94; letter-spacing:-.04em;
  margin:clamp(24px,4vw,48px) 0 clamp(34px,5vw,64px);
}
.lab__title em{font-family:var(--serif);font-weight:400;color:var(--red);}

.lab__groups{display:grid; gap:clamp(34px,5vw,64px);}
.lab__groupname{
  margin:0 0 14px; font-weight:400; opacity:.5;
}
.lab__list{
  border-top:1px solid color-mix(in srgb, var(--ink) 18%, transparent);
}
.lab__row{border-bottom:1px solid color-mix(in srgb, var(--ink) 18%, transparent);}

/* Three columns on a wide screen: name, one-line description, status.
   On a phone the description drops under the name. */
.lab__rowinner{
  display:grid; gap:4px 18px;
  grid-template-columns:1fr auto;
  grid-template-areas:'name status' 'desc desc';
  align-items:baseline;
  padding:clamp(13px,1.6vw,18px) 0;
}
@media(min-width:820px){
  .lab__rowinner{
    grid-template-columns:minmax(0,1.1fr) minmax(0,1.5fr) auto;
    grid-template-areas:'name desc status';
    align-items:center;
  }
}
.lab__name{
  grid-area:name;
  font-weight:500; font-size:clamp(16px,1.9vw,23px);
  letter-spacing:-.02em;
}
.lab__desc{grid-area:desc; opacity:.55; line-height:1.7;}
.lab__status{
  grid-area:status; justify-self:end; white-space:nowrap;
  padding:5px 10px; border-radius:999px;
  border:1px solid color-mix(in srgb, var(--ink) 25%, transparent);
  opacity:.75;
}
/* Things that actually exist get the red chip; ideas stay quiet, so the
   status column tells you what's real at a glance. Which is which is
   decided by the LIVE list in script.js. */
.lab__status--live{
  border-color:var(--red); color:var(--red); opacity:1;
}
a.lab__rowinner:hover .lab__name{color:var(--red);}
a.lab__rowinner:hover .lab__status{border-color:var(--red); color:var(--red); opacity:1;}

/* ============ 04 — CONTACT ============ */
.contact{
  padding:clamp(60px,10vw,130px) var(--pad) 24px;
  min-height:92svh;
  display:flex; flex-direction:column;
}
.contact__title{
  font-family:var(--serif); font-weight:400;
  font-size:clamp(50px,13vw,180px);
  line-height:.88; letter-spacing:-.02em;
  margin:clamp(30px,6vw,70px) 0 clamp(24px,4vw,48px);
}
.contact__mail{
  display:inline-block; align-self:flex-start;
  font-weight:500; font-size:clamp(15px,3vw,38px);
  letter-spacing:-.02em;
  border-bottom:1px solid color-mix(in srgb, var(--cream) 45%, transparent);
  padding-bottom:4px;
  transition:border-color .25s;
}
.contact__mail:hover{border-color:var(--cream);}
.contact__links{
  display:flex; flex-wrap:wrap; gap:clamp(12px,2.4vw,32px);
  margin-top:clamp(26px,4vw,48px);
}
.contact__links a{opacity:.7;transition:opacity .25s;}
.contact__links a:hover{opacity:1;}

.contact__actions{
  display:flex; flex-wrap:wrap; gap:12px;
  margin-top:clamp(22px,3.4vw,40px);
}

/* ---- the two buttons under the links. Colours are set outright, not
        transitioned, because [ INVERT ] redefines --cream and
        Chromium won't animate a value arriving that way. ---- */
.contact__save{
  display:inline-block; cursor:pointer;
  padding:13px 22px;
  color:var(--cream); background:none;
  border:1px solid color-mix(in srgb, var(--cream) 30%, transparent);
  border-radius:999px;
  transition:transform .25s, border-color .25s;
}
.contact__save:hover{
  transform:translateY(-2px);
  border-color:color-mix(in srgb, var(--cream) 70%, transparent);
}
.contact__save.is-done{border-color:var(--red);color:var(--red);}

/* ============ NOW-PLAYING STRIP ============ */
/* One row: bouncing bars, a label, the song scrolling past, a link.
   Colours are set outright and never transitioned — [ INVERT ] redefines
   --cream/--oxblood, and Chromium won't animate a value that arrives from
   a redefined custom property. */
.np{
  display:flex; align-items:center; gap:clamp(10px,1.6vw,20px);
  margin-top:clamp(34px,5vw,60px);
  padding:clamp(11px,1.5vw,16px) clamp(13px,1.8vw,20px);
  border:1px solid color-mix(in srgb, var(--cream) 26%, transparent);
  border-radius:999px;
  color:var(--cream);
  overflow:hidden;
}
.np:hover{border-color:color-mix(in srgb, var(--cream) 60%, transparent);}

/* the four little equaliser bars */
.np__eq{display:flex; align-items:flex-end; gap:3px; height:14px; flex:none;}
.np__eq i{
  width:3px; height:100%; background:var(--red); border-radius:1px;
  transform-origin:50% 100%;
  animation:eq .9s ease-in-out infinite alternate;
}
.np__eq i:nth-child(2){animation-duration:.6s;}
.np__eq i:nth-child(3){animation-duration:1.15s;}
.np__eq i:nth-child(4){animation-duration:.75s;}
@keyframes eq{from{transform:scaleY(.22)} to{transform:scaleY(1)}}

.np__label{font-size:10px; letter-spacing:.16em; opacity:.6; flex:none;}

/* the scrolling song name. Two identical copies live inside .np__slide,
   so sliding exactly -50% lands back where it started with no seam. */
.np__track{
  flex:1 1 auto; min-width:0; overflow:hidden;
  mask-image:linear-gradient(90deg,transparent,#000 6%,#000 94%,transparent);
}
/* width:max-content is what makes -50% exact: the box is both halves wide,
   so half of it is always one half, whatever the song is called. */
.np__slide{display:flex; width:max-content; animation:npslide var(--np-dur,16s) linear infinite;}
.np__set{
  font-family:var(--serif); font-style:italic;
  font-size:clamp(17px,2vw,24px); line-height:1.3; white-space:pre;
  flex:0 0 auto;
}
@keyframes npslide{to{transform:translateX(-50%)}}
.np:hover .np__slide{animation-play-state:paused;}

.np__on{font-size:10px; letter-spacing:.16em; opacity:.6; flex:none;}
.np:hover .np__on{opacity:1;}

@media(max-width:640px){
  .np{gap:10px; padding:10px 14px;}
  .np__label, .np__on{display:none;}
}
@media(prefers-reduced-motion:reduce){
  .np__eq i{animation:none; transform:scaleY(.6);}
  .np__slide{animation:none;}
}

.footer{
  margin-top:auto; padding-top:clamp(40px,8vw,90px);
  display:flex; flex-wrap:wrap; justify-content:space-between; gap:12px;
  opacity:.5;
}
.footer a:hover{opacity:1;}

/* ============ THE HIDDEN PAGE ============ */
/* A faint dot after "TOO MUCH RED". Barely there until you hover it. */
.secret-dot{
  margin-left:7px; padding:0 5px;
  font-family:var(--mono); font-size:inherit; line-height:1;
  /* opacity only. The dot inherits its colour from --cream, and [ INVERT ]
     redefines --cream — Chromium won't re-run a transition for a value that
     arrives that way, so transitioning colour here would freeze it. */
  opacity:.25; transition:opacity .3s;
}
.secret-dot:hover, .secret-dot:focus-visible{opacity:1;color:var(--red);}

/* the flash between clicking and arriving */
.unlock{
  position:fixed; inset:0; z-index:400;
  display:grid; place-items:center; gap:14px;
  grid-auto-flow:row;
  background:#141210; color:#EDE7DE;
  text-align:center;
  opacity:0; visibility:hidden; pointer-events:none;
  transition:opacity .25s ease, visibility .25s;
}
.unlock.show{opacity:1;visibility:visible;}
.unlock__word{
  font-family:var(--serif); font-style:italic;
  font-size:clamp(40px,10vw,120px); line-height:.95; margin:0;
}
.unlock__sub{
  font-family:var(--mono); font-size:10px;
  letter-spacing:.3em; text-transform:uppercase; opacity:.6;
}

@media(prefers-reduced-motion:reduce){
  *{animation:none !important;transition-duration:.01ms !important;}
  .reveal{opacity:1;transform:none;}
}

/* ============ PRINTING ============
   The resume is a real PDF now (riya-tulsiani-resume.pdf), so this
   page has no paper version to swap in. All this does is stop a
   Cmd+P from flooding a page with red ink, and take the moving
   parts off it. */
@media print{
  html, body{background:#fff !important; color:#000 !important;}
  .loader, .nav, .lb, .unlock, .win, .kinetic, .np{display:none !important;}
  .reveal{opacity:1 !important; transform:none !important;}
}
