/* =========================================================================
   Self-hosted webfonts — no third-party requests, nothing render-blocking
   from another origin. Instrument Serif + Inter (both OFL), subset to
   latin / latin-ext and served from /fonts.
   ========================================================================= */

/* --- Instrument Serif (display) --- */
@font-face {
  font-family: "Instrument Serif";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(fonts/instrument-serif-400-normal-latin.woff2) format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Instrument Serif";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(fonts/instrument-serif-400-normal-latin-ext.woff2) format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "Instrument Serif";
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url(fonts/instrument-serif-400-italic-latin.woff2) format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Instrument Serif";
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url(fonts/instrument-serif-400-italic-latin-ext.woff2) format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* --- Inter (UI, variable) --- */
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url(fonts/inter-var-latin.woff2) format("woff2-variations"),
       url(fonts/inter-var-latin.woff2) format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url(fonts/inter-var-latin-ext.woff2) format("woff2-variations"),
       url(fonts/inter-var-latin-ext.woff2) format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* =========================================================================
   ranbam.com — one stylesheet, no build step.
   Rebuilt from scratch around the particle globe. Two themes, both first
   class. Everything below is layout, type and the light/dark tokens; the
   globe itself is drawn in main.js.
   ========================================================================= */

:root {
  --bg: #0a0a10;
  --text: #f4f4f8;
  --muted: #8f8fa3;
  --line: rgba(255, 255, 255, 0.10);
  --chip: rgba(255, 255, 255, 0.05);

  --serif: "Instrument Serif", Georgia, "Times New Roman", serif;
  --sans: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;

  --ease: cubic-bezier(0.22, 0.61, 0.36, 1);
}

:root[data-theme="light"] {
  --bg: #f6f6f9;
  --text: #101018;
  --muted: #6a6a78;
  --line: rgba(16, 16, 24, 0.12);
  --chip: rgba(16, 16, 24, 0.045);
}

* { box-sizing: border-box; }

html, body {
  height: 100%;
  margin: 0;
}

body {
  background: var(--bg);
  color: var(--text);
  font-family: var(--sans);
  font-size: 16px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow: hidden;
  transition: background 0.5s var(--ease), color 0.5s var(--ease);
}

/* Vignette: pulls the eye to the middle of the sphere without hiding anything. */
body::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background: radial-gradient(
    ellipse 78% 68% at 50% 46%,
    transparent 0%,
    transparent 52%,
    color-mix(in srgb, var(--bg) 72%, transparent) 100%
  );
}

/* ============================================================ spotlight
   A camera flash: the light comes from behind the camera, so it's a plain
   bright circle centred on the hovered tile — no beam, no offset — with the
   page falling into shadow around it. Positioned by main.js with custom
   properties; the fade-in is CSS. */

.spotlight {
  position: fixed;
  inset: 0;
  z-index: 3;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.35s var(--ease);
  background: radial-gradient(
    circle var(--spot-r, 120px) at var(--spot-x, 50%) var(--spot-y, 50%),
    rgba(255, 248, 235, 0.10) 0%,
    rgba(255, 248, 235, 0.03) 55%,
    color-mix(in srgb, var(--bg) 84%, transparent) 100%
  );
}

.spotlight.is-on { opacity: 1; }

/* ============================================================== chrome */

.bar,
.foot {
  position: fixed;
  left: 0;
  right: 0;
  z-index: 2;
  display: flex;
  align-items: center;
  padding: 22px clamp(18px, 4vw, 38px);
}

.bar {
  top: 0;
  /* Only the theme toggle lives up here now, so push it to the right rather
     than letting space-between strand it on the left. */
  justify-content: flex-end;
}

.foot {
  bottom: 0;
  flex-direction: column;
  gap: 6px;
  text-align: center;
  /* The bar spans the full width; without this it swallows clicks meant for
     project balls near the bottom of the sphere. */
  pointer-events: none;
}

.wordmark {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  font-size: 15px;
  font-weight: 500;
  letter-spacing: -0.01em;
  color: var(--text);
}

.wordmark-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: #ff60a8;
  box-shadow: 0 0 12px 2px rgba(255, 96, 168, 0.7);
  animation: pulse 3.2s ease-in-out infinite;
}

