/* The site's shared sheet, in the "Blocks" style of docs/design/website/: every
   section a bordered block on the page ground, square corners, a numbered head
   band, Geist and Geist Mono. Pages link /tokens.css, then this, then their own
   sheet (home.css, …).

   Colours, font stacks, durations and easings are tokens, generated into
   /tokens.css from the vendored tokens.source.json. Never redeclare one here:
   change it in kollektiv/design/tokens.json and regenerate. What :root holds
   below is page vocabulary, the sizes and rhythm only this site uses. */

/* Geist is under the SIL Open Font Licence (assets/fonts/OFL.txt), so unlike
   the ITF families in the app's stacks the site can serve it itself (#442). */
@font-face {
  font-family: 'Geist';
  src: url('/assets/fonts/Geist-Variable.woff2') format('woff2');
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'Geist Mono';
  src: url('/assets/fonts/GeistMono-Variable.woff2') format('woff2');
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}

:root {
  --page-max: 1280px;
  --gutter: clamp(12px, 2vw, 24px);
  /* Blocks inside one group sit this far apart, and groups this far. */
  --stack: 16px;
  --group-gap: 56px;
  --pad: 32px;
  --pad-band: 40px;
  --nav-h: 72px;
  --hairline: 1px solid var(--border-subtle);

  /* The focused group lifts and the others soften (site.js). The shadow is the
     one drop shadow on the site; the suite has no shadow token on purpose, so
     it stays here rather than in the token set. */
  --lift: -6px;
  --lift-shadow: 0 28px 56px -24px rgb(0 0 0 / 0.9);
  --dim-blur: 1.5px;
  --dim-opacity: 0.72;
  --focus-ms: 500ms;
  --ease-lift: cubic-bezier(0.2, 0.7, 0.2, 1);

  /* The light an outer block's edge picks up near the cursor (site.js). The
     size is the gradient's, and site.js's GLOW_REACH matches it. */
  --glow-size: 320px;
  --glow-color: var(--accent);
}

/* Moving between the site's own pages crosses over rather than cutting, and
   the nav, the same on every page, holds still through it. A browser without
   cross-document view transitions navigates as before. */
@media (prefers-reduced-motion: no-preference) {
  @view-transition {
    navigation: auto;
  }
}

.nav {
  view-transition-name: nav;
}

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

html {
  background: var(--bg-base);
  scrollbar-gutter: stable;
}

body {
  margin: 0;
  background: var(--bg-base);
  color: var(--text-primary);
  font-family: var(--font-site);
  -webkit-font-smoothing: antialiased;
}

a {
  color: var(--text-primary);
  text-decoration: none;
  transition: color var(--duration-fast) var(--ease-standard);
}

a:hover {
  color: var(--accent);
}

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

p,
li {
  text-wrap: pretty;
}

h1,
h2 {
  text-wrap: balance;
}

.mono {
  font-family: var(--font-site-mono);
}

/* The attribute wins over any display a class sets, so a script can hide a
   flex or grid element without a rule of its own for it. */
[hidden] {
  display: none !important;
}

button {
  font: inherit;
  color: inherit;
}

/* Every control eases between its states, so a page needs no transition of
   its own for a hover or a pressed state to fade. A page rule that sets its
   own transition replaces this one. */
button,
summary,
.btn {
  transition:
    color var(--duration-fast) var(--ease-standard),
    background-color var(--duration-fast) var(--ease-standard),
    border-color var(--duration-fast) var(--ease-standard),
    box-shadow var(--duration-fast) var(--ease-standard),
    opacity var(--duration-fast) var(--ease-standard);
}

summary h2,
summary h3 {
  transition: color var(--duration-fast) var(--ease-standard);
}

.skip-link {
  position: absolute;
  left: var(--gutter);
  top: -100px;
  z-index: 10;
  padding: 12px 20px;
  background: var(--accent);
  color: var(--bg-base);
}

.skip-link:focus {
  top: var(--gutter);
}

/* ── Page frame ─────────────────────────────────────────────────────────── */

