/* ============================================================
   GURUKUL — design tokens

   One token set, mounted twice: on :root for day, and on
   .inverse for any dark island inside a light page. Dark mode
   simply mounts the .inverse set on :root, so every component
   theming itself through these names follows automatically and
   nothing needs a second set of rules.
   ============================================================ */
:root{
  color-scheme: light;

  /* Limestone, not parchment. The old values were yellowed like an aged
     paperback, which reads as dated rather than ancient. Stone is what
     actually survives millennia — cooler, higher in value, and it lets the
     sindoor do the warming instead of the ground. */
  --paper:        #ebe7de;
  --paper-lit:    #f5f2eb;
  --paper-deep:   #ddd7c9;
  --ink:          #1a1916;
  --ink-soft:     #4c4840;
  --ink-faint:    #676153;   /* 5.0:1 on paper — carries every micro-label */
  --sindoor:      #9c3a1b;   /* 5.5:1 on paper — the one accent that holds text */
  --haldi:        #b5851a;   /* decorative only; never sized below 24px */
  --oxblood:      #6e1f1f;

  /* Barely there. A visible noise overlay is the single loudest "2015"
     signal on a page; the material should come from colour and space. */
  --grain-op:     .07;
  --grain-blend:  multiply;
  --lift:         rgba(32,26,19,.5);   /* shadow ink */
  --sheen:        rgba(255,255,255,.5);

  --f-display: "Tiro Devanagari Sanskrit", Georgia, "Times New Roman", serif;
  --f-body:    "Inter", system-ui, -apple-system, sans-serif;
  --f-mono:    "IBM Plex Mono", ui-monospace, monospace;

  --ease:  cubic-bezier(.22,.68,0,1);
  --ease2: cubic-bezier(.33,1,.68,1);
  --gut:   clamp(22px, 5.5vw, 92px);
  --hair:  color-mix(in srgb, var(--ink) 16%, transparent);
  --rule:  1px solid var(--hair);
}

