/* kamjapowered, design v0.
   the contract is ~/projects/kamja/docs/design/v0/design.md. when this file and
   that document disagree, this file wins from m4 on and the document gets fixed.

   dark only. the light theme was dropped 2026-08-23. */

/* self hosted, latin subset, all three OFL. no third party origin on either
   site, which is why the head carries no preconnect. downloaded 2026-08-24. */
@font-face {
  font-family: Orbit; src: url("/static/fonts/orbit-400.woff2") format("woff2");
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "JetBrains Mono"; src: url("/static/fonts/jetbrainsmono-400.woff2") format("woff2");
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "JetBrains Mono"; src: url("/static/fonts/jetbrainsmono-500.woff2") format("woff2");
  font-weight: 500; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "JetBrains Mono"; src: url("/static/fonts/jetbrainsmono-700.woff2") format("woff2");
  font-weight: 700; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Space Grotesk"; src: url("/static/fonts/spacegrotesk-400.woff2") format("woff2");
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Space Grotesk"; src: url("/static/fonts/spacegrotesk-500.woff2") format("woff2");
  font-weight: 500; font-style: normal; font-display: swap;
}

:root {
  /* ground. space is the one theme where dark is the subject rather than a
     preference, per design/README.md */
  --ground: #16181d;
  --band: #20242b;          /* laid over ground at 55%, composites to #1c1f25 */
  --card: #1e2127;
  --sink: #0e1013;          /* footer, and video thumbnails */

  --text: #eceef2;
  --muted: #9aa0a6;         /* body copy. 6.73:1 on ground, 6.11:1 on card */
  --card-line: #20242b;   /* the card stroke in the frame */
  --orbit-line: #3c4048;  /* the arc stroke */
  --rule: #2b2f37;

  /* the two accents. green means alive or shipped, pink means attention or
     unfinished. nothing is coloured for decoration. */
  --green: #66b7a2;         /* fills: buttons, live cells */
  --green-text: #7fd2bb;    /* text: eyebrows, links, tags. 10.01:1 on ground */
  --green-wash: #22443d;    /* released tag background */
  --pink-solid: #b7667c;    /* the frame's pink: cells, eyebrow markers, ships */
  --pink: #d98ba0;          /* fills. raised from #b7667c at m4, see below */
  --pink-text: #d98ba0;     /* text: eyebrow markers. 6.89:1 on ground */
  --pink-wash: #452a34;     /* in progress tag background */

  /* a solid button's label is the ground, never --text. verified 2026-08-24:
     --text on --green is 2.04:1 and on the old #b7667c pink 3.47:1, both far
     under the 4.5 floor design/README.md sets. ground on green is 7.49:1 and
     on this pink 6.89:1. the pink was raised to the colour the palette already
     used for pink text, so m4 introduced no new colour. */
  --on-accent: var(--ground);

  /* four sizes, nothing between them. a size not on this list does not go on
     the page, and adding one is a decision that lands in design.md first. */
  --display: 64px;   --display-lh: 82px;
  --section: 44px;   --section-lh: 52px;
  --lead: 20px;      --lead-lh: 32px;
  --body: 16px;      --body-lh: 26px;

  --display-font: Orbit, ui-sans-serif, system-ui, sans-serif;
  --mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
  --prose: "Space Grotesk", ui-sans-serif, system-ui, sans-serif;

  --gutter: 120px;
  --column: 1200px;
  --viewport: 900px;    /* every section is exactly one, except the footer */
  --pad: 96px;          /* a minimum, not a fixed value: sections centre */
  --gap: 24px;
  --card-pad: 28px;
}

/* the type scale below the phone stop. no fifth size: the two Orbit steps
   scale by 0.6 and the two Space Grotesk steps do not move, because 20 and 16
   already read fine at 390. decided 2026-08-21 in design.md. */
