/* ============================================================
   FOUAD MALKIA — PORTFOLIO
   Design system : "Alliage"
   Du signal clinique au produit — verre, métal, lumière basse.
   ============================================================ */

/* IBM Plex Mono 600 is loaded, not just 400/500: .nav-lang-btn and
   .domain-grid ask for 600, and without the real face the browser
   synthesises a fake bold by smearing the lighter one — which is exactly
   what made the FR/EN pill look blurry. Montserrat carries the wordmark:
   the geometric uppercase of the app icon (FOUMIL bold, STUDIO light). */
@import url('https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght@0,9..144,340;0,9..144,440;0,9..144,560;0,9..144,650;1,9..144,440;1,9..144,500&family=Public+Sans:ital,wght@0,400;0,500;0,600;0,700;1,400&family=IBM+Plex+Mono:wght@400;500;600&family=Montserrat:wght@400;500;700&display=swap');

:root{
  /* -- couleurs -- */
  --void:       #0A0B0C;
  --void-rgb:   10,11,12;
  --void-2:     #101214;
  --void-3:     #17191C;
  --glass:      rgba(255,255,255,0.045);
  --glass-hi:   rgba(255,255,255,0.09);
  --ink:        #EFEEE9;
  --ink-soft:   #9A9FA1;
  --ink-faint:  #63676A;
  --line:       rgba(255,255,255,0.10);
  --line-soft:  rgba(255,255,255,0.06);
  --card:       #131517;

  --accent:     #34D8B3;
  --accent-rgb: 52,216,179;
  --accent-ink: #06110E;
  --accent-soft:rgba(52,216,179,0.12);
  --vital:      #FF6E4E;
  --vital-soft: rgba(255,110,78,0.12);

  /* -- type -- */
  --display: "Fraunces", ui-serif, Georgia, serif;
  --sans: "Public Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --data: "IBM Plex Mono", ui-monospace, Menlo, Consolas, monospace;
  --brand: "Montserrat", "Public Sans", -apple-system, BlinkMacSystemFont, sans-serif;

  --measure: 62ch;

  color-scheme: dark;
}

/* Light theme — explicit opt-in only (toggle in the nav), default stays
   dark. Same accent family, deepened where it doubles as text/icon color
   so it keeps enough contrast against a light ground. */
:root[data-theme="light"]{
  --void:       #F6F4EF;
  --void-rgb:   246,244,239;
  --void-2:     #EDEAE2;
  --void-3:     #E3DFD4;
  --glass:      rgba(10,11,12,0.035);
  --glass-hi:   rgba(10,11,12,0.07);
  --ink:        #17181A;
  --ink-soft:   #55585B;
  /* Both nudged darker to clear 4.5:1 against the page. At the previous
     #8B8D8C / #0E8F72 they measured ~3.1:1 and ~4.0:1 — fine on the dark
     ground they were derived from, under the bar on this one, and the
     accent carries links and eyebrows rather than decoration only. */
  --ink-faint:  #6E7173;
  --line:       rgba(10,11,12,0.13);
  --line-soft:  rgba(10,11,12,0.07);
  --card:       #FFFFFF;

  --accent:     #0B7C62;
  --accent-rgb: 11,124,98;
  --accent-ink: #FFFFFF;
  --accent-soft:rgba(11,124,98,0.12);
  --vital:      #C2410C;
  --vital-soft: rgba(194,65,12,0.12);

  color-scheme: light;
}

*{ box-sizing:border-box; }
html{ scroll-behavior:smooth; }
html,body{ margin:0; padding:0; }

body{
  background:var(--void);
  color:var(--ink);
  font-family:var(--sans);
  font-size:17px;
  line-height:1.6;
  -webkit-font-smoothing:antialiased;
  text-rendering:optimizeLegibility;
  position:relative;
}