/* A dark island inside the light page — used by the night band. */
.inverse,
:root[data-theme="dark"]{
  color-scheme: dark;

  --paper:        #14161e;
  --paper-lit:    #1e222e;
  --paper-deep:   #0d0f15;
  --ink:          #f0ece1;
  --ink-soft:     #b6b1a4;
  --ink-faint:    #918c7e;   /* 4.9:1 on the night ground */
  --sindoor:      #dd7a4f;   /* lifted for a dark ground — 6.0:1 */
  --haldi:        #e8b743;
  --oxblood:      #d78a6a;

  --grain-op:     .05;
  --grain-blend:  overlay;
  --lift:         rgba(0,0,0,.65);
  --sheen:        rgba(255,255,255,.07);
}
@media (prefers-color-scheme: dark){
  :root:not([data-theme="light"]){
    color-scheme: dark;
    --paper:#14161e; --paper-lit:#1e222e; --paper-deep:#0d0f15;
    --ink:#f0ece1; --ink-soft:#b6b1a4; --ink-faint:#918c7e;
    --sindoor:#dd7a4f; --haldi:#e8b743; --oxblood:#d78a6a;
    --grain-op:.05; --grain-blend:overlay;
    --lift:rgba(0,0,0,.65); --sheen:rgba(255,255,255,.07);
  }
  /* Inside dark mode the night band must still read as a *deeper* room,
     not an identical one. */
  :root:not([data-theme="light"]) .inverse{ --paper:#0d0f15; --paper-lit:#171a24; }
}
:root[data-theme="dark"] .inverse{ --paper:#0d0f15; --paper-lit:#171a24; }

.inverse{ background:var(--paper); color:var(--ink); }

*,*::before,*::after{ box-sizing:border-box; }
html{ scroll-behavior:smooth; -webkit-text-size-adjust:100%; }
body{
  margin:0; background:var(--paper); color:var(--ink);
  font-family:var(--f-body); font-weight:400; font-size:17px; line-height:1.66;
  overflow-x:hidden; -webkit-font-smoothing:antialiased;
  transition:background .6s var(--ease), color .6s var(--ease);
}

/* Pressed-fibre grain. Kept low: at .5 it greyed the whole page. */
body::before{
  content:""; position:fixed; inset:0; z-index:9998; pointer-events:none;
  opacity:var(--grain-op); mix-blend-mode:var(--grain-blend);
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='280' height='280'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='4' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='280' height='280' filter='url(%23n)' opacity='.42'/%3E%3C/svg%3E");
}

img{ max-width:100%; display:block; }
a{ color:inherit; text-decoration:none; }
button{ font:inherit; color:inherit; }
::selection{ background:color-mix(in srgb, var(--haldi) 42%, transparent); }

h1,h2,h3,.display{ font-family:var(--f-display); font-weight:400; letter-spacing:-.012em; line-height:1; margin:0; }
.mono{ font-family:var(--f-mono); }

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

/* Visible to assistive tech, invisible on screen. */
.sr-only{
  position:absolute; width:1px; height:1px; padding:0; margin:-1px;
  overflow:hidden; clip-path:inset(50%); white-space:nowrap; border:0;
}

/* ---------- focus: one visible ring for the whole site ---------- */
:focus-visible{
  outline:2px solid var(--sindoor); outline-offset:3px; border-radius:2px;
}
.skip{
  position:fixed; top:10px; left:10px; z-index:10000;
  background:var(--ink); color:var(--paper); padding:11px 18px; border-radius:4px;
  font-family:var(--f-mono); font-size:12px; letter-spacing:.08em;
  transform:translateY(-160%); transition:transform .3s var(--ease);
}
.skip:focus{ transform:none; }

.eyebrow{
  font-family:var(--f-mono); font-size:11px; font-weight:400;
  letter-spacing:.13em; text-transform:uppercase; color:var(--ink-faint);
  display:flex; align-items:center; gap:.7em;
}
.eyebrow::before{ content:""; width:26px; height:1px; background:var(--sindoor); }

/* ---------- scroll progress ---------- */
#progress{
  position:fixed; top:0; left:0; height:2px; width:0%; z-index:9999;
  background:linear-gradient(90deg, var(--haldi), var(--sindoor));
  transform-origin:left;
}

/* ============================================================
   NAV
   ============================================================ */
header{
  position:fixed; top:0; left:0; right:0; z-index:900;
  transition:padding .5s var(--ease), background .5s, backdrop-filter .5s, border-color .5s;
  padding-block:26px; border-bottom:1px solid transparent;
}
header.tuck{
  padding-block:13px;
  background:color-mix(in srgb, var(--paper) 86%, transparent);
  backdrop-filter:blur(10px) saturate(1.15);
  border-bottom-color:var(--hair);
}
.nav{ display:flex; align-items:center; gap:20px; }
.mark{ line-height:1.15; margin-right:auto; }
.mark b{ font-family:var(--f-display); font-weight:400; font-size:21px; display:block; }
.mark small{ font-family:var(--f-mono); font-size:10.5px; letter-spacing:.11em; text-transform:uppercase; color:var(--ink-faint); }

.nav-links ul{ display:flex; gap:32px; list-style:none; margin:0; padding:0; align-items:center; }
.nav-links a{ position:relative; font-size:14px; padding-block:9px; }
.nav-links a::after{
  content:""; position:absolute; left:0; bottom:0; height:1px; width:0;
  background:var(--sindoor); transition:width .45s var(--ease);
}
.nav-links a:hover::after,
.nav-links a[aria-current="true"]::after{ width:100%; }

.nav-act{ display:flex; align-items:center; gap:12px; }
/* 44px: comfortably past the 24px WCAG 2.2 minimum and the size a thumb
   actually expects. */
.icon-btn{
  width:44px; height:44px; display:grid; place-items:center; cursor:pointer;
  border:1px solid var(--hair); border-radius:50%; background:transparent;
  transition:border-color .4s var(--ease), background .4s var(--ease);
}
.icon-btn:hover{ border-color:var(--sindoor); background:color-mix(in srgb, var(--sindoor) 8%, transparent); }
.icon-btn svg{ width:17px; height:17px; fill:none; stroke:currentColor; stroke-width:1.4; stroke-linecap:round; }
.moon{ display:none; } .sun{ display:block; }
:root[data-theme="dark"] .moon{ display:block; } :root[data-theme="dark"] .sun{ display:none; }
@media (prefers-color-scheme: dark){
  :root:not([data-theme="light"]) .moon{ display:block; }
  :root:not([data-theme="light"]) .sun{ display:none; }
}

.btn{
  display:inline-flex; align-items:center; gap:.6em; border:1px solid var(--ink);
  padding:11px 22px; border-radius:999px; font-size:13.5px; letter-spacing:.01em;
  background:transparent; color:var(--ink); cursor:pointer; overflow:hidden;
  position:relative; isolation:isolate; transition:color .5s var(--ease), border-color .5s var(--ease);
}
.btn::before{
  content:""; position:absolute; inset:0; z-index:-1; background:var(--ink);
  transform:translateY(101%); transition:transform .55s var(--ease);
}
.btn:hover{ color:var(--paper); }
.btn:hover::before{ transform:translateY(0); }
.btn.solid{ background:var(--ink); color:var(--paper); }
.btn.solid::before{ background:var(--sindoor); }
.btn.solid:hover{ color:#fff; border-color:var(--sindoor); }

#menuBtn{ display:none; }
#menuBtn .bars, #menuBtn .bars::before, #menuBtn .bars::after{
  content:""; display:block; width:15px; height:1.4px; background:currentColor;
  transition:transform .4s var(--ease), opacity .25s;
}
#menuBtn .bars::before{ transform:translateY(-5px); }
#menuBtn .bars::after{  transform:translateY(3.6px); }
#menuBtn[aria-expanded="true"] .bars{ background:transparent; }
#menuBtn[aria-expanded="true"] .bars::before{ transform:rotate(45deg); }
#menuBtn[aria-expanded="true"] .bars::after{ transform:rotate(-45deg); }

@media (max-width:900px){
  #menuBtn{ display:grid; }
  .nav-act .btn{ display:none; }
  .nav-links{
    position:fixed; inset:0 0 auto; top:0; padding:96px var(--gut) 44px;
    background:color-mix(in srgb, var(--paper) 97%, transparent);
    backdrop-filter:blur(14px); border-bottom:var(--rule);
    transform:translateY(-102%); transition:transform .55s var(--ease);
    visibility:hidden;
  }
  .nav-links.open{ transform:none; visibility:visible; }
  .nav-links ul{ flex-direction:column; align-items:flex-start; gap:6px; }
  .nav-links a{ font-family:var(--f-display); font-size:34px; padding-block:10px; }
  .nav-links a::after{ display:none; }
}

/* ============================================================
   HERO — headline and figure share the first screen.
   The old layout stacked a 480px wheel above the h1, which
   pushed the proposition entirely below the fold.
   ============================================================ */
/* The hero resolves on one screen. It used to run 995px against an 860px
   viewport — 16% past the fold — so it was never seen composed. Now the
   grid takes the height that's left after the header and the foot, and the
   two columns are centred against each other rather than floating. */
.hero{
  min-height:100svh; display:flex; flex-direction:column;
  padding-top:clamp(104px,13vh,150px); padding-bottom:clamp(28px,3.5vw,44px);
}
.hero > .shell{ flex:1; display:flex; flex-direction:column; }
.hero-grid{
  flex:1;
  display:grid; grid-template-columns:1.05fr .95fr;
  gap:clamp(28px,5vw,88px); align-items:center;
}
.hero-copy .eyebrow{ margin-bottom:clamp(20px,2.6vw,34px); }

/* Masking/rise behaviour lives entirely in the [data-rv="mask"] rules so
   nothing here out-specifies them — an #id rule beat them once already. */
#headline{ font-size:clamp(46px,7.6vw,116px); line-height:1.04; }
#headline em{ font-style:italic; color:var(--sindoor); }
#headline .tick{
  display:inline-block; width:.5em; height:.05em; background:var(--sindoor);
  vertical-align:.26em; margin-left:.14em;
}
.lede{ margin:clamp(24px,3vw,38px) 0 0; color:var(--ink-soft); font-size:16.5px; max-width:44ch; }

