/* KOENJI — www.koenji.cam
 *
 * Every value below is lifted from the app, not invented for the web. Source per block:
 *   colours       SAL 0.1/CoreDesign/KoenjiColors.swift  (+ Contracts/KoenjiPalette.md)
 *   paper         SAL 0.1/CoreDesign/KoenjiPaperTokens.swift
 *   type          SAL 0.1/CoreDesign/KoenjiTypography.swift
 *   spacing       SAL 0.1/CoreDesign/KoenjiSpacing.swift
 *   chrome/lines  SAL 0.1/CoreDesign/KoenjiChrome.swift
 *   motion        SAL 0.1/CoreDesign/KoenjiMotion.swift
 *   the run       SAL 0.1/Features/SALMenu/KoenjiMenuView.swift (settingCard, separatorCard,
 *                 akten-zeile) + MenuRoller.swift + FormulaFilmRoller.swift (card metrics)
 *   the verb      SAL 0.1/Features/SALMenu/KoenjiPaywallView.swift (confirmPlaque: clay, caps)
 *   the mark      Scripts/icon-gen/genpill.swift + build-app-icon.py
 *
 * The canon, as the app keeps it:
 *   - four chromatic colours plus black and white. No other colour.
 *   - one monospaced voice. Size is activity.
 *   - hairlines are 0.5 pt. Instruments are square. ONLY PAPER MAY BE ROUND.
 *   - exactly ONE filled verb per surface, in clay.
 *   - instrument words are lowercase (the label school); verbs on plaques are caps;
 *     KOENJI is always caps.
 */

@font-face {
  /* Fallback only. On iPhone, iPad and Safari on the Mac `ui-monospace` resolves to
     SF Mono — the app's own face — and this file is never downloaded. Geist Mono is one of
     the eight faces the app bundles (SAL 0.1/Fonts, OFL), subset to Latin. */
  font-family: "Koenji Mono";
  src: url("/assets/fonts/GeistMono-subset.woff2") format("woff2");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}

:root {
  color-scheme: dark;

  /* colours — KoenjiColors */
  --black: #000;
  --white: #fff;
  --lemon: #cbd2b9;
  --wimbledon: #bbbeaf;
  --clay: #df764c;
  --clay-ink: #8a4a30;

  /* roles — the viewfinder contract */
  --info: var(--wimbledon);
  --info-muted: rgba(187, 190, 175, 0.6);
  --info-dim: rgba(187, 190, 175, 0.4);
  --divider: rgba(187, 190, 175, 0.5);
  --line-subtle: rgba(203, 210, 185, 0.09);
  --line: rgba(203, 210, 185, 0.18);
  --line-strong: rgba(203, 210, 185, 0.32);
  --text: var(--white);
  --text-2: var(--lemon);

  /* paper — KoenjiPaperTokens */
  --paper: var(--wimbledon);
  --ink: rgba(0, 0, 0, 0.92);
  --ink-hairline: rgba(0, 0, 0, 0.3);
  --r-paper: 8px;

  /* spacing — KoenjiSpacing */
  --s-xs: 4px;
  --s-sm: 8px;
  --s-md: 16px;
  --s-lg: 24px;
  --s-xl: 32px;
  --s-xxl: 48px;
  --s-xxxl: 72px;

  /* chrome — KoenjiChrome. 0.5 pt is one device pixel on a 2x screen. */
  --hairline: 1px;
  --control: 44px;
  --control-compact: 34px;

  /* motion — KoenjiMotion */
  --t-fast: 0.16s;
  --ease: cubic-bezier(0, 0, 0.58, 1); /* easeOut */

  /* type — KoenjiTypography, one voice */
  --mono: ui-monospace, "SF Mono", SFMono-Regular, "Koenji Mono", Menlo, Consolas, monospace;
  --fs-micro: 11px;       /* micro */
  --fs-caption: 12px;     /* caption / monoLabel */
  --fs-body: 15px;        /* bodyText */
  --fs-readout-l: 16px;   /* readoutLarge */
  --fs-wordmark: 22px;    /* wordmark, medium */
  --fs-display: 30px;     /* displayNumeral, light */

  --page: 1120px;
  --gutter: var(--s-md);
}

