/* Candela — site styles.
   Copied to docs/styles.css by site/build.py; edit this file, not that one.

   The visual language is kami (紙): an editorial paper system — warm parchment
   canvas, serif-led hierarchy, ink-blue used sparingly, and print fidelity rather
   than the compositing tricks that mark a page as a web page. Its explicit
   prohibitions are honoured here: no #ffffff page background, no cool blue-greys,
   no weights above 500, no italic, no sans-serif for headings or body, no
   backdrop-filter, no drop shadows, no round-disc bullets.

   Where kami and Apple's guidance disagree, they are disagreeing about different
   things. kami governs how the page looks; Apple's guidance is followed where it
   governs how the page behaves — 44px minimum touch targets, relative units that
   respect the reader's text size, real contrast, reduced-motion, safe areas, and
   nothing that only works on hover. */

:root {
  /* Surfaces */
  --parchment: #f5f4ed;
  --ivory: #faf9f5;
  --sand: #e8e6dc;

  /* Text, four levels */
  --ink: #141413;
  --ink-2: #3d3d3a;
  --ink-3: #504e49;
  --ink-4: #6b6a64;

  /* Accent, kept under 5% of the surface */
  --blue: #1b365d;
  --blue-light: #2d5a8a;

  --good: #4a6b3a;
  --warn: #8a6b1f;
  --danger: #8a3a30;

  --border: #e8e6dc;
  --border-soft: #e5e3d8;

  /* Spacing */
  --s1: 4px;  --s2: 8px;  --s3: 12px; --s4: 16px;
  --s5: 20px; --s6: 24px; --s8: 32px; --s12: 48px;
  --section: 72px;

  --radius: 8px;
  --radius-sm: 4px;
  --measure: 34rem;   /* ~66 characters at this size: the readable line length */

  --serif-en: Charter, "Bitstream Charter", Georgia, "Palatino Linotype", Palatino,
              "Times New Roman", serif;
  --serif-cjk: "Songti SC", "Source Han Serif SC", "Noto Serif CJK SC", STSong,
               "Hiragino Mincho ProN", YuMincho, SimSun, serif;
  --mono: "JetBrains Mono", ui-monospace, "SF Mono", SFMono-Regular, Menlo, monospace;
}

@media (prefers-color-scheme: dark) {
  :root {
    /* kami's own dark surfaces: warm, not the cool blue-grey it forbids. */
    --parchment: #141413;
    --ivory: #1c1c1a;
    --sand: #30302e;
    --ink: #edece6;
    --ink-2: #d3d1c9;
    --ink-3: #b3b1a8;
    --ink-4: #8f8d84;
    --blue: #8fb3dd;
    --blue-light: #a9c6e8;
    --good: #8fb377;
    --warn: #d0a94e;
    --danger: #d38b83;
    --border: #30302e;
    --border-soft: #2a2a28;
  }
}

* { box-sizing: border-box; }

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

body {
  margin: 0;
  background: var(--parchment);
  color: var(--ink-2);
  font-family: var(--serif-en), var(--serif-cjk);
  /* 17px is Apple's body size and kami's `lg` step — the one place the two systems
     name the same number. 1.55 is the top of kami's reading range, which suits a
     screen better than the denser end. */
  font-size: 17px;
  line-height: 1.55;
  font-weight: 400;
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
}

/* Latin inside Chinese text was being drawn by Songti SC, because Songti was first
   in the stack and it does contain Latin glyphs — thin, small, Song-style ones. That
   is why "macOS" looked a size smaller than the characters beside it. The Latin face
   goes first so it wins for Latin, and CJK falls through to Songti.
   
   It also has to be scaled up. A CJK glyph fills its em box; a Latin one occupies
   only x-height plus ascender, so at the same nominal size it reads smaller. 112%
   is what makes Charter's lowercase sit level with Songti's characters. */
@font-face {
  font-family: "Charter Mixed";
  src: local("Charter"), local("Charter Roman"), local("Georgia");
  size-adjust: 112%;
}
:lang(zh-Hans), :lang(zh-Hant), :lang(zh) {
  font-family: "Charter Mixed", var(--serif-cjk);
  letter-spacing: .35px;
  line-break: strict;
}

a {
  color: var(--blue);
  text-decoration: none;
  border-bottom: 1px solid color-mix(in srgb, var(--blue) 30%, transparent);
}
a:hover { border-bottom-color: var(--blue); }