.stats{
  display:flex; flex-wrap:wrap; gap:clamp(20px,3vw,40px);
  margin:clamp(26px,3vw,40px) 0 0; padding:22px 0 0; border-top:var(--rule); list-style:none;
}
.stats b{ font-family:var(--f-display); font-weight:400; font-size:clamp(28px,3vw,40px); display:block; line-height:1; font-variant-numeric:tabular-nums; }
.stats span{ font-family:var(--f-mono); font-size:10px; letter-spacing:.11em; text-transform:uppercase; color:var(--ink-faint); display:block; margin-top:8px; }

/* The figure column used to be 183px shorter than the type beside it, which
   left 164px of dead air under it. Sizing it to the column and pinning the
   caption to the bottom makes the two columns read as one composition. */
.hero-figure{
  display:flex; flex-direction:column; align-items:center; justify-content:center;
  gap:clamp(20px,2.4vw,34px); height:100%;
}
/* The hero figure is a diagram, not a toy: no drag, no grab cursor, and
   nothing to fight the page for a touch gesture. It states the thing the
   whole page argues — one core, every Apple platform. */
.platform-fig{
  position:relative; width:min(100%, clamp(260px, 40vw, 560px)); aspect-ratio:1/1;
  will-change:transform, opacity;
}
.platform-fig canvas{ width:100%; height:100%; display:block; }
.platform-fig::after{
  content:""; position:absolute; inset:-10%; z-index:-1; border-radius:50%;
  background:radial-gradient(closest-side, color-mix(in srgb, var(--haldi) 12%, transparent), transparent 72%);
}
.fig-cap{
  display:flex; align-items:center; gap:14px; font-family:var(--f-mono);
  font-size:10px; letter-spacing:.1em; text-transform:uppercase; color:var(--ink-faint);
  text-align:center; flex-wrap:wrap; justify-content:center;
}
.fig-cap .momentum{ color:var(--sindoor); }
@media (hover:none){ .hover-only{ display:none; } }
.fig-cap .momentum b{ font-size:13px; font-variant-numeric:tabular-nums; }

.hero-foot{
  margin-top:clamp(28px,3.5vw,48px); padding-top:24px; border-top:var(--rule);
  display:flex; align-items:flex-start; justify-content:space-between; gap:32px; flex-wrap:wrap;
}
.side-note{ margin:0; color:var(--ink-soft); font-size:14px; max-width:52ch; }
.scrollcue{ display:flex; align-items:center; gap:12px; font-family:var(--f-mono); font-size:10px; letter-spacing:.13em; text-transform:uppercase; color:var(--ink-faint); white-space:nowrap; }
.scrollcue i{ display:block; width:52px; height:1px; background:var(--hair); position:relative; overflow:hidden; }
.scrollcue i::after{ content:""; position:absolute; inset:0; background:var(--sindoor); animation:sweep 2.8s var(--ease) infinite; }
@keyframes sweep{ 0%{transform:translateX(-100%)} 60%,100%{transform:translateX(100%)} }

@media (max-width:980px){
  .hero{ min-height:0; }
  .hero-grid{ grid-template-columns:1fr; gap:clamp(30px,6vw,46px); }
  .hero-figure{ order:-1; }
  .platform-fig{ width:min(78vw, 340px); }
  .hero-foot{ flex-direction:column-reverse; }
}