@media (max-width: 719px) {
  :root {
    --display: 38px;  --display-lh: 46px;
    --section: 26px;  --section-lh: 32px;
    --gutter: 24px;
    --pad: 56px;
    --viewport: auto;  /* a 900 box on a 844 phone is not a viewport */
  }
}

@media (max-width: 1100px) and (min-width: 720px) {
  :root { --gutter: 56px; }
}

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

html { color-scheme: dark; }

body {
  margin: 0;
  position: relative;
  overflow-x: hidden;
  background: var(--ground);
  color: var(--muted);
  font: var(--body)/var(--body-lh) var(--prose);

  /* the starfield. the art is 1440x1200 and figma renders it at HALF scale,
     and the approved look is the rendered one. serving it at natural size
     doubles every star. see traps in claude/repo.md. */
  background-image: url("/static/img/stars.png");
  background-size: 720px 600px;
  background-repeat: repeat;
  image-rendering: pixelated;
}

/* zero radius anywhere, one pixel borders, square buttons */
h1, h2, h3, p, ul { margin: 0; }
ul { list-style: none; padding: 0; }
a { color: var(--green-text); }

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

header, footer, section {
  padding-inline: var(--gutter);
  position: relative;
  z-index: 1;   /* above the plate. the band fills are semi transparent, so the
                   arcs still show through, which is what the frame does. */
}

section {
  min-height: var(--viewport);
  padding-block: var(--pad);
  display: flex;
  flex-direction: column;
  justify-content: center;   /* content centres, the padding is the floor */
  align-items: flex-start;
  gap: var(--gap);
}

/* the column rule is for content. keep it at the lowest specificity that works,
   so a later rule on a specific element still wins: `h1 { max-width }` sets the
   headline measure and a :not() here would silently outrank it. */
section > * { width: 100%; max-width: var(--column); }

/* the plate is art bled to the frame edges, and the button hugs its label.
   neither is content, so both opt out by class rather than by exclusion. */
section > .plate { width: auto; max-width: none; }
section > .more { width: auto; }

/* alternating ground. the fill is computed from position by the generator, so
   these two classes are the only place the alternation is expressed. */
.paper { background: transparent; }
.band  { background: color-mix(in srgb, var(--band) 55%, transparent); }

/* ---- type ------------------------------------------------------------ */

h1 {
  font: var(--display)/var(--display-lh) var(--display-font);
  letter-spacing: -0.02em;
  color: var(--text);
  /* the frame breaks after "space". constrain the measure rather than putting
     a <br> in the content, which would be markup in a copy string. */
  max-width: 11em;
  text-wrap: balance;
}

.hero p { max-width: 560px; }

h2 {
  font: var(--section)/var(--section-lh) var(--display-font);
  letter-spacing: -0.02em;
  color: var(--text);
  text-wrap: balance;
}

h3 {
  font: 700 var(--body)/1.4 var(--mono);
  color: var(--text);
}

/* the eyebrow is a pair: a 9px cell and a tracked label, both the same accent.
   the frame sets it per section and the order is not positional, so it is
   authored in the content rather than computed. */
.eyebrow {
  font: 500 var(--body)/1.4 var(--mono);
  letter-spacing: 0.04em;
  display: flex;
  align-items: center;
  gap: 12px;
}

.eyebrow::before {
  content: "";
  width: 9px; height: 9px;
  flex: none;
}