/* ---------- masthead ---------- */

/* A floating bar rather than a full-width band: it detaches from the page, which is
   what lets a masthead stay available without becoming part of the reading surface.
   Solid parchment and a hairline instead of a blur — kami rules out backdrop-filter,
   and an opaque bar over paper is what a printed masthead does anyway. */
.nav {
  position: sticky;
  top: var(--s3);
  z-index: 20;
  padding: 0 var(--s4);
  margin-bottom: calc(var(--s3) * -1);
}

.nav-inner {
  max-width: 62rem;
  margin: 0 auto;
  padding: var(--s2) var(--s3) var(--s2) var(--s5);
  display: flex;
  align-items: center;
  gap: var(--s4);
  /* Never wrap. Wrapping turned the masthead into a three-row, 160px-tall pill on a
     390px phone — a permanent 19% of the viewport, with a 999px radius stretched
     around it. A bar that cannot fit its contents should drop contents, not grow. */
  flex-wrap: nowrap;
  background: var(--parchment);
  border: 1px solid var(--border);
  border-radius: 999px;
}

.wordmark {
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  font-size: 22px;
  font-weight: 500;
  letter-spacing: -.2px;
  color: var(--ink);
  border-bottom: 0;
}
.wordmark img { border-radius: var(--radius-sm); }

.nav-links {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: var(--s1);
  flex-wrap: wrap;
}
.nav-links > a {
  /* 44px minimum target: the padding, not the type size, is what makes a link
     tappable on a phone. */
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  padding: 0 var(--s3);
  border-radius: var(--radius-sm);
  border-bottom: 0;
  color: var(--ink-3);
  font-size: 14px;
  white-space: nowrap;
}
.nav-links > a:hover { color: var(--ink); background: var(--sand); }
.nav-links .lang { color: var(--ink-4); }

.btn {
  border-radius: var(--radius-sm);
  font-size: 14px;
  font-weight: 500;
}
.nav-links > a.btn-dl {
  background: var(--blue);
  color: var(--parchment);
  padding: 0 var(--s4);
}
.nav-links > a.btn-dl:hover { background: var(--blue-light); color: var(--parchment); }
.nav-links > a.btn-kofi { border: 1px solid var(--border); }
.nav-links > a.btn-kofi:hover { background: var(--sand); }

/* On a phone the masthead keeps two things: where you are, and the one action the
   page is for. Everything else moves to the footer, which is where a reader looks
   for navigation on a long page anyway.

   Each rule is written as `.nav-links > a.x` rather than `.x`, because `.nav-links >
   a` is the more specific selector and a bare `.hide-sm { display: none }` lost to
   it — the Guides link was still being drawn at every width. */
@media (max-width: 760px) {
  .nav { top: var(--s2); padding: 0 var(--s3); }
  .nav-inner {
    padding: var(--s1) var(--s2) var(--s1) var(--s4);
    gap: var(--s2);
  }
  /* Marked with a class, not matched on href. Hiding "the GitHub link" by
     `[href^="https://github.com"]` also hid the download button, whose URL is a
     GitHub release asset — so the phone lost the one action the page is for. */
  .nav-links > a.nav-secondary,
  .nav-links > a.btn-kofi { display: none; }
  .nav-links > a { padding: 0 var(--s2); font-size: 13px; }
  .nav-links > a.btn-dl { padding: 0 var(--s3); }
  .wordmark { font-size: 19px; }
}

/* Narrower still, the wordmark drops to its mark. The name is two characters from
   the icon and stays in the accessible name, so nothing is lost but the width. */