/* The kolam dot divider was removed. It separated two sections that
   were already separated — .craft carries its own border-block and a
   background shift — so it was pure ornament, and it painted an
   opaque var(--paper) patch to knock a hole in its own hairline,
   which read as a visible mismatched block on any ground but the
   default one. Two failures for zero work done. */

/* ============================================================
   SECTION FRAME
   ============================================================ */
.sec{ padding-block:clamp(88px,12vw,180px); }
.sec-head{ display:grid; grid-template-columns:auto 1fr; gap:clamp(24px,4.5vw,72px); align-items:end; margin-bottom:clamp(48px,7vw,92px); }
.sec-head h2{ font-size:clamp(38px,6vw,92px); }
/* The numeral sits beside the heading, never inside it — in the old build
   the accessible name and the document outline both read "01Selected work". */
.sec-num{
  display:block; font-family:var(--f-mono); font-size:11px; letter-spacing:.15em;
  color:var(--sindoor); margin-bottom:1.4em;
}
.sec-head .note{ color:var(--ink-soft); font-size:15px; max-width:46ch; justify-self:end; }
@media (max-width:860px){ .sec-head{ grid-template-columns:1fr; align-items:start; } .sec-head .note{ justify-self:start; } }

/* The scrolling technology belt was removed deliberately: a
   decontextualised list of eighteen framework names is a skills
   badge wall, and it claimed expertise the projects should be
   demonstrating instead. Tags stayed — those are scoped to the
   project that earned them, which makes them evidence. */

/* ============================================================
   WORK
   ============================================================ */
/* The right panel now carries the substance (architecture + chips), so it
   gets the wider column, and both columns hang from the top — centring a
   short title block against a tall panel left it floating in space. */
.card{
  display:grid; grid-template-columns:56px .92fr 1.08fr; gap:clamp(18px,3vw,48px);
  align-items:start; padding-block:clamp(28px,3.4vw,44px); border-bottom:var(--rule);
  position:relative; isolation:isolate;
}
.cards > .card:first-child{ border-top:var(--rule); }
.card::before{
  content:""; position:absolute; inset:0 calc(var(--gut) * -.4); z-index:-1;
  background:linear-gradient(180deg, color-mix(in srgb, var(--paper-lit) 92%, transparent), transparent);
  opacity:0; transition:opacity .55s var(--ease);
}
.card:hover::before{ opacity:1; }
/* The numeral used to float at the very top of the card with nothing near
   it, reading as debris. Nudged onto the project name's baseline so it
   belongs to something. */
.card .idx{
  font-family:var(--f-mono); font-size:11px; letter-spacing:.12em;
  color:var(--ink-faint); transition:color .45s var(--ease);
  padding-top:.9em;
}
.card:hover .idx{ color:var(--sindoor); }
/* Whose product this is, stated before its name. */
.owner{
  display:block; margin-bottom:14px;
  font-family:var(--f-mono); font-size:9.5px; letter-spacing:.16em;
  text-transform:uppercase; color:var(--ink-faint);
}
.owner-mine{ color:var(--sindoor); }
.owner-mine::before{
  content:""; display:inline-block; width:16px; height:1px; vertical-align:.28em;
  margin-right:.7em; background:var(--sindoor);
}
.card h3{ font-size:clamp(26px,3.4vw,48px); }
.card .role{ font-family:var(--f-mono); font-size:11px; letter-spacing:.11em; text-transform:uppercase; color:var(--ink-faint); margin-top:12px; display:block; }
.card .blurb{ margin:16px 0 0; color:var(--ink-soft); font-size:15px; max-width:46ch; }
/* Chips sit under the blurb, not in the panel. The left column was ~200px
   shorter than the right, which left a visible void beside every project;
   this gives it mass and keeps the panel about the writing. */
.card-id .tags{ margin-top:22px; }
.tags{ display:flex; flex-wrap:wrap; gap:7px; margin:16px 0 0; padding:0; list-style:none; }
.tags li{ font-family:var(--f-mono); font-size:10.5px; letter-spacing:.1em; text-transform:uppercase; border:var(--rule); border-radius:999px; padding:4px 11px; color:var(--ink-soft); }

/* The decision panel replaces the screenshot. A screenshot shows
   what the app looks like — which the App Store link already does,
   better. This shows the one engineering choice that mattered,
   which is the only thing on the card a reader can't get elsewhere. */
.decision{
  position:relative; align-self:stretch; display:flex; flex-direction:column; justify-content:center;
  padding:clamp(22px,2.4vw,34px) clamp(20px,2.2vw,32px);
  border-left:2px solid color-mix(in srgb, var(--sindoor) 45%, transparent);
  background:linear-gradient(100deg, color-mix(in srgb, var(--paper-lit) 70%, transparent), transparent 85%);
  transition:border-color .6s var(--ease), background .6s var(--ease);
}
.card:hover .decision{ border-left-color:var(--sindoor); }
.decision .lbl{
  font-family:var(--f-mono); font-size:9.5px; letter-spacing:.1em; text-transform:uppercase;
  color:var(--ink-faint); margin-bottom:14px;
}
/* Architecture prose. Body face, not the display italic: this is a
   technical description meant to be read, not a pull-quote meant to be
   admired. The earlier version read well and said nothing. */