@media (min-resolution: 2dppx) {
  :root { --hairline: 0.5px; }
}

@media (min-width: 720px) {
  :root { --gutter: var(--s-xl); --fs-body: 16px; --fs-display: 34px; }
}

@media (min-width: 1080px) {
  :root { --gutter: var(--s-xxl); }
}

/* ------------------------------------------------------------------ base */

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

html {
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

body {
  margin: 0;
  background: var(--black);
  color: var(--text-2);
  font-family: var(--mono);
  font-size: var(--fs-body);
  line-height: 1.6;
  font-variant-numeric: tabular-nums;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

img, svg { display: block; max-width: 100%; height: auto; }

h1, h2, h3, p, ul, dd { margin: 0; }
ul { padding: 0; list-style: none; }

a {
  color: var(--info);
  text-decoration-line: underline;
  text-decoration-thickness: var(--hairline);
  text-underline-offset: 3px;
  text-decoration-color: var(--divider);
  transition: color var(--t-fast) var(--ease), text-decoration-color var(--t-fast) var(--ease);
}
a:hover { color: var(--white); text-decoration-color: var(--white); }

:focus-visible {
  outline: var(--hairline) solid var(--white);
  outline-offset: 3px;
}

::selection { background: var(--clay); color: var(--ink); }

.visually-hidden {
  position: absolute !important;
  width: 1px; height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
}

.skip {
  position: absolute;
  left: var(--gutter);
  top: -100px;
  z-index: 10;
  padding: var(--s-sm) var(--s-md);
  background: var(--clay);
  color: var(--ink);
  border-radius: var(--r-paper);
  text-decoration: none;
  font-size: var(--fs-caption);
  font-weight: 500;
  text-transform: uppercase;
}
.skip:focus { top: var(--s-sm); color: var(--ink); }

.wrap {
  width: 100%;
  max-width: calc(var(--page) + 2 * var(--gutter));
  margin-inline: auto;
  padding-inline: var(--gutter);
}

/* the mark — ring + clay pill in the icon's own units: ring D = 38, pill b = R, h = R/2. */
.mark .ring { fill: none; stroke: var(--wimbledon); }
.mark .pill { fill: var(--clay); }
/* below ~50 px the icon's pixel floor takes over: a stroke never thinner than 1.1 device px */
.mark .ring { stroke-width: 1.1px; vector-effect: non-scaling-stroke; }
@media (min-resolution: 2dppx) { .mark .ring { stroke-width: 0.6px; } }

/* status LED — KoenjiStatusLED: a ≥5 pt lamp + caption, the dot carries the state */
.led {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: var(--fs-caption);
  color: var(--info);
  text-transform: lowercase;
}
.led::before {
  content: "";
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--clay);
  flex: none;
}
.led.off::before { background: rgba(187, 190, 175, 0.22); }

/* ------------------------------------------------------------------ the run (the page is the menu) */

/* KoenjiMenuView + MenuRoller + FormulaFilmRoller, in web pixels = app points: the run is a
   phone-width column. On a phone it starts at the top like the menu; on a wide screen it
   stands in the middle, the size of the menu in a hand. */
.run-page {
  min-height: 100vh;
  min-height: 100svh;
  display: flex;
  flex-direction: column;
}
.run-stage {
  flex: 1;
  display: flex;
  justify-content: center;
  align-items: flex-start;
  padding: var(--s-sm) var(--s-md) var(--s-lg);
}
@media (min-width: 720px) and (min-height: 720px) {
  .run-stage { align-items: center; padding-block: var(--s-xl); }
}
.run { width: 100%; max-width: 460px; }

/* the akten-zeile: the surface names itself, a hairline under it */
.akte {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-md);
  min-height: 88px;
  padding: 0 9px 0 15px; /* on the etiketten' own axis: the name starts where theirs do */
  border-bottom: var(--hairline) solid var(--line);
  margin-bottom: var(--s-md);
}