.page {
  max-width: var(--page-max);
  margin: 0 auto;
  padding: var(--stack) var(--gutter) 24px;
  display: flex;
  flex-direction: column;
  gap: var(--group-gap);
}

/* A run of blocks that reads as one piece: the nav and the hero, the
   local-first row and the download block. */
.group {
  display: flex;
  flex-direction: column;
  gap: var(--stack);
}

.block {
  border: var(--hairline);
}

.block-raised {
  background: var(--bg-surface);
}

/* A jump to a section (#features from the nav) leaves the page's own gap
   above it, rather than putting it hard against the top of the window. */
.page [id] {
  scroll-margin-top: var(--group-gap);
}

/* ── Loading: a pulse while GitHub answers, a fade when it has ───────────── */
/* What a page fetches arrives behind a small pulsing square, the site's
   marker shape, and fades in when it lands rather than cutting in a moment
   after the page. The fade is opacity alone, so it never fights the focus
   lift's transform. */

.loader {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  font-family: var(--font-site-mono);
  font-size: 13px;
  color: var(--text-secondary);
}

.loader::before {
  content: '';
  flex: none;
  width: 8px;
  height: 8px;
  background: var(--accent);
  animation: loader-pulse calc(var(--duration-panel) * 3) var(--ease-standard) infinite alternate;
}

@keyframes loader-pulse {
  from {
    opacity: 0.2;
  }
}

.appear {
  animation: appear var(--duration-panel) var(--ease-standard) both;
}