.decision .arch{
  margin:0; font-size:14.5px; line-height:1.68; color:var(--ink-soft);
}
.decision .go{ margin-top:22px; }

/* An unanswered detail, marked as one. */
.gap{
  font-style:italic; color:var(--ink-faint);
  border-bottom:1px dotted color-mix(in srgb, var(--ink-faint) 55%, transparent);
}
/* The CTA sits in the panel's flow, at the bottom. It used to be
   absolutely positioned at the card's vertical centre, which was fine
   over a screenshot but printed straight through the decision quote
   once real text moved in there. */
.card .go{
  margin-top:20px; align-self:flex-start;
  font-family:var(--f-mono); font-size:11px; letter-spacing:.1em; text-transform:uppercase;
  color:var(--ink-faint);
  opacity:0; transform:translateX(-8px);
  transition:opacity .5s var(--ease), transform .5s var(--ease), color .5s var(--ease);
}
.card:hover .go{ opacity:1; transform:none; color:var(--sindoor); }
/* Touch devices never hover, so the CTA would never appear at all. */
@media (hover:none){ .card .go{ opacity:1; transform:none; } }

@media (max-width:880px){
  .card{ grid-template-columns:34px 1fr; gap:14px; }
  .card .decision{ grid-column:1/-1; margin-top:20px; padding:20px 0 20px 20px; }
}

/* ============================================================
   CRAFT — palm-leaf strips (tāla-patra)
   ============================================================ */
.craft{ background:color-mix(in srgb, var(--paper-lit) 55%, transparent); border-block:var(--rule); }
/* Was 4 columns for 5 items: three empty cells rendered as grey blocks,
   because the container painted the gaps. And 250px columns squeezed the
   prose to 23 characters a line, which is unreadable.
   Now: wider columns (≈48ch), and each leaf carries its own hairline so an
   empty cell is simply empty. */
.leaves{
  display:grid; grid-template-columns:repeat(auto-fit, minmax(300px,1fr));
  gap:1px; background:transparent;
}
.leaf{
  background:transparent; box-shadow:0 0 0 1px var(--hair);
  padding:clamp(34px,3.2vw,52px) clamp(28px,2.6vw,42px);
  position:relative; overflow:hidden; transition:background .5s var(--ease);
  /* Column so the evidence line can be pushed to the floor — otherwise it
     sits wherever each paragraph happens to end and the row's footers step
     up and down across the grid. */
  display:flex; flex-direction:column;
}
/* A whisper of fibre, not a costume. At opacity .5 over 3px stripes this
   was mimicking a dried leaf literally; abstracted, it just reads as
   material. */
.leaf::before{
  content:""; position:absolute; inset:0; pointer-events:none; opacity:.16;
  background:repeating-linear-gradient(
    to bottom,
    transparent 0 6px,
    color-mix(in srgb, var(--ink) 4%, transparent) 6px 7px
  );
}
.leaf:hover{ background:color-mix(in srgb, var(--paper-lit) 65%, transparent); }
.leaf > *{ position:relative; }
/* The drawn binding cord and stitch holes were removed: literal props.
   A single accent rule states "this is a leaf in a stack" without
   illustrating one. */
.leaf::after{
  content:""; position:absolute; left:0; top:0; width:34px; height:2px;
  background:color-mix(in srgb, var(--sindoor) 55%, transparent);
}
.leaf .n{ font-family:var(--f-mono); font-size:10.5px; letter-spacing:.11em; color:var(--sindoor); text-transform:uppercase; display:block; }
.leaf h3{ font-size:clamp(22px,2.1vw,29px); margin:16px 0 14px; }
.leaf p{ margin:0 0 26px; color:var(--ink-soft); font-size:15px; line-height:1.68; }
/* Evidence anchor, not a chip row. The old <code> badges listed
   frameworks — claiming expertise. This names the project where the
   conviction above was actually paid for. */
.leaf-sig{
  margin-top:auto; padding-top:16px; border-top:var(--rule);
  font-family:var(--f-mono); font-size:10px; letter-spacing:.1em;
  text-transform:uppercase; color:var(--ink-faint);
}
.leaf-sig b{ color:var(--sindoor); font-weight:400; }

/* ============================================================
   PATH — timeline, lit by diyas
   ============================================================ */
.line{ position:relative; margin:0; padding:0; list-style:none; }
.line::before{ content:""; position:absolute; left:7px; top:10px; bottom:10px; width:1px; background:var(--hair); }
.line li{ position:relative; padding:0 0 clamp(34px,4vw,54px) clamp(34px,4vw,56px); }
.line li:last-child{ padding-bottom:0; }
.lamp{ position:absolute; left:0; top:6px; width:15px; height:15px; border-radius:50%; background:var(--paper); border:1px solid var(--sindoor); }
.lamp::after{ content:""; position:absolute; inset:3px; border-radius:50%; background:var(--haldi); animation:flicker 3.6s ease-in-out infinite; }
.line li:nth-child(2) .lamp::after{ animation-delay:-1.1s; }
.line li:nth-child(3) .lamp::after{ animation-delay:-2.2s; }
.line li:nth-child(4) .lamp::after{ animation-delay:-.6s; }
@keyframes flicker{
  0%,100%{ opacity:.82; box-shadow:0 0 0 0 color-mix(in srgb, var(--haldi) 40%, transparent); }
  50%    { opacity:1;   box-shadow:0 0 13px 3px color-mix(in srgb, var(--haldi) 42%, transparent); }
}
.line .yr{ font-family:var(--f-mono); font-size:11px; letter-spacing:.11em; color:var(--ink-faint); }
.line h3{ font-size:clamp(23px,2.5vw,34px); margin:8px 0 6px; }
.line .co{ color:var(--sindoor); font-size:14px; }
.line p{ margin:12px 0 0; color:var(--ink-soft); font-size:14.5px; max-width:62ch; }

