/* elea.co: matte-paper dark, blueprint lines, IBM Plex Mono.
   Font passport: 12/14/16/18 only; body min 16. Presence via weight/tracking/caps. */
:root {
  --bg: #0b0b0b;
  --ink: #fafafa;
  --ink-soft: rgba(250, 250, 250, 0.92);
  --muted: #8b959e;
  --line: rgba(255, 255, 255, 0.055);
  --accent: #0165ff;
  --font: 'IBM Plex Mono', ui-monospace, Menlo, Consolas, monospace;
  --ease: cubic-bezier(0.16, 1, 0.3, 1);
  /* --ease leaps almost the whole way in its first frames, which reads as a
     snap on things that appear. --ease-soft eases in and out instead. */
  --ease-soft: cubic-bezier(0.4, 0, 0.2, 1);
}

* { box-sizing: border-box; }
/* The gutter is reserved for good, so locking the page behind the contacts
   dialog cannot make the scrollbar vanish and shove the layout sideways. */
html { scroll-behavior: smooth; scrollbar-gutter: stable; }
html, body {
  margin: 0; padding: 0;
  background: var(--bg);
  color: var(--ink-soft);
  font-family: var(--font);
  font-size: 16px;
  line-height: 1.5;
}

/* matte paper: fractal-noise grain over the whole page */
body::before {
  content: '';
  position: fixed; inset: 0;
  z-index: 60;
  pointer-events: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)' opacity='0.55'/%3E%3C/svg%3E");
  opacity: 0.16;
  mix-blend-mode: screen;
}
/* blueprint verticals */
body::after {
  content: '';
  position: fixed; inset: 0;
  z-index: 0;
  pointer-events: none;
  background-image: linear-gradient(to right, var(--line) 1px, transparent 1px);
  background-size: 25% 100%;
}
main, .nav, .footer { position: relative; z-index: 1; }

a { color: var(--ink); text-decoration: none; border-bottom: 1px solid rgba(250, 250, 250, 0.3); transition: border-color 0.25s var(--ease), color 0.25s var(--ease); }
a:hover { border-color: var(--ink); }

/* ---------- nav ---------- */
.nav {
  position: sticky; top: 0;
  z-index: 40;
  display: flex; align-items: center; justify-content: space-between;
  height: 64px;
  padding: 0 40px;
  background: rgba(11, 11, 11, 0.85);
  backdrop-filter: blur(8px);
  border-bottom: 1px solid var(--line);
}
.nav-mark { border: none; display: flex; align-items: center; }
.nav-links { position: relative; display: flex; gap: 28px; }
.nav-links a, .nav-links .nav-link {
  border: none;
  font-size: 14px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
}
/* the section jumps and the contacts dialog are buttons, not links: no URL
   changes, no history entries. They still have to read as one nav row. */
.nav-links .nav-link {
  padding: 0;
  font-family: var(--font);
  background: none;
  cursor: pointer;
}
.nav-links a:hover, .nav-links .nav-link:hover { color: var(--ink); }
.nav-links a.is-current, .nav-links .nav-link.is-current { color: var(--ink); }
/* The keyboard ring is the page's own mark, not the platform's white box
   around the word: the item lights up and takes a short accent line under its
   own text, well clear of the rail. */
.nav-links a, .nav-links .nav-link { position: relative; }
.nav-links a:focus-visible,
.nav-links .nav-link:focus-visible { outline: none; color: var(--ink); }
.nav-links a:focus-visible::after,
.nav-links .nav-link:focus-visible::after {
  content: '';
  position: absolute;
  left: 0; right: 0; bottom: -7px;
  height: 2px;
  background: var(--accent);
  box-shadow: 0 0 12px rgba(1, 101, 255, 0.55);
}
/* the rail slides between links instead of blinking on and off */
.nav-rail {
  position: absolute;
  left: 0; bottom: -21px;
  height: 2px;
  width: 0;
  background: var(--accent);
  box-shadow: 0 0 12px rgba(1, 101, 255, 0.55);
  opacity: 0;
  transform: translateX(0);
  transition: transform 0.5s var(--ease), width 0.5s var(--ease), opacity 0.4s var(--ease);
}
.nav-rail.is-on { opacity: 1; }
@media (prefers-reduced-motion: reduce) {
  .nav-rail { transition: opacity 0.2s linear; }
}
.nav-burger {
  display: none;
  flex-direction: column;
  justify-content: center;
  gap: 5px;
  width: 44px; height: 44px;
  padding: 10px;
  background: transparent;
  border: none;
  cursor: pointer;
}
.nav-burger span {
  display: block;
  height: 1.5px;
  width: 100%;
  background: var(--ink);
  transition: transform 0.3s var(--ease), opacity 0.2s var(--ease);
}
.nav.is-open .nav-burger span:nth-child(1) { transform: translateY(6.5px) rotate(45deg); }
.nav.is-open .nav-burger span:nth-child(2) { opacity: 0; }
.nav.is-open .nav-burger span:nth-child(3) { transform: translateY(-6.5px) rotate(-45deg); }

/* ---------- hero ---------- */
.hero {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 520px);
  align-items: center;
  gap: 40px;
  min-height: 60dvh;
  padding: 40px 120px;
  max-width: 1880px;
  margin: 0 auto;
}
.hero h1 {
  margin: 0 0 24px;
  font-size: 18px;
  font-weight: 700;
  letter-spacing: 0.5em;
  text-transform: uppercase;
  color: var(--ink);
}
/* Armenia, stated in the page's own pixel language: three 4px squares stacked
   to the cap height of the name. The colours are pulled well down from the
   flag's own saturation so the blue accent stays the only bright thing here. */