/* the name at the head of the run, larger than the header of a document page (Linh:
   "logo muss größer"): the wordmark's medium at the display size, and the mark at the
   height of the capitals plus the ring's air. At this size the icon's own stroke, D/65 in
   the mark's units, clears the 1.1 device-pixel floor on any 2x screen, so it scales with
   the mark like the icon does; a 1x screen keeps the floor. */
/* the name steps back from the mark (Linh: "Koenji sollte kleiner sein in relation mit dem
   logo" — "noch kleiner"): the app's own wordmark size, 22 medium (KoenjiTypography.wordmark),
   against a 48 mark; the mark leads */
.akte .brand { gap: 10px; font-size: var(--fs-wordmark); }
.akte .mark { width: 48px; height: 48px; }
.akte .mark .ring { stroke-width: 0.585; vector-effect: none; }
@media (max-resolution: 1.5dppx) {
  .akte .mark .ring { stroke-width: 1.1px; vector-effect: non-scaling-stroke; }
}

/* one etikette — settingCard: wimbledon, ink .82, radius 3, 26 compact, name + the one fact.
   It grows by exactly the height its content needs (a fact that does not fit wraps under). */
.etiketten { display: grid; gap: 5px; }
.etikett {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  column-gap: var(--s-sm);
  min-height: 26px;
  padding: 3px 9px 3px 15px; /* leading = spine width 7 + 8, trailing 9 */
  background: var(--wimbledon);
  border-radius: 3px;
  color: rgba(0, 0, 0, 0.82); /* FormulaFilmRoller.cardInk */
}
/* site.js sets the recede (MenuRoller's presence); this only smooths the hand, and only
   after the first picture stands */
.run[data-settled] .etiketten .etikett { transition: opacity var(--t-fast) var(--ease); }
.etikett .name {
  font-size: 15px;          /* typography.label */
  font-weight: 500;
  line-height: 20px;
  letter-spacing: 1.1px;
  text-transform: lowercase; /* the label school */
}
.etikett .fact {
  margin-left: auto;
  font-size: var(--fs-micro);
  font-weight: 500;
  line-height: 20px;
  color: rgba(0, 0, 0, 0.45); /* FormulaFilmRoller.cardInkDim */
  text-transform: lowercase;
  white-space: nowrap;
}

/* the standing face of an etikette — settingCard's body under its hairline (black .22):
   the one explaining line, micro, in ink .38 (KoenjiPaperTokens.inkDim) */
.etikett .detail {
  flex-basis: 100%;
  margin-top: 3px;
  padding-top: 3px;
  border-top: var(--hairline) solid rgba(0, 0, 0, 0.22);
  font-size: var(--fs-micro);
  font-weight: 500;
  line-height: 18px;
  color: rgba(0, 0, 0, 0.38);
  text-transform: lowercase;
}

/* a name keeps the case it was given — S.E.E. is a name, not an instrument word (the app's
   koenjiFilmName rule: names are exempt from every case school) */
.etikett .authored { text-transform: none; }

/* the graphite landmark — separatorCard: the studio glass, micro, tracked 1.4 */
.separator {
  display: flex;
  align-items: center;
  min-height: 26px;
  margin: 5px 0;
  padding: 0 9px 0 15px;
  background: #1a1a1c;
  border-radius: 3px;
  color: rgba(187, 190, 175, 0.6);
  font-size: var(--fs-micro);
  font-weight: 500;
  letter-spacing: 1.4px;
  text-transform: lowercase;
}
/* inside the run the grid's 5 pt gap is the spacing, as between the menu's own cards */
.etiketten .separator { margin: 0; }
/* the separator's fact: full graphite ink (labels muted, facts full), untracked like a rest
   fact, and in its authored case — "iPhone" is a name, not an instrument word */
.separator .fact {
  margin-left: auto;
  padding-left: var(--s-sm);
  color: var(--wimbledon);
  letter-spacing: 0;
  text-transform: none;
  white-space: nowrap;
}

/* operable etiketten are the paywall plate's height: control height, a thumb's size */
.notify .fields { display: grid; gap: 5px; }