@keyframes pulse {
  0%, 100% { opacity: 1; transform: scale(1); }
  50%      { opacity: 0.55; transform: scale(0.82); }
}

.theme-toggle {
  display: grid;
  place-items: center;
  width: 38px;
  height: 38px;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: 50%;
  background: var(--chip);
  color: var(--text);
  cursor: pointer;
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  transition: border-color 0.25s, background 0.25s, transform 0.25s var(--ease);
}

.theme-toggle:hover {
  transform: scale(1.07);
  border-color: color-mix(in srgb, var(--text) 32%, transparent);
}

.theme-toggle:focus-visible {
  outline: 2px solid #ff60a8;
  outline-offset: 3px;
}

.theme-toggle svg {
  width: 17px;
  height: 17px;
  transition: opacity 0.3s var(--ease), transform 0.4s var(--ease);
}

/* Show exactly one glyph per theme. */
.theme-toggle .i-moon { display: none; }
:root[data-theme="light"] .theme-toggle .i-moon { display: block; }
:root[data-theme="light"] .theme-toggle .i-sun { display: none; }

/* ================================================================= type */

/* The slogan used to sit here; the wordmark takes its place. Sized up a little
   from its old corner-of-the-bar size, since it is now the only thing down
   here and carries the bottom of the frame on its own. */
.foot .wordmark {
  margin: 0;
  font-size: 17px;
  line-height: 1.2;
}

/* Entrance: the chrome arrives after the sphere has mostly formed.

   Deliberately gated behind no-preference rather than collapsed by the reduce
   block. With fill-mode:both, visibility is a function of animation progress —
   so shortening the duration doesn't make the content appear, it freezes it at
   the `from` state (opacity 0) and the page renders blank. Opting in means
   reduced-motion users simply never have the problem. */
@keyframes rise {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: no-preference) {
  .bar  { animation: rise 0.9s var(--ease) 0.55s both; }
  .foot { animation: rise 0.9s var(--ease) 0.75s both; }
}

/* ============================================================= a11y */

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-delay: 0s !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    transition-delay: 0s !important;
  }

  /* The pulse ring is the only infinite animation left; park it on. */
  .wordmark-dot {
    animation: none !important;
    opacity: 1 !important;
    transform: none !important;
  }
}

/* ============================================================ project balls
   Positioned by main.js from the same projection as the dots, so they sit on
   the sphere and get dragged around by the same magnet. Real buttons, so
   hover, links and keyboard focus come free. */

:root {
  --card-w: 268px;
  --ball: 61px;
  --ball-halo: rgba(120, 190, 255, 0.24);
}

:root[data-theme="light"] {
  --ball-halo: rgba(18, 92, 224, 0.16);
}

/* No z-index on the container: it must not form a stacking context, so the
   hovered tile can rise above the spotlight while its siblings sit under it. */
.nodes {
  position: fixed;
  inset: 0;
  pointer-events: none;
}

.node {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 2;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 7px;
  padding: 0;
  border: 0;
  background: none;
  color: var(--text);
  font: inherit;
  cursor: pointer;
  pointer-events: auto;
}

/* The hovered tile rises above the spotlight; everything else stays dimmed
   under it. */
.node.is-open { z-index: 4; }

/* The macOS-style app tile: a gradient squircle with a glyph, plus an inset
   sheen along the top edge for the lit-from-above look. */
.node-icon {
  display: grid;
  place-items: center;
  width: var(--ball);
  height: var(--ball);
  border-radius: 24%;
  background: linear-gradient(168deg, var(--from, #8aa2c8), var(--to, #5b6b8a));
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.38),
    inset 0 -1px 0 rgba(0, 0, 0, 0.16),
    0 2px 6px rgba(0, 0, 0, 0.32),
    0 0 22px -4px var(--ball-halo);
  transition: transform 0.3s var(--ease), box-shadow 0.3s var(--ease);
}

.node-icon svg {
  width: 60%;
  height: 60%;
  color: #fff;
}

.node-label {
  font-size: 11px;
  font-weight: 500;
  letter-spacing: -0.005em;
  white-space: nowrap;
  color: var(--muted);
  opacity: 0;
  transform: translateY(-3px);
  transition: opacity 0.25s var(--ease), transform 0.25s var(--ease), color 0.25s;
}

.node:hover .node-icon,
.node:focus-visible .node-icon,
.node.is-open .node-icon {
  transform: scale(1.14);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.38),
    inset 0 -1px 0 rgba(0, 0, 0, 0.16),
    0 3px 9px rgba(0, 0, 0, 0.34),
    0 0 34px -2px var(--ball-halo);
}