.flag-pixels {
  display: inline-block;
  width: 4px; height: 16px;
  margin-right: 14px;
  vertical-align: -2px;
  background-image:
    linear-gradient(#9c3b3f, #9c3b3f),
    linear-gradient(#3a4f8a, #3a4f8a),
    linear-gradient(#b5893f, #b5893f);
  background-repeat: no-repeat;
  background-size: 4px 4px;
  background-position: 0 0, 0 6px, 0 12px;
}
.hero-lede {
  margin: 0 0 16px;
  font-size: 18px;
  font-weight: 400;
  line-height: 1.6;
  max-width: 34ch;
  color: var(--ink-soft);
}
.hero-life { justify-self: center; width: 100%; max-width: 520px; aspect-ratio: 6 / 5; }
.hero-life canvas { display: block; width: 100%; height: 100%; }

/* ---------- engagements strip ---------- */
.engagements {
  /* every section carries the same 96px above and below its rule, so each
     divider sits in equal air instead of hugging the strip below it */
  padding: 96px 120px;
  max-width: 1880px;
  margin: 0 auto;
  border-top: 1px solid var(--line);
}
/* same rank as ABOUT ME / EXPERTISE / CONTACTS */
.strip-title {
  /* 24 + the rail's own 18px of padding holds the gap the logos had before */
  margin: 0 0 24px;
  font-size: 16px;
  font-weight: 700;
  letter-spacing: 0.35em;
  text-transform: uppercase;
  color: var(--ink);
}
/* every customer gets the same box. The 1px gaps show the strip's own
   background through, which is the hairline grid: no borders to double up
   at the seams, no stray line on the outside edge. */
.logo-strip {
  /* --line (0.055 alpha) disappears at 1px on this background, so the grid
     gets its own slightly stronger hairline, plus a frame around the block */
  --grid-line: rgba(255, 255, 255, 0.1);
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 1px;
  background: var(--grid-line);
  border: 1px solid var(--grid-line);
  align-items: stretch;
  justify-items: stretch;
}
.logo-strip img {
  width: auto;
  max-width: 100%;
  height: auto;
  max-height: 92px;
  object-fit: contain;
  /* raster emblems keep their internal shading: flattening them to a
     single white value turned the crests into blobs */
  /* raster emblems keep their internal shading: flattening them to a
     single white value turned the crests into blobs.
     Both states list the SAME filter functions in the same order, which is
     what lets the browser interpolate them instead of snapping. */
  filter: grayscale(1) brightness(1.55);
  opacity: 0.85;
  transition: opacity 0.45s var(--ease-soft), filter 0.45s var(--ease-soft);
}
/* a short delay on the way in: crossing the strip on the way somewhere else
   no longer lights every cell it passes */
.logo-cell:hover img,
.logo-cell:focus-visible img { transition-delay: 0.07s; }
/* the emblems are full-colour artwork underneath: hover drops the greyscale
   and lets the real crest colours through */
.logo-cell:hover img,
.logo-cell:focus-visible img { opacity: 1; filter: grayscale(0) brightness(1.05); }
/* vector wordmarks carry real brand ink, so they are flattened to white at
   rest and released on hover */
.logo-strip img.logo-vector { filter: brightness(0) invert(1); }
.logo-cell:hover img.logo-vector,
.logo-cell:focus-visible img.logo-vector { filter: brightness(0.96) invert(0); }
/* the rabbit's eyes are cut out of the mark itself: flattening the file to one
   value would fill them in and leave a blob where the face should be */
.logo-strip img[src$="pancakeswap.svg"],
.logo-cell:hover img[src$="pancakeswap.svg"],
.logo-cell:focus-visible img[src$="pancakeswap.svg"] { filter: none; }
/* wide wordmarks sit lower than the tall emblem PNGs above them */
.logo-strip img.logo-vector { max-height: 40px; }

/* ---------- logo tooltips ---------- */
.logo-cell {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  /* one box for every mark, tall enough for the crests and the wordmarks
     alike, so the rows read as a grid instead of four loose lines */
  min-height: 168px;
  padding: 24px;
  background: var(--bg);
  outline: none;
  transition: background 0.45s var(--ease-soft);
}
.logo-cell:hover { background: rgba(255, 255, 255, 0.02); }
.logo-cell::after {
  content: attr(data-tip);
  position: absolute;
  bottom: calc(100% + 12px);
  left: 50%;
  transform: translate(-50%, 10px) scale(0.985);
  transform-origin: bottom center;
  z-index: 30;
  width: max-content;
  max-width: 280px;
  padding: 12px 16px;
  background: rgba(16, 16, 16, 0.97);
  border: 1px solid rgba(250, 250, 250, 0.14);
  color: var(--ink);
  font-size: 14px;
  line-height: 1.5;
  letter-spacing: 0.02em;
  text-align: left;
  text-transform: none;
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  /* leaving is quicker than arriving, and visibility flips only once the
     fade has finished, so the label never disappears mid-motion */
  transition:
    opacity 0.34s var(--ease-soft),
    transform 0.34s var(--ease-soft),
    visibility 0s linear 0.34s;
}
/* arriving: the same delay the logo colour waits out, then a longer fade */
.logo-cell:hover::after,
.logo-cell:focus-visible::after {
  opacity: 1;
  visibility: visible;
  transform: translate(-50%, 0) scale(1);
  transition:
    opacity 0.44s var(--ease-soft) 0.07s,
    transform 0.44s var(--ease-soft) 0.07s,
    visibility 0s linear 0.07s;
}
/* no room above: the label opens below the cell and rises the other way */
.logo-cell.tip-below::after {
  bottom: auto;
  top: calc(100% + 12px);
  transform: translate(-50%, -10px) scale(0.985);
  transform-origin: top center;
}
.logo-cell.tip-below:hover::after,
.logo-cell.tip-below:focus-visible::after,
.logo-cell.tip-below.is-held::after { transform: translate(-50%, 0) scale(1); }
.logo-strip .logo-cell:nth-child(4n + 1).tip-below::after { transform: translate(0, -10px) scale(0.985); }
.logo-strip .logo-cell:nth-child(4n + 1).tip-below:hover::after,
.logo-strip .logo-cell:nth-child(4n + 1).tip-below:focus-visible::after { transform: translate(0, 0) scale(1); }
.logo-strip .logo-cell:nth-child(4n).tip-below::after { transform: translate(0, -10px) scale(0.985); }
.logo-strip .logo-cell:nth-child(4n).tip-below:hover::after,
.logo-strip .logo-cell:nth-child(4n).tip-below:focus-visible::after { transform: translate(0, 0) scale(1); }
.logo-cell:focus-visible { box-shadow: 0 0 0 1px var(--accent); }
/* outer columns: pin the tooltip inside the section instead of centring it */
.logo-strip .logo-cell:nth-child(4n + 1)::after { left: 0; transform: translate(0, 10px) scale(0.985); }
.logo-strip .logo-cell:nth-child(4n + 1):hover::after,
.logo-strip .logo-cell:nth-child(4n + 1):focus-visible::after { transform: translate(0, 0) scale(1); }
.logo-strip .logo-cell:nth-child(4n)::after { left: auto; right: 0; transform: translate(0, 10px) scale(0.985); }
.logo-strip .logo-cell:nth-child(4n):hover::after,
.logo-strip .logo-cell:nth-child(4n):focus-visible::after { transform: translate(0, 0) scale(1); }

/* ---------- expertise ---------- */
.expertise {
  padding: 96px 120px;
  max-width: 1880px;
  margin: 0 auto;
  border-top: 1px solid var(--line);
}
.expertise h2, .modal-card h2, .about h2, .mentions h2 {
  margin: 0 0 16px;
  font-size: 16px;
  font-weight: 700;
  letter-spacing: 0.35em;
  text-transform: uppercase;
  color: var(--ink);
}
/* the accent rail the dialog titles wear, carried onto the page's own section
   headings so every title of this rank is marked the same way. Static here:
   these titles are part of the page, not an entrance to choreograph. */
.expertise h2, .mentions h2, .strip-title, .about h2 { position: relative; }
.expertise h2, .mentions h2, .strip-title { padding-bottom: 18px; }
.expertise h2::after, .mentions h2::after, .strip-title::after, .about h2::after {
  content: '';
  position: absolute;
  left: 0; bottom: 0;
  width: 48px; height: 2px;
  background: var(--accent);
  box-shadow: 0 0 12px rgba(1, 101, 255, 0.55);
}
/* ABOUT ME shares a flex row with the bio tabs, so its rail hangs below the
   heading box instead of padding it: the row keeps the height it has today
   and the tabs stay centred against the title. */
.about h2::after { bottom: -18px; }

.expertise-lede { margin: 0 0 48px; font-size: 16px; line-height: 1.6; color: var(--muted); max-width: 68ch; }
.mentions-lede { margin: 0 0 48px; font-size: 16px; line-height: 1.6; color: var(--muted); max-width: 68ch; }
.contacts-lede { margin: 16px 0 0; font-size: 16px; line-height: 1.6; color: var(--muted); max-width: 68ch; }

.square-wrap {
  /* the corner buttons straddle the square's edges, so half a button sticks
     out top and bottom. The map frame borrows the same figure to line up with
     the outer edges of COGNITIVE SCIENCE and CYBERSECURITY. */
  --corner-h: 36px;
  /* the square follows the window instead of holding 600px: on a laptop it
     was eating the width the text needed. The floor keeps the four corner
     labels from colliding, the ceiling stops it growing past its old size. */
  --stage: clamp(420px, 34vw, 600px);
  display: grid;
  grid-template-columns: minmax(0, var(--stage)) minmax(0, 1fr);
  gap: 48px;
  align-items: start;
  transition: grid-template-columns 0.5s var(--ease);
}
.square-stage {
  position: relative;
  width: 100%;
  aspect-ratio: 1 / 1;
  cursor: pointer;
}
.square-stage canvas { position: absolute; inset: 0; width: 100%; height: 100%; }
.corner {
  position: absolute;
  padding: 8px 14px;
  font-family: var(--font);
  font-size: 14px;
  /* pinned so --corner-h stays exact: 18 + 16 padding + 2 border = 36 */
  line-height: 18px;
  font-weight: 600;
  letter-spacing: 0.14em;
  color: var(--muted);
  background: rgba(11, 11, 11, 0.8);
  border: 1px solid var(--line);
  cursor: pointer;
  transition: color 0.3s var(--ease), border-color 0.3s var(--ease), background 0.3s var(--ease),
              top 0.5s var(--ease), left 0.5s var(--ease), transform 0.5s var(--ease);
  white-space: nowrap;
}
.corner:hover { color: var(--ink); border-color: rgba(250, 250, 250, 0.35); }
/* the pointer is in this corner's quarter of the square: the button answers
   softly, so the square reads as clickable without shouting. The slow soft
   curve matches the line drawn inside the square, which fades in over ~0.56s */
.corner.is-near:not(.is-on) {
  color: var(--ink);
  border-color: rgba(1, 101, 255, 0.45);
  background: rgba(1, 101, 255, 0.05);
}
.corner:not(.is-on) {
  transition: color 0.5s var(--ease-soft), border-color 0.5s var(--ease-soft),
              background 0.5s var(--ease-soft),
              top 0.5s var(--ease), left 0.5s var(--ease), transform 0.5s var(--ease);
}
.corner.is-on { color: var(--ink); border-color: var(--accent); background: rgba(1, 101, 255, 0.08); }
/* corner labels straddle the top/bottom edges but never spill horizontally
   outside the square. All four anchor on top/left (bottom/right said via 100%
   + translate), so the same properties can slide them into the rail. */
.corner-ai      { top: 0;    left: 0;    transform: translateY(-50%); }
.corner-cogsci  { top: 0;    left: 100%; transform: translate(-100%, -50%); }
.corner-intel   { top: 100%; left: 0;    transform: translateY(-50%); }
.corner-cyber   { top: 100%; left: 100%; transform: translate(-100%, -50%); }

/* star chart embed: the AI scene is the live map itself. Preview sits beside
   the square; EXPAND slides the corners into a left rail and hands the whole
   row to the map. Expanded state is never remembered. */
.star-embed {
  --tick: rgba(250, 250, 250, 0.3);
  position: relative;
  height: 100%;
  min-height: 320px;
  border: 1px solid var(--line);
  background: #080b0f;
  overflow: hidden;
}
.star-embed .rooms { position: absolute; inset: 0; }
/* The map arrives from another project, so the frame does the naturalising:
   the page's own corner ticks are drawn over it and the edges are sunk into
   the section, which stops the panel reading as a pasted rectangle. */
.star-embed::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  box-shadow:
    inset 0 0 70px 14px rgba(11, 11, 11, 0.72),
    inset 0 0 0 1px rgba(250, 250, 250, 0.05);
  background-image:
    linear-gradient(var(--tick), var(--tick)), linear-gradient(var(--tick), var(--tick)),
    linear-gradient(var(--tick), var(--tick)), linear-gradient(var(--tick), var(--tick)),
    linear-gradient(var(--tick), var(--tick)), linear-gradient(var(--tick), var(--tick)),
    linear-gradient(var(--tick), var(--tick)), linear-gradient(var(--tick), var(--tick));
  background-repeat: no-repeat;
  background-size:
    20px 1px, 1px 20px,
    20px 1px, 1px 20px,
    20px 1px, 1px 20px,
    20px 1px, 1px 20px;
  background-position:
    left 12px top 12px, left 12px top 12px,
    right 12px top 12px, right 12px top 12px,
    left 12px bottom 12px, left 12px bottom 12px,
    right 12px bottom 12px, right 12px bottom 12px;
}
/* the claim sits over the map, top-center, and never eats a pointer event */
.star-claim {
  position: absolute;
  top: 16px; left: 50%; transform: translateX(-50%);
  z-index: 4;
  width: min(92%, 640px);
  text-align: center;
  pointer-events: none;
  /* asymmetric on purpose: it leaves quickly, it returns slowly */
  transition: opacity 1.1s var(--ease) 0.28s;
}
/* an open dossier owns the frame: the map lives in its own stacking context,
   so the claim yields by fading instead of fighting for a higher z-index */
.star-embed:has(.rw-dossier[aria-hidden="false"]) .star-claim {
  opacity: 0;
  transition: opacity 0.3s var(--ease) 0s;
}
.star-claim strong {
  display: block;
  font-size: 14px;
  font-weight: 700;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--ink);
  text-shadow: 0 0 18px rgba(8, 11, 15, 0.9);
}
.star-claim span {
  display: block;
  margin-top: 8px;
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.16em;
  line-height: 1.6;
  text-transform: uppercase;
  color: var(--muted);
  text-shadow: 0 0 18px rgba(8, 11, 15, 0.9);
}
/* a hidden scene must park the 3D world: display none drops offsetParent,
   which is the world's own start/stop gate */
.scene:not(.is-on) .star-embed { display: none; }
.star-expand {
  position: absolute;
  bottom: 12px; right: 12px;
  z-index: 5;
  padding: 8px 14px;
  font-family: var(--font);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.14em;
  color: var(--muted);
  background: rgba(11, 11, 11, 0.8);
  border: 1px solid var(--line);
  cursor: pointer;
  transition: color 0.3s var(--ease), border-color 0.3s var(--ease);
}
.star-expand:hover { color: var(--ink); border-color: rgba(250, 250, 250, 0.35); }
.star-fallback { display: none; }
/* ---- the star chart, read as a list (phones only) ----
   Each world keeps its colour from the map, so the two views stay one story.
   Public worlds lead; the ones that have no door yet sit quietly below. */
.proj-lede { margin: 0 0 18px; font-size: 16px; line-height: 1.55; color: var(--muted); }
/* Hairlines were not enough to tell one product from the next, so each one
   gets its own panel. The panel is tuned tight: this card carries six of
   them and still has to sit inside the height of the longest domain. */
.proj-list { display: grid; gap: 8px; margin: 0; padding: 0; list-style: none; }
.proj { position: relative; }
.proj > a,
.proj-list.is-quiet .proj {
  display: grid;
  grid-template-columns: 36px minmax(0, 1fr);
  align-items: start;
  gap: 14px;
  padding: 14px;
  background: rgba(250, 250, 250, 0.022);
  border: 1px solid var(--line);
  color: inherit;
  transition: background 0.3s var(--ease), border-color 0.3s var(--ease);
}
.proj > a:active {
  background: rgba(250, 250, 250, 0.05);
  border-color: rgba(250, 250, 250, 0.16);
}
/* a product with a real mark shows the mark; the planet is the fallback for
   the one that has none yet */
.proj-mark {
  display: block;
  width: 36px; height: 36px;
  border-radius: 6px;
  object-fit: contain;
  background: rgba(255, 255, 255, 0.04);
}
/* the planet: a lit sphere with its own ring, not a coloured dot */
.proj-orb {
  position: relative;
  width: 28px; height: 28px;
  margin: 4px 0 0 4px;
  border-radius: 50%;
  background:
    radial-gradient(circle at 33% 27%, rgba(255, 255, 255, 0.85), rgba(255, 255, 255, 0) 34%),
    radial-gradient(circle at 46% 42%, var(--orb), color-mix(in srgb, var(--orb) 42%, #05070a) 66%, #04060a 100%);
  box-shadow:
    0 0 26px -6px color-mix(in srgb, var(--orb) 70%, transparent),
    inset -5px -7px 12px rgba(0, 0, 0, 0.55);
}
/* the ring, tilted and flattened the same way the map draws its orbits */
.proj-orb::before {
  content: '';
  position: absolute;
  inset: -7px -11px;
  border: 1px solid color-mix(in srgb, var(--orb) 42%, transparent);
  border-radius: 50%;
  transform: rotate(-19deg) scaleY(0.36);
}
.proj-orb::after {
  content: '';
  position: absolute;
  inset: -3px;
  border-radius: 50%;
  border: 1px solid color-mix(in srgb, var(--orb) 16%, transparent);
}
.proj-body { display: block; min-width: 0; padding-right: 22px; }
.proj-name {
  display: block;
  font-size: 16px;
  font-weight: 700;
  letter-spacing: 0.12em;
  color: var(--ink);
}
.proj-domain {
  display: block;
  margin-top: 4px;
  font-size: 12px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--muted);
}
.proj-desc {
  display: block;
  margin-top: 8px;
  font-size: 16px;
  line-height: 1.45;
  color: var(--ink-soft);
}
/* the arrow belongs to the panel, so it sits in its corner instead of
   floating somewhere beside the third line of the description */
.proj-go {
  position: absolute;
  top: 15px; right: 14px;
  font-size: 16px;
  line-height: 1;
  color: var(--muted);
  transition: transform 0.3s var(--ease), color 0.3s var(--ease);
}
.proj > a:active .proj-go { transform: translateX(3px); color: var(--ink); }
/* a real break between what is open and what is not */
.proj-group {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 22px 0 10px;
  font-size: 12px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--muted);
}
.proj-group::after {
  content: '';
  flex: 1;
  height: 1px;
  background: var(--line);
}
/* no door yet: the panel recedes and the planet loses its light */
.proj-list.is-quiet .proj { background: transparent; }
.proj-list.is-quiet .proj-orb {
  box-shadow: inset -5px -7px 12px rgba(0, 0, 0, 0.55);
  opacity: 0.55;
}
.proj-list.is-quiet .proj-orb::before { opacity: 0.5; }
.proj-list.is-quiet .proj-name { color: var(--ink-soft); }
.proj-list.is-quiet .proj-desc { color: var(--muted); }
/* the preview keeps the map quiet: legend and view switch appear only expanded */
.square-wrap:not(.is-expanded) .rw-legend,
.square-wrap:not(.is-expanded) .rw-view { display: none; }
/* The preview is a short frame, and at the full sink the falloff reached a
   third of the way in from every edge: the outer planets went with it. Same
   sunken edge, half the reach. */
.square-wrap:not(.is-expanded) .star-embed::after {
  box-shadow:
    inset 0 0 48px 4px rgba(11, 11, 11, 0.58),
    inset 0 0 0 1px rgba(250, 250, 250, 0.05);
}
/* Expanded, the top edge belongs to the legend and the view switch, and on any
   frame narrower than a wide desktop there is no lane left between them: the
   claim was landing on both. It steps down to the foot of the frame, clear of
   the hint at 24px and the collapse button at 12px. Collapsed there is no HUD,
   so the claim keeps the top. */
.square-wrap.is-expanded .star-claim { top: auto; bottom: 64px; }
/* On a laptop the expanded frame is only about 700px wide once the corner rail
   and the page margins are paid for, and the HUD was eating it: a key in one
   corner, a switch in the other, a claim across the foot, all of it sitting on
   the constellation. Below a wide desktop the map takes the frame back. The
   claim leaves (the map is the claim here) and the key drops its title and its
   padding, since every planet already prints its own category under its name. */
@media (max-width: 1699px) {
  .square-wrap.is-expanded .star-claim { display: none; }
  .square-wrap.is-expanded .rw-legend { top: 16px; left: 16px; padding: 8px 12px 9px; }
  .square-wrap.is-expanded .rw-legend-title { display: none; }
  .square-wrap.is-expanded .rw-legend-row { margin-top: 5px; }
  .square-wrap.is-expanded .rw-view { top: 16px; right: 16px; }
}

@media (min-width: 1101px) {
  /* the row height is pinned so the section never jumps between states */
  /* the 3px nudge puts the crossing of the diagonals on the page's blueprint
     vertical. It is a tuning value, not a rule: the verticals are laid on the
     viewport, the square on the section, so the two only meet around this
     window width. */
  .square-stage { aspect-ratio: auto; height: var(--stage); transform: translateX(3px); }
  /* the frame runs from the top edge of COGNITIVE SCIENCE down to the bottom
     edge of CYBERSECURITY: the square's height plus one whole button, lifted
     by the half button that sticks out above the square */
  .star-embed {
    height: calc(var(--stage) + var(--corner-h));
    margin-top: calc(var(--corner-h) / -2);
    min-height: 0;
  }
  .square-stage canvas { transition: opacity 0.5s var(--ease); }
  /* same minmax() track shapes as the base grid, so the browser can
     interpolate the column change instead of snapping */
  .square-wrap.is-expanded { grid-template-columns: minmax(0, 240px) minmax(0, 1fr); }
  .square-wrap.is-expanded .square-stage canvas { opacity: 0; }
  .square-wrap.is-expanded .corner { left: 0; transform: none; }
  .square-wrap.is-expanded .corner-ai     { top: 0; }
  .square-wrap.is-expanded .corner-cogsci { top: 56px; }
  .square-wrap.is-expanded .corner-intel  { top: 112px; }
  .square-wrap.is-expanded .corner-cyber  { top: 168px; }
}

/* the height is written by the script from the open scene: animating it means
   switching domains settles the page instead of snapping it */
.scene-panel { position: relative; min-height: 320px; transition: height 0.45s var(--ease); }
@media (prefers-reduced-motion: reduce) {
  .scene-panel { transition: none; }
}
.scene-dots { display: none; }
.scene {
  position: absolute; inset: 0;
  opacity: 0;
  transform: translateY(10px);
  pointer-events: none;
  transition: opacity 0.3s var(--ease), transform 0.3s var(--ease);
}
.scene.is-on { opacity: 1; transform: none; pointer-events: auto; position: relative; }
.scene h3 {
  position: relative;
  margin: 0 0 14px;
  padding-bottom: 14px;
  font-size: 16px;
  font-weight: 700;
  letter-spacing: 0.25em;
  color: var(--ink);
}
/* the accent rail draws itself under the title each time a domain opens */
.scene h3::after {
  content: '';
  position: absolute;
  left: 0; bottom: 0;
  width: 48px; height: 2px;
  background: var(--accent);
  box-shadow: 0 0 12px rgba(1, 101, 255, 0.55);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 0.55s var(--ease) 0.12s;
}
.scene.is-on h3::after { transform: scaleX(1); }
@media (prefers-reduced-motion: reduce) {
  .scene h3::after { transition: none; transform: scaleX(1); }
}
.scene p { margin: 0 0 20px; max-width: 62ch; font-size: 16px; }
.scene-small { font-size: 14px; color: var(--muted); }
/* cognitive science: the reach of UX Core, stated as evidence rather than
   prose. Names are set as wordmarks in the page's own type: a row mixing a
   few real brand marks with plain text for the rest reads cheap, and the
   universities have no mark that may be used at all. */
.cog-stat {
  display: flex;
  align-items: center;
  gap: 14px;
  margin: 32px 0 36px;
}
.cog-wreath {
  width: 26px; height: 26px;
  opacity: 0.82;
  filter: brightness(0) invert(1);
}
.cog-stat-value {
  font-size: 18px;
  font-weight: 700;
  letter-spacing: 0.08em;
  color: var(--ink);
}
.cog-stat-label {
  font-size: 12px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--muted);
}
.cog-row { margin: 0 0 32px; }
.cog-row-label {
  display: block;
  margin-bottom: 16px;
  font-size: 12px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--muted);
}
/* a logo wall: real marks where one exists, the name set in the page's own
   type where the brand has none. One white treatment holds the row together. */