.etikett.input { min-height: var(--control); cursor: text; }
.etikett.input input {
  flex: 1;
  min-width: 0;
  appearance: none;
  border: 0;
  border-radius: 0;
  padding: 0;
  background: transparent;
  color: rgba(0, 0, 0, 0.82);
  font: 500 16px / 20px var(--mono); /* 16, not 15: iOS zooms into anything smaller */
  letter-spacing: 0.4px;
}
.etikett.input input::placeholder { color: rgba(0, 0, 0, 0.45); opacity: 1; }
.etikett.input input:focus { outline: none; }
.etikett.input:focus-within { box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.55); }
.etikett.input:has(input[aria-invalid="true"]) { box-shadow: inset 0 0 0 1px var(--clay-ink); }

/* THE LAST ETIKETTE IS THE VERB — the one filled verb of the surface, in clay, caps */
.etikett.verb {
  appearance: none;
  width: 100%;
  min-height: var(--control);
  border: 0;
  background: var(--clay);
  color: var(--ink);
  font-family: var(--mono);
  text-align: left;
  cursor: pointer;
  transition: background-color var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
  -webkit-tap-highlight-color: transparent;
}
.etikett.verb .name { font-size: var(--fs-caption); letter-spacing: 1.4px; text-transform: uppercase; }
.etikett.verb .fact { color: var(--ink); font-size: var(--fs-caption); }
/* pressed: the cursor is its ground's inversion */
.etikett.verb:active { background: var(--clay-ink); color: var(--wimbledon); }
.etikett.verb:active .fact { color: var(--wimbledon); }
.etikett.verb:focus-visible { outline: 1px solid var(--clay); outline-offset: 2px; }
.etikett.verb:disabled { opacity: 0.55; cursor: default; }

/* the honeypot Brevo reads as `email_address_check` — off-screen, never focusable */
.notify .trap { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

.notify .status {
  margin-top: var(--s-sm);
  padding: 0 9px 0 15px;
  font-size: var(--fs-caption);
  color: var(--info);
}
.notify .status:empty { display: none; }
.notify .status[data-state="error"] { color: var(--clay); }
.notify .status[data-state="done"] { color: var(--text); }
.notify[data-state="done"] .fields { display: none; }

.notify .note {
  margin-top: var(--s-sm);
  padding: 0 9px 0 15px;
  font-size: var(--fs-micro);
  line-height: 1.5;
  color: var(--info-muted);
}

/* the window's bottom edge: signature left, legal right, on the page gutter — furniture of
   the browser frame, not an etikette of the run */
.run-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0 var(--s-lg);
  padding: 0 var(--gutter) env(safe-area-inset-bottom);
  font-size: var(--fs-micro);
  color: var(--info-muted);
}
.run-foot ul { display: flex; flex-wrap: wrap; gap: 0 var(--s-md); }
.run-foot a {
  display: inline-flex;
  align-items: center;
  min-height: var(--control);
  color: var(--info);
  text-decoration: none;
  text-transform: lowercase;
}
.run-foot a:hover { color: var(--white); }
.run-foot .marks {
  flex-basis: 100%;
  padding-bottom: var(--s-sm);
  color: var(--info-dim);
  line-height: 1.5;
}

/* ------------------------------------------------------------------ the iPhone version */

/* TWO VERSIONS, ONE ADDRESS (Linh, 2026-09-16: most visitors are on an iPhone). Below the
   wide layout the run is shorter (data-wide etiketten stay on the desktop) and the notify
   block docks at the screen's foot, like the plaque at the foot of the app's menu: the verb is
   in reach from the first second, the run scrolls behind it, and at the end of the page the
   dock settles into its own place above the footer. */