/* ============================================================
   THE NIGHT BAND — the one place the page changes value.
   Day → dusk → day gives the scroll a rhythm the all-parchment
   version never had, and it is the only ground on which the
   haldi accent actually glows.
   ============================================================ */
.night{
  position:relative; overflow:hidden; text-align:center;
  padding-block:clamp(88px,12vw,168px);
}
.night::before{
  content:""; position:absolute; left:50%; bottom:-30%; translate:-50% 0;
  width:min(880px, 120%); aspect-ratio:1/1; pointer-events:none;
  background:radial-gradient(closest-side, color-mix(in srgb, var(--haldi) 20%, transparent), transparent 70%);
}
.night > *{ position:relative; }
.verse{
  font-family:var(--f-display); font-style:italic;
  font-size:clamp(26px,3.8vw,50px); line-height:1.34;
  max-width:20ch; margin:0 auto; color:var(--ink);
}
.verse-src{ font-family:var(--f-mono); font-size:10px; letter-spacing:.13em; text-transform:uppercase; color:var(--ink-faint); margin-top:28px; }
/* Was a drawn oil lamp with a wobbling flame — charming, and firmly
   clip-art. Now it is just light: a point source that breathes. */
.diya{ width:10px; height:10px; margin:0 auto clamp(38px,5vw,60px); position:relative; }
.diya::after{
  content:""; position:absolute; inset:0; border-radius:50%;
  background:var(--haldi);
  box-shadow:0 0 34px 10px color-mix(in srgb, var(--haldi) 34%, transparent);
  animation:emberBreath 5s ease-in-out infinite;
}
@keyframes emberBreath{
  0%,100%{ opacity:.72; transform:scale(1); }
  50%    { opacity:1;   transform:scale(1.16); }
}

/* ============================================================
   CONTACT
   ============================================================ */
.talk h2{ font-size:clamp(44px,9vw,142px); }
.talk .mailto{ display:inline-block; position:relative; font-family:var(--f-display); font-size:clamp(20px,3.1vw,42px); margin-top:24px; }
.talk .mailto::after{ content:""; position:absolute; left:0; bottom:.06em; height:1px; width:100%; background:currentColor; opacity:.35; transform-origin:left; transition:transform .55s var(--ease); }
.talk .mailto:hover::after{ transform:scaleX(0); }
.socials{ display:flex; flex-wrap:wrap; gap:12px; margin:44px 0 0; padding:0; list-style:none; }
footer{ border-top:var(--rule); padding-block:26px; display:flex; flex-wrap:wrap; gap:14px; justify-content:space-between; font-family:var(--f-mono); font-size:10.5px; letter-spacing:.1em; text-transform:uppercase; color:var(--ink-faint); }

/* ============================================================
   REVEAL — four gestures, not one.
   Every element used to arrive with the same 1s fade-and-rise,
   which is why nothing felt authored. Each variant is now tied
   to what the thing is: rules draw, headings unmask, images
   settle, everything else rises.
   ============================================================ */
/* Scroll-DRIVEN, not scroll-triggered. `animation-timeline: view()` binds
   each element's progress to its own position in the viewport, so the
   reveal is scrubbed — scroll back and it plays backwards. It also runs off
   the compositor rather than the main thread, and it replaced an
   IntersectionObserver plus ~15 lines of class-toggling JS.

   Everything sits inside @supports and starts VISIBLE. A browser without
   scroll timelines shows a finished page rather than a blank one, which is
   the right failure. */
[data-rv="mask"] .m{ display:block; padding-bottom:.16em; margin-bottom:-.16em; }

@supports (animation-timeline: view()){
  .js [data-rv]{
    animation:rvUp linear both;
    animation-timeline:view();
    animation-range:entry 4% cover 20%;
  }
  .js [data-rv="scale"]{ animation-name:rvScale; animation-range:entry 2% cover 26%; }

  /* The heading masks animate their inner line, not the block. */
  .js [data-rv="mask"]{ animation:none; }
  .js [data-rv="mask"] .m{ overflow:hidden; }
  .js [data-rv="mask"] .m > span{
    display:block;
    animation:rvMask linear both;
    animation-timeline:view();
    animation-range:entry 4% cover 18%;
  }

  /* The headline is already on screen at load, so a view() timeline would
     show it finished. It keeps a one-shot entrance instead. */
  .js #headline{ animation:none; }
  .js #headline .m{ overflow:hidden; }
  .js #headline .m > span{
    display:block; animation:rvMask .85s var(--ease) both; animation-timeline:auto;
  }
  .js #headline .m:nth-child(2) > span{ animation-delay:.09s; }
  .js #headline .m:nth-child(3) > span{ animation-delay:.18s; }

  /* Siblings entering on the same line get a small positional stagger. */
  [data-d="1"]{ animation-range:entry 7% cover 22%; }
  [data-d="2"]{ animation-range:entry 10% cover 24%; }
  [data-d="3"]{ animation-range:entry 13% cover 26%; }
  [data-d="4"]{ animation-range:entry 16% cover 28%; }
}

