/* One self-hosted type contract for every route. Keeping the font-face sources
   and fallback stacks here avoids synthetic weight changes and mixed fallback
   glyphs when navigating between the CSR portfolio and static Writing pages. */
@font-face { font-family: "Archivo"; font-weight: 400; font-style: normal; font-display: swap; src: url(/assets/fonts/archivo-400.woff2) format("woff2"); }
@font-face { font-family: "Archivo"; font-weight: 700; font-style: normal; font-display: swap; src: url(/assets/fonts/archivo-700.woff2) format("woff2"); }
@font-face { font-family: "Archivo"; font-weight: 800; font-style: normal; font-display: swap; src: url(/assets/fonts/archivo-800.woff2) format("woff2"); }
@font-face { font-family: "Archivo"; font-weight: 900; font-style: normal; font-display: swap; src: url(/assets/fonts/archivo-900.woff2) format("woff2"); }
/* The version pins the corrected Latin subsets against stale browser caches. */
@font-face { font-family: "IBM Plex Mono"; font-weight: 500; font-style: normal; font-display: swap; src: url(/assets/fonts/ibm-plex-mono-500.woff2?v=2) format("woff2"); }
@font-face { font-family: "IBM Plex Mono"; font-weight: 700; font-style: normal; font-display: swap; src: url(/assets/fonts/ibm-plex-mono-700.woff2?v=2) format("woff2"); }

:root {
  --font-sans: "Archivo", Arial, Helvetica, sans-serif;
  --font-mono: "IBM Plex Mono", ui-monospace, "Courier New", monospace;
  font-family: var(--font-sans);
  font-synthesis: none;
  text-rendering: geometricPrecision;
}

:root {
  --ink: #0a0a0a;
  --paper: #f2f0e9;
  --signal: #ff3b00;
  --line: rgba(10, 10, 10, 0.18);
  --muted: #65635e;
  --pad: clamp(1rem, 3vw, 2.5rem);
  --code-bg: #2b303b;
  --code-font: var(--font-mono);
}
* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font: 400 1.15rem/1.6 var(--font-sans);
}
a { color: var(--ink); text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: 2px; text-decoration-color: var(--line); }
a:hover { color: var(--signal); text-decoration-color: var(--signal); }
::selection { background: var(--signal); color: var(--paper); }

main { max-width: 50rem; margin: 0 auto; padding: clamp(2rem, 6vw, 4.5rem) clamp(1rem, 4vw, 2rem) 5rem; }

.eyebrow { font: 700 0.72rem/1 var(--font-mono); letter-spacing: 0.16em; text-transform: uppercase; color: var(--muted); margin: 0 0 1rem; }
h1.title { font-family: var(--font-sans); font-weight: 800; font-size: clamp(2rem, 5vw, 2.9rem); line-height: 1.04; letter-spacing: -0.02em; margin: 0 0 1rem; }
/* .post-meta, not .meta: syntect emits code tokens with classes like "meta group rust",
   so a bare .meta rule leaks into code blocks and flex-scrambles them. */
.post-meta { font: 500 0.8rem/1 var(--font-mono); color: var(--muted); letter-spacing: 0.04em; margin: 0 0 2.5rem; display: flex; flex-wrap: wrap; gap: 0.6rem; align-items: center; }
.post-meta .tag { text-decoration: none; border: 1px solid var(--line); padding: 0.22rem 0.5rem; border-radius: 2px; }
.post-meta .tag:hover { border-color: var(--signal); color: var(--signal); }

/* Headline furniture can use the full page measure, but continuous prose stays
   narrow enough to preserve the reader's place when moving between lines. */