/* Fine metallic grain, applied once over the whole page */
body::before{
  content:"";
  position:fixed; inset:0;
  z-index:0; pointer-events:none;
  opacity:0.5;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='matrix' values='0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.028 0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* Soft radial glow that follows the cursor, very restrained */
body::after{
  content:"";
  position:fixed; inset:0;
  z-index:0; pointer-events:none;
  background:radial-gradient(600px circle at var(--mx,50%) var(--my,20%), rgba(var(--accent-rgb),0.05), transparent 60%);
  transition:background .3s ease;
}

main, .site-nav, .site-footer{ position:relative; z-index:1; }

/* Constellation field — drifting particle network, injected by script.js,
   sits behind all page content (see main/.site-nav/.site-footer z-index above). */
.site-constellation{ position:fixed; inset:0; z-index:0; pointer-events:none; opacity:.6; }
/* Dialled back on the light ground: the same alpha that reads as faint
   depth over near-black reads as grey speckle across a page of text. */
:root[data-theme="light"] .site-constellation{ opacity:.28; }

@media (prefers-reduced-motion: reduce){
  html{ scroll-behavior:auto; }
  *{ animation-duration:0.001ms !important; animation-iteration-count:1 !important; transition-duration:0.001ms !important; }
}

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

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

a{ color:inherit; }

.wrap{
  max-width:1180px;
  margin:0 auto;
  padding:0 40px;
}
@media (max-width:720px){ .wrap{ padding:0 22px; } }

/* ---------- Typographic scale ---------- */
h1,h2,h3,h4{
  font-family:var(--display);
  font-weight:560;
  letter-spacing:-0.01em;
  margin:0;
  text-wrap:balance;
  color:var(--ink);
}
h1{ font-size:clamp(34px, 5.4vw, 62px); line-height:1.05; font-weight:440; }
h2{ font-size:clamp(26px, 3.6vw, 38px); line-height:1.14; font-weight:560; }
h3{ font-size:clamp(20px, 2.4vw, 24px); line-height:1.28; font-weight:600; }

p{ margin:0 0 1em; max-width:var(--measure); color:var(--ink); }
p:last-child{ margin-bottom:0; }

.lede{
  font-family:var(--display);
  font-style:italic;
  font-weight:440;
  font-size:clamp(19px,2.1vw,23px);
  line-height:1.5;
  color:var(--ink-soft);
  max-width:56ch;
}

.data{ font-family:var(--data); font-size:0.86em; color:var(--ink-soft); }

/* ---------- Scroll progress — "signal vital" ---------- */
.scroll-vitals{
  position:fixed; top:0; left:0; right:0; height:2px; z-index:90;
  background:var(--line-soft);
  pointer-events:none;
}
.scroll-vitals::after{
  content:"";
  display:block; height:100%; width:100%;
  background:var(--accent);
  box-shadow:0 0 8px rgba(52,216,179,0.7);
  transform:scaleX(var(--scroll-progress, 0));
  transform-origin:left center;
  transition:transform .08s linear;
}

/* ---------- Intro — scroll-driven sequence: the FoumilStudio mark gives
   way to a rotating wireframe icosahedron that shatters into particles,
   then reforms and migrates toward the hero's own wireframe as the header
   and hero scroll into view underneath it, entirely normally. Homepage
   only, replayed on every load (see the early <head> script that sets
   [data-intro="skip"] only for reduced-motion, before paint).
   Two separate pieces, deliberately not nested: .intro-gate is a plain
   spacer — it just reserves scroll distance at the top of the page, so
   there's something to scroll through. .intro-overlay is a fixed,
   transparent, click-through layer that floats above it, independent of
   any position:sticky. This split matters: sticky would keep the intro
   pinned in front of everything until it fully released, which is
   exactly what stopped the header/hero from ever appearing "during" the
   scroll — they physically couldn't render until sticky let go. With a
   plain spacer, the header and hero are normal in-flow content the whole
   time; they scroll into view like anything else, at the same time the
   fixed overlay is still finishing its own animation on top of them. */
html[data-intro="skip"] .intro-gate,
html[data-intro="skip"] .intro-overlay{ display:none; }
.intro-gate{ height:260vh; }
.intro-overlay{
  position:fixed; inset:0; z-index:70;
  pointer-events:none;
  display:flex; align-items:center; justify-content:center;
}
/* Full-viewport, not a 720px box: the shape is drawn at a fixed nominal
   size (see "stage" in script.js) but travels well outside its own
   starting square on the way to the hero, and a smaller canvas simply
   clipped it mid-flight. */
.intro-icosa{
  position:absolute; inset:0;
  width:100%; height:100%;
  opacity:0;
}
.intro-logo{
  position:relative; z-index:2;
}
.intro-logo-icon{
  width:150px; height:150px; border-radius:32px;
  filter:drop-shadow(0 14px 36px rgba(0,0,0,0.6));
}
.intro-scroll-hint{
  position:absolute; left:50%; bottom:44px; transform:translateX(-50%);
  display:flex; flex-direction:column; align-items:center; gap:8px;
  font-family:var(--data); font-size:11px; letter-spacing:0.12em; text-transform:uppercase;
  color:var(--ink-faint);
  animation:introHintBob 1.8s ease-in-out infinite;
}
@keyframes introHintBob{
  0%,100%{ transform:translate(-50%,0); }
  50%{ transform:translate(-50%,8px); }
}
@media (max-width:640px){ .intro-gate{ height:200vh; } }

/* ---------- Nav ---------- */
.site-nav{
  position:sticky; top:0; z-index:80;
  background:rgba(var(--void-rgb),0.72);
  backdrop-filter:blur(14px) saturate(1.3);
  -webkit-backdrop-filter:blur(14px) saturate(1.3);
  border-bottom:1px solid var(--line-soft);
}
/* Nav entrance — a wipe-in on load. Skipped via .no-entrance on the
   homepage specifically: its nav sits ~260vh below the fold (behind the
   intro spacer), so a load-triggered animation would finish long before
   it ever scrolls into view — it just appears plainly there instead,
   like any normal in-flow content does once scrolled to. Fill-mode is
   "backwards", not "both": once the reveal finishes, .site-nav must fall
   back to its real, unset clip-path (i.e. none) rather than hold the
   keyframe's inset(0 0 0 0) — visually identical for the bar itself, but
   inset(...) still clips descendants that overflow the bar's own box,
   which silently broke the Travaux dropdown (it hangs below the nav)
   once the animation "finished" and kept holding that clip. */
@media (prefers-reduced-motion: no-preference){
  .site-nav:not(.no-entrance){ animation:navRise .6s cubic-bezier(.16,1,.3,1) backwards; }
}
@keyframes navRise{
  from{ opacity:0; clip-path:inset(0 100% 0 0); }
  to{ opacity:1; clip-path:inset(0 0% 0 0); }
}
.site-nav .wrap{
  display:flex; align-items:center; justify-content:space-between;
  padding:13px 40px;
}
@media (max-width:720px){ .site-nav .wrap{ padding:11px 22px; } }

.nav-burger{
  display:none;
  flex-direction:column; justify-content:center; align-items:center; gap:5px;
  width:38px; height:38px; margin-left:8px;
  background:none; border:1px solid var(--line); border-radius:10px;
  cursor:pointer; flex:0 0 auto;
}
.nav-burger span{
  display:block; width:16px; height:1.5px; background:var(--ink);
  transition:transform .25s ease, opacity .2s ease;
}
.nav-burger[aria-expanded="true"] span:nth-child(1){ transform:translateY(6.5px) rotate(45deg); }
.nav-burger[aria-expanded="true"] span:nth-child(2){ opacity:0; }
.nav-burger[aria-expanded="true"] span:nth-child(3){ transform:translateY(-6.5px) rotate(-45deg); }
@media (max-width:640px){ .nav-burger{ display:flex; } }

.mobile-menu{
  max-height:0; overflow:hidden;
  border-bottom:1px solid transparent;
  background:rgba(var(--void-rgb),0.75);
  backdrop-filter:blur(22px) saturate(180%);
  -webkit-backdrop-filter:blur(22px) saturate(180%);
  transition:max-height .32s cubic-bezier(.2,.8,.2,1), border-color .32s ease;
}
.mobile-menu.open{ max-height:220px; border-bottom-color:var(--line-soft); }
.mobile-menu-inner{ display:flex; flex-direction:column; padding:6px 22px 20px; }
.mobile-menu a{
  padding:13px 0; font-size:15.5px; text-decoration:none; color:var(--ink-soft);
  border-bottom:1px solid var(--line-soft);
}
.mobile-menu a:last-child{ border-bottom:none; }
.mobile-menu a:hover{ color:var(--accent); }
@media (min-width:641px){ .mobile-menu{ display:none; } }

/* ---------- Nav utility — language + theme toggle ---------- */
.nav-utility{ display:flex; align-items:center; gap:8px; margin-left:8px; }
.nav-lang{
  display:flex; align-items:center; border:1px solid var(--line); border-radius:999px;
  padding:3px; background:var(--glass); gap:2px;
}
.nav-lang-btn{
  font-family:var(--data); font-size:11.5px; font-weight:600; letter-spacing:.02em;
  color:var(--ink-faint); background:none; border:none; border-radius:999px;
  padding:5px 10px; cursor:pointer;
  transition:background .18s ease, color .18s ease;
}
.nav-lang-btn:hover{ color:var(--ink); }
.nav-lang-btn[aria-pressed="true"]{ background:var(--accent); color:var(--accent-ink); }
.nav-theme-btn{
  width:32px; height:32px; flex:0 0 auto;
  display:flex; align-items:center; justify-content:center;
  border:1px solid var(--line); border-radius:999px; background:var(--glass);
  color:var(--ink-soft); cursor:pointer;
  transition:background .18s ease, color .18s ease, border-color .18s ease;
}
.nav-theme-btn:hover{ background:var(--accent-soft); color:var(--accent); border-color:var(--accent); }
.nav-theme-btn .icon-sun{ display:none; }
:root[data-theme="light"] .nav-theme-btn .icon-sun{ display:block; }
:root[data-theme="light"] .nav-theme-btn .icon-moon{ display:none; }
@media (max-width:640px){ .nav-utility{ display:none; } }

.mobile-menu-utility{
  display:flex; align-items:center; justify-content:space-between;
  padding:14px 0 4px;
  border-top:1px solid var(--line-soft); margin-top:2px;
}
.mobile-menu-utility .nav-lang{ background:var(--glass-hi); }

.nav-brand{
  display:flex; align-items:center; gap:11px;
  text-decoration:none; color:var(--ink);
}
.nav-mark-icon{
  width:32px; height:32px; border-radius:9px;
  flex:0 0 auto;
  filter:drop-shadow(0 2px 10px rgba(0,0,0,0.5));
  transition:transform .35s cubic-bezier(.2,.8,.2,1), filter .35s ease;
}
.nav-brand:hover .nav-mark-icon{
  transform:rotate(-8deg) scale(1.06);
  filter:drop-shadow(0 2px 14px rgba(52,216,179,0.35));
}
/* Wordmark — set to read like the lockup on the app icon rather than as
   ordinary running text: geometric uppercase, generously tracked, with
   the weight break falling between FOUMIL and STUDIO. The <b> carries the
   first half; the rest of the span stays light. */
.nav-mark{
  font-family:var(--brand); font-style:normal; font-weight:400;
  font-size:14.5px; letter-spacing:.13em; text-transform:uppercase;
  color:var(--ink-soft);
  white-space:nowrap;
  transition:color .18s ease;
}
.nav-mark b{ font-weight:700; color:var(--ink); transition:color .18s ease; }
.nav-brand:hover .nav-mark{ color:var(--ink); }
.nav-brand:hover .nav-mark b{ color:var(--accent); }
.nav-links{ display:flex; gap:28px; align-items:center; }
.nav-links a{
  font-size:14.5px; text-decoration:none; color:var(--ink-soft);
  transition:color .18s ease;
}
.nav-links a:hover{ color:var(--ink); }
.nav-links .nav-cta{
  color:var(--ink); border:1px solid var(--line); border-radius:999px;
  padding:8px 17px;
  background:var(--glass);
  transition:background .2s ease, border-color .2s ease, box-shadow .2s ease;
}
.nav-links .nav-cta:hover{
  background:var(--accent-soft); border-color:var(--accent); color:var(--accent);
  box-shadow:0 0 0 1px rgba(52,216,179,0.15), 0 8px 24px -12px rgba(52,216,179,0.4);
}
@media (max-width:640px){ .nav-links a:not(.nav-cta){ display:none; } }

/* ---------- "Travaux" hover dropdown — every project, one click away ---------- */
.nav-item-dropdown{ position:relative; }
.nav-item-dropdown > a{ display:flex; align-items:center; gap:5px; }
.nav-item-dropdown .caret{ transition:transform .2s ease; }
.nav-item-dropdown:hover .caret,
.nav-item-dropdown:focus-within .caret{ transform:rotate(180deg); }
.nav-dropdown{
  position:absolute; top:100%; left:50%;
  transform:translateX(-50%) translateY(-6px);
  width:280px; margin-top:16px; padding:8px;
  background:rgba(var(--void-rgb),.75); backdrop-filter:blur(22px) saturate(180%); -webkit-backdrop-filter:blur(22px) saturate(180%);
  border:1px solid var(--line); border-radius:14px;
  box-shadow:0 30px 60px -20px rgba(0,0,0,0.7);
  opacity:0; pointer-events:none;
  transition:opacity .18s ease, transform .18s ease;
  z-index:90;
  max-height:70vh; overflow-y:auto;
}
.nav-item-dropdown:hover .nav-dropdown,
.nav-item-dropdown:focus-within .nav-dropdown,
.nav-item-dropdown.open .nav-dropdown{
  opacity:1; pointer-events:auto; transform:translateX(-50%) translateY(0);
}
.nav-item-dropdown.open .caret{ transform:rotate(180deg); }
.nav-dropdown a{
  display:flex; align-items:center; gap:10px; padding:7px 12px; border-radius:9px;
  font-size:13.5px; color:var(--ink-soft); text-decoration:none;
  transition:background .16s ease, color .16s ease;
}
.nav-dropdown a:hover{ background:var(--accent-soft); color:var(--accent); }
.nav-dropdown a.nav-dropdown-all{
  color:var(--ink); font-weight:600;
  border-bottom:1px solid var(--line-soft); margin-bottom:6px; padding-bottom:11px;
}
.nav-dropdown-icon{
  width:26px; height:26px; border-radius:7px; object-fit:contain;
  background:#101214; border:1px solid var(--line-soft); flex-shrink:0;
}

/* ---------- Buttons / links ---------- */
.btn{
  display:inline-flex; align-items:center; gap:10px;
  font-family:var(--sans); font-weight:600; font-size:15px;
  padding:13px 24px; border-radius:999px;
  text-decoration:none; border:1px solid var(--line); color:var(--ink);
  background:var(--glass);
  transition:background .18s ease, color .18s ease, border-color .18s ease, box-shadow .18s ease, transform .18s ease;
  cursor:pointer;
}
.btn:hover{ border-color:var(--glass-hi); transform:translateY(-1px); }
.btn-solid{ background:var(--ink); color:var(--void); border-color:var(--ink); }
.btn-solid:hover{
  background:var(--accent); border-color:var(--accent); color:var(--accent-ink);
  box-shadow:0 10px 30px -12px rgba(52,216,179,0.55);
}

.text-link{
  text-decoration:none; color:var(--ink);
  border-bottom:1px solid var(--line);
  padding-bottom:2px;
  transition:border-color .18s ease, color .18s ease;
}
.text-link:hover{ border-color:var(--accent); color:var(--accent); }

/* ---------- Layout rhythm ---------- */
section{ padding:104px 0; }
.section-tight{ padding:64px 0; }
@media (max-width:720px){
  section{ padding:72px 0; }
  .section-tight{ padding:48px 0; }
}
.hairline{ border:none; border-top:1px solid var(--line); margin:0; }

/* ---------- Report grid: label column + content column ---------- */
.report-row{
  display:grid;
  grid-template-columns:200px 1fr;
  gap:48px;
  border-top:1px solid var(--line);
  padding:52px 0;
}
.report-row:last-child{ border-bottom:1px solid var(--line); }
.report-label{
  font-family:var(--data);
  font-size:13px;
  color:var(--ink-faint);
  padding-top:4px;
  letter-spacing:0.01em;
}
@media (max-width:780px){
  .report-row{ grid-template-columns:1fr; gap:14px; padding:38px 0; }
}

/* Small semantic icon above a report label — a touch of visual identity
   per section (premise/solution/role/limits…) without a photo. Purely
   decorative (aria-hidden in markup). */
.report-icon{
  width:30px; height:30px; border-radius:9px; margin-bottom:14px;
  display:flex; align-items:center; justify-content:center;
  background:var(--accent-soft); color:var(--accent);
}
.report-icon svg{ width:16px; height:16px; }

/* Inline content photo — scattered next to the paragraph it illustrates
   rather than clustered in one carousel; opens the same lightbox as the
   gallery. */
.content-photo{
  margin-top:24px; max-width:520px;
  border-radius:14px; overflow:hidden;
  border:1px solid var(--line);
  box-shadow:0 30px 60px -30px rgba(0,0,0,0.6);
  cursor:pointer;
}
.content-photo img{
  width:100%; display:block;
  filter:saturate(0.94) brightness(0.96);
  transition:transform .5s cubic-bezier(.2,.8,.2,1), filter .35s ease;
}
.content-photo:hover img{ transform:scale(1.025); filter:saturate(1) brightness(1); }
.content-photo figcaption{
  padding:10px 14px; font-size:12px; color:var(--ink-faint);
  font-family:var(--data); border-top:1px solid var(--line-soft);
}
.content-photo.align-right{ margin-left:auto; }
@media (max-width:780px){ .content-photo{ max-width:none; } }

/* Text + photo side by side, instead of the photo floating below a big
   gap of empty space — used inside a .report-row content column. */
.report-row-split{ display:flex; gap:36px; align-items:flex-start; }
.report-row-split > .rrs-text{ flex:1 1 300px; min-width:0; }
.report-row-split > .content-photo{ flex:1 1 300px; margin-top:0; max-width:440px; }
@media (max-width:900px){
  .report-row-split{ flex-direction:column; }
  .report-row-split > .content-photo{ max-width:100%; }
}

/* Compact data table — real structured facts instead of a paragraph
   mentioning them inline. */
.report-table{ width:100%; border-collapse:collapse; margin-top:22px; font-size:13.5px; }
.report-table th, .report-table td{
  text-align:left; padding:10px 14px; border-bottom:1px solid var(--line-soft);
}
.report-table th{
  font-family:var(--data); font-size:10.5px; letter-spacing:.06em; text-transform:uppercase;
  color:var(--ink-faint); font-weight:500; border-bottom:1px solid var(--line);
}
.report-table td{ color:var(--ink-soft); }
.report-table td:first-child{ color:var(--ink); font-weight:600; font-family:var(--sans); }
.report-table .yes{ color:var(--accent); font-family:var(--data); }

/* Feature grid — small icon+label cards instead of a plain bullet list. */
.feature-grid{ list-style:none; display:grid; grid-template-columns:1fr 1fr; gap:12px; margin:22px 0 0; padding:0; }
.feature-grid li{
  display:flex; gap:12px; align-items:flex-start; padding:14px 16px;
  border:1px solid var(--line-soft); border-radius:12px; background:var(--glass);
  transition:background .2s ease, border-color .2s ease;
}
.feature-grid li:hover{ background:var(--glass-hi); border-color:var(--line); }
.feature-grid li svg{ width:17px; height:17px; flex:0 0 auto; color:var(--accent); margin-top:2px; }
.feature-grid li span{ font-size:13.5px; color:var(--ink-soft); line-height:1.5; }
@media (max-width:640px){ .feature-grid{ grid-template-columns:1fr; } }

/* Domain grid — a numbered spec-sheet grid (2x2 on desktop) for a system
   broken into distinct areas, hairline-separated like the stat-grid but
   built for prose-length content rather than a single number. */
.domain-grid{
  display:grid; grid-template-columns:1fr 1fr; gap:1px;
  background:var(--line); border:1px solid var(--line); border-radius:14px;
  overflow:hidden; margin-top:22px;
}
.domain-grid > div{ background:var(--void-2); padding:26px; }
.domain-grid .domain-num{ font-family:var(--data); font-size:12px; color:var(--accent); font-weight:600; margin-bottom:10px; }
.domain-grid h3{ margin-bottom:8px; }
.domain-grid p{ color:var(--ink-soft); font-size:13.5px; }
@media (max-width:640px){ .domain-grid{ grid-template-columns:1fr; } }

/* Vertical timeline — a process broken into connected steps rather than
   one run-on sentence. */
.timeline{ list-style:none; margin:22px 0 0; padding:0; position:relative; }
.timeline::before{
  content:""; position:absolute; left:5px; top:6px; bottom:6px; width:1px;
  background:var(--line);
}
.timeline li{ position:relative; padding-left:28px; margin-bottom:22px; }
.timeline li:last-child{ margin-bottom:0; }
.timeline li::before{
  content:""; position:absolute; left:0; top:5px; width:11px; height:11px; border-radius:50%;
  background:var(--void); border:2px solid var(--accent); box-sizing:border-box;
}
.timeline b{ display:block; font-family:var(--sans); color:var(--ink); margin-bottom:3px; font-size:14.5px; }
.timeline span{ font-size:13px; color:var(--ink-soft); line-height:1.5; }

/* Method timeline — the same component, made answerable to the pointer:
   each step lights up on hover and gives up one extra line of concrete
   detail (where the needs actually came from, what gets validated, what
   is checked server-side) that would clutter the step if always shown. */
.timeline-method li{
  padding:10px 14px 11px 28px; margin-left:-14px; margin-bottom:12px;
  border-radius:12px; border:1px solid transparent;
  transition:background-color .22s ease, border-color .22s ease, transform .22s ease;
}
.timeline-method li::before{ left:14px; top:16px; transition:background-color .22s ease, box-shadow .22s ease; }
.timeline-method li:hover,
.timeline-method li:focus-within{
  background:var(--glass); border-color:var(--glass-hi); transform:translateX(3px);
}
.timeline-method li:hover::before,
.timeline-method li:focus-within::before{
  background:var(--accent); box-shadow:0 0 0 4px rgba(var(--accent-rgb),.16);
}
.timeline-method li:hover b,
.timeline-method li:focus-within b{ color:var(--accent); }
.timeline-method b{ transition:color .22s ease; }
.tl-note{
  display:block; margin-top:8px;
  font-family:var(--data); font-size:11.5px; font-style:normal;
  letter-spacing:.02em; line-height:1.6; color:var(--accent);
}
/* Collapsed only where there is a real pointer to reveal it with — on
   touch it simply reads as a third line, since there is no hover there. */
@media (hover: hover){
  .tl-note{
    opacity:0; max-height:0; margin-top:0; overflow:hidden;
    transition:opacity .24s ease, max-height .28s ease, margin-top .28s ease;
  }
  .timeline-method li:hover .tl-note,
  .timeline-method li:focus-within .tl-note{ opacity:1; max-height:90px; margin-top:8px; }
}
@media (prefers-reduced-motion: reduce){
  .timeline-method li, .timeline-method li::before, .timeline-method b, .tl-note{ transition:none; }
  .timeline-method li:hover{ transform:none; }
}

/* ---------- Hero ---------- */
.hero{ padding:88px 0 48px; position:relative; overflow:hidden; }
.hero-wireframe{
  position:absolute; top:-6%; right:-4%; z-index:0;
  width:min(620px, 58vw); height:min(620px, 58vw);
  pointer-events:none;
}
@media (max-width:900px){ .hero-wireframe{ opacity:.55; width:70vw; height:70vw; top:-2%; right:-14%; } }
@media (max-width:640px){ .hero-wireframe{ display:none; } }
.hero > .wrap{ position:relative; z-index:1; }
.hero-trace{ margin:0 0 44px; overflow:visible; }

/* ---------- Footer scope monitor — oscilloscope/ECG-style frame ---------- */
.scope-monitor{ margin:40px 0 4px; }
.scope-monitor-bezel{
  position:relative;
  border:1px solid var(--line); border-radius:16px;
  background:linear-gradient(180deg, rgba(255,255,255,.03), rgba(255,255,255,0));
  padding:16px 18px 14px;
}
.scope-monitor-bezel::before{
  content:""; position:absolute; inset:0; border-radius:16px;
  box-shadow:inset 0 1px 0 rgba(255,255,255,.05);
  pointer-events:none;
}
.scope-monitor-head{
  display:flex; align-items:center; justify-content:space-between;
  font-family:var(--data); font-size:11.5px; letter-spacing:.06em; text-transform:uppercase;
  color:var(--ink-faint); margin-bottom:10px;
}
.scope-monitor-bpm{ display:flex; align-items:center; gap:7px; color:var(--accent); }
.scope-bpm-value{ font-size:14px; font-weight:600; }
.scope-bpm-dot{
  width:7px; height:7px; border-radius:50%; background:var(--accent);
  box-shadow:0 0 8px rgba(var(--accent-rgb),.8);
  animation:scopeBpmPulse 1.1s ease-in-out infinite;
}
@keyframes scopeBpmPulse{
  0%,100%{ opacity:.4; transform:scale(.8); }
  15%{ opacity:1; transform:scale(1.15); }
  30%{ opacity:.6; transform:scale(.95); }
}
.scope-monitor-screen{
  position:relative; overflow:hidden;
  border-radius:9px; background:#06110D;
  border:1px solid rgba(var(--accent-rgb),.18);
}
.scope-grid{ position:absolute; inset:0; width:100%; height:100%; }
.scope-monitor-screen .hero-trace{ position:relative; margin:0; height:70px; }
@media (max-width:640px){
  .scope-monitor-head{ flex-direction:column; align-items:flex-start; gap:6px; }
}

/* ECG trace — the draw-on is looped in JS (redraw, hold, redraw…) rather
   than kept as a static line; this rule only handles the easter-egg flash. */
.trace-burst .trace-path{ animation:ecgGlowBurst .5s ease-in-out 3; }
@keyframes ecgGlowBurst{
  0%,100%{ filter:drop-shadow(0 0 0px rgba(255,110,78,0)); }
  40%{ filter:drop-shadow(0 0 14px rgba(255,110,78,1)); }
}
.hero-kicker{
  font-family:var(--sans); font-weight:600; font-size:14.5px;
  color:var(--accent); margin-bottom:20px;
  display:flex; align-items:center; gap:10px;
}
.hero-title{ max-width:17ch; margin-bottom:28px; }
.hero-title em{
  font-style:italic; font-weight:440;
  background:linear-gradient(100deg, var(--accent) 10%, #BFF3E7 45%, var(--accent) 80%);
  -webkit-background-clip:text; background-clip:text; color:transparent;
}
.hero-dek{ font-size:18.5px; color:var(--ink-soft); max-width:52ch; margin-bottom:0; }

.hero-meta{
  display:flex;
  flex-wrap:wrap;
  gap:28px 32px;
  margin-top:52px; padding-top:28px; border-top:1px solid var(--line);
}
.hero-meta div{ font-size:14px; color:var(--ink-soft); flex:1 1 190px; min-width:160px; }
.hero-meta b{ display:block; color:var(--ink); font-weight:600; margin-bottom:5px; font-family:var(--sans); }
@media (max-width:720px){ .hero-meta div{ flex-basis:42%; } }

.hero-meta-list{ list-style:none; margin:7px 0 0; padding:0; display:flex; flex-direction:column; gap:6px; }
.hero-meta-list li{ position:relative; padding-left:13px; line-height:1.4; }
.hero-meta-list li::before{
  content:""; position:absolute; left:0; top:.6em;
  width:4px; height:4px; border-radius:50%; background:var(--accent);
}
.hero-meta-list a{ color:var(--ink-soft); text-decoration:none; border-bottom:1px solid var(--line); transition:color .18s ease, border-color .18s ease; }
.hero-meta-list a:hover{ color:var(--accent); border-color:var(--accent); }

/* Items with extra info (data-detail) get a small affordance dot and open
   a custom popover on hover/focus — never a native title tooltip. */
.hero-meta-list li.hm-item{ cursor:help; }
.hero-meta-list li.hm-item::after{
  content:"i"; display:inline-flex; align-items:center; justify-content:center;
  width:13px; height:13px; margin-left:7px; border-radius:50%;
  border:1px solid var(--line); font-family:var(--data); font-size:9px; font-style:normal;
  color:var(--ink-faint); vertical-align:middle;
  transition:color .15s ease, border-color .15s ease;
}
.hero-meta-list li.hm-item:hover::after,
.hero-meta-list li.hm-item:focus-visible::after{ color:var(--accent); border-color:var(--accent); }
.hm-popover{
  position:fixed; z-index:95; max-width:280px;
  background:rgba(var(--void-rgb),.92); backdrop-filter:blur(16px) saturate(180%); -webkit-backdrop-filter:blur(16px) saturate(180%);
  border:1px solid var(--line); border-radius:12px; padding:14px 16px;
  font-family:var(--sans); font-size:13px; line-height:1.5; color:var(--ink-soft);
  box-shadow:0 30px 60px -20px rgba(0,0,0,0.7);
  opacity:0; pointer-events:none; transform:translateY(4px);
  transition:opacity .16s ease, transform .16s ease;
}
.hm-popover.open{ opacity:1; transform:translateY(0); }

/* ---------- Selected work: vertical case log ---------- */
.case-list{ border-top:1px solid var(--line); }
.case-row{
  display:flex;
  align-items:center;
  gap:28px;
  padding:34px 0;
  border-bottom:1px solid var(--line);
  text-decoration:none;
  color:var(--ink);
  position:relative;
  overflow:hidden;
}
.case-row::before{
  content:"";
  position:absolute; inset:0 -40px;
  background:linear-gradient(100deg, transparent 30%, rgba(255,255,255,0.05) 48%, transparent 66%);
  transform:translateX(-120%);
  transition:transform .6s cubic-bezier(.2,.8,.2,1);
  pointer-events:none;
}
.case-row:hover::before{ transform:translateX(20%); }
.case-row-main{ flex:1 1 auto; min-width:0; }
.case-name{
  font-family:var(--display); font-weight:560; font-size:clamp(21px,2.6vw,27px);
  margin-bottom:8px;
  transition:color .2s ease;
}
.case-desc{ color:var(--ink-soft); font-size:15.5px; max-width:56ch; }
.case-tags{
  flex:0 0 auto;
  display:flex; gap:8px; flex-wrap:wrap; align-items:center;
}
.case-tag{
  font-size:12.5px; font-family:var(--sans); font-weight:600;
  color:var(--ink-soft); border:1px solid var(--line); border-radius:999px;
  padding:5px 12px; white-space:nowrap;
}
/* Frosted glass rather than a flat card fill: the page's own drifting
   particle field blurs through the tile behind the app icon, so the
   plate reads as a pane of glass held over the background instead of a
   grey rectangle sitting on top of it. */
.case-thumb{
  flex:0 0 160px;
  width:160px; height:106px; border-radius:10px; overflow:hidden;
  border:1px solid rgba(255,255,255,.16);
  background:
    radial-gradient(130% 100% at 20% 0%, rgba(255,255,255,.22), rgba(255,255,255,.05) 55%, rgba(255,255,255,0) 82%),
    radial-gradient(90% 130% at 88% 100%, rgba(var(--accent-rgb),.16), rgba(var(--accent-rgb),0) 62%),
    linear-gradient(155deg, rgba(255,255,255,.13), rgba(255,255,255,.05));
  backdrop-filter:blur(18px) saturate(1.5);
  -webkit-backdrop-filter:blur(18px) saturate(1.5);
  display:flex; align-items:center; justify-content:center;
  opacity:0; transform:translateX(10px) scale(.97);
  transition:opacity .28s ease, transform .28s ease, border-color .28s ease;
  box-shadow:
    0 20px 40px -20px rgba(0,0,0,0.6),
    inset 0 1px 0 rgba(255,255,255,.28),
    inset 0 -1px 0 rgba(255,255,255,.06);
}
:root[data-theme="light"] .case-thumb{
  background:
    radial-gradient(120% 90% at 22% 8%, rgba(255,255,255,.75), rgba(255,255,255,0) 60%),
    linear-gradient(155deg, rgba(255,255,255,.55), rgba(255,255,255,.2) 45%, rgba(10,11,12,.06));
  box-shadow:
    0 20px 40px -22px rgba(10,11,12,0.28),
    inset 0 1px 0 rgba(255,255,255,.8);
}
.case-thumb img{ width:100%; height:100%; object-fit:cover; object-position:top; filter:saturate(0.92) brightness(0.95); }
/* App icon reveal — on hover, the case shows the project's own app icon
   rather than a stretched screenshot; a standard-size tile, same rim on
   every project regardless of the source image's own resolution, with a
   light bezel (inset highlight + outer shadow) for a slight 3D lift. */
.case-icon-frame{
  position:relative;
  width:68px; height:68px; border-radius:17px; flex:0 0 auto;
  padding:2px;
  background:linear-gradient(155deg, rgba(255,255,255,.28), rgba(255,255,255,.02) 40%, rgba(0,0,0,.35));
  box-shadow:0 10px 26px -10px rgba(0,0,0,.65);
  transition:transform .35s cubic-bezier(.2,.9,.25,1), box-shadow .35s ease;
}
.case-thumb img.case-icon{
  display:block; width:100%; height:100%; border-radius:15px;
  object-fit:contain; filter:none;
  /* Glass tile instead of a flat fill: a soft highlight catching the
     top-left, fading into a darker translucent wash. */
  background:
    radial-gradient(circle at 28% 20%, rgba(255,255,255,.18), rgba(255,255,255,0) 55%),
    linear-gradient(155deg, rgba(255,255,255,.07), rgba(0,0,0,.4));
  box-shadow:inset 0 1px 0 rgba(255,255,255,.12), inset 0 0 0 1px rgba(0,0,0,.25);
}
.case-row:hover .case-thumb{ opacity:1; transform:translateX(0) scale(1); border-color:rgba(255,255,255,.26); }
.case-row:hover .case-icon-frame{
  transform:scale(1.08) rotate(-4deg);
  box-shadow:0 14px 32px -10px rgba(52,216,179,.45);
}
.case-row:hover .case-icon-frame::after{
  content:""; position:absolute; inset:0; border-radius:17px;
  box-shadow:0 0 0 1px rgba(52,216,179,.4);
  pointer-events:none;
}
.case-row:hover .case-name{ color:var(--accent); }
.case-arrow{
  font-family:var(--data); color:var(--ink-faint); font-size:14px;
  transition:transform .2s ease, color .2s ease;
}
.case-row:hover .case-arrow{ transform:translateX(4px); color:var(--accent); }

@media (max-width:820px){
  .case-row{ flex-wrap:wrap; gap:10px; padding:26px 0; }
  .case-thumb{ display:none; }
  .case-row-main{ flex-basis:100%; }
}

/* ---------- Stat grid ---------- */
.stat-grid{
  display:grid; grid-template-columns:repeat(4,1fr);
  border-top:1px solid var(--line); border-left:1px solid var(--line);
  border-radius:14px; overflow:hidden;
}
.stat-cell{
  border-right:1px solid var(--line); border-bottom:1px solid var(--line);
  padding:26px 22px;
  background:var(--glass);
  transition:background .2s ease;
}
.stat-cell:hover{ background:var(--glass-hi); }
.stat-num{ font-family:var(--display); font-weight:560; font-size:30px; color:var(--ink); }
.stat-label{ font-size:12.5px; color:var(--ink-soft); margin-top:6px; line-height:1.4; }
@media (max-width:720px){ .stat-grid{ grid-template-columns:repeat(2,1fr); } }

/* ---------- Pills / status ---------- */
.pill{
  display:inline-flex; align-items:center; gap:7px;
  font-size:12.5px; font-weight:600; font-family:var(--sans);
  border-radius:999px; padding:5px 13px;
  background:var(--accent-soft); color:var(--accent);
}
.pill-vital{ background:var(--vital-soft); color:var(--vital); }
.pill-dot{ width:6px; height:6px; border-radius:50%; background:currentColor; }

/* ---------- RDV / booking section ---------- */
.rdv-section{ border-top:1px solid var(--line); }
.rdv-grid{
  display:grid; grid-template-columns:0.85fr 1.15fr; gap:64px; align-items:start;
}
@media (max-width:900px){ .rdv-grid{ grid-template-columns:1fr; gap:40px; } }

.rdv-points{ display:flex; flex-direction:column; gap:22px; margin-top:36px; }
.rdv-point{ display:flex; gap:16px; align-items:flex-start; }
.rdv-point svg{ flex:0 0 auto; margin-top:3px; color:var(--accent); }
.rdv-point p{ margin:0; color:var(--ink-soft); font-size:15px; }
.rdv-point b{ display:block; color:var(--ink); font-family:var(--sans); font-weight:600; margin-bottom:3px; }

.rdv-panel{
  background:rgba(var(--void-rgb),.32);
  backdrop-filter:blur(22px) saturate(180%);
  -webkit-backdrop-filter:blur(22px) saturate(180%);
  border:1px solid var(--line);
  border-radius:20px;
  padding:38px;
  position:relative;
  overflow:hidden;
}
.rdv-panel::before{
  content:"";
  position:absolute; inset:0;
  background:radial-gradient(680px circle at 84% 6%, rgba(52,216,179,0.18), transparent 62%);
  background-repeat:no-repeat;
  pointer-events:none;
  animation:rdvGlowDrift 18s ease-in-out infinite alternate;
}
@keyframes rdvGlowDrift{
  0%{ background-position:0% 0%; }
  50%{ background-position:-22% 24%; }
  100%{ background-position:14% -14%; }
}
@media (max-width:540px){ .rdv-panel{ padding:26px 22px; } }

.field{ margin-bottom:20px; display:flex; flex-direction:column; gap:8px; }
.field label{ font-size:13px; font-weight:600; color:var(--ink-soft); }
.field input, .field textarea, .field select{
  font-family:var(--sans); font-size:15.5px; color:var(--ink);
  background:rgba(255,255,255,0.03);
  border:1px solid var(--line); border-radius:10px;
  padding:12px 14px;
  outline:none;
  transition:border-color .18s ease, background .18s ease, box-shadow .18s ease;
}
.field input::placeholder, .field textarea::placeholder{ color:var(--ink-faint); }
.field input:focus, .field textarea:focus, .field select:focus{
  border-color:var(--accent);
  background:rgba(255,255,255,0.05);
  box-shadow:0 0 0 3px rgba(52,216,179,0.14);
}
.field select{
  appearance:none; -webkit-appearance:none;
  background-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='14' height='9' viewBox='0 0 14 9'><path d='M1 1l6 6 6-6' fill='none' stroke='%23A9B4B0' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/></svg>");
  background-repeat:no-repeat; background-position:right 14px center;
  padding-right:36px;
  cursor:pointer;
}
.field select option{ background:var(--void-2); color:var(--ink); }
.field select:invalid{ color:var(--ink-faint); }
.field textarea{ resize:vertical; min-height:110px; font-family:var(--sans); }
.field-row{ display:grid; grid-template-columns:1fr 1fr; gap:16px; }
@media (max-width:540px){ .field-row{ grid-template-columns:1fr; } }

.rdv-submit{ width:100%; justify-content:center; margin-top:6px; }
.rdv-note{ font-size:13px; color:var(--ink-faint); margin-top:14px; text-align:center; }
.rdv-status{ font-size:13.5px; margin-top:12px; min-height:1em; color:var(--accent); }

/* ---------- Custom date picker — no native <input type=date> ---------- */
.date-field{ position:relative; }
.date-trigger{
  width:100%; text-align:left;
  font-family:var(--sans); font-size:15.5px; color:var(--ink);
  background:rgba(255,255,255,0.03);
  border:1px solid var(--line); border-radius:10px;
  padding:12px 14px;
  display:flex; align-items:center; justify-content:space-between; gap:10px;
  cursor:pointer;
  transition:border-color .18s ease, background .18s ease, box-shadow .18s ease;
}
.date-trigger:hover{ border-color:var(--glass-hi); }
.date-trigger[aria-expanded="true"]{
  border-color:var(--accent);
  background:rgba(255,255,255,0.05);
  box-shadow:0 0 0 3px rgba(52,216,179,0.14);
}
.date-trigger .dt-value.is-placeholder{ color:var(--ink-faint); }
.date-trigger svg{ flex:0 0 auto; color:var(--ink-faint); }

.date-popover{
  position:absolute; z-index:60; top:calc(100% + 8px); left:0;
  width:300px; max-width:calc(100vw - 44px);
  background:rgba(var(--void-rgb),.75); backdrop-filter:blur(22px) saturate(180%); -webkit-backdrop-filter:blur(22px) saturate(180%);
  border:1px solid var(--line); border-radius:14px;
  box-shadow:0 30px 60px -20px rgba(0,0,0,0.7);
  padding:16px;
  opacity:0; transform:translateY(-6px) scale(.98);
  pointer-events:none;
  transition:opacity .18s ease, transform .18s ease;
}
.date-popover.open{ opacity:1; transform:none; pointer-events:auto; }
@media (max-width:540px){
  .date-popover{
    position:fixed; top:auto; bottom:20px; left:50%; right:auto;
    transform:translate(-50%,10px) scale(.98);
    width:min(340px, calc(100vw - 32px));
  }
  .date-popover.open{ transform:translate(-50%,0) scale(1); }
}

.dp-head{ display:flex; align-items:center; justify-content:space-between; margin-bottom:12px; }
.dp-nav-btn{
  width:30px; height:30px; border-radius:8px; border:1px solid var(--line);
  background:var(--glass); color:var(--ink-soft);
  display:flex; align-items:center; justify-content:center; cursor:pointer;
  transition:background .18s ease, color .18s ease, border-color .18s ease;
}
.dp-nav-btn:hover{ background:var(--accent-soft); color:var(--accent); border-color:var(--accent); }
.dp-title-btn{
  font-family:var(--sans); font-weight:600; font-size:14.5px; color:var(--ink);
  background:none; border:none; cursor:pointer; padding:6px 10px; border-radius:8px;
  transition:background .18s ease, color .18s ease;
}
.dp-title-btn:hover{ background:var(--accent-soft); color:var(--accent); }

.dp-grid-days{ display:grid; grid-template-columns:repeat(7,1fr); gap:3px; }
.dp-dow{ font-family:var(--data); font-size:10.5px; color:var(--ink-faint); text-align:center; padding:4px 0 8px; }
.dp-day{
  aspect-ratio:1; display:flex; align-items:center; justify-content:center;
  font-size:13.5px; color:var(--ink); background:none; border:none; border-radius:8px;
  cursor:pointer; transition:background .16s ease, color .16s ease;
}
.dp-day:hover:not(:disabled){ background:var(--glass-hi); }
.dp-day.is-muted{ color:var(--ink-faint); }
.dp-day:disabled{ color:var(--ink-faint); opacity:.35; cursor:not-allowed; }
.dp-day.is-today{ box-shadow:inset 0 0 0 1px var(--line); }
.dp-day.is-selected{ background:var(--accent); color:var(--accent-ink); font-weight:600; }

.dp-grid-months, .dp-grid-years{ display:grid; grid-template-columns:repeat(3,1fr); gap:8px; }
.dp-cell{
  font-family:var(--sans); font-size:13.5px; color:var(--ink);
  background:var(--glass); border:1px solid transparent; border-radius:9px;
  padding:12px 6px; text-align:center; cursor:pointer;
  transition:background .16s ease, border-color .16s ease, color .16s ease;
}
.dp-cell:hover:not(:disabled){ background:var(--accent-soft); color:var(--accent); }
.dp-cell.is-current{ border-color:var(--line); }
.dp-cell.is-selected{ background:var(--accent); color:var(--accent-ink); }
.dp-cell:disabled{ color:var(--ink-faint); opacity:.35; cursor:not-allowed; background:var(--glass); }

/* ---------- Custom select — no native <select>/listbox ---------- */
.select-field{ position:relative; }
.select-trigger{
  width:100%; text-align:left;
  font-family:var(--sans); font-size:15.5px; color:var(--ink);
  background:rgba(255,255,255,0.03);
  border:1px solid var(--line); border-radius:10px;
  padding:12px 14px;
  display:flex; align-items:center; justify-content:space-between; gap:10px;
  cursor:pointer;
  transition:border-color .18s ease, background .18s ease, box-shadow .18s ease;
}
.select-trigger:hover{ border-color:var(--glass-hi); }
.select-trigger[aria-expanded="true"]{
  border-color:var(--accent);
  background:rgba(255,255,255,0.05);
  box-shadow:0 0 0 3px rgba(52,216,179,0.14);
}
.select-trigger.is-invalid{ border-color:var(--vital); box-shadow:0 0 0 3px rgba(255,110,78,0.14); }
.select-trigger .st-value.is-placeholder{ color:var(--ink-faint); }
.select-trigger svg{ flex:0 0 auto; color:var(--ink-faint); }

.select-list{
  position:absolute; z-index:60; top:calc(100% + 8px); left:0; right:0;
  margin:0; padding:6px; list-style:none;
  background:rgba(var(--void-rgb),.75); backdrop-filter:blur(22px) saturate(180%); -webkit-backdrop-filter:blur(22px) saturate(180%);
  border:1px solid var(--line); border-radius:14px;
  box-shadow:0 30px 60px -20px rgba(0,0,0,0.7);
  opacity:0; transform:translateY(-6px) scale(.98);
  pointer-events:none;
  transition:opacity .18s ease, transform .18s ease;
}
.select-list.open{ opacity:1; transform:none; pointer-events:auto; }
.select-list li{
  padding:11px 14px; border-radius:9px; font-size:14.5px; color:var(--ink);
  cursor:pointer;
  transition:background .16s ease, color .16s ease;
}
.select-list li:hover{ background:var(--glass-hi); }
.select-list li.is-selected{ background:var(--accent); color:var(--accent-ink); font-weight:600; }

/* ---------- Lightbox — no native <dialog> ---------- */
.lightbox-overlay{
  position:fixed; inset:0; z-index:200;
  background:rgba(6,7,8,0.86);
  backdrop-filter:blur(6px);
  display:flex; align-items:center; justify-content:center;
  padding:40px;
  opacity:0; pointer-events:none;
  transition:opacity .22s ease;
}
.lightbox-overlay.open{ opacity:1; pointer-events:auto; }
.lightbox-overlay img{
  max-width:min(1100px, 100%); max-height:88vh;
  border-radius:12px; border:1px solid var(--line);
  box-shadow:0 40px 90px -30px rgba(0,0,0,0.8);
  transform:scale(.97); transition:transform .22s ease;
}
.lightbox-overlay.open img{ transform:scale(1); }
.lightbox-close{
  position:absolute; top:22px; right:26px;
  width:40px; height:40px; border-radius:50%;
  background:var(--glass); border:1px solid var(--line); color:var(--ink);
  display:flex; align-items:center; justify-content:center;
  font-size:20px; cursor:pointer;
  transition:background .18s ease, border-color .18s ease;
}
.lightbox-close:hover{ background:var(--accent-soft); border-color:var(--accent); color:var(--accent); }
.gallery img{ cursor:zoom-in; }

/* ---------- Easter egg toast ---------- */
.vitals-toast{
  position:fixed; bottom:26px; left:50%; z-index:300;
  transform:translate(-50%,12px);
  background:var(--void-2); border:1px solid var(--vital);
  color:var(--ink); font-family:var(--data); font-size:12.5px;
  padding:11px 18px; border-radius:999px;
  box-shadow:0 20px 50px -20px rgba(0,0,0,0.7), 0 0 0 1px rgba(255,110,78,0.15);
  opacity:0; pointer-events:none;
  transition:opacity .3s ease, transform .3s ease;
  display:flex; align-items:center; gap:9px;
}
.vitals-toast.show{ opacity:1; transform:translate(-50%,0); }
.vitals-toast .pill-dot{ background:var(--vital); }

/* ---------- Uplink loader — scroll-reactive signal HUD ----------
   Adapted from ThreeUI's "uplink-loader" (github.com/MengTo/threeui, MIT
   license): recolored to the site's teal accent, and progress is driven
   by scroll position through the block instead of a timed animation loop. */
.uplink-block{ padding:64px 0 8px; }
.uplink-stage{
  position:relative; overflow:hidden;
  border:1px solid var(--line); border-radius:20px;
  background:
    radial-gradient(ellipse 70% 100% at 50% 40%, rgba(var(--accent-rgb),.09), transparent 68%),
    #070908;
  padding:52px 24px 34px;
  text-align:center;
}
.uplink-stage::after{
  content:""; position:absolute; inset:0; pointer-events:none;
  background:repeating-linear-gradient(to bottom, rgba(var(--accent-rgb),.05) 0 1px, transparent 1px 3px);
  opacity:.4;
}
.uplink-label{
  font-family:var(--data); font-size:11.5px; letter-spacing:.14em; text-transform:uppercase;
  color:var(--ink-faint); margin-bottom:22px;
}
.uplink-readout{
  font-family:var(--data); line-height:1; margin-bottom:26px;
  color:var(--accent);
  text-shadow:0 0 6px rgba(var(--accent-rgb),.55), 0 0 22px rgba(var(--accent-rgb),.35);
}
.uplink-readout-num{ font-size:clamp(46px,7vw,72px); font-weight:500; letter-spacing:.01em; }
.uplink-readout-pct{ font-size:clamp(20px,2.6vw,28px); opacity:.6; margin-left:2px; }
.uplink-bar{
  display:flex; align-items:flex-end; justify-content:space-between; gap:3px;
  max-width:640px; height:34px; margin:0 auto 20px;
}
.uplink-tick{
  flex:1 1 auto; height:22px; min-width:2px;
  background:rgba(var(--accent-rgb),.14);
  transform:skewX(-10deg);
  transition:background .12s ease, box-shadow .12s ease;
}
.uplink-tick.mk{ height:34px; }
.uplink-tick.on{
  background:var(--accent);
  box-shadow:0 0 5px rgba(var(--accent-rgb),.7), 0 0 14px rgba(var(--accent-rgb),.35);
}
.uplink-status{
  font-family:var(--data); font-size:12.5px; font-weight:500; letter-spacing:.08em; text-transform:uppercase;
  color:var(--ink-soft); min-height:1.2em;
}
.uplink-corner{ position:absolute; width:0; height:0; }
.uplink-corner-tl{ top:20px; left:20px; }
.uplink-corner-tr{ top:20px; right:20px; }
.uplink-corner-bl{ bottom:20px; left:20px; }
.uplink-corner-br{ bottom:20px; right:20px; }
.uplink-corner i{ position:absolute; background:rgba(var(--accent-rgb),.55); }
.uplink-corner i:nth-child(1){ width:11px; height:1.2px; }
.uplink-corner i:nth-child(2){ width:1.2px; height:11px; }
.uplink-corner-tl i:nth-child(1),.uplink-corner-tl i:nth-child(2){ top:0; left:0; }
.uplink-corner-tr i:nth-child(1){ top:0; right:0; } .uplink-corner-tr i:nth-child(2){ top:0; right:0; }
.uplink-corner-bl i:nth-child(1){ bottom:0; left:0; } .uplink-corner-bl i:nth-child(2){ bottom:0; left:0; }
.uplink-corner-br i:nth-child(1){ bottom:0; right:0; } .uplink-corner-br i:nth-child(2){ bottom:0; right:0; }
.uplink-corner b{
  position:absolute; width:6px; height:6px; top:-3px; left:-3px;
  background:var(--accent); transform:rotate(45deg);
  box-shadow:0 0 6px rgba(var(--accent-rgb),.7);
  animation:uplinkDiaPulse 3.2s ease-in-out infinite;
}
.uplink-corner-tr b{ left:auto; right:-3px; animation-delay:.5s; }
.uplink-corner-bl b{ animation-delay:1.1s; }
.uplink-corner-br b{ left:auto; right:-3px; animation-delay:1.6s; }
@keyframes uplinkDiaPulse{ 0%,100%{ opacity:.5; transform:rotate(45deg) scale(.8); } 50%{ opacity:1; transform:rotate(45deg) scale(1); } }
@media (max-width:640px){
  .uplink-stage{ padding:40px 16px 28px; }
  .uplink-bar{ height:26px; }
  .uplink-tick{ height:16px; } .uplink-tick.mk{ height:26px; }
}

/* ---------- Footer ---------- */
.site-footer{
  border-top:1px solid var(--line);
  padding:72px 0 40px;
  overflow:hidden;
  position:relative;
}
.footer-top{
  display:flex; justify-content:space-between; align-items:flex-end; flex-wrap:wrap;
  gap:24px; margin-bottom:56px;
}
.footer-brand{ display:flex; gap:24px; align-items:center; max-width:560px; }
.footer-mark-link{ display:block; flex:0 0 auto; }
.footer-mark-icon{
  width:96px; height:96px; border-radius:22px; display:block;
  /* Stays a filter rather than a box-shadow: the source PNG is ~44%
     transparent, so the shadow has to follow the tile's own alpha and
     not the square element box. It is given its own compositing layer
     instead, so animating that filter on hover repaints this element
     alone rather than the whole footer band around it. */
  filter:drop-shadow(0 4px 10px rgba(0,0,0,0.45));
  will-change:transform, filter;
  transition:transform .35s cubic-bezier(.2,.8,.2,1), filter .35s ease;
}
.footer-mark-link:hover .footer-mark-icon{
  transform:rotate(-8deg) scale(1.06);
  filter:drop-shadow(0 4px 14px rgba(52,216,179,0.4));
}
.footer-grid{
  display:flex; justify-content:space-between; gap:40px; flex-wrap:wrap;
  margin-bottom:48px;
}
.footer-note{ font-size:14px; color:var(--ink-faint); }
.footer-links{ display:flex; flex-direction:column; gap:8px; }

/* Brand orbs — real particle-cloud canvases, sampled from each mark's own
   outline (moteur "Orbes de marque", cf. script.js), not decorative circles. */
.orbe-marque-card{
  display:flex; gap:22px; align-items:flex-start; flex-wrap:wrap;
}
.orbe-marque-item{
  display:flex; flex-direction:column; align-items:center; gap:9px;
  width:82px; padding:8px 6px; border-radius:14px;
  text-decoration:none; color:inherit; background:none; border:none;
  font-family:inherit;
  transition:transform .3s cubic-bezier(.2,.8,.3,1), background .2s ease;
}
a.orbe-marque-item, button.orbe-marque-item{ cursor:pointer; }
.orbe-marque-item:hover{
  background:var(--accent-soft);
  transform:translateY(-5px);
}
.orbe-marque-canvas{
  width:56px; height:56px; display:block; border-radius:14px;
  /* Fixed dark ground regardless of theme: the particle clouds are painted
     bright, and would wash out against a light background. No ring/border
     around it — reads as a mark, not a badge. */
  background:radial-gradient(circle at 34% 30%, rgba(255,255,255,0.06), rgba(255,255,255,0) 55%), #101214;
  /* box-shadow, deliberately, not the drop-shadow filter this used to
     carry. These canvases repaint every frame, and a CSS filter has to
     re-derive its shadow from the bitmap's alpha each time — so any
     neighbouring repaint (hovering the footer mark, whose own filter
     animates) made them visibly stutter. The ground here is opaque and
     the corners are rounded by border-radius, so the box shadow traces
     exactly the same shape for none of the per-frame cost. */
  box-shadow:0 10px 20px rgba(0,0,0,0.55);
}
.orbe-marque-legende{
  font-family:var(--data); font-size:11px; letter-spacing:.02em;
  color:var(--ink-faint); text-align:center;
}

/* ---------- Floating "back to top" — bottom-right, appears once you've
   scrolled a bit; hidden during the homepage intro scroll (JS holds off
   showing it until the intro has finished). ---------- */
.scroll-top-fab{
  position:fixed; right:22px; bottom:22px; z-index:60;
  width:44px; height:44px; border-radius:13px;
  display:flex; align-items:center; justify-content:center;
  background:rgba(var(--void-rgb),.75); backdrop-filter:blur(14px) saturate(180%); -webkit-backdrop-filter:blur(14px) saturate(180%);
  border:1px solid var(--line); color:var(--ink-soft);
  box-shadow:0 16px 34px -16px rgba(0,0,0,.6);
  opacity:0; pointer-events:none; transform:translateY(8px);
  transition:opacity .25s ease, transform .25s ease, color .2s ease, border-color .2s ease;
}
.scroll-top-fab.show{ opacity:1; pointer-events:auto; transform:translateY(0); }
.scroll-top-fab:hover{ color:var(--accent); border-color:var(--glass-hi); }
@media (max-width:640px){ .scroll-top-fab{ right:16px; bottom:16px; width:40px; height:40px; } }

/* ---------- Section dividers — real photography as a section break,
   not decoration: a letterboxed strip with a dark vignette and a small
   mono caption pill, echoing the "signal clinique" thread between sections. ---------- */
.section-divider{
  position:relative; height:150px; overflow:hidden;
  border-top:1px solid var(--line); border-bottom:1px solid var(--line);
}
.section-divider img{
  width:100%; height:100%; object-fit:cover; object-position:center;
  filter:grayscale(.4) brightness(.5) saturate(.9);
  transform:scale(1.02);
}
.section-divider::after{
  content:""; position:absolute; inset:0; pointer-events:none;
  background:linear-gradient(180deg, var(--void) 0%, transparent 22%, transparent 78%, var(--void) 100%);
}
.section-divider-label{
  position:absolute; left:50%; top:50%; transform:translate(-50%,-50%);
  z-index:2;
  font-family:var(--data); font-size:11px; letter-spacing:.12em; text-transform:uppercase;
  color:var(--ink); white-space:nowrap;
  background:rgba(var(--void-rgb),.55); backdrop-filter:blur(10px) saturate(180%); -webkit-backdrop-filter:blur(10px) saturate(180%);
  padding:9px 18px; border:1px solid var(--line); border-radius:999px;
}
/* Light theme: these photographs are intrinsically dark (a monitor in a
   dim room, a server aisle), so the dark-theme treatment — darken further,
   fade to the page colour — turns them into a black bar slammed across a
   white page, and drags the caption pill down with it. Lift the image and
   lay a white veil over it instead, so the band reads as a pale grey strip
   that belongs to the page, and give the pill an opaque backing so its
   dark text stays legible over whatever part of the photo it lands on. */
:root[data-theme="light"] .section-divider img{
  filter:grayscale(.45) brightness(1.5) contrast(.72) saturate(.7);
}
:root[data-theme="light"] .section-divider::before{
  content:""; position:absolute; inset:0; z-index:1; pointer-events:none;
  background:rgba(255,255,255,.55);
}
:root[data-theme="light"] .section-divider-label{
  background:rgba(255,255,255,.86);
  border-color:rgba(10,11,12,.16);
  box-shadow:0 4px 18px -8px rgba(10,11,12,.4);
}
@media (max-width:640px){ .section-divider{ height:96px; } .section-divider-label{ font-size:10px; padding:7px 14px; } }

/* CRT terminal stage — mentions-legales.html only, see crt-effect.js.
   The canvas (if WebGL + motion are available) absolutely covers
   .crt-fallback, which stays in the DOM as the real, always-accessible
   legal text — screen readers and no-JS/reduced-motion visitors get it
   directly, sighted capable browsers see it rendered as CRT phosphor. */
.crt-stage{
  position:relative; overflow:hidden; background:#03100a;
  border-top:1px solid var(--line); border-bottom:1px solid var(--line);
}
.crt-stage.crt-page{ min-height:82vh; }
.crt-stage canvas{ position:absolute; inset:0; display:block; width:100%; height:100%; z-index:2; }
/* Must outrank the display:block above, or the [hidden] the skip control
   sets would do nothing — an author rule beats the UA's [hidden] mapping. */
.crt-stage canvas[hidden]{ display:none; }
.crt-fallback{
  position:relative; z-index:1;
  padding:56px 0 64px;
  background:#03100a;
}
.crt-fallback .report-row{ border-color:rgba(52,216,179,0.16); }
.crt-fallback .report-label{ color:#5fcf98; }
.crt-fallback p, .crt-fallback li b{ color:#c9f5dc; }
.crt-fallback .text-link{ color:#8df0b4; }
/* Skip control — injected by crt-effect.js, so it exists only when there
   is actually an animation to skip. Sits above the canvas; hiding the
   canvas uncovers .crt-fallback, i.e. the whole text at once. */
.crt-skip{
  position:absolute; top:18px; right:18px; z-index:3;
  font-family:var(--data); font-size:11px; letter-spacing:0.08em; text-transform:uppercase;
  color:#8df0b4; background:rgba(3,16,10,0.72);
  border:1px solid rgba(52,216,179,0.38); border-radius:999px;
  padding:8px 15px; cursor:pointer;
  backdrop-filter:blur(6px); -webkit-backdrop-filter:blur(6px);
  transition:color .2s ease, border-color .2s ease, background-color .2s ease, transform .2s ease;
}
.crt-skip:hover{
  color:#eafff3; border-color:rgba(52,216,179,0.75);
  background:rgba(6,32,20,0.85); transform:translateY(-1px);
}
.crt-skip:focus-visible{ outline:2px solid rgba(52,216,179,0.8); outline-offset:3px; }
@media (max-width:640px){
  .crt-stage.crt-page{ min-height:70vh; }
  .crt-skip{ top:12px; right:12px; font-size:10px; padding:7px 12px; }
}

/* ---------- Utility ---------- */
.mt-0{margin-top:0;} .mb-0{margin-bottom:0;}
.center{ text-align:center; }
.eyebrow-plain{ font-size:14.5px; color:var(--accent); font-weight:600; margin-bottom:16px; }

/* ---------- Reveal on scroll (single, restrained) ---------- */
.reveal{
  opacity:0; transform:translateY(14px);
  transition:opacity .6s ease, transform .6s ease;
  transition-delay:calc(var(--reveal-i, 0) * 55ms);
}
.reveal.in{ opacity:1; transform:none; }

/* ---------- Word-by-word hover — dynamic glow, ported from the "magnetic-glow"
   / data-hover-scope="word" treatment. JS wraps each word of a .hv-scope
   element in a .hv-word span and toggles .hv-hot on whichever word is
   nearest the cursor. Desktop/fine-pointer only (set by JS). ---------- */
.hv-word{ transition:color .18s ease, text-shadow .18s ease; }
.hv-word.hv-hot{ color:var(--accent); text-shadow:0 0 12px rgba(var(--accent-rgb),.7); }

/* ---------- Project page specifics ---------- */
.hero-project{ padding:58px 0 24px; }
main > .hero-project + .wrap{ margin-top:44px; }
.hero-project h1{ max-width:20ch; }
.hero-project .hero-dek{ max-width:60ch; }

.proj-nav-back{
  display:inline-flex; align-items:center; gap:8px;
  font-size:14px; color:var(--ink-soft); text-decoration:none;
  transition:color .18s ease;
}
.proj-nav-back:hover{ color:var(--accent); }

/* Project hero — the banner *is* the header: title overlaid on the shot,
   app icon anchored to its bottom-right corner. Replaces the older
   side-by-side icon layout entirely. */
.proj-banner-hero{
  position:relative; overflow:hidden;
  min-height:360px;
  display:flex; align-items:flex-end;
  border-bottom:1px solid var(--line);
}
.proj-banner-hero-bg{
  position:absolute; inset:0; width:100%; height:100%; object-fit:cover; object-position:center;
  filter:saturate(.85) brightness(.5);
}
@media (prefers-reduced-motion: no-preference){
  .proj-banner-hero-bg{ animation:bannerReveal 1.3s cubic-bezier(.16,1,.3,1) both; }
}
@keyframes bannerReveal{
  from{ clip-path:inset(0 0 100% 0); transform:scale(1.1); }
  to{ clip-path:inset(0 0 0% 0); transform:scale(1); }
}

/* Scroll-triggered reveal for images encountered further down the page
   (section dividers, gallery figures not already scroll-animated) —
   a clip-path wipe + settle, fired once via IntersectionObserver. */
.img-scroll-reveal{
  transition:clip-path 1s cubic-bezier(.16,1,.3,1), transform 1.1s cubic-bezier(.16,1,.3,1);
}
.img-scroll-reveal.is-pending{ clip-path:inset(0 0 100% 0); transform:scale(1.08); }
.img-scroll-reveal.is-in{ clip-path:inset(0 0 0% 0); transform:none; }
.proj-banner-hero::after{
  content:""; position:absolute; inset:0; pointer-events:none;
  background:linear-gradient(180deg, rgba(var(--void-rgb),.35) 0%, rgba(var(--void-rgb),.6) 60%, var(--void) 97%);
}
.proj-banner-hero-content{
  position:relative; z-index:1; width:100%;
  padding-top:64px; padding-bottom:42px;
}
.proj-banner-hero-content .proj-nav-back{ color:var(--ink-soft); }
.proj-banner-hero-icon{
  position:absolute; z-index:2; right:40px; bottom:28px;
  width:84px; height:84px; border-radius:20px; flex:0 0 auto;
  border:1px solid var(--line); background:#101214; overflow:hidden;
  box-shadow:0 20px 50px -18px rgba(0,0,0,.8);
}
.proj-banner-hero-icon img{ width:100%; height:100%; object-fit:contain; display:block; }
@media (max-width:720px){
  .proj-banner-hero{ min-height:280px; }
  .proj-banner-hero-content{ padding-top:48px; padding-bottom:30px; }
  .proj-banner-hero-icon{ width:58px; height:58px; border-radius:15px; right:22px; bottom:20px; }
}

/* Standalone hero icon — for project pages with no banner photo (e.g.
   ICARUS), the app icon sits inline next to the eyebrow instead of
   anchored to a banner corner. */
.hero-icon-row{ display:flex; align-items:center; gap:18px; margin-top:28px; }
.hero-icon-standalone{
  width:64px; height:64px; border-radius:16px; flex:0 0 auto;
  border:1px solid var(--line); background:#101214; overflow:hidden;
  box-shadow:0 16px 40px -16px rgba(0,0,0,.7);
}
.hero-icon-standalone img{ width:100%; height:100%; object-fit:contain; display:block; }
.hero-icon-row .eyebrow-plain{ margin-top:0; }

/* Homepage intro hand-off. Two stages, set on <html> by the intro engine.
   "pending": the intro shape is still travelling, so the hero's own
   wireframe stays invisible — otherwise both icosahedra are on screen at
   once and the arrival reads as two shapes meeting rather than one
   arriving. The nav and hero content wait too. "arrived": the intro shape
   has reached its mark and starts dissolving, so the hero's wireframe
   fades up underneath it — a genuine hand-off, one shape becoming the
   other — and the nav wipes in while the hero content rises behind it.
   No stage attribute at all (reduced motion, no JS) means everything is
   simply visible, no animation to miss. */
html[data-intro-stage] .hero-wireframe{ transition:opacity .8s ease; }
html[data-intro-stage] .site-nav,
html[data-intro-stage] .hero.no-entrance .wrap > *{ transition:opacity .4s ease; }
html[data-intro-stage="pending"] .hero-wireframe{ opacity:0; }
html[data-intro-stage="pending"] .site-nav,
html[data-intro-stage="pending"] .hero.no-entrance .wrap > *{
  opacity:0; pointer-events:none;
}
@media (prefers-reduced-motion: no-preference){
  html[data-intro-stage="arrived"] .site-nav{
    animation:navRise .7s cubic-bezier(.16,1,.3,1) backwards;
  }
  html[data-intro-stage="arrived"] .hero.no-entrance .wrap > *{
    animation:heroRise .9s cubic-bezier(.16,1,.3,1) backwards;
  }
  html[data-intro-stage="arrived"] .hero.no-entrance .wrap > *:nth-child(1){ animation-delay:.16s; }
  html[data-intro-stage="arrived"] .hero.no-entrance .wrap > *:nth-child(2){ animation-delay:.26s; animation-name:heroTitleReveal; }
  html[data-intro-stage="arrived"] .hero.no-entrance .wrap > *:nth-child(3){ animation-delay:.38s; }
  html[data-intro-stage="arrived"] .hero.no-entrance .wrap > *:nth-child(4){ animation-delay:.50s; }
}

/* Hero entrance — a real reveal on load, project pages and the homepage
   alike. Skipped via .no-entrance on the homepage's own hero specifically:
   there the entrance is fired by the intro hand-off above instead, since a
   load-triggered animation would finish long before the hero is reached,
   ~260vh below the fold behind the intro spacer. */
@media (prefers-reduced-motion: no-preference){
  .hero:not(.no-entrance) .wrap > *,
  .proj-banner-hero-content > *{
    /* "backwards", not "both": heroTitleReveal (below) uses clip-path,
       which — like on .site-nav — would otherwise keep clipping any
       overflowing descendant (e.g. a future popover) forever after the
       animation ends, for a purely cosmetic hold that isn't needed. */
    animation:heroRise .85s cubic-bezier(.16,1,.3,1) backwards;
  }
  .hero:not(.no-entrance) .wrap > *:nth-child(1),
  .proj-banner-hero-content > *:nth-child(1){ animation-delay:.04s; }
  .hero:not(.no-entrance) .wrap > *:nth-child(2),
  .proj-banner-hero-content > *:nth-child(2){ animation-delay:.13s; }
  .hero:not(.no-entrance) .wrap > *:nth-child(3),
  .proj-banner-hero-content > *:nth-child(3){ animation-delay:.22s; animation-name:heroTitleReveal; }
  .hero:not(.no-entrance) .wrap > *:nth-child(4){ animation-delay:.34s; }
  .hero:not(.no-entrance) .hero-title, .proj-banner-hero-content h1{ animation-name:heroTitleReveal !important; }
  .proj-banner-hero-icon{ animation:heroRiseIcon 1s cubic-bezier(.16,1,.3,1) .3s both; }
}
@keyframes heroRise{
  from{ opacity:0; transform:translateY(30px) scale(.98); filter:blur(4px); }
  to{ opacity:1; transform:none; filter:blur(0); }
}
@keyframes heroTitleReveal{
  from{ opacity:0; clip-path:inset(0 100% 0 0); transform:translateY(6px); }
  to{ opacity:1; clip-path:inset(0 0 0 0); transform:none; }
}
@keyframes heroRiseIcon{ from{ opacity:0; transform:translateY(14px) scale(.9) rotate(-8deg); } to{ opacity:1; transform:none; } }

.gallery{
  display:grid; grid-template-columns:1fr 1fr; gap:18px;
}
.gallery figure{ margin:0; }
.gallery img{
  width:100%; border-radius:12px; border:1px solid var(--line);
  box-shadow:0 30px 60px -30px rgba(0,0,0,0.7);
  filter:saturate(0.95) brightness(0.97);
}
.gallery figcaption{ font-size:13px; color:var(--ink-faint); margin-top:10px; }
.gallery.single{ grid-template-columns:1fr; }
@media (max-width:780px){ .gallery{ grid-template-columns:1fr; } }

/* ---------- Project banner — the same wide shot used on the live
   portfolio, framed consistently across every project page. ---------- */
.proj-banner{
  border-radius:14px; overflow:hidden; border:1px solid var(--line);
  box-shadow:0 40px 80px -40px rgba(0,0,0,0.75);
  margin:8px 0 0;
}
.proj-banner img{
  width:100%; height:clamp(220px, 34vw, 420px); object-fit:cover; object-position:center;
  filter:saturate(0.95) brightness(0.92);
  display:block;
}

/* ---------- Scroll-driven gallery — advances with the page scroll, no
   buttons, no dots, no clicks (see the matching engine in script.js).
   .gallery-scroll needs real height to give the scroll something to
   travel through; .gallery-scroll-sticky pins while that height scrolls
   past; each .gallery-scroll-slide gets its transform from JS. ---------- */
.gallery-scroll{
  position:relative;
  height:calc(var(--gs-slides, 3) * 62vh);
  min-height:340px;
  margin:34px 0 20px;
}
.gallery-scroll-sticky{
  position:sticky; top:90px; height:380px; overflow:hidden;
  display:flex; align-items:center; justify-content:center;
  perspective:1400px;
}
.gallery-scroll-track{ position:relative; width:100%; height:100%; }
.gallery-scroll-slide{
  position:absolute; top:0; left:50%; width:min(74%, 520px); height:100%;
  margin:0; margin-left:calc(min(74%, 520px) / -2);
  transform-style:preserve-3d;
  will-change:transform, opacity;
}
.gallery-scroll-slide img,
.gallery-scroll-slide video{
  width:100%; height:100%; object-fit:cover; border-radius:14px;
  border:1px solid var(--line);
  box-shadow:0 40px 70px -30px rgba(0,0,0,0.75);
  filter:saturate(0.95) brightness(0.95);
  display:block;
}
.gallery-scroll-slide img{ cursor:pointer; }
.gallery-scroll-slide figcaption{
  position:absolute; left:14px; bottom:14px; right:14px;
  font-size:12.5px; color:var(--ink);
  background:rgba(var(--void-rgb),.6); backdrop-filter:blur(10px) saturate(180%); -webkit-backdrop-filter:blur(10px) saturate(180%);
  border:1px solid var(--line-soft); border-radius:8px;
  padding:8px 11px;
  opacity:0; transition:opacity .3s ease;
}
.gallery-scroll-slide.is-active figcaption{ opacity:1; }
@media (max-width:640px){
  .gallery-scroll{ height:calc(var(--gs-slides, 3) * 46vh); }
  .gallery-scroll-sticky{ top:70px; height:260px; }
  .gallery-scroll-slide{ width:88%; margin-left:-44%; }
}
@media (prefers-reduced-motion: reduce){
  .gallery-scroll{ height:auto; }
  .gallery-scroll-sticky{ position:static; height:auto; flex-wrap:wrap; gap:16px; perspective:none; }
  .gallery-scroll-slide{ position:static; width:100%; margin-left:0; }
}

.limits-list{ list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:22px; }
.limits-list li{
  border-top:1px solid var(--line); padding-top:20px;
}
.limits-list b{ display:block; margin-bottom:6px; font-family:var(--sans); color:var(--ink); }

.tag-cloud{ display:flex; flex-wrap:wrap; gap:9px; }
.tag-cloud span{
  font-family:var(--data); font-size:12.5px; color:var(--ink-soft);
  border:1px solid var(--line); border-radius:6px; padding:5px 10px;
}

/* ---------- Pull-quote (real terrain feedback, when there is one) ---------- */
.proj-quote{
  font-family:var(--display); font-style:italic; font-weight:440;
  font-size:clamp(17px,1.8vw,20px); line-height:1.55; color:var(--ink-soft);
  border-left:2px solid var(--accent); padding:2px 0 2px 22px; margin:0;
  max-width:60ch;
}
.proj-quote cite{
  display:block; font-style:normal; font-family:var(--data); font-size:12px;
  color:var(--ink-faint); margin-top:12px;
}

/* ---------- Bug console — real fixes shipped, named as such ---------- */
.bug-console{ display:flex; flex-direction:column; gap:16px; }
.bug-line{
  display:flex; gap:12px; align-items:baseline;
  font-family:var(--data); font-size:13.5px; line-height:1.6; color:var(--ink-soft);
}
.bug-tag{
  flex:0 0 auto; font-size:10.5px; font-weight:700; letter-spacing:.03em;
  padding:3px 8px; border-radius:5px;
  background:var(--accent-soft); color:var(--accent);
}

.next-project{
  display:flex; justify-content:space-between; align-items:baseline;
  padding:44px 0; border-top:1px solid var(--line);
  text-decoration:none; color:var(--ink);
}
.next-project:hover .np-name{ color:var(--accent); }
.np-label{ font-size:13px; color:var(--ink-faint); }
.np-name{ font-family:var(--display); font-size:clamp(22px,3vw,30px); margin-top:6px; transition:color .2s ease; }

/* Prev/next pager — two-up, either side of the project chain */
.proj-pager{ display:grid; grid-template-columns:1fr 1fr; border-top:1px solid var(--line); }
.proj-pager .next-project{ border-top:none; padding:44px 0 44px 32px; }
.proj-pager .prev-project{
  padding:44px 32px 44px 0; border-right:1px solid var(--line);
  justify-content:flex-start; gap:16px;
}
@media (max-width:640px){
  .proj-pager{ grid-template-columns:1fr; }
  .proj-pager .prev-project{ border-right:none; border-bottom:1px solid var(--line); padding:32px 0; }
  .proj-pager .next-project{ padding:32px 0; }
}

/* ---------- Page transitions (cross-document View Transitions API) ----------
   Progressive enhancement only: unsupported browsers navigate normally,
   no JS, no risk of breakage. */
@view-transition{ navigation:auto; }
@media (prefers-reduced-motion: no-preference){
  ::view-transition-old(root){ animation:pageFadeOut .32s cubic-bezier(.4,0,.2,1) both; }
  ::view-transition-new(root){ animation:pageFadeIn .38s cubic-bezier(.2,.8,.2,1) both; }
}
@keyframes pageFadeOut{ to{ opacity:0; transform:scale(.99); } }
@keyframes pageFadeIn{ from{ opacity:0; transform:scale(1.01); } }