.cog-marks {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 20px 36px;
  margin: 0;
  padding: 0;
  list-style: none;
}
.cog-marks li { display: flex; align-items: center; }
/* the marks come from the KeepSimple homepage strip, each still on its own
   60px canvas, so the optical sizing the strip was designed with survives */
.cog-marks img {
  display: block;
  height: 34px;
  width: auto;
  opacity: 0.8;
  /* the sources ship in their own greys and colours: flatten every one of
     them to the same white so the row reads as a single wall. The identity
     pair (brightness 1, invert 0) is what the colour states animate back to,
     so the two filters interpolate instead of snapping. */
  filter: brightness(0) invert(1);
  transition: opacity 0.4s var(--ease-soft), filter 0.4s var(--ease-soft);
}
/* the lecture hall keeps its own colours: four schools, four marks, each in
   the school's blue or red, lifted only as far as this ground demands */
.cog-marks.is-colour img { filter: brightness(1) invert(0); }
/* the leadership wall stays white until a mark is pointed at, and only that
   one mark answers */
.cog-marks li:hover img,
.cog-marks li:focus-within img {
  opacity: 1;
  filter: brightness(1) invert(0);
}
/* a bare mark carries no canvas, so its box was mostly air and the mark itself
   came out a third of its neighbours. The glyph is cropped to its own bars now,
   and this height is the ink the other marks show, not their canvas. */
