/* ══ FONT ═══════════════════════════════════════════════════════════
   Inter variable, self-hosted. One 48 KB file covers every weight —
   no third-party request, nothing leaves the page. */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url('fonts/inter-var.woff2') format('woff2');
}

/* ══ TOKENS ═════════════════════════════════════════════════════════ */
:root {
  --bg:        #000000;   /* true black — anything lighter reads as "app", not as instrument */
  --bg-elev:   #08090b;

  --text:      #ffffff;
  --muted:     #9aa3ad;   /* 8.2:1 on black */
  --dim:       #6f7883;   /* 4.6:1 on black — passes AA */

  --line:      rgba(255,255,255,0.10);
  --line-hi:   rgba(255,255,255,0.22);

  --surface:   linear-gradient(180deg, rgba(255,255,255,0.038), rgba(255,255,255,0.011));
  --edge:      inset 0 1px 0 rgba(255,255,255,0.055);
  --edge-hi:   inset 0 1px 0 rgba(255,255,255,0.10);

  --mono:      ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;

  --max:       1440px;
  --gutter:    clamp(20px, 4vw, 56px);
  --r:         2px;    /* hard edges. rounded corners read consumer-software */
  --header-h:  64px;

  --pad-y:     clamp(76px, 10vw, 128px);
  --cell:      74px;   /* grid pitch — shared by the base and revealed layers */
  --ease:      cubic-bezier(0.16, 1, 0.3, 1);
}

/* ══ BASE ═══════════════════════════════════════════════════════════ */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

html {
  scroll-behavior: smooth;
  scroll-padding-top: calc(var(--header-h) + 24px);
  -webkit-text-size-adjust: 100%;
}

body {
  font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  background: var(--bg);
  color: var(--text);
  line-height: 1.65;
  font-synthesis-weight: none;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
}

a { color: inherit; text-decoration: none; }
img { max-width: 100%; display: block; }
button { font: inherit; color: inherit; background: none; border: none; cursor: pointer; }

:focus-visible {
  outline: 2px solid rgba(255,255,255,0.55);
  outline-offset: 3px;
  border-radius: 4px;
}

::selection { background: rgba(255,255,255,0.16); }

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

.skip {
  position: absolute;
  left: -9999px;
  top: 8px;
  z-index: 200;
  padding: 10px 18px;
  background: var(--bg-elev);
  border: 1px solid var(--line-hi);
  border-radius: 999px;
  font-size: 0.85rem;
}
.skip:focus { left: 16px; }

/* ══ GRAIN + SCROLL PROGRESS ════════════════════════════════════════
   Grain is what keeps a flat dark page from looking like plastic: it
   breaks up the gradient banding and adds a bit of tooth. Very low
   opacity — you should feel it, not see it. */
.grain {
  position: fixed;
  inset: -50%;
  z-index: 1;
  pointer-events: none;
  opacity: 0.04;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='220' height='220'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}

.progress {
  position: fixed;
  top: 0; left: 0;
  height: 1px;
  width: 0;
  z-index: 90;
  background: linear-gradient(90deg, rgba(255,255,255,0.08), rgba(255,255,255,0.55));
  transition: width 0.1s linear;
}

/* ══ HEADER ═════════════════════════════════════════════════════════ */
header {
  position: sticky;
  top: 0;
  z-index: 80;
  background: rgba(6,8,11,0.55);
  backdrop-filter: blur(20px) saturate(140%);
  -webkit-backdrop-filter: blur(20px) saturate(140%);
  border-bottom: 1px solid transparent;
  transition: background 0.3s ease, border-color 0.3s ease;
}

header.scrolled {
  background: rgba(6,8,11,0.82);
  border-bottom-color: var(--line);
}

/* the chrome drops in first, before the headline moves */
.js header  { animation: head-in 0.9s var(--ease) backwards; }
.js .hero-rule { animation: rule-in 1s var(--ease) 0.75s backwards; }

@keyframes head-in {
  from { transform: translateY(-100%); opacity: 0; }
}

@keyframes rule-in {
  from { opacity: 0; transform: translateY(18px); }
}

.nav {
  height: var(--header-h);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
}

.brand {
  display: flex;
  align-items: center;
  flex-shrink: 0;
  padding: 9px 0;          /* lifts the tap target to 44px without moving anything */
  opacity: 0.95;
  transition: opacity 0.25s ease;
}
.brand:hover { opacity: 1; }


.nav-right { display: flex; align-items: center; gap: 8px; }

.nav-links { display: flex; align-items: center; gap: 4px; }

.nav-links a {
  position: relative;
  padding: 10px 14px;
  font-size: 0.875rem;
  color: var(--muted);
  border-radius: 8px;
  transition: color 0.22s ease, background 0.22s ease;
}

/* hairline that grows from the centre — quiet, but it makes the nav
   feel responsive instead of inert */
