/* Self-hosted IBM Plex, latin + latin-ext subsets pulled from Google Fonts (v20).
   Sans ships as a variable font: one file covers weights 400-500. */

@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(/fonts/ibm-plex-mono-400.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(/fonts/ibm-plex-mono-400-ext.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url(/fonts/ibm-plex-mono-500.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url(/fonts/ibm-plex-mono-500-ext.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url(/fonts/ibm-plex-mono-600.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url(/fonts/ibm-plex-mono-600-ext.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: 'IBM Plex Sans';
  font-style: normal;
  font-weight: 400 500;
  font-display: swap;
  src: url(/fonts/ibm-plex-sans-var.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'IBM Plex Sans';
  font-style: normal;
  font-weight: 400 500;
  font-display: swap;
  src: url(/fonts/ibm-plex-sans-var-ext.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* Well-Aware Technologies — site styles.
   Colours are authored in oklch, the space the design was tuned in.
   No border radius, no shadows, no gradients. Rules are 1px hairlines. */

:root {
  /* surfaces */
  --bg:               #0d0f11;
  --row-hover:        oklch(0.21 0.008 200);
  --code-bg:          oklch(0.19 0.008 200);

  /* text */
  --text:             oklch(0.9 0.008 200);
  --heading:          oklch(0.96 0.008 200);
  --wordmark:         oklch(0.93 0.008 200);
  --prose:            oklch(0.78 0.01 200);
  --prose-bright:     oklch(0.8 0.01 200);
  --prose-dim:        oklch(0.72 0.01 200);
  --prose-dimmer:     oklch(0.68 0.01 200);
  --prose-faint:      oklch(0.66 0.01 200);
  --bullet-text:      oklch(0.74 0.01 200);
  --code-text:        oklch(0.85 0.008 200);

  /* chrome */
  --label:            oklch(0.55 0.01 200);
  --group-label:      oklch(0.62 0.01 200);
  --muted:            oklch(0.5 0.01 200);

  /* accent */
  --accent:           oklch(0.82 0.11 178);
  --accent-hover:     oklch(0.92 0.06 178);

  /* lines */
  --rule:             oklch(0.24 0.01 200);
  --rule-header:      oklch(0.28 0.01 200);

  /* type */
  --mono: 'IBM Plex Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
  --sans: 'IBM Plex Sans', system-ui, -apple-system, Segoe UI, sans-serif;

  /* measure */
  --column: 780px;
  --column-narrow: 680px;

  color-scheme: dark;
}

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

html,
body { margin: 0; padding: 0; background: var(--bg); }


::selection { background: rgba(120, 220, 200, 0.25); }

body {
  background: var(--bg);
  color: var(--text);
  font-family: var(--mono);
  font-size: 15px;
  line-height: 1.65;
  min-height: 100vh;
  padding: 0 28px 120px;
  -webkit-font-smoothing: antialiased;
}

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

/* ---------------------------------------------------------------- chrome */

.site-header,
.site-footer,
.column { max-width: var(--column); margin-left: auto; margin-right: auto; }

.site-header {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 24px;
  padding: 28px 0 14px;
  border-bottom: 1px solid var(--rule-header);
}

.wordmark {
  font-size: 17px;
  color: var(--wordmark);
  font-weight: 500;
  letter-spacing: -0.01em;
}
.wordmark:hover { color: var(--heading); }

/* The header links carry no colour of their own: they are links, and take the
   accent from the base rule like the footer's and the body's do. */
.site-nav { display: flex; gap: 20px; font-size: 15px; }
/* The design gives nav links no current-page state; aria-current is set in the
   markup for screen readers only. Uncomment to surface it visually too.
.site-nav a[aria-current='page'] { color: var(--text); }
*/

.site-footer {
  margin-top: 96px;
  padding-top: 20px;
  border-top: 1px solid var(--rule);
  display: flex;
  justify-content: space-between;
  gap: 24px;
  flex-wrap: wrap;
  font-size: 13px;
  color: var(--muted);
}
/* Lowercased, and otherwise set exactly as the links beside it: same family,
   size and tracking, so the two halves of the footer read as one line. */
.site-footer > div { text-transform: uppercase; }
/* The gap either side of a separator, stated rather than left to a space
   character in the template. */
.site-footer .sep { margin: 0 0.45em; }
.site-footer nav { display: flex; gap: 18px; }

/* --------------------------------------------------------------- sections */

.section { padding: 76px 0 0; }
.section:first-child { padding-top: 88px; }

/* =========================================================== markdown ====

   What Markdown looks like, stated once for the whole site. Both renderers
   emit plain semantic HTML into a .md container, so a heading, a table or a
   list is the same thing on the landing page as it is in a post. Nothing
   below is allowed to key off which page it is on.

   Layout stays out of here: the two columns supply the measure, and .md only
   caps prose where the column is wider than comfortable (--measure). */
.md {
  --measure: 68ch;
  font-family: var(--sans);
  font-size: 16px;
  line-height: 1.7;
  color: var(--prose-bright);
  /* A URL or other unbreakable run has no space to wrap at, and would carry
     on past the column. Break it rather than let it overflow. Code blocks are
     unaffected: `white-space: pre` there keeps them scrolling instead. */
  overflow-wrap: break-word;
}
/* The post column is already a measure, so prose runs its full width. */
.column-narrow .md { --measure: none; }

.md > * { margin: 0 0 22px; }
.md > :last-child { margin-bottom: 0; }

.md p,
.md li,
.md blockquote { max-width: var(--measure); }

.md p { text-wrap: pretty; }

/* `##` opens a section: mono, uppercase, tracked out, brightest text. */
.md h2 {
  font-family: var(--mono);
  font-size: 16px;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--heading);
  margin: 44px 0 20px;
}
/* On the landing page each section opens with its `##`; the section's own
   padding is the space above it. */
.md h2:first-child { margin-top: 0; }

/* `###` is the same voice a step down — a label over the block it heads.
   Size and tracking carry the step down, not brightness. */
.md h3 {
  font-family: var(--mono);
  font-size: 12px;
  font-weight: 400;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--heading);
  margin: 34px 0 10px;
}

.md strong { font-weight: 500; color: var(--heading); }
.md em { color: var(--text); }

/* A URL used as its own link text is a technical string, not prose: mono at
   the size inline code takes, and no rule under it — the URL is already
   unmistakably a link. It also moves to the next line whole rather than
   breaking across two; it is narrower than either column, so it always lands,
   and the phone breakpoint lets it break again, where off-screen is worse. */
.md a.url,
.standfirst a.url {
  font-family: var(--mono);
  font-size: 14px;
  white-space: nowrap;
}

/* Lists carry an em dash rather than a disc. */
.md ul,
.md ol { padding: 0; list-style: none; }
.md li {
  display: grid;
  grid-template-columns: 14px 1fr;
  gap: 12px;
  margin-bottom: 12px;
  color: var(--bullet-text);
}
.md li:last-child { margin-bottom: 0; }
.md ul > li::before { content: '\2014'; color: var(--muted); }
.md ol { counter-reset: md-ol; }
.md ol > li { counter-increment: md-ol; }
.md ol > li::before { content: counter(md-ol) '.'; color: var(--muted); }

/* One table, everywhere: hairline rules, a fixed accent key column, no fill.
   The header row is the same label voice as an `###`. */
.md table {
  width: 100%;
  border-collapse: collapse;
  font-size: 15px;
  line-height: 1.5;
  text-align: left;
}
.md thead th {
  font-family: var(--mono);
  font-size: 12px;
  font-weight: 400;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--group-label);
  padding: 0 20px 8px 0;
}
.md tbody td {
  padding: 8px 20px 8px 0;
  border-top: 1px solid var(--rule);
  color: var(--prose-dim);
  vertical-align: baseline;
}
.md tbody tr:last-child td { border-bottom: 1px solid var(--rule); }
.md thead th:last-child,
.md tbody td:last-child { padding-right: 0; }
.md tbody td:first-child { width: 170px; color: var(--accent); }

/* A code block wears the same bar as a quote, in the same place, and a
   slightly lighter ground. The ground reaches back into the margin so that
   the bar sits on its left edge and the code stays flush with the prose. */
.md pre {
  border-left: 4px solid var(--accent);
  background: var(--code-bg);
  margin-left: -16px;
  padding: 14px 16px 14px 12px;
  font-family: var(--mono);
  font-size: 14px;
  line-height: 1.6;
  color: var(--code-text);
  white-space: pre;
  overflow-x: auto;
}
.md pre code { font-size: inherit; color: inherit; font-family: inherit; }
/* In a console transcript the `#` lines are commentary, not commands. */
.md pre.console .comment { color: var(--label); font-size: 13px; }

/* A quote and a console block carry the same accent bar the blog index shows
   beside a row on hover — here it is always on. Like that marker, it stands
   in the margin, so the text lines up with the prose around it. */
.md blockquote {
  position: relative;
  padding: 0;
  color: var(--prose-dim);
}
.md blockquote::before {
  content: '';
  position: absolute;
  top: 0;
  bottom: 0;
  left: -16px;
  width: 4px;
  background: var(--accent);
}

.md hr { border: 0; border-top: 1px solid var(--rule); margin: 40px 0; }
.md img { max-width: 100%; height: auto; display: block; }

/* -------------------------------------------------------------- page head */

.terminal-line {
  font-size: 17px;
  color: var(--accent);
  margin-bottom: 26px;
}
.prompt { color: var(--muted); user-select: none; }
.cursor { animation: blink 1.1s step-end infinite; }

@keyframes blink {
  0%, 49%   { opacity: 1 }
  50%, 100% { opacity: 0 }
}

@media (prefers-reduced-motion: reduce) {
  .cursor { animation: none; }
}

/* Every page opens the same way: terminal line, title, standfirst. The
   landing page's title is authored on two lines, joined by a <br>. */
.page-title {
  font-family: var(--sans);
  font-size: 38px;
  line-height: 1.2;
  font-weight: 500;
  letter-spacing: -0.025em;
  margin: 0 0 22px;
  color: var(--heading);
  /* No measure cap or balancing: either would fight the authored <br> on the
     landing page and force a third line. */
}

.standfirst + .standfirst { margin-top: 18px; }

/* Optional: a page renders no paragraph at all when it has none, so the
   space under the title is the next block's either way. */
.standfirst {
  font-family: var(--sans);
  font-size: 17px;
  line-height: 1.6;
  color: var(--prose-dim);
  margin: 0;
  max-width: 64ch;
  text-wrap: pretty;
}

/* Inline code inside sans prose steps back to mono at a matched size, in the
   accent. Inside a link it follows the link, so hover still reads. */
code {
  font-family: var(--mono);
  font-size: 14px;
  color: var(--accent);
}
a code { color: inherit; }

/* ------------------------------------------------------------ blog index */

.post-list { display: flex; flex-direction: column; margin-top: 44px; }

.post-row {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 22px 0;
  border-top: 1px solid var(--rule);
  color: inherit;
}
.post-row:last-child { border-bottom: 1px solid var(--rule); }


/* The date/author label, stated once: it is the same line above a title on the
   blog index and on the post itself. Same voice as an `###` and a table header
   — mono, uppercase, tracked out — a shade below them in brightness. */
.post-row .meta,
.post-meta,
.post-nav .label {
  font-family: var(--mono);
  font-size: 12px;
  font-weight: 400;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
}
/* Title and excerpt are grouped so the hover marker can span exactly those
   two and leave the label above them alone. */
.post-row .body {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

/* The marker: a hairline bar in the margin beside them, out of the text so
   nothing shifts. Revealed the same way as the arrow. */
.post-row .body::before {
  content: '';
  position: absolute;
  top: 0;
  bottom: 0;
  left: -16px;
  width: 4px;
  background: var(--accent);
  opacity: 0;
}
.post-row:hover .body::before { opacity: 1; }

/* A post's title standing in a list — the index rows and the newer/older
   steps. Only the size tells them apart. */
.post-row .title,
.post-nav .title {
  font-family: var(--sans);
  color: var(--wordmark);
}
.post-row .title { font-size: 19px; }
/* The arrow shows only on hover. It sits inside the title, so it takes the
   title's colour by inheritance; opacity rather than display keeps its space
   reserved, so nothing on the line moves. */
.post-row .arrow { opacity: 0; }
.post-row:hover .arrow { opacity: 1; }
.post-row .excerpt {
  font-family: var(--sans);
  font-size: 15px;
  color: var(--prose-faint);
  max-width: 66ch;
  text-wrap: pretty;
}

.empty-note { color: var(--muted); font-size: 14px; }

/* ----------------------------------------------------------------- post */

.column-narrow {
  max-width: var(--column-narrow);
  margin-left: auto;
  margin-right: auto;
}

.article { padding: 88px 0 0; }

.post-meta { margin: 0 0 10px; }

.post-title {
  font-family: var(--sans);
  font-size: 31px;
  line-height: 1.25;
  font-weight: 500;
  letter-spacing: -0.02em;
  margin: 0 0 36px;
  color: var(--heading);
  text-wrap: balance;
}

/* Newer and older, either side. `margin-left: auto` rather than
   space-between, so a lone older step still holds the right-hand side. */
.post-nav {
  margin-top: 56px;
  padding-top: 20px;
  border-top: 1px solid var(--rule);
  display: flex;
  gap: 24px;
}
.post-nav a {
  display: flex;
  flex-direction: column;
  gap: 6px;
  max-width: 48%;
}
.post-nav .older { margin-left: auto; text-align: right; }
.post-nav .title { font-size: 16px; }
.post-nav a:hover .title { color: var(--accent); }

/* ------------------------------------------------------------ not found */

.notfound { padding: 120px 0 0; }

/* ---------------------------------------------------------------- mobile */

@media (max-width: 600px) {
  body { padding: 0 20px 88px; font-size: 14px; }

  .site-header { gap: 14px; flex-wrap: wrap; }
  .site-nav { gap: 16px; }

  .section { padding-top: 60px; }
  .section:first-child { padding-top: 64px; }

  .page-title { font-size: 30px; }
  .standfirst { font-size: 16px; }
  /* Keep the install command on one line: a wrapped shell command reads as broken. */
  .terminal-line { font-size: 15px; }
  /* 20px of body padding out here, so the bar keeps clear of the edge. */
  .post-row .body::before,
  .md blockquote::before { left: -12px; }
  .md pre { margin-left: -12px; padding-left: 8px; }
  .article { padding-top: 64px; }
  .post-title { font-size: 26px; }
  /* A 170px key column is too wide to hold on a phone. */
  .md a.url,
  .standfirst a.url { white-space: normal; }
  .md tbody td:first-child { width: 34%; }
  .md table { font-size: 14px; }

  .md pre { font-size: 13px; }

  .site-footer { flex-direction: column; gap: 12px; }

  /* Two titles side by side do not fit; stack them and let both read left. */
  .post-nav { flex-direction: column; gap: 22px; }
  .post-nav a { max-width: none; }
  .post-nav .older { margin-left: 0; text-align: left; }
}

/* Below ~380px "Ship native applications." no longer fits on one line at 30px;
   step it down so the landing title keeps the two lines it is authored as. */
@media (max-width: 380px) {
  .page-title { font-size: 26px; }
  .terminal-line { font-size: 13px; }
}