@keyframes rvUp   { from{ opacity:0; transform:translateY(20px); } to{ opacity:1; transform:none; } }
@keyframes rvScale{ from{ opacity:0; transform:scale(1.05); } to{ opacity:1; transform:none; } }
@keyframes rvMask { from{ transform:translateY(105%); } to{ transform:none; } }

@media (prefers-reduced-motion:reduce){
  *{ animation-duration:.001s !important; animation-iteration-count:1 !important; transition-duration:.001s !important; }
  .js [data-rv]{ animation:none !important; opacity:1 !important; transform:none !important; }
  .js [data-rv="mask"] .m > span,
  .js #headline .m > span{ animation:none !important; transform:none !important; }
  html{ scroll-behavior:auto; }
}

/* ============================================================
   CASE STUDY (case.html)
   One narrow column. A case study is read, not scanned, so the
   measure stays near 68ch and nothing competes with the prose.
   ============================================================ */
.case{ padding-top:clamp(104px,13vh,150px); padding-bottom:clamp(60px,8vw,110px); }
.case-back{
  display:inline-flex; align-items:center; gap:.6em; font-family:var(--f-mono);
  font-size:10.5px; letter-spacing:.12em; text-transform:uppercase; color:var(--ink-faint);
  transition:color .4s var(--ease);
}
.case-back:hover{ color:var(--sindoor); }
.case-head{ margin:clamp(28px,4vw,52px) 0 0; max-width:20ch; }
.case-head h1{ font-size:clamp(44px,7vw,104px); line-height:1.04; }
.case-meta{
  display:flex; flex-wrap:wrap; gap:10px 26px; margin-top:26px;
  font-family:var(--f-mono); font-size:10.5px; letter-spacing:.11em;
  text-transform:uppercase; color:var(--ink-faint);
}
.case-stand{
  margin:clamp(26px,3vw,40px) 0 0; max-width:52ch;
  font-family:var(--f-display); font-size:clamp(19px,2.1vw,27px);
  line-height:1.42; color:var(--ink-soft);
}
.case-body{ max-width:68ch; margin-top:clamp(52px,7vw,96px); }
.case-body h2{
  font-size:clamp(26px,3.2vw,40px); margin:clamp(48px,6vw,84px) 0 0;
  padding-top:clamp(26px,3vw,38px); border-top:var(--rule);
}
.case-body h2:first-child{ margin-top:0; border-top:0; padding-top:0; }
.case-body p{ margin:20px 0 0; color:var(--ink-soft); font-size:16.5px; line-height:1.72; }
.case-body > p:first-of-type{ margin-top:22px; }

/* numbered decision list — the spine of the study */
.dec{ list-style:none; margin:26px 0 0; padding:0; counter-reset:d; }
.dec > li{ counter-increment:d; padding:26px 0 0; }
.dec > li + li{ margin-top:26px; border-top:var(--rule); }
.dec h3{
  font-size:clamp(18px,1.9vw,23px); display:flex; gap:.75em; align-items:baseline;
}
.dec h3::before{
  content:counter(d,decimal-leading-zero); font-family:var(--f-mono); font-size:11px;
  letter-spacing:.11em; color:var(--sindoor); flex:none;
}
.dec p{ margin-top:12px; }

.takeaways{ list-style:none; margin:26px 0 0; padding:0; }
.takeaways li{
  position:relative; padding:0 0 0 30px; margin-top:18px;
  color:var(--ink-soft); font-size:16.5px; line-height:1.7;
}
.takeaways li::before{
  content:""; position:absolute; left:0; top:.62em; width:14px; height:1px;
  background:var(--sindoor);
}

.case-out{ margin-top:clamp(56px,7vw,96px); padding-top:34px; border-top:var(--rule); display:flex; flex-wrap:wrap; gap:12px; }

/* A study that isn't written yet says so, rather than 404-ing. */
.case-soon{ margin-top:36px; color:var(--ink-soft); font-size:16.5px; max-width:56ch; }

/* ---------- at a glance: the ten-second read ---------- */
/* Flex, not grid: a grid with a 1px-gap background leaves a filled
   grey cell whenever the item count doesn't divide by the column
   count. Flex-wrap simply has no empty cells, at any count. */
.glance{
  display:flex; flex-wrap:wrap; margin:clamp(34px,4vw,52px) 0 0; padding:0;
  border-block:var(--rule);
}
.glance > div{
  flex:1 1 165px; padding:18px 24px;
  border-right:var(--rule);
}
.glance > div:last-child{ border-right:0; }
.glance dt{
  font-family:var(--f-mono); font-size:9.5px; letter-spacing:.13em;
  text-transform:uppercase; color:var(--ink-faint);
}
.glance dd{ margin:8px 0 0; font-size:14.5px; color:var(--ink); }

/* ---------- figures ----------
   Full-bleed of the reading measure, because a diagram constrained
   to 68ch is a diagram nobody can read. */