.eyebrow.green            { color: var(--green-text); }
.eyebrow.green::before    { background: var(--green); }
.eyebrow.pink             { color: var(--pink-text); }
.eyebrow.pink::before     { background: #b7667c; }

.hero p, section > p {
  font: var(--lead)/var(--lead-lh) var(--prose);
}

/* the frame gives the section intro a fixed 720 measure */
.intro { max-width: 720px; }

/* ---- header, footer -------------------------------------------------- */

header {
  padding-block: 40px;
  display: flex;
  align-items: center;
  gap: var(--gap);
  flex-wrap: wrap;
}

.wordmark {
  font: var(--lead)/1.4 var(--display-font);
  color: var(--text);
  text-decoration: none;
  margin-inline-end: auto;
}

header nav {
  display: flex;
  align-items: center;
  gap: 32px;
}

header nav a {
  font: 500 var(--body)/1.4 var(--mono);
  letter-spacing: 0.01em;
  color: var(--muted);
  text-decoration: none;
}

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

/* a solid accent button's label is the ground, never --text. 7.49:1. */
header nav a.btn {
  color: var(--on-accent);
  padding: 9px 14px;
}
header nav a.btn.green { background: var(--green); }
header nav a.btn.pink  { background: var(--pink); }
header nav a.btn.green:hover, header nav a.btn.green:focus-visible { background: var(--green-text); }
header nav a.btn.pink:hover,  header nav a.btn.pink:focus-visible  { background: var(--pink-text); }

@media (max-width: 719px) {
  header nav { gap: 18px; width: 100%; }
  .wordmark { margin-inline-end: 0; width: 100%; }
}

footer {
  background: var(--sink);
  padding-block: 80px 48px;
  display: flex;
  flex-direction: column;
  gap: 56px;
}

footer .cols {
  display: grid;
  grid-template-columns: 1fr repeat(4, auto);
  gap: 80px;
  align-items: start;
}

footer .wordmark {
  font: var(--lead)/1.4 var(--display-font);
  color: var(--text);
}

footer .col {
  display: flex;
  flex-direction: column;
  gap: 14px;
}

footer .col .head {
  font: 500 var(--body)/1.4 var(--mono);
  color: var(--text);
}

footer .col a, footer .col span {
  font: var(--body)/var(--body-lh) var(--prose);
  color: var(--muted);
  text-decoration: none;
}

footer .col a:hover, footer .col a:focus-visible { color: var(--green-text); }

footer hr {
  border: 0;
  border-top: 1px solid var(--rule);
  margin: 0;
  width: 100%;
}

footer .legal {
  display: flex;
  justify-content: space-between;
  gap: var(--gap);
  font: var(--body)/1.4 var(--mono);
  color: var(--muted);
}

@media (max-width: 900px) {
  footer .cols { grid-template-columns: repeat(2, 1fr); gap: 40px; }
  footer .legal { flex-direction: column; }
}

/* ---- cards ----------------------------------------------------------- */

.cards ul {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(320px, 100%), 1fr));
  gap: var(--gap);
  width: 100%;
}

.cards li {
  background: var(--card);
  border: 1px solid var(--card-line);
  padding: var(--card-pad);
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.cards li .bar {
  width: 28px;
  height: 4px;
  background: var(--green);
  flex: none;
}

.cards li .meta {
  font: var(--body)/1.4 var(--mono);
  color: var(--muted);
}

.cards li a {
  font: 500 var(--body)/1.4 var(--mono);
  color: var(--green-text);
  text-decoration: none;
  margin-block-start: auto;
}

.cards li a::after { content: "  \2192"; white-space: pre; }
.cards li a:hover, .cards li a:focus-visible { color: var(--text); }



.tag {
  font: 500 var(--body)/1 var(--mono);
  padding: 4px 8px;
  margin-inline-start: 8px;
}

.tag.released { background: var(--green-wash); color: var(--green-text); }
.tag.pending  { background: var(--pink-wash);  color: var(--pink-text);  }

.videos ul {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(320px, 100%), 1fr));
  gap: var(--gap);
  width: 100%;
}