.cog-marks .is-icon img { height: 16px; }
.cog-marks .is-seal img { height: 30px; }
/* a crest is not a name: the same label the customer wall uses, on the same
   timings, so a mark the reader does not recognise still says what it is */
.cog-marks li[data-tip] { position: relative; }
.cog-marks li[data-tip]::after {
  content: attr(data-tip);
  position: absolute;
  bottom: calc(100% + 12px);
  left: 50%;
  transform: translate(-50%, 10px) scale(0.985);
  transform-origin: bottom center;
  z-index: 30;
  width: max-content;
  max-width: 280px;
  padding: 12px 16px;
  background: rgba(16, 16, 16, 0.97);
  border: 1px solid rgba(250, 250, 250, 0.14);
  color: var(--ink);
  font-size: 14px;
  line-height: 1.5;
  letter-spacing: 0.02em;
  text-align: left;
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition:
    opacity 0.34s var(--ease-soft),
    transform 0.34s var(--ease-soft),
    visibility 0s linear 0.34s;
}
.cog-marks li[data-tip]:hover::after,
.cog-marks li[data-tip]:focus-visible::after {
  opacity: 1;
  visibility: visible;
  transform: translate(-50%, 0) scale(1);
  transition:
    opacity 0.44s var(--ease-soft) 0.07s,
    transform 0.44s var(--ease-soft) 0.07s,
    visibility 0s linear 0.07s;
}
/* the first mark stands at the column edge, so its label opens from that edge
   instead of centring off the left of the page */