@media (max-width: 719px) {
  .etiketten [data-wide] { display: none; }

  .dock {
    position: sticky;
    bottom: 0;
    z-index: 2;
    /* the top margin is the rim's own height: when the dock settles at the page's end, the
       fade falls on this air instead of over the last etikette */
    margin: var(--s-lg) calc(-1 * var(--s-md)) 0;
    padding: var(--s-sm) var(--s-md) max(var(--s-sm), env(safe-area-inset-bottom));
    background: var(--black);
  }
  /* the run melts into the dock instead of being cut by it — the roller's rim */
  .dock::before {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    top: calc(-1 * var(--s-lg));
    height: var(--s-lg);
    background: linear-gradient(rgba(0, 0, 0, 0), var(--black));
    pointer-events: none;
  }
  .dock .separator { margin-top: 0; }
}

/* ------------------------------------------------------------------ document pages */

.site-header { border-bottom: var(--hairline) solid var(--line); }
.site-header .wrap { display: flex; align-items: center; min-height: 64px; }

.brand {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  color: var(--text);
  text-decoration: none;
  font-size: var(--fs-wordmark);
  font-weight: 500;
  line-height: 1;
}
.brand:hover { color: var(--text); }
.mark { width: 28px; height: 28px; flex: none; }

/* KoenjiPaperCard — wimbledon sheet, paper radius, ink brings its own contrast */
.paper {
  background: var(--paper);
  color: var(--ink);
  border-radius: var(--r-paper);
  padding: var(--s-md);
}
.paper a { color: var(--ink); text-decoration-color: var(--ink-hairline); }

.doc { padding-block: var(--s-xxl) var(--s-xxxl); }
.doc .wrap { max-width: calc(760px + 2 * var(--gutter)); }
.doc h1 { color: var(--text); font-size: var(--fs-display); font-weight: 300; line-height: 1.2; margin-bottom: var(--s-sm); }
.doc .led + h1 { margin-top: var(--s-md); }
.doc .meta { font-size: var(--fs-caption); color: var(--info-muted); margin-bottom: var(--s-xxl); }
/* section titles as KoenjiSectionHeader draws them: the word, then the rule */
.doc h2 {
  display: flex;
  align-items: baseline;
  gap: var(--s-md);
  margin: var(--s-xxl) 0 var(--s-md);
  color: var(--text);
  font-size: var(--fs-caption);
  font-weight: 500;
  text-transform: lowercase;
}
.doc h2::after { content: ""; flex: 1; height: var(--hairline); background: var(--line-strong); align-self: center; }
.doc h3 { margin: var(--s-lg) 0 var(--s-xs); color: var(--text); font-size: var(--fs-body); font-weight: 500; }
.doc p, .doc li { max-width: 68ch; }
.doc p + p { margin-top: var(--s-md); }
.doc ul { margin-top: var(--s-sm); }
.doc ul li { position: relative; padding-left: var(--s-lg); }
.doc ul li + li { margin-top: var(--s-xs); }
.doc ul li::before { content: ""; position: absolute; left: 0; top: 0.8em; width: 12px; height: var(--hairline); background: var(--divider); }
.doc address { font-style: normal; color: var(--text); }
.doc .lang { margin-bottom: var(--s-lg); font-size: var(--fs-caption); }
.doc .paper p { max-width: none; }

.site-footer { border-top: var(--hairline) solid var(--line); padding-block: var(--s-xl) var(--s-xxl); }
.site-footer .wrap { display: grid; gap: var(--s-lg); }
@media (min-width: 720px) { .site-footer .wrap { grid-template-columns: 1fr auto; align-items: end; } }
.site-footer .sign { color: var(--text); }
.site-footer .sign p + p { color: var(--info-muted); }
.site-footer nav ul { display: flex; flex-wrap: wrap; gap: var(--s-xs) var(--s-lg); }
.site-footer nav a {
  display: inline-flex;
  align-items: center;
  min-height: var(--control-compact);
  color: var(--info);
  text-decoration: none;
  font-size: var(--fs-caption);
  text-transform: lowercase;
}
.site-footer nav a:hover { color: var(--white); }
.site-footer .legal { grid-column: 1 / -1; font-size: var(--fs-micro); color: var(--info-dim); }

/* a placeholder that must be filled before launch — Website/tools/preflight.sh refuses it */
.todo { color: var(--clay); }

/* ------------------------------------------------------------------ motion */

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { transition: none !important; }
}