.nav-links a::after {
  content: "";
  position: absolute;
  left: 14px; right: 14px;
  bottom: 6px;
  height: 1px;
  background: currentColor;
  transform: scaleX(0);
  opacity: 0.5;
  transition: transform 0.3s var(--ease);
}

.nav-links a:hover { color: var(--text); }
.nav-links a:hover::after { transform: scaleX(1); }
.nav-links a.active { color: var(--text); }
.nav-links a.active::after { transform: scaleX(1); opacity: 0.28; }

/* ── lang toggle ── */
.lang {
  display: flex;
  align-items: center;
  border: 1px solid var(--line);
  border-radius: var(--r);
  padding: 2px;
  margin-left: 6px;
}

.lang button {
  min-width: 38px;
  padding: 6px 9px;
  border-radius: var(--r);
  font-family: var(--mono);
  font-size: 0.68rem;
  font-weight: 600;
  letter-spacing: 0.1em;
  color: var(--dim);
  transition: color 0.22s ease, background 0.22s ease;
}

.lang button:hover { color: var(--text); }
.lang button[aria-pressed="true"] {
  color: var(--text);
  background: rgba(255,255,255,0.09);
  box-shadow: var(--edge);
}

/* ── burger ── */
/* flex, not grid: grid would stretch the three rows across the full 44px
   and the bars would no longer meet when they rotate into the X */
.burger {
  display: none;
  width: 44px;
  height: 44px;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--line);
  border-radius: 10px;
  transition: border-color 0.22s ease, background 0.22s ease;
}
.burger:hover { border-color: var(--line-hi); background: rgba(255,255,255,0.04); }

.burger span {
  display: block;
  width: 17px;
  height: 1.5px;
  background: var(--text);
  border-radius: 2px;
  transition: transform 0.35s var(--ease), opacity 0.2s ease;
}
.burger span + span { margin-top: 4.5px; }

.burger[aria-expanded="true"] span:nth-child(1) { transform: translateY(6px) rotate(45deg); }
.burger[aria-expanded="true"] span:nth-child(2) { opacity: 0; }
.burger[aria-expanded="true"] span:nth-child(3) { transform: translateY(-6px) rotate(-45deg); }

/* ── mobile panel ── */
.panel {
  position: fixed;
  inset: var(--header-h) 0 auto 0;
  z-index: 79;
  background: rgba(8,10,14,0.97);
  backdrop-filter: blur(24px);
  -webkit-backdrop-filter: blur(24px);
  border-bottom: 1px solid var(--line);
  padding: 12px var(--gutter) 28px;
  display: none;
  flex-direction: column;
  opacity: 0;
  transform: translateY(-10px);
  transition: opacity 0.3s ease, transform 0.35s var(--ease);
}

.panel.open { opacity: 1; transform: none; }

.panel a {
  padding: 17px 4px;
  font-size: 1.05rem;
  color: var(--muted);
  border-bottom: 1px solid var(--line);
  display: flex;
  align-items: center;
  justify-content: space-between;
  transition: color 0.2s ease, padding-left 0.25s var(--ease);
}
.panel a:last-child { border-bottom: none; }
.panel a:hover, .panel a:active { color: var(--text); padding-left: 10px; }

.panel a::after {
  content: "";
  width: 6px; height: 6px;
  border-right: 1px solid currentColor;
  border-bottom: 1px solid currentColor;
  transform: rotate(-45deg);
  opacity: 0.45;
}

/* ══ BUTTONS ════════════════════════════════════════════════════════ */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 14px;
  min-height: 52px;
  padding: 14px 30px;
  border-radius: var(--r);
  border: 1px solid var(--line-hi);
  background: transparent;
  color: var(--text);
  font-size: 0.76rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  transition: border-color 0.25s ease, background 0.25s ease,
              transform 0.25s var(--ease), box-shadow 0.25s ease;
}

.btn::after {
  content: "";
  width: 6px; height: 6px;
  border-top: 1.2px solid currentColor;
  border-right: 1.2px solid currentColor;
  transform: translateX(-2px) rotate(45deg);
  opacity: 0.5;
  transition: transform 0.3s var(--ease), opacity 0.25s ease;
}

/* a solid panel wipes up from the bottom edge and the label inverts to
   black — the arrow follows because it is drawn in currentColor */
.btn {
  position: relative;
  overflow: hidden;
  isolation: isolate;
}

.btn::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: #fff;
  transform: translateY(101%);
  transition: transform 0.55s var(--ease);
}

.btn:hover {
  border-color: #fff;
  color: #000;
  transform: none;
}

.btn:hover::before { transform: none; }
.btn:hover::after  { opacity: 1; }
.btn:hover::after { transform: translateX(2px) rotate(45deg); opacity: 0.9; }
.btn:active { transform: translateY(0); }