.cog-marks li[data-tip]:first-child::after { left: 0; transform: translate(0, 10px) scale(0.985); }
.cog-marks li[data-tip]:first-child:hover::after,
.cog-marks li[data-tip]:first-child:focus-visible::after { transform: translate(0, 0) scale(1); }
.cog-marks li[data-tip]:focus-visible { outline: none; box-shadow: 0 0 0 1px var(--accent); }
/* a hairline that fades out at both ends: it separates the two halves of the
   scene without drawing a hard rule across the column */
/* it centres on the TEXT measure, not on the column: the scene box is wider
   than the 62ch the paragraphs are set to, so auto margins would push the
   line to the right of everything the reader sees. */
.cog-divider {
  width: min(62ch, 100%);
  height: 1px;
  margin: 44px 0;
  border: 0;
  background: linear-gradient(90deg,
    rgba(250, 250, 250, 0) 0%,
    rgba(250, 250, 250, 0.28) 50%,
    rgba(250, 250, 250, 0) 100%) center / 220px 1px no-repeat;
}
.scene-link { font-size: 16px; font-weight: 600; letter-spacing: 0.08em; }

.intel-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 20px;
}
/* the clips are square artwork, so a 16/9 box left a wide empty margin on
   either side and made the tiles far taller than the four labels deserved.
   The clip now sits in its own square cell with the label beside it. */
.intel-tile {
  margin: 0;
  position: relative;
  display: grid;
  grid-template-columns: 116px minmax(0, 1fr);
  align-items: center;
  border: 1px solid var(--line);
  overflow: hidden;
}
.intel-tile video {
  display: block;
  width: 116px; height: 116px;
  aspect-ratio: 1 / 1;
  object-fit: contain;
  padding: 10px;
  opacity: 0.55;
  transition: opacity 0.35s var(--ease);
}
.intel-tile:hover video { opacity: 1; }
.intel-tile figcaption {
  padding: 14px 18px;
  border-left: 1px solid var(--line);
  font-size: 14px;
  font-weight: 600;
  letter-spacing: 0.08em;
  color: var(--ink-soft);
}
.intel-tile figcaption span { display: block; margin-top: 4px; font-size: 12px; font-weight: 400; letter-spacing: 0.04em; color: var(--muted); }

.cyber-list { margin: 0; padding: 0; list-style: none; max-width: 68ch; }
.cyber-list li { padding: 14px 0; font-size: 16px; border-top: 1px solid var(--line); }
.cyber-list li:first-child { border-top: none; padding-top: 0; }
/* the source of a claim, set quieter than the claim itself */
.cyber-ref {
  white-space: nowrap;
  font-size: 14px;
  color: var(--muted);
}
.cyber-ref a { color: var(--ink-soft); border-bottom-color: rgba(250, 250, 250, 0.2); }
.cyber-ref a:hover { color: var(--ink); border-bottom-color: var(--ink); }

/* ---------- about ---------- */
/* both panels share one grid cell, so the section keeps the height of the
   taller tab and nothing below it moves when the tab changes */
.about {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  align-content: start;
  padding: 96px 120px;
  max-width: 1880px;
  margin: 0 auto;
  border-top: 1px solid var(--line);
}
.about-head {
  display: flex; align-items: center; gap: 32px;
  flex-wrap: wrap;
  margin-bottom: 36px;
}
.about-head h2 { margin: 0; }
.about-tabs { display: flex; gap: 12px; }
.tab {
  padding: 8px 18px;
  font-family: var(--font);
  font-size: 14px;
  font-weight: 600;
  letter-spacing: 0.14em;
  color: var(--muted);
  background: transparent;
  border: 1px solid var(--line);
  cursor: pointer;
  transition: color 0.3s var(--ease), border-color 0.3s var(--ease), background 0.3s var(--ease);
}
.tab:hover { color: var(--ink); }
.tab.is-on { color: var(--ink); border-color: var(--accent); background: rgba(1, 101, 255, 0.08); }
.about-head { grid-row: 1; }
.tab-panel {
  grid-row: 2;
  grid-column: 1;
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transform: translateY(8px);
  transition: opacity 0.3s var(--ease), transform 0.3s var(--ease), visibility 0.3s;
}
.tab-panel.is-on { opacity: 1; visibility: visible; pointer-events: auto; transform: none; }
.bio-list { margin: 0; padding: 0; list-style: none; max-width: 68ch; }
.bio-list li {
  position: relative;
  padding: 12px 0 12px 28px;
  font-size: 16px;
  border-top: 1px solid var(--line);
}
.bio-list li:first-child { border-top: none; padding-top: 0; }
/* bullets are live cells lifted from the hero: accent blue for the
   professional record, quiet grey for the casual one */
.bio-list li::before {
  content: '';
  position: absolute;
  left: 0;
  top: 20px;
  width: 8px; height: 8px;
  background: var(--accent);
}
.bio-list li:first-child::before { top: 8px; }
[data-tab-panel="casual"] .bio-list li::before { background: var(--muted); }
/* both tabs open with one framing line, so the panels stay symmetric */
.bio-intro { margin: 0 0 24px; font-size: 18px; line-height: 1.6; max-width: 68ch; color: var(--ink); }

/* ---------- contacts modal + footer ----------
   Contacts is a dialog, so the address bar stays on elea.co and the reader
   never loses the place they were reading. */
/* ---------- mentions ----------
   Two rows, not two columns. Side by side, two publications could never hold a
   column against four quotes: the left half ran out halfway down and left a
   hole the width of the page. The places that published the work take the top
   row, the people who use it take the two rows under it, and every plate cuts
   its corners the way the projects shelf does. */
.mentions {
  padding: 96px 120px;
  max-width: 1880px;
  margin: 0 auto;
  border-top: 1px solid var(--line);
}
.mentions-grid { display: grid; gap: 40px; }
.mention-list {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px;
  margin: 0;
  padding: 0;
  list-style: none;
}
/* the plate: same frame the projects shelf and the quotes wear */
.mention-list li,
.quote {
  position: relative;
  margin: 0;
  padding: 20px;
  background: linear-gradient(180deg, rgba(250, 250, 250, 0.03), rgba(250, 250, 250, 0.012));
  border: 1px solid var(--line);
  clip-path: polygon(12px 0, 100% 0, 100% calc(100% - 12px), calc(100% - 12px) 100%, 0 100%, 0 12px);
}
/* the mention card is a plate too: same soft hover language as the
   projects shelf, so the two collections read as one family */