@keyframes appear {
  from {
    opacity: 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  .loader::before,
  .appear {
    animation: none;
  }
}

/* ── Resize: what opens, closes or refilters (site.js) ────────────────── */
/* A box animating its height clips what it is growing over or closing on. */

.is-resizing {
  overflow: clip;
}

/* ── Glow: the outer blocks' edges light up near the cursor ─────────────── */
/* site.js writes --mx/--my (px, block-relative) on each outermost block
   within reach of the pointer; the ring is that radial gradient masked down to
   its own 1px padding, which lies over the block's hairline. Without the mask
   it would fill the block, so browsers that cannot do it get nothing.

   A block of parts that each stand for something else (.glow-parts: the
   roadmap's stages) lights per part instead, each in its own --glow-color.
   site.js's GLOW_TARGETS is this same selector. */

.block:not(.block .block):not(.glow-parts),
.glow-parts > * {
  position: relative;
}

@supports (mask-composite: exclude) or (-webkit-mask-composite: xor) {
  .block:not(.block .block):not(.glow-parts)::after,
  .glow-parts > *::after {
    content: '';
    position: absolute;
    inset: -1px;
    padding: 1px;
    pointer-events: none;
    background: radial-gradient(
      var(--glow-size) circle at var(--mx, -999px) var(--my, -999px),
      color-mix(in srgb, var(--glow-color) 30%, transparent),
      transparent 60%
    );
    -webkit-mask:
      linear-gradient(#000 0 0) content-box,
      linear-gradient(#000 0 0);
    -webkit-mask-composite: xor;
    mask:
      linear-gradient(#000 0 0) content-box,
      linear-gradient(#000 0 0);
    mask-composite: exclude;
    transition: opacity var(--duration-panel) var(--ease-standard);
  }

  /* The pointer left the window: the light fades where it was. */
  [data-glow-idle] .block:not(.block .block):not(.glow-parts)::after,
  [data-glow-idle] .glow-parts > *::after {
    opacity: 0;
  }
}

/* ── Focus: the group nearest the middle of the screen lifts ───────────── */

[data-focus],
[data-focus] > .block {
  transition:
    filter var(--focus-ms) ease,
    opacity var(--focus-ms) ease,
    transform var(--focus-ms) var(--ease-lift),
    border-color var(--focus-ms) ease,
    box-shadow var(--focus-ms) ease;
}

/* Not on phones: there a blurred block is one you are about to read, and a
   column of them is mostly that. */
@media (min-width: 861px) {
  [data-focus].is-dim {
    filter: blur(var(--dim-blur));
    opacity: var(--dim-opacity);
  }

  [data-focus].is-focus {
    transform: translateY(var(--lift));
  }

  [data-focus].is-focus.block,
  [data-focus].is-focus > .block {
    border-color: var(--border-hover);
    box-shadow: var(--lift-shadow);
  }
}

/* ── Nav ────────────────────────────────────────────────────────────────── */

.nav {
  display: flex;
  flex-wrap: wrap;
  align-items: stretch;
}

.nav-brand,
.footer-brand {
  display: flex;
  align-items: center;
  gap: 10px;
  font-weight: 600;
  font-size: 20px;
  letter-spacing: -0.02em;
}

.nav-brand {
  padding: 0 var(--pad);
  min-height: var(--nav-h);
  border-right: var(--hairline);
}

.nav-links {
  display: flex;
  flex: 1 1 360px;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 28px;
  min-height: var(--nav-h);
  margin: 0;
  padding: 0 var(--pad);
  list-style: none;
  font-size: 15px;
}

.nav-links a,
.nav-side {
  color: var(--text-secondary);
}

.nav-links a:hover,
.nav-side:hover,
.nav-links a[aria-current='page'] {
  color: var(--text-primary);
}

.nav-side {
  display: flex;
  align-items: center;
  padding: 0 28px;
  min-height: var(--nav-h);
  border-left: var(--hairline);
  font-size: 15px;
}

.nav .btn {
  min-height: var(--nav-h);
  padding: 0 40px;
}

.mark {
  flex: none;
}

/* The <picture> around the mark (the full mark on high-density screens, the
   24 cut otherwise) is a box of the mark's own size, so the mark sits where
   it did without one rather than on a text baseline. */
picture:has(> .mark) {
  display: flex;
  flex: none;
}

/* ── Buttons ────────────────────────────────────────────────────────────── */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  min-height: 56px;
  padding: 0 28px;
  font-family: var(--font-site-mono);
  font-size: 14px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.btn-primary {
  padding: 0 36px;
  background: var(--accent);
  color: var(--bg-base);
  font-weight: 500;
}

.btn-primary:hover {
  color: var(--bg-base);
}

.btn-secondary {
  border: 1px solid var(--border-hover);
  color: var(--text-primary);
}

.btn-secondary:hover {
  color: var(--text-primary);
  background: var(--hover-surface);
}

/* A row of buttons. */
.actions {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  padding-top: 8px;
}

/* ── Head band: a number and a title ────────────────────────────────────── */

.band {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px 20px;
  padding: var(--pad-band) var(--pad);
  border-bottom: var(--hairline);
  background: var(--bg-surface);
}

.band-title {
  display: flex;
  align-items: baseline;
  gap: 20px;
}

.num,
.meta {
  font-family: var(--font-site-mono);
  font-size: 13px;
  color: var(--text-secondary);
}

.band h2 {
  margin: 0;
  font-size: clamp(36px, 4vw, 56px);
  line-height: 1;
  font-weight: 600;
  letter-spacing: -0.04em;
}

.band-link {
  font-family: var(--font-site-mono);
  font-size: 13px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

/* A label over a short list: the footer's columns, the "Works with" row. */
.label {
  font-family: var(--font-site-mono);
  font-size: 12px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-secondary);
}

/* ── Code: inline in prose, and blocks of commands ──────────────────────── */

code {
  padding: 1px 6px;
  border: var(--hairline);
  background: var(--bg-overlay);
  font-family: var(--font-site-mono);
  font-size: 0.88em;
}

pre {
  margin: 0;
  padding: 18px 20px;
  overflow-x: auto;
  border: var(--hairline);
  background: var(--bg-overlay);
  font-family: var(--font-site-mono);
  font-size: 14px;
  line-height: 1.6;
}

pre code {
  padding: 0;
  border: 0;
  background: none;
  font-size: inherit;
}

/* ── Page hero: the title in one half, what the page is about in the other ─ */

.page-hero {
  display: flex;
  flex-wrap: wrap;
}

.page-hero-title,
.page-hero-side {
  flex: 1 1 420px;
  display: flex;
  flex-direction: column;
  gap: 16px;
  padding: clamp(40px, 6vw, 72px) var(--pad);
}

.page-hero-title {
  border-right: var(--hairline);
}

.page-hero h1 {
  margin: 0;
  font-size: clamp(56px, 7vw, 96px);
  line-height: 0.95;
  font-weight: 600;
  letter-spacing: -0.05em;
}

.page-hero-side {
  justify-content: flex-end;
  background: var(--bg-surface);
}

.page-hero-side > p {
  margin: 0;
  font-size: 18px;
  line-height: 1.55;
  color: var(--text-secondary);
}

/* ── Stages: one colour per release stage, from the status tokens ────────
   Alpha danger, beta sun, release accent, later a dashed outline in
   secondary text. --stage-color tints, --stage-ink writes, and .marker is the
   square that carries it. The roadmap and the docs' platform table use it. */

.stage-alpha,
[data-stage='alpha'] {
  --stage-color: var(--danger);
  --stage-ink: var(--danger);
}

.stage-beta,
[data-stage='beta'] {
  --stage-color: var(--sun);
  --stage-ink: var(--sun);
}

.stage-release,
[data-stage='release'] {
  --stage-color: var(--accent);
  --stage-ink: var(--accent);
}

.stage-later,
[data-stage='later'] {
  --stage-color: var(--text-primary);
  --stage-ink: var(--text-secondary);
}

.marker {
  display: inline-block;
  flex: none;
  width: 10px;
  height: 10px;
  background: var(--stage-ink);
}

[data-stage='later'] .marker {
  background: transparent;
  border: 1.5px dashed var(--stage-ink);
}

/* ── Search: a field that stands alone, "/" focuses it (site.js) ───────── */

.search {
  flex: 1 1 320px;
  display: flex;
  align-items: center;
  gap: 14px;
  min-height: 64px;
  padding: 0 var(--pad);
  font-family: var(--font-site-mono);
  font-size: 15px;
  color: var(--text-secondary);
}

.search input {
  flex: 1;
  min-width: 0;
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--text-primary);
  font: inherit;
}

.search input::placeholder {
  color: var(--text-muted);
}

.search input:focus {
  outline: none;
}

.search:focus-within {
  box-shadow: inset 0 -2px 0 var(--accent);
}

/* ── Footer ─────────────────────────────────────────────────────────────── */

.footer-cols {
  display: flex;
  flex-wrap: wrap;
}

.footer-about {
  flex: 1.6 1 320px;
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding: var(--pad-band) var(--pad);
}

.footer-about p {
  margin: 0;
  max-width: 32ch;
  font-size: 15px;
  line-height: 1.5;
  color: var(--text-secondary);
}

.footer-col {
  flex: 1 1 160px;
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding: var(--pad-band) var(--pad);
  border-left: var(--hairline);
  font-size: 15px;
}

.footer-base {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 12px 24px;
  padding: 20px var(--pad);
  border-top: var(--hairline);
  font-family: var(--font-site-mono);
  font-size: 13px;
  color: var(--text-secondary);
}

.footer-base a {
  text-decoration: underline;
}

@media (max-width: 760px) {
  :root {
    --pad: 20px;
    --pad-band: 28px;
  }

  .nav-brand {
    flex: 1 1 auto;
    border-right: 0;
  }

  .nav-links {
    order: 3;
    flex-basis: 100%;
    min-height: 56px;
    border-top: var(--hairline);
  }

  .nav-side {
    display: none;
  }

  .nav .btn {
    padding: 0 24px;
  }

  .footer-col {
    border-left: 0;
    border-top: var(--hairline);
  }

  .page-hero-title {
    border-right: 0;
    border-bottom: var(--hairline);
  }

  .page-hero-side {
    padding-block: 24px;
  }
}

@media (prefers-reduced-motion: reduce) {
  [data-focus],
  [data-focus] > .block {
    transition: none;
  }

  .block:not(.block .block):not(.glow-parts)::after,
  .glow-parts > *::after {
    display: none;
  }

  [data-focus].is-dim {
    filter: none;
    opacity: 1;
  }

  [data-focus].is-focus {
    transform: none;
  }
}