.node:hover .node-label,
.node:focus-visible .node-label,
.node.is-open .node-label {
  opacity: 1;
  transform: none;
  color: var(--text);
}

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

.node:focus-visible .node-icon {
  outline: 2px solid var(--text);
  outline-offset: 3px;
}

/* ===================================================================== card
   Docks beside the hovered tile, on whichever side has room — main.js sets
   left/top from the tile's position each frame. In slow motion the tile
   barely moves, so the card reads as resting next to it. */

.panel {
  position: fixed;
  z-index: 5;
  width: var(--card-w);
  padding: 15px;
  border: 1px solid var(--line);
  border-radius: 16px;
  background: color-mix(in srgb, var(--bg) 90%, transparent);
  backdrop-filter: blur(18px) saturate(1.5);
  -webkit-backdrop-filter: blur(18px) saturate(1.5);
  box-shadow: 0 20px 50px -20px rgba(0, 0, 0, 0.65);
  animation: card-in 0.22s var(--ease) both;
}

.panel[hidden] { display: none; }

@keyframes card-in {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; }
}

.card-head {
  display: flex;
  align-items: center;
  gap: 11px;
}

.panel.is-pinned .card-head { padding-right: 22px; }

.card-icon {
  display: grid;
  place-items: center;
  flex: 0 0 auto;
  width: 34px;
  height: 34px;
  border-radius: 24%;
  background: linear-gradient(168deg, var(--from, #8aa2c8), var(--to, #5b6b8a));
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.38),
    0 2px 6px rgba(0, 0, 0, 0.3);
}

.card-icon svg {
  width: 60%;
  height: 60%;
  color: #fff;
}

.card-titles {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 0;
}

.card-name {
  font-size: 14.5px;
  font-weight: 600;
  letter-spacing: -0.01em;
  line-height: 1.2;
}

.card-sub {
  font-size: 11px;
  color: var(--muted);
}

.card-use {
  margin: 12px 0 0;
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--muted);
}

.card-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 7px;
  margin-top: 14px;
}

.card-action {
  display: inline-flex;
  align-items: center;
  padding: 7px 12px;
  border: 1px solid var(--line);
  border-radius: 999px;
  font-size: 12px;
  color: var(--text);
  text-decoration: none;
  transition: border-color 0.2s, transform 0.2s var(--ease);
}

.card-action:hover {
  border-color: color-mix(in srgb, var(--text) 34%, transparent);
  transform: translateY(-1px);
}

/* Monochrome, like everything else: the accent pink is long gone. */
.card-action.is-primary {
  background: var(--text);
  border-color: var(--text);
  color: var(--bg);
  font-weight: 500;
}

.panel-close {
  position: absolute;
  top: 8px;
  right: 8px;
  display: none;
  place-items: center;
  width: 22px;
  height: 22px;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: 50%;
  background: var(--chip);
  color: var(--muted);
  cursor: pointer;
  transition: color 0.2s, transform 0.2s var(--ease);
}

/* Only a pinned card needs dismissing — a hover card closes itself. */
.panel.is-pinned .panel-close { display: grid; }
.panel-close:hover { color: var(--text); transform: scale(1.1); }
.panel-close:focus-visible { outline: 2px solid var(--text); outline-offset: 2px; }

/* No-JS fallback list */
.plain-list {
  position: relative;
  z-index: 2;
  max-width: 520px;
  margin: 0 auto;
  padding: 0 24px;
  list-style: none;
  font-size: 14px;
}

.plain-list li { margin-bottom: 10px; }
.plain-list span { color: var(--muted); font-size: 12px; }
.plain-list a { color: var(--text); }

/* Phones have no hover, so the card parks above the wordmark and is opened
   by tapping a tile. Inline left/top from main.js is overridden wholesale. */
@media (max-width: 640px) {
  :root { --ball: 47px; }

  .panel {
    top: auto !important;
    bottom: 84px;
    left: 50% !important;
    translate: -50% 0;
    width: min(330px, 88vw);
  }

  .node-label { display: none; }
}