.mention-list li {
  transition: background 0.4s var(--ease-soft), border-color 0.4s var(--ease-soft);
}
.mention-list li:hover {
  background: linear-gradient(180deg, rgba(250, 250, 250, 0.06), rgba(250, 250, 250, 0.02));
  border-color: rgba(250, 250, 250, 0.16);
}
.mention-list li::before,
.mention-list li::after {
  content: '';
  position: absolute;
  width: 22px; height: 2px;
  background: rgba(250, 250, 250, 0.22);
  transition: background 0.4s var(--ease-soft), box-shadow 0.4s var(--ease-soft);
}
.mention-list li::before { top: 0; left: 12px; }
.mention-list li::after { right: 12px; bottom: 0; }
.mention-list li:hover::before,
.mention-list li:hover::after {
  background: var(--accent);
  box-shadow: 0 0 12px rgba(1, 101, 255, 0.55);
}
.mention-where {
  display: block;
  margin-bottom: 10px;
  font-size: 12px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--muted);
}
/* a title runs to three lines, and a border under the box would underline the
   empty half of the last one: the decoration follows the words instead */
.mention-title {
  display: block;
  max-width: 58ch;
  font-size: 18px;
  line-height: 1.45;
  color: var(--ink);
  border-bottom: none;
  text-decoration: underline;
  text-decoration-color: rgba(250, 250, 250, 0.28);
  text-underline-offset: 5px;
  transition: text-decoration-color 0.25s var(--ease);
}
.mention-title:hover { text-decoration-color: var(--ink); }
.mention-note {
  display: block;
  margin-top: 10px;
  max-width: 62ch;
  font-size: 14px;
  line-height: 1.55;
  color: var(--muted);
}
/* the quotes carry the row the publications started, two by two, and the rule
   above them is the only thing between the two halves */
.mention-quotes {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px;
  padding-top: 40px;
  border-top: 1px solid var(--line);
}
.quote::before,
.quote::after {
  content: '';
  position: absolute;
  width: 22px; height: 2px;
  background: rgba(250, 250, 250, 0.22);
  transition: background 0.4s var(--ease-soft), box-shadow 0.4s var(--ease-soft);
}
.quote::before { top: 0; left: 12px; }
.quote::after { right: 12px; bottom: 0; }
/* the quote plate answers the same way the mention card and the projects
   shelf do: one hover language across the whole section */
.quote {
  transition: background 0.4s var(--ease-soft), border-color 0.4s var(--ease-soft);
}
.quote:hover {
  background: linear-gradient(180deg, rgba(250, 250, 250, 0.06), rgba(250, 250, 250, 0.02));
  border-color: rgba(250, 250, 250, 0.16);
}
.quote:hover::before,
.quote:hover::after {
  background: var(--accent);
  box-shadow: 0 0 12px rgba(1, 101, 255, 0.55);
}
.quote { display: flex; flex-direction: column; }
.quote blockquote {
  margin: 0 0 16px;
  font-size: 16px;
  line-height: 1.5;
  color: var(--ink-soft);
}
/* real typographic quotes, drawn rather than typed, so the text stays copyable
   as the person actually wrote it */
.quote blockquote::before { content: '\201C'; }
.quote blockquote::after { content: '\201D'; }
/* row mates can differ by a line of quote text; the rule stays level across
   the row by riding the bottom instead of trailing the blockquote */
.quote figcaption { margin-top: auto; padding-top: 14px; border-top: 1px solid var(--line); }
.quote-name {
  display: block;
  font-size: 14px;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink);
}
.quote-role {
  display: block;
  margin-top: 5px;
  font-size: 12px;
  line-height: 1.5;
  letter-spacing: 0.06em;
  color: var(--muted);
  /* reserves 2 lines so the rule above a 1-line role lands on the same row
     as one next to a role that wraps */
  min-height: 36px;
}
/* a plate needs about half a page to hold a title or a quote without breaking
   every second word: below that the shelves run one wide */
@media (max-width: 1100px) {
  .mention-list, .mention-quotes { grid-template-columns: minmax(0, 1fr); }
  .mention-quotes { padding-top: 32px; }
}

.modal[hidden] { display: none; }
.modal {
  position: fixed; inset: 0;
  z-index: 60;
  display: flex; align-items: center; justify-content: center;
  padding: 24px;
}
.modal-veil {
  position: absolute; inset: 0;
  background: rgba(6, 6, 6, 0.72);
  backdrop-filter: blur(6px);
  opacity: 0;
  transition: opacity 0.4s var(--ease);
}
.modal-card {
  position: relative;
  width: min(720px, 100%);
  max-height: calc(100dvh - 48px);
  overflow-y: auto;
  padding: 40px 40px 44px;
  background: #0e0e0e;
  border: 1px solid rgba(255, 255, 255, 0.1);
  box-shadow: 0 40px 120px rgba(0, 0, 0, 0.6);
  opacity: 0;
  transform: translateY(12px) scale(0.985);
  transition: opacity 0.4s var(--ease), transform 0.4s var(--ease);
}
/* the dialog scrolls in the page's own language: a hairline gutter sunk into
   the card's edge and a graphite thumb with no radius, instead of the
   platform's grey pill */
.modal-card {
  scrollbar-width: thin;
  scrollbar-color: rgba(250, 250, 250, 0.18) transparent;
}
.modal-card::-webkit-scrollbar { width: 10px; }
.modal-card::-webkit-scrollbar-track {
  background: transparent;
  box-shadow: inset 1px 0 0 var(--line);
}
.modal-card::-webkit-scrollbar-thumb {
  background: rgba(250, 250, 250, 0.18);
  /* the transparent border is the thumb's own margin: it keeps the bar off
     the gutter line instead of painting over it */
  border: 3px solid transparent;
  background-clip: content-box;
  transition: background 0.25s var(--ease);
}
.modal-card::-webkit-scrollbar-thumb:hover {
  background: rgba(250, 250, 250, 0.42);
  background-clip: content-box;
}
.modal.is-on .modal-veil { opacity: 1; }
.modal.is-on .modal-card { opacity: 1; transform: none; }
.modal-close {
  position: absolute; top: 16px; right: 16px;
  padding: 8px 12px;
  font-family: var(--font);
  font-size: 12px;
  letter-spacing: 0.16em;
  color: var(--muted);
  background: transparent;
  border: 1px solid var(--line);
  cursor: pointer;
  transition: color 0.25s var(--ease), border-color 0.25s var(--ease);
}
.modal-close:hover { color: var(--ink); border-color: rgba(250, 250, 250, 0.35); }
/* the same accent rail the nav uses, drawn under the dialog title once the
   card has settled */
.modal-card h2 { position: relative; padding-bottom: 18px; }
.modal-card h2::after {
  content: '';
  position: absolute;
  left: 0; bottom: 0;
  width: 48px; height: 2px;
  background: var(--accent);
  box-shadow: 0 0 12px rgba(1, 101, 255, 0.55);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 0.5s var(--ease) 0.18s;
}
.modal.is-on .modal-card h2::after { transform: scaleX(1); }
@media (prefers-reduced-motion: reduce) {
  .modal-veil, .modal-card { transition: opacity 0.2s linear; }
  .modal-card { transform: none; }
  .pcard { transition: opacity 0.2s linear; transform: none; }
}

/* ---------- projects dialog ----------
   Six products, six panels, each one landscape so the name, the domain and the
   line that says what it is all sit on one plate. The panels ride in behind the
   card, one after another: the dialog opens, then the shelf fills. */
.modal-card.is-wide { width: min(1040px, 100%); }
.projects-lede {
  margin: 0 0 28px;
  max-width: 72ch;
  font-size: 16px;
  line-height: 1.55;
  color: var(--muted);
}
/* the claim inside the sentence carries the page's own weight */
.projects-lede strong { font-weight: 700; color: var(--ink); }
/* the rule under the header is what makes its empty half deliberate: the
   sentence stops where reading wants it to, the hairline carries the eye on
   to the edge of the card */
.pgrid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px;
  padding-top: 28px;
  border-top: 1px solid var(--line);
}
/* three shelves in one grid: same break the AI list uses between what is
   open and what is not, just spanning both columns here */