@media (max-width: 460px) {
  .wordmark span { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
  .wordmark { gap: 0; }
}

/* ---------- the page ---------- */

.wrap {
  max-width: var(--measure);
  margin: 0 auto;
  padding: calc(var(--section) + var(--s6)) var(--s5) var(--s12);
}
.wrap:has(> .hero) {
  max-width: 58rem;
  padding-top: var(--section);
}

h1, h2, h3 {
  color: var(--ink);
  font-weight: 500;      /* kami has no bold; 500 is the top of its range */
  text-wrap: balance;
}

h1 {
  font-size: clamp(32px, 4.6vw, 48px);
  line-height: 1.14;
  letter-spacing: -1.2px;
  margin: 0 0 var(--s5);
}
h2 {
  font-size: 22px;
  line-height: 1.3;
  letter-spacing: -.3px;
  margin: var(--section) 0 var(--s3);
}
h3 {
  font-size: 17px;
  line-height: 1.35;
  margin: var(--s8) 0 var(--s2);
}
:lang(zh-Hans) h1, :lang(zh-Hant) h1 { letter-spacing: -.5px; }

p { margin: 0 0 var(--s5); }
strong { font-weight: 500; color: var(--ink); }
em { font-style: normal; color: var(--ink); }   /* kami: no italic anywhere */

/* The opening paragraph carries the answer; it reads as a standfirst. */
.wrap > p:first-of-type {
  font-size: 19px;
  line-height: 1.5;
  color: var(--ink-3);
}

ul, ol { margin: 0 0 var(--s5); padding-left: 0; list-style: none; }
li { margin: var(--s2) 0; padding-left: var(--s5); position: relative; }
/* An en dash, not a round disc — kami rules those out, and a mark set in ink reads
   as typography rather than as an interface element. */
ul > li::before {
  content: "–";
  position: absolute;
  left: 0;
  color: var(--ink-4);
}
ol { counter-reset: item; }
ol > li { counter-increment: item; }
ol > li::before {
  content: counter(item) ".";
  position: absolute;
  left: 0;
  color: var(--ink-4);
  font-variant-numeric: tabular-nums;
}

code {
  font-family: var(--mono);
  font-size: .88em;
  background: var(--ivory);
  border: 1px solid var(--border-soft);
  padding: .05em .3em;
  border-radius: 2px;
}
pre {
  background: var(--ivory);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--s4) var(--s5);
  overflow-x: auto;
  margin: 0 0 var(--s5);
  font-size: 14px;
  line-height: 1.5;
}
pre code { background: none; border: 0; padding: 0; }

hr {
  border: 0;
  border-top: 1px solid var(--border);
  margin: var(--section) 0;
}

/* ---------- plates ---------- */

figure.shot {
  margin: var(--s8) 0;
  text-align: center;
}
.shot img {
  max-width: 100%;
  /* Panel screenshots are tall and narrow; constrained only by width, the one at the
     top of the home page filled the whole viewport and pushed every word below the
     fold. Height is the binding dimension for a portrait image. */
  max-height: 62vh;
  width: auto;
  height: auto;
  border-radius: var(--radius);
  /* A hairline, not a drop shadow: kami treats shadows as a compositing trick, and
     a rule is what separates a plate from the page in print. */
  border: 1px solid var(--border);
}
.shot figcaption {
  margin-top: var(--s3);
  font-size: 12px;
  line-height: 1.4;
  color: var(--ink-4);
  max-width: 28rem;
  margin-inline: auto;
}

/* ---------- tables ---------- */

table {
  width: 100%;
  border-collapse: collapse;
  margin: 0 0 var(--s6);
  font-size: 14px;
  line-height: 1.45;
}
th, td {
  text-align: left;
  padding: var(--s3) var(--s4) var(--s3) 0;
  border-bottom: 1px solid var(--border-soft);
  vertical-align: baseline;
}
thead th {
  color: var(--ink);
  font-weight: 500;
  border-bottom: 1px solid var(--ink-4);
  padding-bottom: var(--s2);
}
tbody tr:last-child td { border-bottom: 1px solid var(--border); }

/* A wide table scrolls inside itself rather than making the page scroll sideways. */
@media (max-width: 700px) {
  table { display: block; overflow-x: auto; }
  table th, table td { min-width: 8rem; }
}

.yes { color: var(--good); }
.no  { color: var(--ink-4); }

/* The picture under the hero is an introduction, not the section itself. At the
   figure default it filled the viewport on its own and pushed the first sentence of
   the page below the fold — which is the same mistake as before in a smaller size. */

/* ---------- home ---------- */

.hero {
  text-align: center;
  padding: 0 0 var(--s6);
}
/* No eyebrow above the headline. The line it carried — free, open source — is in
   the note under the buttons, where it is one of several facts rather than a label
   the headline has to be introduced by. */
.hero h1 { max-width: 24ch; margin-inline: auto; }
/* A `ch` is the width of a zero, so a measure set in `ch` is a Latin measure. Applied
   to Chinese it collapsed the headline into a narrow four-line column. CJK characters
   are a full em wide, so the same headline needs a measure counted in ems. */