.btn-ghost {
  background: transparent;
  border-color: var(--line);
  box-shadow: none;
  color: var(--muted);
}
.btn-ghost:hover {
  background: rgba(255,255,255,0.035);
  color: var(--text);
  box-shadow: none;
}

/* ══ SPOTLIGHT ══════════════════════════════════════════════════════
   A soft light that tracks the cursor inside whichever card it's over.
   One delegated listener drives every card via --sx/--sy, so this
   costs one rAF regardless of how many cards are on the page. */
.cap, .usecase, .position-item, .contact-card { position: relative; }

.cap::after, .usecase::after, .position-item::after, .contact-card::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.4s ease;
  background: radial-gradient(260px circle at var(--sx, 50%) var(--sy, 50%),
              rgba(255,255,255,0.075), transparent 72%);
}

@media (hover: hover) and (pointer: fine) {
  .cap:hover::after,
  .usecase:hover::after,
  .position-item:hover::after,
  .contact-card:hover::after { opacity: 1; }
}

/* ══ TYPE ═══════════════════════════════════════════════════════════ */
.label {
  display: inline-block;
  font-family: var(--mono);
  font-size: 0.7rem;
  font-weight: 500;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--dim);
  margin-bottom: 20px;
}

/* Big jump in scale. The copy is deliberately abstract, so hierarchy has
   to do the work the words won't: huge display type against 11px mono
   labels reads as confidence rather than as filler. */
.heading {
  font-size: clamp(1.95rem, 4.6vw, 3.6rem);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: -0.025em;
  line-height: 0.98;
  text-wrap: balance;
  margin-bottom: 24px;
  max-width: 20ch;
}

.sub {
  font-size: clamp(1rem, 1.4vw, 1.08rem);
  color: var(--muted);
  max-width: 60ch;
  line-height: 1.75;
  text-wrap: pretty;
}

.section { padding: var(--pad-y) 0; position: relative; z-index: 2; }

/* ══ HERO ═══════════════════════════════════════════════════════════
   Three stacked layers: a hairline grid (masked so it dissolves before
   it reaches the text), a soft off-screen light, and the grain above
   everything. Nothing decorative — just depth. */
.hero {
  position: relative;
  min-height: calc(100svh - var(--header-h));
  display: flex;
  flex-direction: column;
  padding: clamp(48px, 7vh, 90px) 0 clamp(28px, 4vh, 48px);
  overflow: hidden;
  z-index: 2;
}

.hero > .container {
  flex: 1;
  display: flex;
  flex-direction: column;
  position: relative;
  z-index: 3;
}

/* two auto margins split the free space: the headline settles just above
   centre, the stat row locks to the bottom edge */
.hero-inner { margin-top: auto; }
.hero-rule  { margin-top: auto; }