.pgrid-group {
  grid-column: 1 / -1;
  display: flex;
  align-items: center;
  gap: 12px;
  margin-top: 8px;
  font-size: 12px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--muted);
}
.pgrid-group:first-child { margin-top: 0; }
.pgrid-group::after {
  content: '';
  flex: 1;
  height: 1px;
  background: var(--line);
}
/* The panel speaks the chart's own frame language: the corners are cut the way
   the legend and the dossier cut theirs, so the shelf reads as part of the same
   instrument instead of a card grid borrowed from somewhere else. */
.pcard {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 14px;
  min-height: 196px;
  padding: 20px;
  background: linear-gradient(180deg, rgba(250, 250, 250, 0.03), rgba(250, 250, 250, 0.012));
  /* the page's own link underline has no business on a whole panel */
  border: 1px solid var(--line);
  clip-path: polygon(12px 0, 100% 0, 100% calc(100% - 12px), calc(100% - 12px) 100%, 0 100%, 0 12px);
  color: inherit;
  opacity: 0;
  transform: translateY(10px);
  transition:
    opacity 0.5s var(--ease) calc(var(--i) * 55ms + 0.1s),
    transform 0.5s var(--ease) calc(var(--i) * 55ms + 0.1s),
    background 0.4s var(--ease-soft),
    border-color 0.4s var(--ease-soft);
}
.modal.is-on .pcard { opacity: 1; transform: none; }
/* the cut corners carry a short bar each, the legend's signature */
.pcard::before,
.pcard::after {
  content: '';
  position: absolute;
  width: 22px; height: 2px;
  background: rgba(250, 250, 250, 0.22);
  transition: background 0.4s var(--ease-soft), box-shadow 0.4s var(--ease-soft);
}
.pcard::before { top: 0; left: 12px; }
.pcard::after { right: 12px; bottom: 0; }
.pcard[href]:hover,
.pcard[href]:focus-visible {
  background: linear-gradient(180deg, rgba(250, 250, 250, 0.06), rgba(250, 250, 250, 0.02));
  border-color: rgba(250, 250, 250, 0.16);
  outline: none;
}
.pcard[href]:hover::before,
.pcard[href]:hover::after,
.pcard[href]:focus-visible::before,
.pcard[href]:focus-visible::after {
  background: var(--accent);
  box-shadow: 0 0 12px rgba(1, 101, 255, 0.55);
}
.pcard[href]:focus-visible { box-shadow: 0 0 0 1px var(--accent); }
.pcard[href]:hover .pcard-go,
.pcard[href]:focus-visible .pcard-go { transform: translateX(3px); color: var(--ink); }
.pcard-head { display: grid; grid-template-columns: 48px minmax(0, 1fr); gap: 16px; align-items: center; }
/* Five marks drawn in five different worlds: a flat monogram, an illustrated
   house, a rendered planet. One frame at one size is what makes them a set. */
.pcard-frame {
  display: grid;
  place-items: center;
  width: 48px; height: 48px;
  background: rgba(255, 255, 255, 0.03);
  border: 1px solid var(--line);
}
.pcard-mark { display: block; width: 30px; height: 30px; object-fit: contain; }
/* the one with no door yet keeps its planet instead of a mark */
.pcard .proj-orb { width: 22px; height: 22px; margin: 0; }
/* seven products in two columns leaves the last one alone in its row: it takes
   the whole width instead of sitting beside a hole */
.pcard.is-wide { grid-column: 1 / -1; min-height: 0; }
/* no door yet: the panel keeps the shape and gives up the light */
.pcard.is-quiet { background: rgba(250, 250, 250, 0.012); }
.pcard.is-quiet::before,
.pcard.is-quiet::after { background: rgba(250, 250, 250, 0.12); }
.pcard.is-quiet .proj-orb { opacity: 0.62; }
.pcard-titles { display: block; min-width: 0; }
.pcard-name {
  display: block;
  font-size: 16px;
  font-weight: 700;
  letter-spacing: 0.12em;
  color: var(--ink);
}
.pcard-domain {
  display: block;
  margin-top: 5px;
  font-size: 12px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--muted);
}
/* the description takes the slack, which pins the foot to the bottom edge and
   levels every panel in the row */
.pcard-desc {
  display: block;
  flex: 1;
  font-size: 16px;
  line-height: 1.45;
  color: var(--ink-soft);
}
.pcard-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding-top: 12px;
  border-top: 1px solid var(--line);
  font-size: 12px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--muted);
}
.pcard-go {
  font-size: 16px;
  line-height: 1;
  color: var(--muted);
  transition: transform 0.35s var(--ease-soft), color 0.35s var(--ease-soft);
}
/* the map is still a place, it is just no longer the door */
.projects-chart {
  display: inline-block;
  margin-top: 24px;
  font-size: 12px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--muted);
  border-bottom-color: rgba(139, 149, 158, 0.4);
}
.projects-chart:hover { color: var(--ink); border-bottom-color: var(--ink); }
/* seven channels now: wrap instead of running off the edge */
.contact-links { display: flex; flex-wrap: wrap; gap: 20px 40px; margin-top: 32px; }
.contact-links a { font-size: 16px; letter-spacing: 0.08em; }
/* email copies itself; the chip confirms it and fades back out */
.copy-link { position: relative; }
.copy-chip {
  position: absolute;
  left: 50%;
  bottom: calc(100% + 10px);
  transform: translate(-50%, 6px);
  padding: 6px 12px;
  background: rgba(1, 101, 255, 0.14);
  border: 1px solid var(--accent);
  color: var(--ink);
  font-size: 12px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  white-space: nowrap;
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity 0.35s var(--ease), transform 0.35s var(--ease), visibility 0.35s;
}
.copy-link.is-copied .copy-chip { opacity: 1; visibility: visible; transform: translate(-50%, 0); }

.footer {
  display: flex; align-items: center; gap: 16px;
  padding: 32px 120px;
  max-width: 1880px;
  margin: 0 auto;
  border-top: 1px solid var(--line);
  font-size: 12px;
  letter-spacing: 0.08em;
  color: var(--muted);
}

/* ---------- rich link cards ---------- */
.rich-link { border-bottom-color: rgba(250, 250, 250, 0.55); }
.rich-card {
  position: fixed;
  top: 0; left: 0;
  z-index: 50;
  width: 380px;
  max-width: calc(100vw - 32px);
  padding: 24px;
  background: rgba(14, 14, 14, 0.98);
  border: 1px solid rgba(250, 250, 250, 0.16);
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.55);
  opacity: 0;
  transform: translateY(8px);
  pointer-events: none;
  transition: opacity 0.3s var(--ease), transform 0.3s var(--ease);
}
.rich-card.is-on { opacity: 1; transform: none; pointer-events: auto; }
/* a card with a strand runs the clip as a full-height column on the right */
.rich-card.has-strand {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 84px;
  gap: 20px;
  width: 440px;
}
/* the clip sits first in the DOM for loading, but always in the right column */
.rich-card.has-strand .rich-card-body { grid-column: 1; grid-row: 1; min-width: 0; }
.rich-card.has-strand .rich-card-strand { grid-column: 2; grid-row: 1; }
/* the clip ships on light paper, exactly as the source site does: invert it
   and screen it onto the card so the paper drops out and the strand stays */
.rich-card-strand {
  display: block;
  width: 84px;
  height: 100%;
  object-fit: cover;
  filter: invert(1) grayscale(1) brightness(0.85) contrast(2.4);
  mix-blend-mode: screen;
  opacity: 0.9;
}
.rich-card-head {
  display: flex;
  align-items: center;
  gap: 16px;
  margin-bottom: 16px;
}
.rich-card-icon {
  flex: 0 0 auto;
  width: 48px; height: 48px;
  border-radius: 50%;
  object-fit: cover;
}
.rich-card-heading { min-width: 0; }
.rich-card-title {
  margin: 0;
  font-size: 18px;
  font-weight: 700;
  line-height: 1.4;
  letter-spacing: 0.02em;
  color: var(--ink);
}
.rich-card-by {
  margin: 8px 0 0;
  font-size: 12px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
}
.rich-card-desc {
  margin: 0 0 12px;
  font-size: 16px;
  line-height: 1.5;
  color: var(--ink-soft);
}
.rich-card-desc:last-of-type { margin-bottom: 0; }
.rich-stats {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 20px 12px;
  margin-top: 24px;
  padding-top: 16px;
  border-top: 1px solid var(--line);
  text-align: center;
}
/* each stat owns its own column and wraps inside it, so a two-word label
   can never slide under its neighbour */