:lang(zh-Hans) .hero h1,
:lang(zh-Hant) .hero h1 {
  max-width: 17em;
  text-wrap: pretty;
}
.hero p {
  font-size: 19px;
  color: var(--ink-3);
  max-width: 32rem;
  margin-inline: auto;
}
.hero .actions {
  display: flex;
  gap: var(--s3);
  justify-content: center;
  flex-wrap: wrap;
  margin: var(--s6) 0 var(--s4);
}
.hero .actions .btn {
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  padding: 0 var(--s6);
  font-size: 15px;
  border-bottom: 0;
}
.hero .actions .btn-dl { background: var(--blue); color: var(--parchment); }
.hero .actions .btn-dl:hover { background: var(--blue-light); }
.hero .actions .btn-kofi { color: var(--ink-3); border: 1px solid var(--border); }
.hero .actions .btn-kofi:hover { background: var(--sand); color: var(--ink); }
.hero .note { font-size: 12px; color: var(--ink-4); margin: 0; }

/* ---------- the film ---------- */

/* The demo is the one thing on this page that moves, so it is handled as the plate
   the hero hands you rather than as an effect: the same hairline and caption the
   screenshots get, and no shadow. It is wider than the measure because it is
   landscape where every screenshot is portrait — a menu bar panel photographed on a
   whole desk, which is the thing the still shots cannot show. */
.hero + .film { margin-top: var(--s8); }

.film {
  margin-block: var(--s12) var(--section);
  /* Where the prose wants a measure, a 16:9 frame wants width — so the film steps
     out of the text column. Bounded by the viewport rather than set free, because a
     film that runs to both edges on a laptop stops reading as a plate on a page. */
  width: min(68rem, 100vw - 3rem);
  position: relative;
  left: 50%;
  translate: -50% 0;
}

.film-frame {
  display: block;
  width: 100%;
  height: auto;
  /* Reserved from the intrinsic ratio, so the page does not reflow when the film
     arrives — the same job the width/height pair does on every screenshot. */
  aspect-ratio: 16 / 9;
  background: var(--sand);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

.film figcaption {
  margin-top: var(--s3);
  font-size: 12px;
  line-height: 1.4;
  color: var(--ink-4);
  text-align: center;
}

@media (max-width: 700px) {
  /* A 16:9 desktop recording is at its worst on a phone: the panel inside it is a
     fraction of an already narrow screen. Shrinking it further inside the text
     column would be the wrong answer, so here it breaks the margins and takes the
     full width. The detail work is handed to the portrait screenshots below, which
     are the right shape for this screen and stay legible at it. */
  .film {
    width: 100vw;
    left: 50%;
    translate: -50% 0;
    margin-inline: 0;
    margin-block: var(--s8) var(--s12);
  }
  .film-frame {
    border-inline: 0;
    border-radius: 0;
  }
  .film figcaption { padding-inline: var(--s5); }
}

/* The home page's capabilities were a grid of six equal cards, which is the lazy
   container: it makes every claim the same size and the same shape, so none of them
   is read. Set as an editorial list instead — each entry a heading and a sentence,
   divided by a hairline the way a printed contents page is — they can be scanned in
   order and the longer ones are allowed to be longer. */
.entries {
  margin: var(--s8) 0 var(--s6);
  border-top: 1px solid var(--border);
}
.entries h3 {
  margin: 0;
  padding-top: var(--s5);
  font-size: 17px;
}
.entries p {
  margin: var(--s1) 0 0;
  padding-bottom: var(--s5);
  border-bottom: 1px solid var(--border-soft);
  color: var(--ink-3);
  max-width: 38rem;
}
.entries p:last-child { border-bottom: 0; }

@media (min-width: 860px) {
  /* Two columns of the same list on a wide screen, still hairline-divided rather
     than boxed — the reading order stays vertical within each column. */
  .entries {
    column-count: 2;
    column-gap: var(--s12);
  }
  .entries h3 { break-after: avoid; }
  .entries p { break-inside: avoid; }
}

/* ---------- magazine devices ---------- */

/* A numbered running head above each section. Not decoration: on a page this long
   the number is how a reader knows where they are and how much is left, which is the
   one thing that earns a label above a heading. The heading still carries the
   meaning; the number carries the position. */
.sect-label {
  margin: var(--section) 0 var(--s3);
  font-size: 12px;
  letter-spacing: .8px;
  color: var(--blue);
  font-variant-numeric: tabular-nums;
}
.sect-label + h2 { margin-top: 0; }
.sect-label + h2 + .sect-lede { margin-top: var(--s3); }
.sect-lede {
  margin: 0 0 var(--s8);
  color: var(--ink-3);
  max-width: 40rem;
}

/* Image and text side by side, alternating which side the picture takes. Reading a
   column of prose is work; a page that puts the evidence next to the claim lets the
   eye rest between them, and alternating stops six of them becoming a pattern the
   reader scrolls past. */
.row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--s12);
  /* Top-aligned, not centred. The screenshots are much taller than the three lines
     beside them, so centring floated the text in the middle of the row with a wide
     band of nothing above it — the eye had to hunt for where the sentence started. */
  align-items: start;
  padding: var(--s8) 0;
}
.row:nth-child(even) .row-figure { order: 2; }
.row h3 { margin: 0 0 var(--s3); font-size: 19px; }
.row ul { margin: 0; }
.row li { font-size: 15px; line-height: 1.5; color: var(--ink-3); }
.row-figure { margin: 0; }
.row-figure img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: var(--radius);
  border: 1px solid var(--border);
}