article { max-width: 41rem; }
article h2, article h3 { position: relative; font-family: var(--font-sans); letter-spacing: -0.01em; line-height: 1.2; margin: 2.6rem 0 0.9rem; }
article h2 .anchor, article h3 .anchor { position: absolute; left: -1.15rem; text-decoration: none; color: var(--line); opacity: 0; transition: opacity 0.12s; }
article h2:hover .anchor, article h3:hover .anchor, article .anchor:focus { opacity: 1; color: var(--signal); }
@media (max-width: 34rem) { article .anchor { display: none; } }
article h2 { font-size: 1.6rem; font-weight: 800; }
article h3 { font-size: 1.25rem; font-weight: 700; }
article p { margin: 0 0 1.3rem; }
article ul, article ol { margin: 0 0 1.3rem; padding-left: 1.4rem; }
article li { margin: 0.35rem 0; }
article img { max-width: 100%; height: auto; border-radius: 4px; display: block; margin: 2rem auto; }
article blockquote { margin: 2rem 0; padding: 0.4rem 0 0.4rem 1.3rem; border-left: 3px solid var(--signal); color: var(--muted); font-style: italic; }
/* Markdown wraps quote text in a paragraph. Reset its global rhythm so the
   blockquote padding, rather than a trailing paragraph margin, sizes the callout. */
article blockquote p { margin: 0; }
article hr { border: none; border-top: 1px solid var(--line); margin: 3rem 0; }

