/* ============================================
   egrm — ecosystem diagram with orbital gravity
   dark mode
   ============================================ */

:root {
  --bg: #04050a;
  --ink: #e8ecf4;
  --muted: #7e8799;
}

*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  background: var(--bg);
  overscroll-behavior: none;
}

body {
  margin: 0;
  min-height: 100vh;
  overflow: hidden;
  overscroll-behavior: none;
  background: radial-gradient(1100px 700px at 50% 50%, #07090f 0%, #05060c 55%, var(--bg) 100%);
  background-attachment: fixed;
  color: var(--ink);
  font-family: "Inter", ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  -webkit-font-smoothing: antialiased;
  cursor: default;
}

#space {
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
}

/* ---- corner header ---- */
.site-header {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 30;
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  padding: 26px 32px;
  pointer-events: none;
}

.site-header .who {
  display: flex;
  flex-direction: column;
  gap: 3px;
}

.site-header strong {
  font-size: 14px;
  font-weight: 650;
  letter-spacing: 0.02em;
  color: var(--ink);
}

.site-header span {
  font-size: 12px;
  color: var(--muted);
  letter-spacing: 0.03em;
}

.site-header .contact {
  pointer-events: auto;
  font-size: 12px;
  color: var(--muted);
  text-decoration: none;
  letter-spacing: 0.04em;
  border-bottom: 1px solid transparent;
  transition: color 0.25s, border-color 0.25s;
}

.site-header .contact:hover,
.site-header .contact:focus-visible {
  color: var(--ink);
  border-color: rgba(232, 236, 244, 0.4);
}

/* ---- project-opening transition ---- */
body.project-opening {
  pointer-events: none;
}

body.project-opening .diagram,
body.project-opening .site-header {
  opacity: 0;
  transition: opacity 560ms ease;
}

.project-transition {
  position: fixed;
  inset: 0;
  z-index: 100;
  background: rgba(4, 5, 10, 0);
  pointer-events: none;
  transition: background-color 480ms ease;
}

.project-transition.active {
  background: #04050a;
}

/* matches .ph-ring / .ph-core on the project page so the handoff is seamless */
.project-transition-circle {
  position: fixed;
  left: 0;
  top: 0;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: #070910;
  border: 1.5px solid color-mix(in srgb, var(--project-color) 45%, transparent);
  transform: translate(-50%, -50%);
  will-change: left, top, width, height;
}

/* ---- diagram container ---- */
.diagram {
  position: fixed;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: min(980px, 96vw);
  height: min(620px, 60.73vw);
  max-height: 90vh;
  z-index: 10;
}

/* ---- orbit trails canvas ---- */
.trails {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}

/* ---- SVG lines ---- */
.diagram-lines {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  /* slight orbit tilt — keep in sync with TILT_DEG in script.js so nodes align */
  transform: rotate(8deg);
}

.orbit-outer { stroke: rgba(148, 163, 184, 0.2); }
.orbit-inner { stroke: rgba(148, 163, 184, 0.14); }
.connector   { stroke: rgba(148, 163, 184, 0.08); }
.anchor      { fill: rgba(148, 163, 184, 0.32); }
/* keep guide-line stroke width even when the box is stretched non-uniformly
   (e.g. the portrait/vertical orbit on phones) */
.orbit, .connector { vector-effect: non-scaling-stroke; }

/* ---- center hub ---- */
.center-hub {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: 96px;
  height: 96px;
  border-radius: 50%;
  background: #0a0d15;
  border: 1.5px solid rgba(170, 185, 210, 0.3);
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow:
    0 2px 24px rgba(0, 0, 0, 0.55),
    0 0 0 12px rgba(170, 185, 210, 0.045);
  z-index: 10;
  user-select: none;
  transition: border-color 0.4s, box-shadow 0.4s;
}

.center-hub:hover {
  border-color: rgba(190, 205, 230, 0.5);
  box-shadow:
    0 2px 24px rgba(0, 0, 0, 0.55),
    0 0 0 12px rgba(170, 185, 210, 0.08);
}

.monogram {
  font-size: 30px;
  font-weight: 700;
  letter-spacing: 0.04em;
  color: var(--ink);
  line-height: 1;
}

/* a tiny moon circling the hub */
.hub-orbit {
  position: absolute;
  inset: -17px;
  border-radius: 50%;
  animation: hub-spin 9s linear infinite;
  pointer-events: none;
}

.hub-moon {
  position: absolute;
  left: -2.5px;
  top: 50%;
  width: 5px;
  height: 5px;
  margin-top: -2.5px;
  border-radius: 50%;
  background: rgba(190, 205, 230, 0.75);
}

@keyframes hub-spin {
  to { transform: rotate(360deg); }
}

@media (prefers-reduced-motion: reduce) {
  .hub-orbit { animation: none; }
  .badge::before { animation: none; }
  .project-node::before { animation: none; }
  .project-node::after { animation: none; }
}