.rich-stat {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  min-width: 0;
}
.rich-stat-value {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  font-size: 18px;
  font-weight: 700;
  line-height: 1.2;
  color: var(--ink);
}
.rich-stat-wreath {
  width: 22px; height: 22px;
  opacity: 0.82;
  filter: brightness(0) invert(1);
}
.rich-stat-label {
  font-size: 12px;
  line-height: 1.4;
  letter-spacing: 0.06em;
  color: var(--muted);
  overflow-wrap: anywhere;
}

/* touch screens have no hover to rest in: a tap-triggered card covers the
   text it belongs to, so both tooltip layers are desktop only */
@media (hover: none), (pointer: coarse) {
  /* no hover on a phone, so the tooltip is earned by holding the cell.
     .is-held is set by the press-and-hold handler in site.js */
  .logo-cell:not(.is-held)::after { opacity: 0; visibility: hidden; }
  .logo-cell.is-held::after { opacity: 1; visibility: visible; transform: translate(-50%, 0) scale(1); }
  .logo-strip .logo-cell:nth-child(odd).is-held::after,
  .logo-strip .logo-cell:nth-child(even).is-held::after { transform: translate(0, 0) scale(1); }
  .logo-cell.is-held { background: rgba(255, 255, 255, 0.04); }
  .logo-cell.is-held img { opacity: 1; filter: grayscale(0) brightness(1.05); }
  .logo-cell.is-held img.logo-vector { filter: brightness(0.96) invert(0); }
  .rich-card { display: none; }
}
/* a long press must show the tooltip, not the system's copy/lookup bar */
.logo-cell {
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
}

/* ---------- reveal on scroll ---------- */
.reveal { opacity: 0; transform: translateY(18px); transition: opacity 0.6s var(--ease), transform 0.6s var(--ease); }
.reveal.in { opacity: 1; transform: none; }

/* ---------- mobile ---------- */
@media (max-width: 1100px) {
  .hero, .engagements, .expertise, .about, .footer { padding-left: 40px; padding-right: 40px; }
  .square-wrap { grid-template-columns: 1fr; }
  /* stacked layout: the map is already full width, nothing to expand into */
  .star-embed { height: 440px; }
  .star-expand { display: none; }
}
@media (max-width: 768px) {
  .nav { padding: 0 20px; }
  .nav-mark img { height: 22px; }
  .nav-burger { display: flex; }
  /* the four labels never fit one phone line, so they move into a panel
     that drops from the bar itself */
  .nav-links {
    position: absolute;
    top: 100%; right: 0; left: 0;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    padding: 4px 20px 12px;
    background: rgba(11, 11, 11, 0.97);
    backdrop-filter: blur(8px);
    border-bottom: 1px solid var(--line);
    opacity: 0;
    visibility: hidden;
    transform: translateY(-10px);
    transition: opacity 0.3s var(--ease), transform 0.3s var(--ease), visibility 0.3s;
  }
  .nav.is-open .nav-links { opacity: 1; visibility: visible; transform: none; }
  .nav-links a, .nav-links .nav-link {
    display: flex;
    align-items: center;
    justify-content: flex-start;
    width: 100%;
    min-height: 48px;
    font-size: 16px;
    letter-spacing: 0.08em;
    text-align: left;
    border-top: 1px solid var(--line);
  }
  .nav-links > :first-child { border-top: none; }
  /* in the dropdown the rail would slide across a column, so the current row
     carries the accent on its own left edge instead */
  .nav-rail { display: none; }
  .nav-links a.is-current, .nav-links .nav-link.is-current {
    box-shadow: inset 2px 0 0 var(--accent);
    padding-left: 12px;
    transition: padding-left 0.3s var(--ease);
  }
  .hero { grid-template-columns: 1fr; padding: 40px 20px; min-height: auto; }
  .hero-copy { text-align: center; }
  .hero h1 { letter-spacing: 0.35em; text-indent: 0.35em; }
  .hero-lede { margin-left: auto; margin-right: auto; }
  .hero-life { max-width: 300px; justify-self: center; }
  .engagements, .expertise, .about, .mentions { padding: 64px 20px; }
  .mention-title { font-size: 16px; }
  .modal { padding: 16px; }
  .modal-card { padding: 32px 20px 36px; }
  /* one plate per row: a landscape panel in a phone's width is just a panel */
  .pgrid { grid-template-columns: 1fr; padding-top: 24px; }
  .pcard { padding: 16px; min-height: 0; gap: 12px; }
  .pcard-head { grid-template-columns: 44px minmax(0, 1fr); gap: 14px; }
  .pcard-frame { width: 44px; height: 44px; }
  .pcard-mark { width: 28px; height: 28px; }
  .footer { padding: 24px 20px; }
  .logo-strip { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1px; }
  .logo-strip img { max-height: 76px; }
  .logo-cell { min-height: 132px; padding: 16px; }
  /* two columns on mobile: left column opens right, right column opens left */
  .logo-strip .logo-cell::after { max-width: 240px; }
  .logo-strip .logo-cell:nth-child(odd)::after { left: 0; right: auto; transform: translate(0, 10px) scale(0.985); }
  .logo-strip .logo-cell:nth-child(even)::after { left: auto; right: 0; transform: translate(0, 10px) scale(0.985); }
  .logo-strip .logo-cell:nth-child(odd):hover::after,
  .logo-strip .logo-cell:nth-child(odd):focus-visible::after,
  .logo-strip .logo-cell:nth-child(even):hover::after,
  .logo-strip .logo-cell:nth-child(even):focus-visible::after { transform: translate(0, 0) scale(1); }
  .logo-strip .logo-cell:nth-child(odd).tip-below::after,
  .logo-strip .logo-cell:nth-child(even).tip-below::after { transform: translate(0, -10px) scale(0.985); }
  .logo-strip .logo-cell:nth-child(odd).tip-below.is-held::after,
  .logo-strip .logo-cell:nth-child(even).tip-below.is-held::after { transform: translate(0, 0) scale(1); }
  /* mobile: no interactive square. The four domains become a swipe deck
     instead of four long stacked walls of text. */
  .square-stage { display: none; }
  .scene-panel {
    display: flex;
    align-items: flex-start;
    gap: 16px;
    min-height: 0;
    margin: 0 -20px;
    padding: 4px 20px 8px;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
  }
  .scene-panel::-webkit-scrollbar { display: none; }
  .scene {
    position: relative;
    flex: 0 0 88%;
    opacity: 1;
    transform: none;
    pointer-events: auto;
    margin-bottom: 0;
    padding: 24px 20px;
    background: rgba(250, 250, 250, 0.02);
    border: 1px solid var(--line);
    scroll-snap-align: center;
    /* one flick moves exactly one domain: without this a fast swipe sails
       past two or three cards at once */
    scroll-snap-stop: always;
  }
  /* the deck shows all four cards at once, so every rail is drawn */
  .scene h3 { margin-bottom: 14px; }
  .scene h3::after { transform: scaleX(1); }
  .scene p, .cyber-list { max-width: none; }
  .scene-dots {
    display: flex;
    justify-content: center;
    gap: 10px;
    margin-top: 20px;
  }
  .scene-dot {
    width: 7px; height: 7px;
    background: transparent;
    border: 1px solid rgba(250, 250, 250, 0.3);
    transition: background 0.3s var(--ease), border-color 0.3s var(--ease);
  }
  .scene-dot.is-on { background: var(--accent); border-color: var(--accent); }
  .intel-grid { grid-template-columns: 1fr; }
  .contact-links { flex-direction: column; gap: 16px; }
  /* phones never pay for the 3D map: static card with the link instead */
  .star-embed { display: none; }
  .star-fallback { display: block; }
}
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .scene, .reveal, .tab-panel.is-on, .logo-cell::after, .rich-card,
  .nav-links, .nav-burger span,
  .square-wrap, .corner, .square-stage canvas { transition: none; animation: none; }
  .reveal { opacity: 1; transform: none; }
}