.videos li {
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.videos .thumb {
  display: block;
  width: 100%;
  aspect-ratio: 384 / 200;
  background: var(--sink);
}

.videos .meta {
  font: var(--body)/1.4 var(--mono);
  color: var(--muted);
}

.videos h3 {
  font: 500 var(--lead)/var(--lead-lh) var(--prose);
  color: var(--text);
}

.more.pink { background: var(--pink); }
.more.pink:hover, .more.pink:focus-visible { background: var(--pink-text); }

/* ---- links and buttons ----------------------------------------------- */

.more::after { content: "  \2192"; white-space: pre; }

.more {
  font: 500 var(--body)/1 var(--mono);
  background: var(--green);
  color: var(--on-accent);
  padding: 15px 22px;
  text-decoration: none;
  align-self: flex-start;
}

/* hovers snap rather than ease. that is a design decision already made and it
   is not to be softened here. design.md, and f1 for the full state set. */
.more:hover, .more:focus-visible { background: var(--green-text); }

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

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

/* a light astronomical plate, not a photograph of space. every number below is
   read out of `kamja landing · dark`: the three arcs are concentric on
   (1600,479) in a 1440x900 frame, so the centre sits 160px past the right edge,
   and both moons sit exactly on an arc, at r=620 and r=880. */

/* 1760 across and centred on y=479, so the largest arc reaches y=1359, well
   into the second section. clipping it to the hero was wrong. */
.plate {
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 1400px;
  z-index: 0;
  pointer-events: none;
  overflow: hidden;
}

/* the origin every arc and moon is placed from */
.system {
  position: absolute;
  right: -160px;
  top: 479px;
  width: 0; height: 0;
}

.system .orbit {
  position: absolute;
  left: 50%; top: 50%;
  transform: translate(-50%, -50%);
  border: 1px solid var(--orbit-line);
  border-radius: 50%;
}
.system .orbit.a { width: 1760px; height: 1760px; }
.system .orbit.b { width: 1240px; height: 1240px; }
.system .orbit.c { width: 800px;  height: 800px;  }

/* a 520 body whose centre is 30px left of the system centre, with a 360x520
   ellipse laid over its right. that ellipse is what makes the terminator a
   curve, and it is why the lit part is the left 160px. */
.system .planet {
  position: absolute;
  width: 520px; height: 520px;
  left: -30px; top: 0;
  transform: translate(-50%, -50%);
  border-radius: 50%;
  background: #20242b;
  border: 1px solid #33373f;
  overflow: hidden;
}
.system .planet::after {
  content: "";
  position: absolute;
  left: 160px; top: 0;
  width: 360px; height: 520px;
  border-radius: 50%;
  background: #1a1d23;
}

/* each moon rides its arc. the wrapper rotates about the system centre and the
   child sits out at the radius, so the moon can never leave its orbit. */
.system .moon {
  position: absolute;
  left: 0; top: 0;
  width: 0; height: 0;
  transform-origin: 0 0;
  animation: orbit var(--dur) linear infinite;
}

.system .moon i {
  position: absolute;
  display: block;
  width: var(--sz); height: var(--sz);
  left: calc(var(--r) - var(--sz) / 2);
  top: calc(var(--sz) / -2);
  border-radius: 50%;
}

.system .m1 { --r: 620px; --sz: 56px; --a0: -162deg; --dur: 42s; }
.system .m1 i { background: var(--pink-wash); border: 1px solid #b7667c; }

.system .m2 { --r: 880px; --sz: 38px; --a0: 152deg; --dur: 61s; }
.system .m2 i { background: var(--green-wash); border: 1px solid var(--green); }

@keyframes orbit {
  from { transform: rotate(var(--a0)); }
  to   { transform: rotate(calc(var(--a0) + 360deg)); }
}

/* the canvas is fixed to the viewport, but that is only how it is SIZED: a
   page sized canvas at 1440x5400 is tens of megabytes. the ships inside it live
   in DOCUMENT coordinates and the draw subtracts the scroll, so a ship is
   anchored to the page and does not slide along with the reader. */
#gliders {
  position: fixed;
  inset: 0;
  z-index: 0;
  width: 100vw;
  height: 100vh;
  pointer-events: none;
}

@media (max-width: 1100px) {
  .system .planet, .system .moon { display: none; }
  .system .orbit.c { display: none; }
}

@media (max-width: 719px) {
  #gliders { display: none; }
  .system .orbit.b { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  #gliders { display: none; }
  .system .moon { animation: none; }
}