article :not(pre) > code { font-family: var(--code-font); font-size: 0.86em; background: rgba(10, 10, 10, 0.06); padding: 0.12em 0.38em; border-radius: 3px; }
pre.code { background: var(--code-bg); color: #c0c5ce; border-radius: 6px; padding: 1.1rem 1.2rem; overflow-x: auto; margin: 1.8rem 0; font-size: 0.86rem; line-height: 1.6; tab-size: 4; -webkit-overflow-scrolling: touch; }
/* Code inherits the site mono face too, so technical examples remain part of the
   same system while retaining their distinct dark code surface. */
pre.code code { font-family: var(--code-font); background: none; padding: 0; font-size: inherit; }
/* Lift the comment token to WCAG AA on the dark code bg (theme default #65737e is ~2.7:1);
   these articles carry real explanation in comments. Specificity beats the appended .comment rule. */
pre.code .comment, pre.code .punctuation.definition.comment { color: #94a1ae; }

/* Language-label header bar. Only fenced blocks with a language get wrapped in a
   <figure> + <figcaption>; plain blocks stay bare with full corners and margin. */
figure.codeblock { margin: 1.8rem 0; }
figure.codeblock figcaption {
  display: flex; justify-content: space-between; align-items: center;
  font: 700 0.7rem/1 var(--font-mono); letter-spacing: 0.1em; text-transform: uppercase;
  color: #94a1ae; background: #232833; padding: 0.5rem 0.7rem 0.5rem 1.2rem; border-radius: 6px 6px 0 0;
  border-bottom: 1px solid rgba(255, 255, 255, 0.06);
}
figure.codeblock pre.code { margin: 0; border-radius: 0 0 6px 6px; }
/* Copy button, injected by the wasm enhancer; absent (and unmissed) without JS. */
figure.codeblock figcaption .copy {
  font: inherit; color: #94a1ae; background: transparent; border: 1px solid rgba(255, 255, 255, 0.16);
  border-radius: 4px; padding: 0.2rem 0.55rem; cursor: pointer; transition: color 0.12s, border-color 0.12s;
}
figure.codeblock figcaption .copy:hover { color: #e6e9ee; border-color: rgba(255, 255, 255, 0.4); }

/* Reading-progress bar, also injected by the enhancer. */
.reading-progress {
  position: fixed; top: 0; left: 0; height: 3px; width: 0; background: var(--signal);
  z-index: 100; transition: width 0.08s linear;
}

.table-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; margin: 1.8rem 0; }
article table { border-collapse: collapse; width: 100%; margin: 0; font-size: 0.92rem; }
article th, article td { border: 1px solid var(--line); padding: 0.5rem 0.7rem; text-align: left; }

.toc { border: 1px solid var(--line); border-radius: 6px; padding: 1rem 1.25rem; margin: 0 0 2.5rem; font-size: 0.92rem; }
.toc-title { font: 700 0.7rem/1 var(--font-mono); letter-spacing: 0.14em; text-transform: uppercase; color: var(--muted); margin: 0 0 0.6rem; }
.toc ul { column-count: 2; column-gap: 2rem; list-style: none; margin: 0; padding: 0; }
.toc li { break-inside: avoid; margin: 0 0 0.6rem; }
.toc li.lvl3 { padding-left: 1rem; }
.toc a { text-decoration: none; color: var(--ink); }
.toc a:hover { color: var(--signal); }
/* A single scanning column avoids cramped or wrapped contents labels once the
   viewport can no longer support two useful columns. */
@media (max-width: 40rem) { .toc ul { column-count: 1; } }
/* The heading itself owns the fragment id. Account for the shared 64px sticky
   top bar so a table-of-contents jump reveals the heading instead of hiding it
   and making the following paragraph look like the destination. */
article h2[id], article h3[id] { scroll-margin-top: calc(64px + 1.5rem); }

.post-nav { display: flex; justify-content: space-between; gap: 1.5rem; margin: 3.5rem 0 0; padding-top: 1.6rem; border-top: 1px solid var(--line); }
.post-nav a { text-decoration: none; max-width: 46%; }
.post-nav .newer { margin-left: auto; text-align: right; }
.post-nav .pn-dir { display: block; font: 700 0.7rem/1 var(--font-mono); letter-spacing: 0.1em; text-transform: uppercase; color: var(--muted); margin-bottom: 0.4rem; }
.post-nav .pn-title { font-family: var(--font-sans); font-weight: 700; line-height: 1.25; }
.post-nav a:hover .pn-title { color: var(--signal); }

.post-list { list-style: none; margin: 0; padding: 0; }
.post-item { padding: 1.6rem 0; border-top: 1px solid var(--line); }
.post-item:last-child { border-bottom: 1px solid var(--line); }
.post-item a.post-title { font-family: var(--font-sans); font-weight: 800; font-size: 1.4rem; text-decoration: none; letter-spacing: -0.01em; display: inline-block; margin-bottom: 0.4rem; }
.post-item a.post-title:hover { color: var(--signal); }
.post-item p { margin: 0.3rem 0 0.6rem; color: #3a3a38; }

footer.site { border-top: 1px solid var(--line); padding: 1.5rem clamp(1rem, 4vw, 2rem); font: 500 0.72rem/1.4 var(--font-mono); letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted); display: flex; justify-content: space-between; gap: 1rem; flex-wrap: wrap; }
footer.site a { text-decoration: none; }

/* Instrumentation figures. Diagrams are authored as inline SVG in the Markdown
   so they inherit the page's type and color tokens instead of shipping a raster
   that goes stale the moment a token changes — and so a re-measurement is a
   diff, not a re-export. SVG text uses var(--font-mono) via presentation
   attributes, which is why no font-family is set here. */
figure.diagram { margin: 2.2rem 0; }
figure.diagram svg { display: block; width: 100%; height: auto; overflow: visible; }
figure.diagram figcaption {
  margin-top: 0.9rem;
  font: 400 0.85rem/1.55 var(--font-sans);
  color: var(--muted);
}
figure.diagram figcaption code { font-family: var(--font-mono); font-size: 0.9em; }

/* ---- syntect code-block token colors appended below ---- */

/* Shared navigation chrome for the Leptos portfolio and build-time Writing
   pages. Both surfaces define the same design tokens before this file is
   applied; keeping component rules here prevents one-pixel and type-rendering
   drift when crossing the route boundary. */
.topbar {
  position: sticky;
  top: 0;
  z-index: 20;
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  min-height: 64px;
  padding: 0 var(--pad);
  border-bottom: 1px solid rgba(10, 10, 10, 0.3);
  font-family: var(--font-sans);
  font-synthesis: none;
  line-height: normal;
  color: var(--ink);
  text-rendering: geometricPrecision;
  text-transform: none;
  letter-spacing: normal;
  background: color-mix(in srgb, var(--paper) 93%, transparent);
  backdrop-filter: blur(14px);
}
.topbar a { color: var(--ink); text-decoration: none; }
.topbar a:hover { color: var(--ink); }
.topbar .wordmark:hover { color: var(--signal); }
.wordmark { font-size: 1.1rem; font-weight: 900; letter-spacing: -0.05em; }
.topbar nav { display: flex; gap: clamp(1rem, 3vw, 2.5rem); }
.topbar nav a { font-size: 0.72rem; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; }
.topbar nav a:hover { color: var(--ink); text-decoration: underline; text-underline-offset: 5px; }
.mobile-nav { display: none; }

.runtime-button {
  justify-self: end;
  display: inline-flex;
  align-items: center;
  gap: 0.55rem;
  border: 0;
  padding: 0;
  background: transparent;
  color: inherit;
  font-family: var(--font-mono);
  font-size: 0.68rem;
  font-weight: 700;
  cursor: pointer;
  transition: color 140ms ease;
}
/* The pulse communicates a live runtime without dominating the header. Orange
   is reserved for hover/open state, which keeps the interaction consistent on
   both static and CSR routes. */
.status-dot { position: relative; width: 8px; height: 8px; border-radius: 50%; background: #149447; }
/* box-shadow animation repaints the header every frame. A pseudo-element pulse
   preserves the same expanding-ring cue using only transform and opacity,
   which browsers can keep on the compositor instead. */
.status-dot::after { content: ""; position: absolute; inset: -4px; border: 1px solid rgba(20, 148, 71, 0.56); border-radius: inherit; animation: status-pulse 1.8s ease-out infinite; }
@keyframes status-pulse { 0%, 100% { opacity: 0.25; transform: scale(1); } 55% { opacity: 0; transform: scale(1.5); } }
.runtime-button:hover, .runtime-button.active { color: var(--signal); }
.runtime-button:hover .status-dot, .runtime-button.active .status-dot { background: var(--signal); }
.runtime-button:hover .status-dot::after, .runtime-button.active .status-dot::after { border-color: rgba(255, 59, 0, 0.56); }

.topbar nav a:focus-visible, .runtime-button:focus-visible {
  outline: 2px solid var(--signal);
  outline-offset: 3px;
}

/* Static Writing pages acquire this panel only after WASM loads, while the
   portfolio renders it directly. A shared off-canvas contract makes the
   progressive enhancement invisible to the user. */
.system-panel { position: fixed; z-index: 40; top: 0; right: 0; width: min(640px, 100%); height: 100dvh; overflow-y: auto; background: var(--ink); color: var(--paper); transform: translateX(102%); transition: transform 260ms cubic-bezier(.2,.8,.2,1); box-shadow: -20px 0 50px rgba(0,0,0,0.25); }
.system-panel.open { transform: translateX(0); }
.panel-head { position: sticky; top: 0; z-index: 2; display: flex; justify-content: space-between; padding: 1rem 1.25rem; border-bottom: 1px solid rgba(242,240,233,0.25); background: var(--ink); font-family: var(--font-mono); font-size: 0.68rem; }
.panel-head button { border: 0; padding: 0; background: transparent; color: var(--paper); font-family: inherit; cursor: pointer; }
.system-panel > p { padding: 1.5rem; color: rgba(242,240,233,0.65); line-height: 1.5; }
.diagnostic-grid { display: grid; grid-template-columns: 1fr 1fr; }
.diagnostic-grid div { display: grid; gap: 0.8rem; min-height: 130px; padding: 1.25rem; border-right: 1px solid rgba(242,240,233,0.2); border-bottom: 1px solid rgba(242,240,233,0.2); }
.diagnostic-grid span { color: rgba(242,240,233,0.55); font-family: var(--font-mono); font-size: 0.62rem; }
.diagnostic-grid strong { color: var(--signal); font-size: 1.05rem; word-break: break-word; }

@media (prefers-reduced-motion: reduce) {
  .status-dot::after { animation: none; opacity: 0.25; transform: scale(1); }
}

@media (max-width: 680px) {
  .topbar { grid-template-columns: 1fr auto auto; gap: 0.75rem; }
  .topbar > nav { display: none; }
  .mobile-nav { display: block; justify-self: end; }
  .mobile-nav summary { list-style: none; cursor: pointer; padding: 0.4rem 0.6rem; border: 1px solid var(--ink); font-family: var(--font-mono); font-size: 0.68rem; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; }
  .mobile-nav summary::-webkit-details-marker { display: none; }
  .mobile-nav[open] summary { background: var(--ink); color: var(--paper); }
  .mobile-nav nav { position: absolute; top: 100%; left: 0; right: 0; display: flex; flex-direction: column; background: var(--paper); border-bottom: 1px solid var(--line); box-shadow: 0 20px 40px rgba(10, 10, 10, 0.14); }
  .mobile-nav nav a { padding: 1rem var(--pad); border-top: 1px solid var(--line); font-family: var(--font-mono); font-size: 0.8rem; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; }
  .mobile-nav nav a:active { background: var(--ink); color: var(--paper); }
}

/*
 * theme "Base16 Ocean Dark" generated by syntect
 */

.code {
 color: #c0c5ce;
 background-color: #2b303b;
}

.variable.parameter.function {
 color: #c0c5ce;
}
.comment, .punctuation.definition.comment {
 color: #65737e;
}
.punctuation.definition.string, .punctuation.definition.variable, .punctuation.definition.string, .punctuation.definition.parameters, .punctuation.definition.string, .punctuation.definition.array {
 color: #c0c5ce;
}
.none {
 color: #c0c5ce;
}
.keyword.operator {
 color: #c0c5ce;
}
.keyword {
 color: #b48ead;
}
.variable, .variable.other.dollar.only.js {
 color: #bf616a;
}
.entity.name.function, .meta.require, .support.function.any-method, .variable.function {
 color: #8fa1b3;
}
.support.class, .entity.name.class, .entity.name.type.class {
 color: #ebcb8b;
}
.meta.class {
 color: #eff1f5;
}
.keyword.other.special-method {
 color: #8fa1b3;
}
.storage {
 color: #b48ead;
}
.support.function {
 color: #96b5b4;
}
.string, .constant.other.symbol, .entity.other.inherited-class {
 color: #a3be8c;
}
.constant.numeric {
 color: #d08770;
}
.none {
 color: #d08770;
}
.none {
 color: #d08770;
}
.constant {
 color: #d08770;
}
.entity.name.tag {
 color: #bf616a;
}
.entity.other.attribute-name {
 color: #d08770;
}
.entity.other.attribute-name.id, .punctuation.definition.entity {
 color: #8fa1b3;
}
.meta.selector {
 color: #b48ead;
}
.none {
 color: #d08770;
}
.markup.heading .punctuation.definition.heading, .entity.name.section {
 color: #8fa1b3;
}
.keyword.other.unit {
 color: #d08770;
}
.markup.bold, .punctuation.definition.bold {
 color: #ebcb8b;
font-weight: bold;
}
.markup.italic, .punctuation.definition.italic {
 color: #b48ead;
font-style: italic;
}
.markup.raw.inline {
 color: #a3be8c;
}
.string.other.link {
 color: #bf616a;
}
.meta.link {
 color: #d08770;
}
.meta.image {
 color: #d08770;
}
.markup.list {
 color: #bf616a;
}
.markup.quote {
 color: #d08770;
}
.meta.separator {
 color: #c0c5ce;
 background-color: #4f5b66;
}
.markup.inserted, .markup.inserted.git_gutter {
 color: #a3be8c;
}
.markup.deleted, .markup.deleted.git_gutter {
 color: #bf616a;
}
.markup.changed, .markup.changed.git_gutter {
 color: #b48ead;
}
.markup.ignored, .markup.ignored.git_gutter {
 color: #4f5b66;
}
.markup.untracked, .markup.untracked.git_gutter {
 color: #4f5b66;
}
.constant.other.color {
 color: #96b5b4;
}
.string.regexp {
 color: #96b5b4;
}
.constant.character.escape {
 color: #96b5b4;
}
.punctuation.section.embedded, .variable.interpolation {
 color: #ab7967;
}
.invalid.illegal {
 color: #2b303b;
 background-color: #bf616a;
}
.markup.deleted.git_gutter {
 color: #f92672;
}
.markup.inserted.git_gutter {
 color: #a6e22e;
}
.markup.changed.git_gutter {
 color: #967efb;
}
.markup.ignored.git_gutter {
 color: #565656;
}
.markup.untracked.git_gutter {
 color: #565656;
}

