:root {
  --bg: #05070d;
  --bg-2: #090c17;
  --surface: #0d1220;
  --surface-hover: #121830;
  --border: #1c2540;
  --border-strong: #2c3968;
  --text-primary: #e9edf7;
  --text-secondary: #8892ac;
  --text-muted: #565f82;
  --accent: #7fa8ff;
  --accent-dim: #37477e;
  --accent-2: #a595e8;
  --font-display: 'Space Grotesk', sans-serif;
  --font-body: 'Inter', sans-serif;
}

* { box-sizing: border-box; }

html { scroll-behavior: smooth; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text-primary);
  font-family: var(--font-body);
  line-height: 1.65;
  position: relative;
  min-height: 100vh;
}

a { color: inherit; text-decoration: none; }

/* ---------- starfield ---------- */

.starfield {
  position: fixed;
  inset: 0;
  z-index: -1;
  overflow: hidden;
  background: radial-gradient(ellipse at 50% -10%, #0d1430 0%, var(--bg) 55%);
}

/* Nebula: generated, not an image. An SVG feTurbulence filter produces
   fractal noise, which masks a field of coloured radial gradients so the
   colour breaks up into wisps instead of reading as soft blobs. Two layers
   at different scales give depth. Painted before .star children, so the
   stars still sit on top. */
.starfield::before,
.starfield::after {
  content: "";
  position: absolute;
  inset: -15%;
  pointer-events: none;
}

/* Every mask below is sized 100% 100% and set to no-repeat. A fixed px
   mask-size tiles the noise, and each tile boundary shows as a straight
   seam - the mask must cover the layer exactly once. */
.starfield::before,
.starfield::after,
.neb-fine {
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-size: 100% 100%;
  mask-size: 100% 100%;
}

/* Broad colour mass: blue core, magenta to the left, violet to the right. */
.starfield::before {
  background:
    radial-gradient(44% 52% at 60% 36%, rgba(58, 112, 236, 0.62), transparent 70%),
    radial-gradient(34% 46% at 6% 50%, rgba(206, 68, 128, 0.52), transparent 70%),
    radial-gradient(34% 44% at 92% 34%, rgba(150, 88, 232, 0.52), transparent 70%),
    radial-gradient(28% 34% at 84% 70%, rgba(226, 104, 158, 0.44), transparent 70%),
    radial-gradient(40% 36% at 30% 90%, rgba(72, 112, 222, 0.44), transparent 70%),
    radial-gradient(32% 28% at 70% 6%, rgba(122, 92, 216, 0.38), transparent 70%);
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='1200' height='800'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.0115' numOctaves='5' seed='3'/%3E%3CfeColorMatrix type='matrix' values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='1200' height='800'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.0115' numOctaves='5' seed='3'/%3E%3CfeColorMatrix type='matrix' values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  filter: blur(12px);
  opacity: 0.52;
}

/* Mid-scale filaments threaded through the broad colour. */
.starfield::after {
  background:
    radial-gradient(26% 30% at 56% 34%, rgba(140, 196, 255, 0.5), transparent 64%),
    radial-gradient(20% 26% at 10% 52%, rgba(255, 132, 186, 0.42), transparent 64%),
    radial-gradient(22% 26% at 88% 38%, rgba(214, 158, 255, 0.44), transparent 64%),
    radial-gradient(18% 22% at 80% 72%, rgba(255, 158, 190, 0.34), transparent 64%),
    radial-gradient(24% 24% at 28% 88%, rgba(130, 170, 255, 0.34), transparent 64%);
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='1200' height='800'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.028' numOctaves='5' seed='11'/%3E%3CfeColorMatrix type='matrix' values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='1200' height='800'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.028' numOctaves='5' seed='11'/%3E%3CfeColorMatrix type='matrix' values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  filter: blur(6px);
  opacity: 0.44;
}

/* Third pass: fine, near-white wisps that give the clouds their edge detail. */
.neb-fine {
  position: absolute;
  inset: -15%;
  pointer-events: none;
  background:
    radial-gradient(16% 20% at 54% 32%, rgba(206, 232, 255, 0.5), transparent 60%),
    radial-gradient(12% 16% at 8% 48%, rgba(255, 176, 214, 0.4), transparent 60%),
    radial-gradient(14% 16% at 90% 36%, rgba(226, 190, 255, 0.4), transparent 60%),
    radial-gradient(12% 14% at 34% 86%, rgba(180, 208, 255, 0.34), transparent 60%);
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='1200' height='800'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.07' numOctaves='4' seed='23'/%3E%3CfeColorMatrix type='matrix' values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='1200' height='800'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.07' numOctaves='4' seed='23'/%3E%3CfeColorMatrix type='matrix' values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  filter: blur(2px);
  opacity: 0.4;
}

/* ---------- planets ---------- */

/* Above the stars: a star drawn over a planet's disc reads as wrong, since
   the planet is the nearer body. */
.planet {
  position: absolute;
  z-index: 2;
  border-radius: 50%;
  transform: translate(-50%, -50%);
  box-shadow: 0 0 2.4em rgba(130, 165, 255, 0.16);
  opacity: 0.78;
}

/* Clips the scrolling texture bands to the disc. The base fill is the
   ocean, i.e. surface layer 1. */
.planet-globe {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  overflow: hidden;
  background: radial-gradient(circle at 34% 30%, #2f6690 0%, #1e4468 45%, #132a4c 76%, #0b1830 100%);
}

/* Both bands are three planet-widths wide and scroll exactly one width per
   cycle, so the loop is seamless. The mask travels with the band, so the
   continents and clouds move as one piece. */
.planet-land,
.planet-clouds {
  position: absolute;
  top: 0;
  left: 0;
  width: 300%;
  height: 100%;
  -webkit-mask-size: 33.3333% 100%;
  mask-size: 33.3333% 100%;
  -webkit-mask-repeat: repeat;
  mask-repeat: repeat;
  will-change: transform;
  animation: planet-spin linear infinite;
}

/* Surface layer 2: land, thresholded out of the noise so it meets the ocean
   at a coastline instead of fading into it.
   A low baseFrequency sets continent-scale landmasses, and the six octaves
   put the fine detail only in the coastlines. High frequency here is what
   produced scattered islands instead of continents.
   Colour bands run by latitude: tundra, forest, jungle, desert. */
.planet-land {
  background: linear-gradient(to bottom,
    #6f6f58 0%, #55704a 12%, #3f6b3c 27%, #37743c 44%,
    #5d7842 58%, #8a7c4e 74%, #7d7357 88%, #6c6b5a 100%);
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='300' height='300'%3E%3Cfilter id='n' x='0' y='0' width='100%25' height='100%25'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.01' numOctaves='6' seed='9' stitchTiles='stitch'/%3E%3CfeColorMatrix type='matrix' values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0'/%3E%3CfeComponentTransfer%3E%3CfeFuncA type='linear' slope='18' intercept='-9.9'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='300' height='300'%3E%3Cfilter id='n' x='0' y='0' width='100%25' height='100%25'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.01' numOctaves='6' seed='9' stitchTiles='stitch'/%3E%3CfeColorMatrix type='matrix' values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0'/%3E%3CfeComponentTransfer%3E%3CfeFuncA type='linear' slope='18' intercept='-9.9'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* Layer 3: cloud deck on its own noise, softer edged and faster moving. */
.planet-clouds {
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.95), rgba(222, 236, 255, 0.8));
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='300' height='300'%3E%3Cfilter id='n' x='0' y='0' width='100%25' height='100%25'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.02' numOctaves='5' seed='27' stitchTiles='stitch'/%3E%3CfeColorMatrix type='matrix' values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0'/%3E%3CfeComponentTransfer%3E%3CfeFuncA type='linear' slope='4' intercept='-2.35'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='300' height='300'%3E%3Cfilter id='n' x='0' y='0' width='100%25' height='100%25'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.02' numOctaves='5' seed='27' stitchTiles='stitch'/%3E%3CfeColorMatrix type='matrix' values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0'/%3E%3CfeComponentTransfer%3E%3CfeFuncA type='linear' slope='4' intercept='-2.35'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  opacity: 0.5;
}

/* ---- Mars variant: rust desert, dark basalt regions, thin dust, small caps ---- */

.planet--mars .planet-globe {
  background: radial-gradient(circle at 34% 30%, #c86a38 0%, #a4502a 44%, #6f3520 76%, #3a1c12 100%);
}

/* No oceans here, so the masked layer reads as dark albedo rock rather than
   land against water. */
.planet--mars .planet-land {
  background: linear-gradient(to bottom,
    #8a4527 0%, #7b3a22 22%, #66301d 46%, #7a3e24 68%, #93512c 88%, #a25a30 100%);
}

/* Mars has haze, not a cloud deck. */
.planet--mars .planet-clouds {
  background: linear-gradient(180deg, rgba(255, 226, 198, 0.9), rgba(255, 206, 170, 0.7));
  opacity: 0.16;
}

.planet--mars .planet-ice {
  background:
    linear-gradient(to bottom, rgba(255, 252, 248, 0.9) 0%, rgba(252, 246, 240, 0.35) 3%, transparent 6%),
    linear-gradient(to top, rgba(255, 252, 248, 0.75) 0%, rgba(252, 246, 240, 0.25) 2.5%, transparent 5%);
}

.planet--mars {
  box-shadow: 0 0 2.4em rgba(255, 150, 90, 0.16);
}

/* Ice caps sit at fixed latitudes, so unlike the land they do not scroll. */
.planet-ice {
  position: absolute;
  inset: 0;
  background:
    linear-gradient(to bottom, rgba(232, 244, 255, 0.85) 0%, rgba(228, 240, 255, 0.4) 5%, rgba(226, 238, 255, 0.12) 9%, transparent 13%),
    linear-gradient(to top, rgba(232, 244, 255, 0.8) 0%, rgba(228, 240, 255, 0.34) 4%, rgba(226, 238, 255, 0.1) 8%, transparent 12%);
}

@keyframes planet-spin {
  from { transform: translateX(0); }
  to { transform: translateX(-33.3333%); }
}

/* Shading sits above the moving bands and does not move, which is what
   reads as a sphere rotating rather than a flat texture sliding. */
.planet-shade {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background:
    radial-gradient(circle at 31% 26%, rgba(255, 255, 255, 0.3) 0%, rgba(255, 255, 255, 0.08) 22%, transparent 47%),
    radial-gradient(circle at 37% 33%, transparent 32%, rgba(4, 6, 14, 0.5) 70%, rgba(3, 4, 10, 0.96) 100%);
}

/* Rim light on the sunward edge. */
.planet-rim {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  box-shadow: inset 0.05em 0.06em 0.1em rgba(190, 220, 255, 0.4);
}

@media (prefers-reduced-motion: reduce) {
  .planet-land, .planet-clouds { animation: none; }
}

/* ---------- bright stars with diffraction spikes ---------- */

.star-bright {
  position: absolute;
  z-index: 1;
  width: var(--sb);
  height: var(--sb);
  transform: translate(-50%, -50%);
  border-radius: 50%;
  background: radial-gradient(circle, #fff 0%, rgba(214, 234, 255, 0.95) 26%, rgba(150, 190, 255, 0.35) 58%, transparent 76%);
  box-shadow: 0 0 calc(var(--sb) * 3) calc(var(--sb) * 0.7) rgba(160, 200, 255, 0.45);
  animation: twinkle var(--dur, 6s) linear infinite;
  animation-delay: var(--delay, 0s);
}

.star-bright::before,
.star-bright::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
}

.star-bright::before {
  width: calc(var(--sb) * 16);
  height: 1px;
  background: linear-gradient(90deg, transparent, rgba(222, 238, 255, 0.85), transparent);
}

.star-bright::after {
  width: 1px;
  height: calc(var(--sb) * 16);
  background: linear-gradient(180deg, transparent, rgba(222, 238, 255, 0.85), transparent);
}

/* Without mask support the gradients would show unmasked as hard colour
   blobs, which is worse than no nebula at all. */
@supports not ((mask-image: linear-gradient(#000, #000)) or (-webkit-mask-image: linear-gradient(#000, #000))) {
  .starfield::before, .starfield::after, .neb-fine, .planet::before { display: none; }
}

/* Large blurred surfaces are the expensive part on phones, so the finest
   layer is dropped and the others blur less. */
@media (max-width: 700px) {
  .neb-fine { display: none; }
  .starfield::before { filter: blur(8px); opacity: 0.46; }
  .starfield::after { filter: blur(5px); opacity: 0.38; }
  .planet { opacity: 0.6; }
}

.star {
  position: absolute;
  background: #cfe0ff;
  border-radius: 50%;
  z-index: 1;
}

/* A raised cosine sampled every 10%: opacity = min + amp * (0.5 - 0.5cos(2*pi*t)).
   The old version had only two stops on an ease-in-out, which lingered at each
   extreme and then dropped away quickly - stars appeared to blink out. Stepping
   the curve and running it linear makes the rise and fall continuous. */
@keyframes twinkle {
  0%, 100% { opacity: var(--min-o, 0.15); }
  10%, 90% { opacity: calc(var(--min-o, 0.15) + var(--amp, 0.7) * 0.0955); }
  20%, 80% { opacity: calc(var(--min-o, 0.15) + var(--amp, 0.7) * 0.3455); }
  30%, 70% { opacity: calc(var(--min-o, 0.15) + var(--amp, 0.7) * 0.6545); }
  40%, 60% { opacity: calc(var(--min-o, 0.15) + var(--amp, 0.7) * 0.9045); }
  50% { opacity: calc(var(--min-o, 0.15) + var(--amp, 0.7)); }
}

.star { animation: twinkle var(--dur, 4s) linear infinite; animation-delay: var(--delay, 0s); }

@media (prefers-reduced-motion: reduce) {
  .star, .star-bright { animation: none; opacity: 0.5; }
  html { scroll-behavior: auto; }
}

/* ---------- header ---------- */

.site-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 1.5rem 2rem;
  border-bottom: 1px solid var(--border);
  position: sticky;
  top: 0;
  background: rgba(5, 7, 13, 0.8);
  backdrop-filter: blur(8px);
  z-index: 10;
}

.logo {
  font-family: var(--font-display);
  font-weight: 500;
  letter-spacing: 0.02em;
  color: var(--text-primary);
}

.logo span { color: var(--accent); transition: color 0.2s ease; }

/* The wordmark links home, so it needs a hover cue like the nav links have. */
a.logo:hover span, a.logo:focus-visible span { color: #9cbcff; }

.site-header nav { display: flex; gap: 2rem; }

.site-header nav a {
  font-size: 0.85rem;
  letter-spacing: 0.03em;
  color: var(--text-secondary);
  padding-bottom: 4px;
  border-bottom: 1px solid transparent;
  transition: color 0.2s ease, border-color 0.2s ease;
}

.site-header nav a:hover,
.site-header nav a:focus-visible {
  color: var(--text-primary);
}

.site-header nav a.active {
  color: var(--accent);
  border-color: var(--accent);
}

/* ---------- layout ---------- */

main {
  max-width: 900px;
  margin: 0 auto;
  padding: 0 2rem;
  position: relative;
}

section { padding: 5rem 0; }

.section-border { border-bottom: 1px solid var(--border); }

h1, h2, h3 { font-family: var(--font-display); font-weight: 500; margin: 0; }

h2 { font-size: 1.05rem; letter-spacing: 0.02em; margin-bottom: 0.5rem; }

.section-sub { color: var(--text-muted); font-size: 0.85rem; margin: 0 0 2.5rem; }

.eyebrow {
  font-family: var(--font-display);
  color: var(--accent);
  font-size: 0.8rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  margin: 0 0 1.25rem;
}

/* ---------- hero ---------- */

.hero { padding: 6rem 0 5rem; }

.hero h1 {
  font-size: clamp(2rem, 5vw, 3rem);
  line-height: 1.2;
  margin-bottom: 1.5rem;
  max-width: 620px;
}

.hero-sub {
  max-width: 520px;
  color: var(--text-secondary);
  font-size: 1.05rem;
  margin: 0 0 2.5rem;
}

.hero-links { display: flex; gap: 0.75rem; flex-wrap: wrap; }

/* ---------- buttons ---------- */

.btn {
  display: inline-block;
  padding: 0.7rem 1.35rem;
  border: 1px solid var(--border-strong);
  border-radius: 4px;
  font-size: 0.85rem;
  color: var(--text-primary);
  transition: border-color 0.2s ease, background 0.2s ease;
}

.btn:hover, .btn:focus-visible { border-color: var(--accent); background: var(--surface-hover); }

.btn-primary {
  background: var(--accent);
  color: #05070d;
  border-color: var(--accent);
  font-weight: 500;
}

.btn-primary:hover, .btn-primary:focus-visible { background: #9cbcff; }

/* ---------- grids & cards ---------- */

.grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  gap: 1.1rem;
}

.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 1.25rem;
  transition: border-color 0.2s ease, background 0.2s ease, transform 0.2s ease;
  display: flex;
  flex-direction: column;
}

.card:hover {
  background: var(--surface-hover);
  border-color: var(--border-strong);
  transform: translateY(-2px);
}

/* Full-bleed thumbnail: negative margins cancel the card's own padding,
   so cards without media stay untouched. */
.card-media {
  display: block;
  margin: -1.25rem -1.25rem 1rem;
  aspect-ratio: 63 / 50;
  background: var(--bg-2);
  border-bottom: 1px solid var(--border);
  border-radius: 7px 7px 0 0;
  overflow: hidden;
}

.card-media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  opacity: 0.88;
  transition: opacity 0.2s ease;
}

.card:hover .card-media img { opacity: 1; }

@media (prefers-reduced-motion: reduce) {
  .card-media img { transition: none; }
}

.card h3 { font-size: 1rem; margin-bottom: 0.5rem; }

.card p { font-size: 0.87rem; color: var(--text-secondary); margin: 0 0 1rem; flex-grow: 1; }

.badge-row { display: flex; gap: 0.4rem; flex-wrap: wrap; margin-bottom: 0.9rem; }

.badge {
  font-family: var(--font-display);
  font-size: 0.68rem;
  letter-spacing: 0.02em;
  padding: 0.2rem 0.55rem;
  border-radius: 3px;
  border: 1px solid var(--border);
  color: var(--text-secondary);
}

.badge-play { color: var(--accent); border-color: var(--accent-dim); }
.badge-lang { color: var(--accent-2); border-color: #3c3468; }
.badge-collab { color: var(--accent-2); border-color: #3c3468; }

/* Group headings repeat down a page, so they sit tighter than a lone section. */
.group + .group { padding-top: 3.5rem; }

/* ---------- view modes: scroll / grid / list ----------
   One set of card markup, three layouts. The mode lives on
   .list-view[data-mode], so switching is a single attribute swap. */

.view-bar { display: flex; justify-content: flex-end; margin-bottom: 1rem; }

.view-toggle {
  display: inline-flex;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--surface);
  padding: 2px;
}

.view-toggle button {
  font-family: var(--font-display);
  font-size: 0.72rem;
  letter-spacing: 0.02em;
  padding: 0.3rem 0.75rem;
  border: 0;
  border-radius: 999px;
  background: none;
  color: var(--text-muted);
  cursor: pointer;
  transition: color 0.2s ease, background 0.2s ease;
}

.view-toggle button:hover { color: var(--text-primary); }

.view-toggle button[aria-pressed="true"] {
  background: var(--surface-hover);
  color: var(--accent);
}

/* -- shared -- */

.list-view { display: grid; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); gap: 1.1rem; }

/* -- 1. scroll: peek shelf -- */

.shelf { position: relative; }

.list-view[data-mode="scroll"] {
  display: flex;
  gap: 1.1rem;
  overflow-x: auto;
  /* "proximity" rather than "mandatory": mandatory can trap keyboard and
     assistive-tech users on content that sits between snap points. */
  scroll-snap-type: x proximity;
  /* Stops a sideways fling at the end of a shelf from chaining into the
     browser's swipe-to-go-back gesture. */
  overscroll-behavior-x: contain;
  padding-bottom: 0.85rem;
  scrollbar-width: thin;
  scrollbar-color: var(--border-strong) transparent;
}

.list-view[data-mode="scroll"] > .card {
  flex: 0 0 clamp(200px, 42vw, 232px);
  scroll-snap-align: start;
}

.list-view[data-mode="scroll"]::-webkit-scrollbar { height: 6px; }
.list-view[data-mode="scroll"]::-webkit-scrollbar-track { background: transparent; }
.list-view[data-mode="scroll"]::-webkit-scrollbar-thumb {
  background: var(--border-strong);
  border-radius: 3px;
}

/* Edge fades hint that the row continues. Toggled by JS, so they never
   sit over an edge that has nothing beyond it. */
.shelf::before, .shelf::after {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0.85rem;
  width: 3.5rem;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.25s ease;
  z-index: 2;
}

.shelf::before { left: 0; background: linear-gradient(to right, var(--bg), transparent); }
.shelf::after { right: 0; background: linear-gradient(to left, var(--bg), transparent); }
.shelf.can-prev::before { opacity: 1; }
.shelf.can-next::after { opacity: 1; }

.shelf-arrow {
  position: absolute;
  top: calc(50% - 0.4rem);
  transform: translateY(-50%);
  z-index: 3;
  display: grid;
  place-items: center;
  width: 2.1rem;
  height: 2.1rem;
  padding: 0;
  border: 1px solid var(--border-strong);
  border-radius: 50%;
  background: rgba(9, 12, 23, 0.92);
  color: var(--text-primary);
  font-family: var(--font-display);
  font-size: 1.05rem;
  line-height: 1;
  cursor: pointer;
  opacity: 0;
  transition: opacity 0.2s ease, border-color 0.2s ease, background 0.2s ease;
}

.shelf-prev { left: 0.35rem; }
.shelf-next { right: 0.35rem; }

.shelf:hover .shelf-arrow, .shelf-arrow:focus-visible { opacity: 1; }
.shelf-arrow:hover { border-color: var(--accent); background: var(--surface-hover); }
.shelf-arrow[disabled] { display: none; }

/* Touch devices scroll directly and never hover, so the arrows are dead weight. */
@media (hover: none) {
  .shelf-arrow { display: none; }
}

/* -- 2. grid: the original layout -- */

.list-view[data-mode="grid"] {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  gap: 1.1rem;
}

/* -- 3. list: compact rows, click to expand -- */

.list-view[data-mode="list"] { display: block; }

.list-view[data-mode="list"] > .card {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  grid-template-areas:
    "media title icons"
    "media desc  icons";
  align-items: center;
  column-gap: 1rem;
  row-gap: 0.15rem;
  padding: 0.8rem 0.6rem;
  border-width: 0 0 1px;
  border-radius: 0;
  background: none;
  cursor: pointer;
}

.list-view[data-mode="list"] > .card:hover {
  background: var(--surface);
  border-color: var(--border);
  transform: none;
}

.list-view[data-mode="list"] > .card:last-child { border-bottom: 0; }

/* .card-media carries negative margins for the full-bleed grid thumbnail,
   so they have to be undone before it can be a small inline square. */
.list-view[data-mode="list"] .card-media {
  grid-area: media;
  margin: 0;
  width: 3.1rem;
  height: 3.1rem;
  aspect-ratio: auto;
  border: 1px solid var(--border);
  border-radius: 4px;
}

.list-view[data-mode="list"] .card h3 { grid-area: title; margin: 0; font-size: 0.92rem; }

.list-view[data-mode="list"] .card > p {
  grid-area: desc;
  margin: 0;
  flex-grow: 0;
  font-size: 0.82rem;
  /* Cap at one line and let CSS add the ellipsis, so it stays correct
     on resize without measuring anything in JS. */
  display: -webkit-box;
  -webkit-line-clamp: 1;
  line-clamp: 1;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* Badges and the link get a row each - sharing one grid area stacks them. */
.list-view[data-mode="list"] .card .badge-row { grid-area: badges; margin: 0.7rem 0 0; }
.list-view[data-mode="list"] .card .card-link { grid-area: link; margin: 0.55rem 0 0; }

/* Collapsed rows show the icon strip only; badges and the link appear on expand. */
.list-view[data-mode="list"] .card .badge-row,
.list-view[data-mode="list"] .card .card-link { display: none; }

.list-view[data-mode="list"] .card.is-open {
  grid-template-areas:
    "media title  icons"
    "media desc   icons"
    "badges badges badges"
    "link   link   link";
  background: var(--surface);
}

.list-view[data-mode="list"] .card.is-open > p { -webkit-line-clamp: unset; line-clamp: unset; overflow: visible; }
.list-view[data-mode="list"] .card.is-open .badge-row { display: flex; }
.list-view[data-mode="list"] .card.is-open .card-link { display: block; margin-bottom: 0.2rem; }

/* -- platform / target icons (list mode) -- */

.card-icons { display: none; gap: 0.5rem; align-items: center; }
.list-view[data-mode="list"] .card-icons { display: flex; grid-area: icons; }

.card-icons svg {
  width: 1rem;
  height: 1rem;
  fill: none;
  stroke: var(--text-muted);
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.card-icons svg.icon-solid { fill: var(--text-muted); stroke: none; }
.list-view[data-mode="list"] .card:hover .card-icons svg { stroke: var(--accent); }
.list-view[data-mode="list"] .card:hover .card-icons svg.icon-solid { fill: var(--accent); stroke: none; }

/* Tooltip: the label is already on the svg for screen readers, this is the
   sighted equivalent. Built in CSS so it matches the site and appears
   instantly, rather than waiting on the browser's native title delay. */
.card-icon { position: relative; display: inline-grid; place-items: center; }

.card-icon::after {
  content: attr(data-tip);
  position: absolute;
  bottom: calc(100% + 0.4rem);
  /* Anchored right rather than centred: the icon strip is flush with the row's
     right edge, so a centred tooltip can spill past it on narrow screens. */
  right: 0;
  z-index: 5;
  white-space: nowrap;
  font-family: var(--font-display);
  font-size: 0.68rem;
  letter-spacing: 0.02em;
  padding: 0.25rem 0.55rem;
  border: 1px solid var(--border-strong);
  border-radius: 4px;
  background: var(--bg-2);
  color: var(--text-primary);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.15s ease;
}

.card-icon:hover::after { opacity: 1; }
.card-icon:hover svg { stroke: var(--accent); }
.card-icon:hover svg.icon-solid { fill: var(--accent); stroke: none; }

@media (prefers-reduced-motion: reduce) {
  .card-icon::after { transition: none; }
}

/* ---------- in-page section jump nav ---------- */

.section-nav { display: flex; gap: 0.5rem; flex-wrap: wrap; }

.section-nav a {
  font-family: var(--font-display);
  font-size: 0.78rem;
  letter-spacing: 0.02em;
  padding: 0.4rem 0.85rem;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--surface);
  color: var(--text-secondary);
  transition: color 0.2s ease, border-color 0.2s ease, background 0.2s ease;
}

.section-nav a:hover, .section-nav a:focus-visible {
  color: var(--text-primary);
  border-color: var(--accent-dim);
  background: var(--surface-hover);
}

/* The header is sticky, so an anchor jump would otherwise land with the
   target heading hidden underneath it. */
[id] { scroll-margin-top: 6rem; }

/* ---------- page end row / back to top ---------- */

.page-end {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
}

.to-top {
  font-family: var(--font-display);
  font-size: 0.8rem;
  color: var(--text-muted);
  transition: color 0.2s ease;
}

.to-top:hover, .to-top:focus-visible { color: var(--accent); }

.card-link { font-size: 0.82rem; color: var(--accent); margin-top: auto; }
.card-link:hover, .card-link:focus-visible { color: #b8ccff; }

/* ---------- stack / list rows ---------- */

.row-list { list-style: none; margin: 0; padding: 0; }

.row-list li {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  padding: 0.9rem 0;
  border-bottom: 1px solid var(--border);
}

.row-list li:last-child { border-bottom: none; }

.row-name { font-family: var(--font-display); }
.row-desc { color: var(--text-secondary); font-size: 0.85rem; text-align: right; }

/* ---------- contact ---------- */

.contact-list { display: flex; flex-direction: column; gap: 0.75rem; margin: 2rem 0; max-width: 420px; }

.contact-item {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 0.9rem 1.1rem;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--surface);
  transition: border-color 0.2s ease;
  font-size: 0.9rem;
}

.contact-item:hover, .contact-item:focus-visible { border-color: var(--accent-dim); }

.contact-label { color: var(--text-muted); font-family: var(--font-display); font-size: 0.75rem; letter-spacing: 0.03em; text-transform: uppercase; }

/* ---------- footer ---------- */

footer {
  text-align: center;
  padding: 2.5rem 2rem;
  color: var(--text-muted);
  font-size: 0.78rem;
  border-top: 1px solid var(--border);
}

/* ---------- accessibility ---------- */

a:focus-visible, .btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* ---------- responsive ---------- */

@media (max-width: 600px) {
  .site-header { padding: 1.1rem 1.4rem; flex-direction: column; gap: 0.8rem; }
  .site-header nav { gap: 1.2rem; }
  main { padding: 0 1.4rem; }
  section { padding: 3rem 0; }
  .hero { padding: 3.5rem 0 3rem; }
  .row-list li { flex-direction: column; align-items: flex-start; gap: 0.3rem; }
  .row-desc { text-align: left; }
  /* The header stacks into two rows here, so anchors need more clearance. */
  [id] { scroll-margin-top: 8.5rem; }
}

/* ---------- news ---------- */

.news-meta {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  margin-bottom: 0.6rem;
  font-family: var(--font-display);
  font-size: 0.7rem;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  color: var(--text-muted);
}

.news-meta time { margin-left: auto; text-transform: none; letter-spacing: 0; }

.news-icon { display: inline-flex; }

.news-icon svg {
  width: 0.95rem;
  height: 0.95rem;
  fill: none;
  stroke: var(--accent);
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* -- full feed: month groups on a timeline -- */

.feed-month {
  font-size: 0.9rem;
  color: var(--text-secondary);
  margin: 2.4rem 0 0.9rem;
  padding-bottom: 0.5rem;
  border-bottom: 1px solid var(--border);
}

.feed-month:first-child { margin-top: 0; }

.feed { list-style: none; margin: 0; padding: 0; }

.feed-item {
  position: relative;
  display: grid;
  grid-template-columns: 3.2rem minmax(0, 1fr);
  gap: 1.1rem;
  padding: 1.1rem 0;
}

/* The spine sits in the gutter between the date and the body, so it never
   crosses text and the date needs no opaque backing plate to hide it -
   which matters here, since a solid plate would punch a hole in the nebula. */
.feed-item::before {
  content: "";
  position: absolute;
  left: calc(3.2rem + 0.55rem);
  top: 0;
  bottom: 0;
  width: 1px;
  background: var(--border);
}

/* Node on the spine, level with each entry's heading. */
.feed-item::after {
  content: "";
  position: absolute;
  left: calc(3.2rem + 0.55rem - 2px);
  top: 1.5rem;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--accent-dim);
}

.feed-item:first-child::before { top: 1.6rem; }
.feed-item:last-child::before { bottom: calc(100% - 1.6rem); }

.feed-date {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-start;
  padding-top: 0.15rem;
  font-family: var(--font-display);
}

.feed-day { font-size: 1.1rem; color: var(--text-primary); line-height: 1.1; }
.feed-mon { font-size: 0.68rem; letter-spacing: 0.06em; text-transform: uppercase; color: var(--text-muted); }

.feed-body { min-width: 0; }
.feed-body h4 { font-family: var(--font-display); font-weight: 500; font-size: 1rem; margin: 0 0 0.4rem; }
.feed-body p { font-size: 0.88rem; color: var(--text-secondary); margin: 0 0 0.9rem; }
.feed-body .news-meta { margin-bottom: 0.35rem; }
.feed-body .badge-row { margin-bottom: 0.6rem; }

@media (max-width: 600px) {
  .feed-item { grid-template-columns: 2.6rem minmax(0, 1fr); gap: 0.8rem; }
  .feed-item::before { left: calc(2.6rem + 0.4rem); }
  .feed-item::after { left: calc(2.6rem + 0.4rem - 2px); }
}

/* ---------- badge icons ----------
   Icons inherit the badge's own colour through currentColor, so the accent
   and language badge variants tint their icon to match without extra rules. */

.badge { display: inline-flex; align-items: center; gap: 0.32rem; }

.tag-icon { display: inline-flex; flex: 0 0 auto; }

.tag-icon svg {
  width: 0.78rem;
  height: 0.78rem;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.7;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.tag-icon svg.icon-solid { fill: currentColor; stroke: none; }

/* ---------- news year / month picker ---------- */

.cal { margin-bottom: 1.6rem; }

.cal-row { display: flex; align-items: center; gap: 0.4rem; flex-wrap: wrap; }
.cal-row + .cal-row { margin-top: 0.55rem; }

.cal-label {
  font-family: var(--font-display);
  font-size: 0.68rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-muted);
  width: 3.2rem;
  flex: 0 0 auto;
}

.cal-btn {
  display: inline-flex;
  align-items: baseline;
  gap: 0.3rem;
  font-family: var(--font-display);
  font-size: 0.76rem;
  letter-spacing: 0.02em;
  padding: 0.32rem 0.7rem;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--surface);
  color: var(--text-secondary);
  cursor: pointer;
  transition: color 0.2s ease, border-color 0.2s ease, background 0.2s ease;
}

.cal-btn:hover:not([disabled]) {
  color: var(--text-primary);
  border-color: var(--accent-dim);
  background: var(--surface-hover);
}

.cal-btn[aria-pressed="true"] {
  color: var(--accent);
  border-color: var(--accent-dim);
  background: var(--surface-hover);
}

/* Empty months stay in place so the row keeps a calendar's shape. */
.cal-btn[disabled] {
  opacity: 0.32;
  cursor: default;
  background: none;
}

.cal-count { font-size: 0.62rem; color: var(--text-muted); }
.cal-btn[aria-pressed="true"] .cal-count { color: var(--accent); }

.cal-summary { color: var(--text-muted); font-size: 0.8rem; margin: 0 0 1.4rem; }

@media (max-width: 600px) {
  .cal-label { width: 100%; margin-bottom: 0.1rem; }
  .cal-btn { padding: 0.3rem 0.6rem; font-size: 0.72rem; }
}