/* Sits on the reading measure. An earlier attempt to bleed these wider
   used `margin-left:min(0px,-8%)`, which resolved negative against the
   container and pushed the diagram off the left edge of the page. */
.fig{ margin:clamp(32px,4vw,48px) 0 0; }
.fig svg{ width:100%; height:auto; display:block; }
.fig figcaption{
  margin-top:18px; padding-top:14px; border-top:var(--rule);
  font-size:13.5px; line-height:1.6; color:var(--ink-faint); max-width:60ch;
}
.fig-band{ fill:color-mix(in srgb, var(--ink) 7%, transparent); }
.fig-gap{ fill:color-mix(in srgb, var(--sindoor) 11%, transparent); }
.fig-edge{ stroke:color-mix(in srgb, var(--ink) 34%, transparent); stroke-width:1; }
.fig-accent-line{ stroke:var(--sindoor); stroke-width:1.5; }
.fig-box rect{ fill:none; stroke:color-mix(in srgb, var(--ink) 30%, transparent); stroke-width:1; }
.fig-arrow{ fill:none; stroke:color-mix(in srgb, var(--ink) 42%, transparent); stroke-width:1.2; }
.fig-dash{ stroke-dasharray:4 4; }
.fig-strong{ fill:var(--ink); font-family:var(--f-display); }
.fig-mute{ fill:var(--ink-faint); font-family:var(--f-body); }
.fig-label{ fill:var(--ink-soft); font-family:var(--f-body); }
.fig-accent{ fill:var(--sindoor); font-family:var(--f-body); }

@media (max-width:720px){
  .fig figcaption{ font-size:13px; }
}

/* ============================================================
   SHIPPED — the breadth ledger.
   Deliberately typographic, not a card grid: this section is the
   footnote to Selected Work, and it must never compete with it.
   ============================================================ */
.shipped{ border-top:var(--rule); }
.ledger{
  display:grid; grid-template-columns:repeat(auto-fit, minmax(250px,1fr));
  gap:clamp(28px,3vw,44px) clamp(24px,3vw,56px);
}
.led-group{ border-top:var(--rule); padding-top:20px; }
.led-domain{
  font-family:var(--f-mono); font-size:10px; font-weight:400; letter-spacing:.13em;
  text-transform:uppercase; color:var(--sindoor);
}
.led-apps{ list-style:none; margin:16px 0 0; padding:0; }
.led-apps li{ padding:7px 0; }
.led-name{ font-family:var(--f-display); font-size:18px; display:block; }
.led-note{
  font-family:var(--f-mono); font-size:10px; letter-spacing:.12em;
  text-transform:uppercase; color:var(--ink-faint); display:block; margin-top:5px;
}
/* Only a listing that still resolves gets link affordance. */
.led-live{ display:block; position:relative; transition:color .4s var(--ease); }
.led-live .led-name{ display:inline-block; position:relative; }
.led-live .led-name::after{
  content:""; position:absolute; left:0; bottom:-2px; height:1px; width:0;
  background:var(--sindoor); transition:width .45s var(--ease);
}
.led-live:hover{ color:var(--sindoor); }
.led-live:hover .led-name::after{ width:100%; }
.led-live .led-name::before{
  content:"↗"; position:absolute; right:-16px; top:-2px;
  font-family:var(--f-mono); font-size:10px; color:var(--ink-faint);
}


/* ---------- path: narrative beside the era's toolset ----------
   The stack column is the quiet argument of this section — read the four
   of them top to bottom and you get Objective-C to visionOS without a
   single sentence claiming it. */
.era{ display:grid; grid-template-columns:1fr minmax(180px, .42fr); gap:clamp(20px,3vw,48px); align-items:start; }
.era-stack{
  margin:0; padding-left:18px; border-left:2px solid color-mix(in srgb, var(--sindoor) 40%, transparent);
  font-family:var(--f-mono); font-size:10px; line-height:2; letter-spacing:.1em;
  text-transform:uppercase; color:var(--ink-faint);
}
@media (max-width:820px){
  .era{ grid-template-columns:1fr; gap:20px; }
  .era-stack{ padding-left:14px; }
}

/* ---------- what hasn't changed ----------
   Closing band of The path. The era column above shows the tools turning
   over every two years; this is the constant underneath it. No icons —
   four decorative glyphs would be four objects with no purpose. */
.constants{
  margin-top:clamp(40px,5vw,68px); padding-top:clamp(26px,3vw,38px);
  border-top:var(--rule);
}
.constants > h3{
  font-family:var(--f-mono); font-size:10px; font-weight:400; letter-spacing:.1em;
  text-transform:uppercase; color:var(--sindoor); margin-bottom:clamp(22px,2.6vw,32px);
}
.constants ul{
  list-style:none; margin:0; padding:0;
  display:grid; grid-template-columns:repeat(auto-fit, minmax(215px,1fr));
  gap:clamp(22px,2.6vw,40px);
}
.constants li{ border-top:2px solid color-mix(in srgb, var(--sindoor) 35%, transparent); padding-top:16px; }
.constants b{ font-family:var(--f-display); font-weight:400; font-size:19px; display:block; }
.constants span{ display:block; margin-top:9px; font-size:14px; line-height:1.6; color:var(--ink-soft); }