/* ---- project nodes ---- */
.project-node {
  position: absolute;
  /* --depth-scale is set per-frame by script.js for the pseudo-3D depth cue */
  transform: translate(-50%, -50%) scale(var(--depth-scale, 1));
  display: block;
  width: 42px;
  height: 42px;
  text-decoration: none;
  z-index: 5;
  will-change: left, top;
  transition: filter 0.25s;
  border-radius: 50%;
}

.project-node:hover,
.project-node:focus-visible {
  z-index: 20;
}

.project-node:focus-visible {
  outline: none;
}

.project-node:focus-visible .badge::after {
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 35%, transparent);
}

/* 21px = badge ring radius at scale 1 (9px badge + 12px outer ring),
   so the gap to the text stays constant at every rank scale */
.label-left .label {
  right: calc(100% + 4px + 21px * (var(--rank-scale, 1) - 1));
  text-align: right;
}

.label-right .label {
  left: calc(100% + 4px + 21px * (var(--rank-scale, 1) - 1));
  text-align: left;
}

/* ---- badge (concentric ring node) ---- */
/* --rank-scale / --rank-glow / --rank-fade are set per node by script.js
   from the project's rank in projects.js (rank 1 = biggest & brightest) */
.badge {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 18px;
  height: 18px;
  transform: translate(-50%, -50%) scale(var(--rank-scale, 1));
  border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 10px color-mix(in srgb, var(--accent) var(--rank-glow, 30%), transparent);
  opacity: var(--rank-fade, 1);
  flex-shrink: 0;
  transition: transform 0.2s, box-shadow 0.25s, opacity 0.25s;
}

.project-node:hover .badge,
.project-node:focus-visible .badge {
  box-shadow: 0 0 16px color-mix(in srgb, var(--accent) calc(var(--rank-glow, 30%) + 15%), transparent);
  opacity: 1;
}

/* inner glow ring — also carries the heartbeat pulse */
.badge::before {
  content: "";
  position: absolute;
  inset: -7px;
  border-radius: 50%;
  background: var(--accent);
  opacity: 0.12;
  transition: opacity 0.25s;
  pointer-events: none;
  animation:
    badge-pulse 5s ease-in-out 0.8s 1,
    badge-pulse var(--rank-pulse-period, 20s) ease-in-out var(--rank-pulse-delay, 5.8s) infinite;
}

/* the flash lives in the first ~30% of the cycle; the rest is idle,
   so longer periods read as a longer wait, not a slower flash */
@keyframes badge-pulse {
  0% {
    opacity: 0.12;
    animation-timing-function: cubic-bezier(0.16, 0.84, 0.44, 1);
  }
  9% {
    opacity: 0.5;
    animation-timing-function: cubic-bezier(0.3, 0.55, 0.6, 1);
  }
  32%, 100% { opacity: 0.12; }
}

/* four-pointed star that flashes with each beat — white-hot core fading
   through the accent into needle tips; rotated 45° so the spikes point
   diagonally and never cross the label text */
.project-node::before {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  width: 60px;
  height: 60px;
  background: radial-gradient(
    circle,
    #fff 0%,
    color-mix(in srgb, var(--accent) 55%, white) 16%,
    var(--accent) 42%,
    transparent 72%
  );
  clip-path: polygon(50% 0%, 55.5% 44.5%, 100% 50%, 55.5% 55.5%, 50% 100%, 44.5% 55.5%, 0% 50%, 44.5% 44.5%);
  filter: drop-shadow(0 0 8px color-mix(in srgb, var(--accent) 70%, transparent));
  opacity: 0;
  pointer-events: none;
  /* first beat: one fixed 5s cycle, same for every node, so they all flash
     in sync after the entry fade (0.8s + 5s = 5.8s, see script.js); then the
     rank-paced infinite heartbeat takes over with staggered delays */
  animation:
    star-flash 5s ease-in-out 0.8s 1,
    star-flash var(--rank-pulse-period, 20s) ease-in-out var(--rank-pulse-delay, 5.8s) infinite;
}

/* fast attack, slow decay, slight clockwise twist as it blooms */
@keyframes star-flash {
  0% {
    transform: translate(-50%, -50%) rotate(38deg) scale(calc(var(--rank-scale, 1) * 0.1));
    opacity: 0;
    animation-timing-function: cubic-bezier(0.16, 0.84, 0.44, 1);
  }
  9% {
    transform: translate(-50%, -50%) rotate(45deg) scale(var(--rank-scale, 1));
    opacity: calc(var(--rank-glow, 30%) + 35%);
    animation-timing-function: cubic-bezier(0.3, 0.55, 0.6, 1);
  }
  16% {
    transform: translate(-50%, -50%) rotate(48deg) scale(calc(var(--rank-scale, 1) * 0.8));
    opacity: calc((var(--rank-glow, 30%) + 35%) * 0.4);
    animation-timing-function: cubic-bezier(0.3, 0.55, 0.6, 1);
  }
  32%, 100% {
    transform: translate(-50%, -50%) rotate(52deg) scale(calc(var(--rank-scale, 1) * 0.2));
    opacity: 0;
  }
}