/* The divider between rows: a hairline with a mark set into it, the way a printed
   page separates two items in the same sequence. */
.row + .row { position: relative; }
.row + .row::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  border-top: 1px solid var(--border-soft);
}
.row + .row::after {
  content: "❖";
  position: absolute;
  top: 0;
  left: 50%;
  transform: translate(-50%, -55%);
  padding: 0 var(--s3);
  background: var(--parchment);
  color: var(--ink-4);
  font-size: 11px;
}

@media (max-width: 760px) {
  .row {
    grid-template-columns: 1fr;
    gap: var(--s5);
    padding: var(--s6) 0;
  }
  /* On one column the picture always leads: alternation is a two-column device, and
     stacked it just makes every other item start with text for no reason. */
  .row:nth-child(even) .row-figure { order: 0; }
  .sect-label { margin-top: var(--section); }
}

/* ---------- colophon ---------- */

.site-footer {
  border-top: 1px solid var(--border);
  padding: var(--s8) var(--s5) var(--s12);
  text-align: center;
  color: var(--ink-4);
  font-size: 12px;
  line-height: 1.4;
}
.site-footer p { margin: 0; }
/* Everything the masthead drops on a phone lands here, so no page becomes
   unreachable at a narrow width. */
.footer-nav {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--s2) var(--s5);
  margin: 0 0 var(--s5);
  font-size: 14px;
}
.footer-nav a {
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  color: var(--ink-3);
  border-bottom: 0;
}
.footer-nav a:hover { color: var(--ink); }
.site-footer a { color: var(--ink-3); border-bottom-color: var(--border); }
.site-footer a:hover { color: var(--ink); }

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

@media (max-width: 760px) {
  :root { --section: 48px; }
  body { font-size: 16px; }
  /* Chinese needs a smaller display size than Latin at the same column width. At
     32px in a 335px column a Chinese headline fits ten characters to a line, which
     breaks words across lines — 显示器 split after 显示, 面板里 split after 面.
     Latin at the same size fits forty-odd characters and breaks at spaces, so it
     does not have the problem and does not need the change. */
  /* `balance` rather than `pretty` here: pretty optimises only the last line, which
     left 设置 alone on line two. Balanced, the two lines carry a phrase each. */
  :lang(zh-Hans) h1, :lang(zh-Hant) h1 {
    font-size: clamp(25px, 7.2vw, 34px);
    text-wrap: balance;
  }
  :lang(zh-Hans) h2, :lang(zh-Hant) h2 { font-size: 20px; }
  .wrap { padding: var(--s8) var(--s5) var(--s8); }
  .shot img { max-height: none; }
}

@media (max-width: 480px) {
  :root { --section: 32px; }
  .wrap { padding: var(--s6) var(--s4) var(--s8); }
  .hero .actions .btn { width: 100%; justify-content: center; }
}

/* Respect the safe area on notched phones held in landscape. */
@supports (padding: max(0px)) {
  .nav-inner, .wrap, .site-footer {
    padding-left: max(var(--s5), env(safe-area-inset-left));
    padding-right: max(var(--s5), env(safe-area-inset-right));
  }
}

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

@media print {
  .nav, .site-footer { display: none; }
  body { background: #fff; font-size: 11pt; }
  .wrap { max-width: none; padding: 0; }
}