/* base grid — barely there, just enough to hint at structure */
.hero::before {
  content: "";
  position: absolute;
  inset: 0;
  background-image:
    linear-gradient(rgba(255,255,255,0.022) 1px, transparent 1px),
    linear-gradient(90deg, rgba(255,255,255,0.022) 1px, transparent 1px);
  background-size: var(--cell) var(--cell);
  -webkit-mask-image: radial-gradient(ellipse 95% 75% at 70% 18%, #000 0%, transparent 78%);
  mask-image: radial-gradient(ellipse 95% 75% at 70% 18%, #000 0%, transparent 78%);
  pointer-events: none;
}

/* …and the same grid at full strength, but only where the cursor is.
   The structure is always there; the light is what makes it legible.
   Mask origin is corrected by --py so it stays under the pointer. */
.hero-reveal {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.45s ease;
  background-image:
    linear-gradient(rgba(196,220,255,0.30) 1px, transparent 1px),
    linear-gradient(90deg, rgba(196,220,255,0.30) 1px, transparent 1px);
  background-size: var(--cell) var(--cell);
  -webkit-mask-image: radial-gradient(circle 230px at var(--mx, -999px) var(--my, -999px),
                      #000 0%, rgba(0,0,0,0.28) 52%, transparent 76%);
  mask-image: radial-gradient(circle 230px at var(--mx, -999px) var(--my, -999px),
              #000 0%, rgba(0,0,0,0.28) 52%, transparent 76%);
}

@media (hover: hover) and (pointer: fine) {
  .hero:hover .hero-reveal { opacity: 1; }
}

/* ambient light that never quite settles — 30s cycle, you register it
   as depth rather than as motion */
.hero::after {
  content: "";
  position: absolute;
  top: -42%; right: -18%;
  width: min(920px, 105vw);
  aspect-ratio: 1;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(150,175,205,0.09) 0%, rgba(120,145,180,0.03) 38%, transparent 68%);
  pointer-events: none;
  animation: adrift 30s ease-in-out infinite;
}

@keyframes adrift {
  0%, 100% { transform: translate3d(0, 0, 0) scale(1); }
  50%      { transform: translate3d(-4%, 3%, 0) scale(1.08); }
}

.hero .container { position: relative; z-index: 2; }
/* the headline runs the full measure of the page — no column to sit in */

/* Set as wide as the page allows, in caps, with the lines almost touching.
   At this scale the type stops being a sentence and becomes the image. */
.hero h1 {
  font-size: clamp(2.05rem, 7.1vw, 6.4rem);   /* sized so the longest line holds on one row */
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: -0.028em;
  line-height: 0.92;
  margin-bottom: clamp(24px, 3.5vh, 40px);
}

/* ── the signature move ──────────────────────────────────────────────
   The middle line isn't coloured — it's a window. A brushed-steel
   plate drifts behind the letterforms and you only ever see it
   through them. Seamless tile, so the loop has no visible jump.
   Guarded by @supports: without background-clip the line simply
   stays grey rather than vanishing. */
.hero h1 em {
  font-style: normal;
  color: var(--muted);
}

@supports ((-webkit-background-clip: text) or (background-clip: text)) {
  .hero h1 em {
    color: transparent;
    /* JPEG first as the literal fallback: if the image never arrives, this
       text is transparent and would otherwise vanish entirely. */
    background-image: url('terrain.jpg');
    background-image: image-set(url('terrain.webp') type('image/webp'),
                                url('terrain.jpg')  type('image/jpeg'));
    /* the plate is authored at 3000x340 and shown at half that, so the
       contour strokes stay crisp on 1x screens and land 1:1 on retina */
    background-size: 1500px 170px;
    background-repeat: repeat-x;
    /* background-position is driven from app.js: a constant drift plus a
       damped cursor offset. Left static here so the fill is still visible
       if the script never runs. */
    background-position: 0 center;
    -webkit-background-clip: text;
    background-clip: text;
  }
}

/* ══ OPTICAL ASSEMBLY ═══════════════════════════════════════════════
   A lens seen at the threshold of noticing. It should never win attention
   from the headline — you register that something is watching before you
   register what it is. Hence 12% opacity and motion measured in minutes,
   not seconds.

   NB stroke widths live here rather than on the elements because CSS beats
   presentation attributes: a blanket `stroke-width` on .lens would flatten
   the tick rings, whose stroke width *is* the tick length. */
.lens {
  position: absolute;
  top: 46%;
  right: clamp(-110px, -3vw, -20px);
  width: clamp(300px, 38vw, 540px);
  aspect-ratio: 1;
  transform: translateY(-50%);
  z-index: 1;
  pointer-events: none;
  stroke: #b3c0d0;
  /* master dimmer only — the real dosage is per part below, because a
     specular highlight is physically far brighter than the object it sits
     on. Flattening them to one opacity is what made the reflection vanish. */
  opacity: 0.44;
}

.lens .tick       { opacity: .22; }   /* ≈ .10 once dimmed */
.lens .tick-major { opacity: .36; }   /* ≈ .16 */
.lens .iris       { opacity: .30; }   /* ≈ .13 */
.lens .lens-glass { opacity: .85; }   /* the gradient is already soft */

.lens circle,
.lens path { stroke-width: 1; }
.lens .tick        { stroke-width: 13; }
.lens .tick-major  { stroke-width: 20; }

/* the highlight is the one part allowed to be thicker and brighter — it is
   doing the work of describing a curved surface */
.lens .glint { stroke-width: 2.5; }

/* the focus ring creeps round once every two and a half minutes, and the
   diaphragm hunts focus on a long cycle — both slow enough to read as
   presence rather than as animation */
.lens .tick,
.lens .iris,
.lens .glint {
  transform-box: fill-box;
  transform-origin: center;
}

.lens .tick  { animation: focus-ring 150s linear infinite; }
.lens .iris  { animation: iris 24s ease-in-out infinite; }

/* the glint drifts a few degrees back and forth, as though the light
   source were somewhere off to the side and not quite still */
.lens .glint { animation: glint 34s ease-in-out infinite; }

/* ≈ .27 to .38 once the master dimmer applies: two to three times the
   structure, which is what lets it describe the curve */
@keyframes glint {
  0%, 100% { transform: rotate(-7deg); opacity: .62; }
  50%      { transform: rotate(9deg);  opacity: .86; }
}

@keyframes focus-ring {
  to { transform: rotate(360deg); }
}

@keyframes iris {
  0%, 100% { transform: scale(1); }
  50%      { transform: scale(0.955) rotate(2deg); }
}

/* On a phone the same element would span 80% of the screen and sit behind
   the headline — dominant, not subliminal. Shrunk, dimmed, and pushed
   mostly past the right edge so only an arc of the barrel is ever there. */
@media (max-width: 700px) {
  .lens {
    width: 210px;
    top: 64%;
    right: -78px;
    opacity: 0.34;   /* master dimmer; per-part ratios hold */
  }
}

/* ── live relief ─────────────────────────────────────────────────────
   The WebGL canvas only ever inserts itself after the context, both
   shaders and the program link have all succeeded, so .has-relief is
   proof the surface is really up. Until then — and again if the context
   is lost — the static plate below owns the hero. */
.relief {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  z-index: 0;
  pointer-events: none;
}

.hero.has-relief .hero-plate { display: none; }

/* the same survey drifting behind everything, almost subliminal, in step
   with the letters — so the line reads as an aperture onto it */
.hero-plate {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  opacity: 0.11;
  background-image: url('terrain.jpg');
  background-image: image-set(url('terrain.webp') type('image/webp'),
                              url('terrain.jpg')  type('image/jpeg'));
  /* cover + no-repeat: one large copy, so there is no vertical tiling edge
     to give the trick away. Motion comes from a slow scale, not a scroll. */
  background-size: cover;
  background-repeat: no-repeat;
  background-position: center;
  /* two animations, two properties: the survey owns transform forever, the
     boot fade owns opacity once. `backwards` holds it at 0 during the delay
     so nothing flashes before the headline starts moving. */
  animation: survey 44s ease-in-out infinite,
             plate-in 2.2s ease 0.25s backwards;
  -webkit-mask-image: radial-gradient(ellipse 78% 60% at 44% 44%, #000 0%, transparent 76%);
  mask-image: radial-gradient(ellipse 78% 60% at 44% 44%, #000 0%, transparent 76%);
}

@keyframes plate-in {
  from { opacity: 0; }
}

@keyframes survey {
  0%, 100% { transform: scale(1)    translate3d(0, 0, 0); }
  50%      { transform: scale(1.09) translate3d(-1.5%, 1%, 0); }
}

/* cursor-tracked light, hero only, pointer devices only */
.hero-light {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.7s ease;
  background: radial-gradient(380px circle at var(--lx, 72%) var(--ly, 18%),
              rgba(165,190,220,0.13), transparent 68%);
}

@media (hover: hover) and (pointer: fine) {
  .hero:hover .hero-light { opacity: 1; }
}

.hero-sub {
  font-size: clamp(1.02rem, 1.6vw, 1.15rem);
  color: var(--muted);
  max-width: 56ch;
  margin-bottom: 38px;
  line-height: 1.75;
  text-wrap: pretty;
}

.hero-cta { display: flex; gap: 12px; flex-wrap: wrap; }

.hero-rule {
  margin-top: clamp(64px, 9vw, 96px);
  padding-top: 34px;
  border-top: 1px solid var(--line);
  display: grid;
  grid-template-columns: repeat(3, 1fr);
}

.hero-stat {
  padding-right: 24px;
  display: flex;
  flex-direction: column;
  gap: 5px;
}
.hero-stat + .hero-stat {
  padding-left: 28px;
  border-left: 1px solid var(--line);
}

.hero-stat strong {
  font-size: 0.855rem;
  font-weight: 600;
  color: var(--text);
  letter-spacing: -0.005em;
}

.hero-stat span { font-size: 0.83rem; color: var(--dim); }

/* ══ POSITIONNEMENT ═════════════════════════════════════════════════ */
.position-block {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(40px, 6vw, 84px);
  align-items: start;
}

.position-right { display: flex; flex-direction: column; }

/* stacked hairline rows, not floating cards — closer to a spec sheet */
.position-item {
  position: relative;
  padding: 26px 0 26px 28px;
  background: transparent;
  border: none;
  border-top: 1px solid var(--line);
  transition: padding-left 0.35s var(--ease);
}

.position-item:last-child { border-bottom: 1px solid var(--line); }
.position-item:hover { padding-left: 36px; }

/* the accent rail is the only bit of "brand" colour on the page —
   a white hairline that lights up on hover */
/* a short index rule that extends on hover */
.position-item::before {
  content: "";
  position: absolute;
  left: 0;
  top: 34px;
  width: 14px;
  height: 1px;
  background: var(--line-hi);
  transition: width 0.35s var(--ease), background 0.3s ease;
}

.position-item:hover::before { width: 24px; background: #fff; }

.position-item p { font-size: 0.95rem; color: var(--muted); line-height: 1.72; }

/* ══ CAPACITÉS ══════════════════════════════════════════════════════ */
/* No outer box. Just three tall columns divided by hairlines — the number
   sits at the top, the text falls to the bottom, and the space between
   them is the design. */
.caps-grid {
  margin-top: clamp(40px, 6vw, 72px);
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1px;
  background: var(--line);
  border-top: 1px solid var(--line);
}

.cap {
  position: relative;
  display: flex;
  flex-direction: column;
  min-height: clamp(300px, 26vw, 380px);
  padding: 28px clamp(24px, 2.6vw, 40px) 34px;
  background: var(--bg);
  transition: background 0.35s ease;
  isolation: isolate;
}

/* Hairline schematics rather than photography — nothing to source, nothing
   to licence, and they stay as abstract as the copy does. The auto bottom
   margin makes the mark absorb the free height and drops the text to the
   baseline of the column. */
.cap-mark {
  width: clamp(88px, 8.5vw, 124px);
  height: auto;
  margin: clamp(26px, 3vw, 38px) 0 auto;
  stroke: var(--muted);
  stroke-width: 1.25;
  vector-effect: non-scaling-stroke;
  opacity: 0.5;
  transition: opacity 0.45s var(--ease), stroke 0.45s ease, transform 0.6s var(--ease);
}

/* each stroke is drawn on: JS measures every path with getTotalLength()
   and writes --len, then the dash offset unwinds. Reads like an instrument
   plotting rather than an icon appearing. */
.js .cap-mark [data-len] {
  stroke-dasharray: var(--len);
  stroke-dashoffset: var(--len);
  transition: stroke-dashoffset 1.5s var(--ease);
  transition-delay: var(--d, 0s);
}

.js .cap-mark.draw [data-len] { stroke-dashoffset: 0; }

.cap-mark .node-on,
.cap-mark .plate-on { fill: var(--bg); }

/* the filled nodes arrive last, once the lines that carry them exist */
.js .cap-mark .node-on,
.js .cap-mark .plate-on {
  opacity: 0;
  transition: opacity 0.5s ease 0.9s, fill 0.45s ease;
}

.js .cap-mark.draw .node-on,
.js .cap-mark.draw .plate-on { opacity: 1; }

.cap:hover .cap-mark {
  opacity: 1;
  stroke: #fff;
  transform: translateY(-4px);
}

.cap:hover .cap-mark .node-on { fill: #fff; }

.cap::before {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--surface);
  opacity: 0;
  transition: opacity 0.35s ease;
  z-index: -1;
}
.cap:hover::before { opacity: 1; }

/* "01" in the markup, "01 / 03" on screen — the denominator is a framing
   device, so it lives in CSS rather than in the copy */
.cap-num {
  font-family: var(--mono);
  font-size: 0.68rem;
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.18em;
  color: var(--dim);
  transition: color 0.3s ease;
}

.cap-num::after {
  content: " / 03";
  color: rgba(255,255,255,0.18);
}
.cap:hover .cap-num { color: var(--muted); }

.cap h3 {
  font-size: clamp(1.05rem, 1.5vw, 1.32rem);
  font-weight: 600;
  letter-spacing: -0.022em;
  line-height: 1.2;
  margin-bottom: 14px;
  color: var(--text);
  text-wrap: balance;
}

.cap p { font-size: 0.925rem; color: var(--muted); line-height: 1.72; }

/* ══ APPROCHE ═══════════════════════════════════════════════════════ */
.steps { margin-top: 48px; }

.step {
  position: relative;
  display: grid;
  grid-template-columns: clamp(56px, 8vw, 110px) 1fr;
  gap: clamp(16px, 3vw, 40px);
  padding: clamp(26px, 3vw, 40px) 16px clamp(28px, 3vw, 42px) 8px;
  border-top: 1px solid var(--line);
  align-items: start;
  transition: background 0.3s ease, padding-left 0.35s var(--ease);
}

.step:hover { padding-left: 20px; }
.step:last-child { border-bottom: 1px solid var(--line); }
.step:hover { background: rgba(255,255,255,0.022); }

.step-num {
  font-family: var(--mono);
  font-size: 0.75rem;
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.1em;
  color: var(--dim);
  padding-top: 4px;
  transition: color 0.3s ease;
}
.step:hover .step-num { color: var(--text); }

.step-body strong {
  display: block;
  font-size: clamp(1.15rem, 1.9vw, 1.65rem);
  font-weight: 600;
  letter-spacing: -0.025em;
  line-height: 1.15;
  margin-bottom: 12px;
  color: var(--text);
}

.step-body p {
  font-size: clamp(0.94rem, 1.1vw, 1.02rem);
  color: var(--muted);
  line-height: 1.65;
  max-width: 56ch;
}

/* ══ MESSAGE ════════════════════════════════════════════════════════ */
.message-section {
  position: relative;
  background: var(--bg-elev);
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
  overflow: hidden;
}

.message-section::before {
  content: "";
  position: absolute;
  inset: 0;
  background: radial-gradient(ellipse 70% 130% at 62% 50%, rgba(150,175,205,0.07), transparent 68%);
  pointer-events: none;
}

.message-inner { max-width: 880px; position: relative; z-index: 2; }

.message-text {
  font-size: clamp(1.35rem, 3.1vw, 2.5rem);
  font-weight: 400;
  line-height: 1.32;
  letter-spacing: -0.032em;
  color: var(--muted);
  text-wrap: pretty;
}

.message-text strong { color: var(--text); font-weight: 600; }

/* ══ CAS D'USAGE ════════════════════════════════════════════════════ */
.usecases-grid {
  margin-top: clamp(40px, 6vw, 72px);
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 1px;
  background: var(--line);
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
}

.usecase {
  position: relative;
  padding: 34px clamp(24px, 2.4vw, 36px) 38px;
  background: var(--bg);
  display: flex;
  gap: 18px;
  align-items: flex-start;
  transition: background 0.3s ease;
  isolation: isolate;
}

.usecase::before {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--surface);
  opacity: 0;
  transition: opacity 0.3s ease;
  z-index: -1;
}
.usecase:hover::before { opacity: 1; }

/* a tick mark rather than a bullet — reads as a checklist item on a form */
.usecase-dot {
  width: 12px; height: 1px;
  background: var(--dim);
  flex-shrink: 0;
  margin-top: 13px;
  transition: width 0.35s var(--ease), background 0.3s ease;
}
.usecase:hover .usecase-dot { width: 22px; background: #fff; }

.usecase p {
  font-size: clamp(0.95rem, 1.15vw, 1.05rem);
  color: var(--muted);
  line-height: 1.62;
  max-width: 34ch;
}

/* ══ PHILOSOPHIE ════════════════════════════════════════════════════ */
.philosophy-block {
  margin-top: clamp(36px, 5vw, 60px);
  padding: clamp(32px, 4.5vw, 64px) 0 0;
  border: none;
  border-top: 1px solid var(--line);
  background: transparent;
  position: relative;
  overflow: hidden;
}

/* the rule draws itself out from the left edge as the block arrives */
.philosophy-block::before {
  content: "";
  position: absolute;
  top: -1px; left: 0;
  width: clamp(80px, 12vw, 180px); height: 2px;
  background: #fff;
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 1.1s var(--ease) 0.15s;
}

.js .philosophy-block.in::before,
.philosophy-block:not(.reveal)::before { transform: none; }

.philosophy-block p {
  position: relative;
  font-size: clamp(1.05rem, 1.7vw, 1.35rem);
  color: var(--muted);
  line-height: 1.62;
  max-width: 58ch;
  letter-spacing: -0.012em;
  text-wrap: pretty;
}

/* ══ CONTACT ════════════════════════════════════════════════════════ */
.contact-grid {
  margin-top: 48px;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 16px;
}

.contact-card {
  padding: clamp(30px, 3.5vw, 48px) clamp(24px, 3vw, 44px) clamp(34px, 4vw, 52px);
  border: 1px solid var(--line);
  border-radius: var(--r);
  background: transparent;
  display: flex;
  flex-direction: column;
}

.contact-card h3 {
  font-family: var(--mono);
  font-size: 0.7rem;
  font-weight: 500;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--dim);
  margin-bottom: 26px;
}

/* single CTA — centre it in the leftover height so the card reads as
   deliberately open rather than half-empty */
.contact-ctas {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 10px;
  flex: 1;
  min-height: 92px;
}

.contact-info { list-style: none; }

.contact-info li {
  padding: 15px 0;
  border-bottom: 1px solid var(--line);
  display: grid;
  grid-template-columns: 104px 1fr;
  gap: 14px;
  align-items: baseline;
  font-size: 0.91rem;
}
.contact-info li:first-child { border-top: 1px solid var(--line); }

.contact-info .lbl {
  font-family: var(--mono);
  font-size: 0.68rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--dim);
}
.contact-info .val { color: var(--muted); }
.contact-info a.val { transition: color 0.2s ease; }
.contact-info a.val:hover { color: var(--text); }

/* ══ FOOTER ═════════════════════════════════════════════════════════ */
footer {
  padding: 30px 0 40px;
  border-top: 1px solid var(--line);
  position: relative;
  z-index: 2;
}

.footer-inner {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 18px;
  flex-wrap: wrap;
}

.footer-inner span {
  font-family: var(--mono);
  font-size: 0.72rem;
  letter-spacing: 0.06em;
  color: var(--dim);
}

/* ══ MASKED LINE REVEAL ═════════════════════════════════════════════
   The single most expensive-looking move in the whole system: type rises
   from behind a hard edge instead of fading in. It requires real structure
   (JS wraps each line in .ln > .ln-i), which is exactly why it reads as
   crafted rather than as a default transition.

   The padding/negative-margin pair widens the clip box past the cap height
   so accents (ADAPTÉE) and descenders survive, without shifting layout. */
.js .ln {
  display: block;
  overflow: hidden;
  padding: 0.14em 0;
  margin: -0.14em 0;
}

.js .ln-i {
  display: block;
  transform: translateY(114%);
  transition: transform 1.05s var(--ease);
  transition-delay: var(--d, 0s);
  will-change: transform;
}

.js .lines-in .ln-i {
  transform: none;
  will-change: auto;
}

/* ══ REVEAL ═════════════════════════════════════════════════════════
   Short, low-travel, slightly de-blurring. The old version moved 22px
   over 0.6s which read as sluggish; this lands before you notice it. */
.js .reveal {
  opacity: 0;
  transform: translateY(14px);
  filter: blur(5px);
  transition: opacity 0.6s var(--ease), transform 0.7s var(--ease), filter 0.6s var(--ease);
  transition-delay: var(--d, 0s);
  will-change: opacity, transform;
}

.js .reveal.in {
  opacity: 1;
  transform: none;
  filter: none;
  will-change: auto;
}

/* ══ SCROLL AS A TIMELINE ═══════════════════════════════════════════
   Not entrance animations — these are tied to scroll *position*, so they
   run forwards and backwards as you move and never "finish". Native CSS,
   no JS, no scroll listener: the compositor drives them.

   Everything here is additive and wrapped in @supports, so browsers
   without scroll-driven animations (Safari today) simply don't get it and
   nothing else changes. */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {

    /* the three capability columns pass at slightly different rates — the
       grid reads as three planes at different depths rather than one flat
       row. This is the effect that cannot be faked with an entrance. */
    .caps-grid .cap {
      animation: col-drift linear both;
      animation-timeline: view();
      animation-range: cover 0% cover 100%;
    }
    .caps-grid .cap:nth-child(1) { --drift: 42px; }
    .caps-grid .cap:nth-child(2) { --drift: 12px; }
    .caps-grid .cap:nth-child(3) { --drift: 30px; }

    @keyframes col-drift {
      from { transform: translateY(var(--drift)); }
      to   { transform: translateY(calc(var(--drift) * -1)); }
    }

    /* the headline sinks and dims as it leaves, so the hero feels like a
       layer you move past rather than a block that scrolls off */
    .hero-inner {
      animation: hero-exit linear both;
      animation-timeline: view();
      animation-range: exit 0% exit 100%;
    }

    @keyframes hero-exit {
      to { transform: translateY(-58px); opacity: 0.35; filter: blur(1.5px); }
    }

    /* and the key message brightens as it crosses the middle of the screen */
    .message-section::before {
      animation: msg-lift linear both;
      animation-timeline: view();
      animation-range: entry 20% exit 80%;
    }

    @keyframes msg-lift {
      0%, 100% { opacity: 0.35; transform: scale(0.94); }
      50%      { opacity: 1;    transform: scale(1.04); }
    }
  }
}

/* ══ RESPONSIVE ═════════════════════════════════════════════════════ */
@media (max-width: 940px) {
  .position-block, .contact-grid, .caps-grid, .usecases-grid { grid-template-columns: 1fr; }
  .hero-rule { grid-template-columns: 1fr; }
  .hero-stat { padding: 15px 0; }
  .hero-stat + .hero-stat {
    padding-left: 0;
    border-left: none;
    border-top: 1px solid var(--line);
  }
  .hero-stat:first-child { padding-top: 0; }
}

@media (max-width: 860px) {
  .nav-links { display: none; }
  .burger { display: flex; }
  .panel { display: flex; }
  .lang { margin-left: 0; }
}

@media (max-width: 560px) {
  .step { grid-template-columns: 1fr; gap: 10px; padding: 24px 8px; }
  .step-num { padding-top: 0; }
  .hero-cta { flex-direction: column; align-items: stretch; }
  .hero-cta .btn { width: 100%; }
  .contact-info li { grid-template-columns: 1fr; gap: 4px; }
}

/* ══ MOTION / PRINT ═════════════════════════════════════════════════ */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;   /* don't spin infinite loops at 0ms */
    transition-duration: 0.001ms !important;
    transition-delay: 0s !important;
  }
  .hero::before { transform: none !important; }
  .js .reveal { opacity: 1; transform: none; filter: none; }
  .grain { display: none; }

  /* the new animations all park content out of sight until they play, so
     under reduced motion they must resolve to the finished state outright
     rather than merely snapping fast */
  .js .ln-i { transform: none !important; }
  .js .cap-mark [data-len] { stroke-dashoffset: 0 !important; }
  .js .cap-mark .node-on,
  .js .cap-mark .plate-on { opacity: 1 !important; }
  .philosophy-block::before { transform: none !important; }
  .btn::before { transform: translateY(101%) !important; }
  .js header, .js .hero-rule, .hero-plate { animation: none !important; }
  .hero-plate { opacity: 0.11 !important; }
}

@media print {
  .grain, .progress, header, .panel { display: none !important; }
  body { background: #fff; color: #000; }
  .js .reveal { opacity: 1 !important; transform: none !important; filter: none !important; }
}

/* ══ WORDMARK ══════════════════════════════════════════════════════
   The original mark, vector. Filled with currentColor so it inverts on a
   light background by itself — which the white PNG could not do. */
.brand-mark {
  height: 30px;
  width: auto;
  display: block;
  color: var(--text);
}

@media (max-width: 560px) {
  .brand-mark { height: 26px; }
}