/* smaller upright cross-glint that pops a beat behind the main star,
   catching the badge like light on glass */
.project-node::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  width: 34px;
  height: 34px;
  background: radial-gradient(
    circle,
    #fff 0%,
    color-mix(in srgb, var(--accent) 40%, white) 30%,
    transparent 70%
  );
  clip-path: polygon(50% 0%, 54% 46%, 100% 50%, 54% 54%, 50% 100%, 46% 54%, 0% 50%, 46% 46%);
  opacity: 0;
  pointer-events: none;
  animation:
    star-glint 5s ease-in-out 0.8s 1,
    star-glint var(--rank-pulse-period, 20s) ease-in-out var(--rank-pulse-delay, 5.8s) infinite;
}

@keyframes star-glint {
  0%, 6% {
    transform: translate(-50%, -50%) scale(calc(var(--rank-scale, 1) * 0.2));
    opacity: 0;
    animation-timing-function: cubic-bezier(0.16, 0.84, 0.44, 1);
  }
  13% {
    transform: translate(-50%, -50%) scale(var(--rank-scale, 1));
    opacity: calc(var(--rank-glow, 30%) + 20%);
    animation-timing-function: cubic-bezier(0.3, 0.55, 0.6, 1);
  }
  28%, 100% {
    transform: translate(-50%, -50%) scale(calc(var(--rank-scale, 1) * 0.3));
    opacity: 0;
  }
}

/* outer border ring */
.badge::after {
  content: "";
  position: absolute;
  inset: -12px;
  border-radius: 50%;
  border: 1.5px solid color-mix(in srgb, var(--accent) 60%, transparent);
  transition: box-shadow 0.25s;
  pointer-events: none;
}

/* ---- label ---- */
.label {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  display: flex;
  flex-direction: column;
  gap: 2px;
  pointer-events: none;
  opacity: var(--rank-fade, 1);
  transition: opacity 0.25s;
  white-space: nowrap;
}

.project-node:hover .label,
.project-node:focus-visible .label {
  opacity: 1;
}

.label-name {
  font-size: 20px;
  font-weight: 650;
  color: var(--accent);
  line-height: 1.25;
  letter-spacing: -0.01em;
}

.label-sub {
  font-size: 14px;
  color: var(--muted);
  line-height: 1.35;
  letter-spacing: 0.01em;
}

/* ---- entry transition ---- */
body.entering .center-hub {
  opacity: 0;
  transform: translate(-50%, -50%) scale(3);
}

body.entering .diagram-lines,
body.entering .trails {
  opacity: 0;
}

body.entering .project-node {
  opacity: 0;
}

body.entering .site-header {
  opacity: 0;
}

body.ready .center-hub {
  opacity: 1;
  transform: translate(-50%, -50%) scale(1);
  transition: opacity 700ms ease, transform 1100ms cubic-bezier(0.16, 1, 0.3, 1);
}

body.ready .diagram-lines,
body.ready .trails {
  opacity: 1;
  transition: opacity 1000ms ease 200ms;
}

body.ready .project-node {
  opacity: 1;
  transition: opacity 800ms ease 500ms;
}

body.ready .site-header {
  opacity: 1;
  transition: opacity 900ms ease 700ms;
}

/* ---- responsive ---- */
@media (max-width: 700px) {
  .diagram {
    /* Portrait orbit on phones: the box is taller than wide, so the same
       SVG a:b ratio renders as a vertical ellipse. script.js scales the node
       positions to this box, so the whole orbit (nodes + trails) follows. */
    width: min(330px, 60vw);
    height: min(560px, 68vh);
  }

  .site-header {
    padding: 18px 20px;
  }

  .site-header .contact {
    display: none;
  }

  .center-hub {
    width: 64px;
    height: 64px;
  }

  .hub-orbit { inset: -12px; }

  .monogram {
    font-size: 22px;
  }

  .badge {
    width: 14px;
    height: 14px;
  }

  .badge::before { inset: -5px; }
  .badge::after  { inset: -9px; }

  .project-node::before {
    width: 44px;
    height: 44px;
  }

  .project-node::after {
    width: 26px;
    height: 26px;
  }

  .label-name { font-size: 15px; }
  .label-sub  { font-size: 11px; }
  .project-node {
    width: 32px;
    height: 32px;
  }
  /* 16px = mobile badge ring radius at scale 1 (7px badge + 9px outer ring) */
  .label-left .label { right: calc(100% + 3px + 16px * (var(--rank-scale, 1) - 1)); }
  .label-right .label { left: calc(100% + 3px + 16px * (var(--rank-scale, 1) - 1)); }
}

@media (max-width: 440px) {
  .label-sub { display: none; }
  .label-name { font-size: 13px; }
  .center-hub {
    width: 52px;
    height: 52px;
  }

  .hub-orbit { inset: -10px; }
  .monogram { font-size: 18px; }
}
