/* =========================================================================
   Portfolio — "Engineering Logbook"
   Static site. No build step, no external dependencies.
   Theme: light ("white") by default; dark applied when the OS prefers dark
   (prefers-color-scheme). All theme colors are CSS variables — see :root and
   the dark @media block below.
   ========================================================================= */

:root {
  color-scheme: light dark;

  /* ---- palette: LIGHT (default / "white") ----
     --bg matches the opaque background tile (static/img/bg_tile.png), so the two
     agree wherever the tile does not paint (e.g. .punch, .dim-plate). */
  --bg:        #f0f0f0;
  --glow:      rgba(14, 111, 191, 0.05);
  --line:      #a9c2dd;
  --line-dim:  #d3e0ee;
  --cyan:      #0e6fbf;   /* accent blue -- 4.56:1 on --bg */
  --cyan-br:   #0a5697;   /* accent hover (deeper on light) */
  --amber:     #c47f12;
  --ink:       #12293f;
  --ink-dim:   #4c667f;
  --ink-faint: #556e88;   /* 4.64:1 on --bg (WCAG AA for small mono labels) */

  /* ---- surfaces / component fills (theme-dependent) ----
     Three opaque tiers: the tile shows only in the margins, the board carries the
     content, and cards sit on the board. Opaque so the hatch never reads through
     text. Neighbouring tiers differ by ~1.07:1 -- perceptible, not loud. */
  --board:              #f7f8fa;
  --topbar-bg:          #f7f8fa;
  --sheet-glow:         rgba(14, 111, 191, 0.06);
  --panel-fill:         #ffffff;
  --panel-fill-hover:   #ffffff;
  --tag-bg:             rgba(14, 111, 191, 0.05);
  /* Markdown fills, translucent because a block sits on --board on a page and on
     --panel-fill inside a card -- one opaque value could only suit one of them.
     --md-fill is GitHub's subtle canvas (code blocks, even table rows),
     --md-code-fill its stronger neutral for an inline code chip. */
  --md-fill:            rgba(16, 40, 78, 0.04);
  --md-code-fill:       rgba(16, 40, 78, 0.08);
  --btn-primary-fg:     #ffffff;
  --accent-glow:        rgba(14, 111, 191, 0.18);
  --chip-pkg-fill:      rgba(14, 111, 191, 0.06);
  --chip-die-fill:      rgba(14, 111, 191, 0.03);
  --chip-block-fill:    rgba(14, 111, 191, 0.05);
  --danger:    #c62828;   /* 5.62:1 on --panel-fill, 5.29:1 on --board */

  /* ---- fonts / metrics ----
     Latin faces first, Korean appended -- GitHub's own structure. GitHub's stack has
     no Hangul face at all, so it hands Korean to whatever the browser picks; the
     Korean faces stay listed here (before generic sans-serif) so the fallback is a
     chosen one. The earlier order put Malgun Gothic first, which rendered *Latin* in
     it too, and its Latin glyphs read badly next to Hangul in mixed sentences.
     --mono is for code and nothing else (code / pre / kbd / samp). An earlier mono
     stack was removed because it had been put on *labels*, where it had no Hangul
     glyph and inherited letter-spacing meant for Latin capitals; this one appends
     Hangul faces, so a Korean word inside a code span falls back to a chosen face
     rather than a browser-picked one. Never put --mono on a label. */
  --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
          "Helvetica Neue", Arial,
          "Malgun Gothic", "맑은 고딕", "Apple SD Gothic Neo", "Noto Sans KR",
          sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas,
          "Liberation Mono", "Noto Sans Mono CJK KR", D2Coding,
          "Malgun Gothic", monospace;
  /* GitHub's 16px is a Latin measure: Hangul fills the em box far more, so the same
     px reads noticeably larger. One step down keeps every GitHub ratio intact. */
  --md-size: .9rem;
  --maxw: 1200px;
  --r: 6px;
}

/* ---- palette: DARK (when the OS prefers dark) ---- */
@media (prefers-color-scheme: dark) {
  :root {
    --bg:        #0a1626;
    --hatch:     rgba(120, 175, 225, 0.09);   /* dark stand-in for the tile's line */
    --glow:      rgba(92, 200, 255, 0.055);
    --line:      #2b5680;
    --line-dim:  #1d3f60;
    --cyan:      #5cc8ff;
    --cyan-br:   #93e0ff;
    --amber:     #ffce7a;
    --ink:       #d7e6f4;
    --ink-dim:   #90a9c2;
    --ink-faint: #7E9AB4;   /* 4.59:1 on the lightest dark surface: --md-fill over a panel */

    --board:              #0d1f38;
    --topbar-bg:          #0d1f38;
    --sheet-glow:         rgba(92, 200, 255, 0.06);
    --panel-fill:         #12263f;
    /* Kept dark enough that --ink-faint still clears 4.5:1 on a hovered card. */
    --panel-fill-hover:   #132840;
    --tag-bg:             rgba(92, 200, 255, 0.04);
    --md-fill:            rgba(120, 175, 225, 0.07);
    --md-code-fill:       rgba(120, 175, 225, 0.14);
    --btn-primary-fg:     #062033;
    --accent-glow:        rgba(92, 200, 255, 0.18);
    --chip-pkg-fill:      rgba(20, 45, 72, 0.55);
    --chip-die-fill:      rgba(10, 28, 48, 0.7);
    --chip-block-fill:    rgba(92, 200, 255, 0.05);
    --danger:    #ff8a80;   /* 6.69:1 on --panel-fill, 6.55:1 on --panel-fill-hover */
  }
}

* { box-sizing: border-box; margin: 0; padding: 0; }

/* The UA rule for [hidden] is a user-agent declaration, so any author `display`
   beats it -- .md-bar, .board-text and .md-b all set one, and `el.hidden = true`
   from JS stopped hiding them. Re-assert it in the author origin. */
[hidden] { display: none !important; }

/* Anchor jumps clear the sticky topbar instead of landing beneath it */
html { scroll-behavior: smooth; scroll-padding-top: 5rem; }

body {
  font-family: var(--sans);
  color: var(--ink);
  background-color: var(--bg);
  /* Diagonal hatch tile carried over from fota_server (5x5, opaque) plus the soft
     top glow. The glow is listed first so it washes over the tile rather than
     being hidden beneath it. */
  background-image:
    radial-gradient(130% 80% at 50% -15%, var(--glow), transparent 55%),
    url("../img/bg_tile.4ef1550c614c.png");
  background-size: 100% 900px, auto;
  background-repeat: no-repeat, repeat;
  background-attachment: fixed;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

/* The tile is an opaque light grey, so dark mode redraws the same "/" hatch as a
   gradient instead. 3.54px is the perpendicular gap of a 1px line on a 5px grid
   (5 / sqrt(2)), which matches the tile's spacing. */
@media (prefers-color-scheme: dark) {
  body {
    background-image:
      radial-gradient(130% 80% at 50% -15%, var(--glow), transparent 55%),
      repeating-linear-gradient(135deg, var(--hatch) 0 1px, transparent 1px 3.54px);
    background-size: 100% 900px, auto;
  }
}

a { color: inherit; text-decoration: none; }

/* shared bits ------------------------------------------------------------- */
.eyebrow {
  font-size: .82rem;
  font-weight: 700;
  letter-spacing: .02em;
  color: var(--cyan);
  margin-bottom: 1.1rem;
}
.eyebrow .tick { color: var(--ink-faint); margin-right: .4em; }

/* =========================================================================
   TOP BAR
   ========================================================================= */
.topbar {
  position: sticky;
  top: 0;
  z-index: 50;
  display: flex;
  align-items: center;
  gap: 1.5rem;
  padding: .85rem clamp(1rem, 4vw, 3rem);
  background: var(--topbar-bg);
  backdrop-filter: blur(9px);
  border-bottom: 1px solid var(--line-dim);
}
.brand { display: flex; align-items: center; gap: .7rem; color: var(--cyan); }
.brand-mark { flex: none; }
.brand-text { display: flex; flex-direction: column; line-height: 1.15; }
.brand-name { font-weight: 700; font-size: 1.02rem; color: var(--ink); letter-spacing: .02em; }
.brand-sub  { font-size: .58rem; font-weight: 700; letter-spacing: .14em; color: var(--ink-faint); }

.mainnav { margin-left: auto; display: flex; gap: 1.6rem; }
.mainnav a {
  font-family: var(--sans);
  font-size: .82rem;
  letter-spacing: .05em;
  color: var(--ink-dim);
  padding: .2rem 0;
  border-bottom: 1px solid transparent;
  transition: color .18s, border-color .18s;
}
.mainnav a:hover,
.mainnav a.active { color: var(--cyan); border-color: var(--cyan); }

.lang-toggle {
  font-family: var(--sans);
  font-size: .78rem;
  letter-spacing: .06em;
  color: var(--ink-dim);
  background: transparent;
  border: 1px solid var(--line);
  border-radius: var(--r);
  padding: .34rem .6rem;
  cursor: pointer;
  transition: border-color .18s, color .18s;
}
.lang-toggle:hover { border-color: var(--cyan); }
.lang-sep { color: var(--ink-faint); margin: 0 .1em; }
.lang-ko .lang-opt-ko,
.lang-en .lang-opt-en { color: var(--cyan); font-weight: 700; }

/* mobile menu button (shown < 860px) */
.nav-toggle { display: none; flex-direction: column; justify-content: center; gap: 4px; width: 40px; height: 34px; padding: 0 9px; background: transparent; border: 1px solid var(--line); border-radius: var(--r); cursor: pointer; }
.nav-toggle span { display: block; height: 2px; width: 100%; background: var(--ink-dim); transition: transform .2s, opacity .2s; }
.nav-toggle[aria-expanded="true"] span:nth-child(1) { transform: translateY(6px) rotate(45deg); }
.nav-toggle[aria-expanded="true"] span:nth-child(2) { opacity: 0; }
.nav-toggle[aria-expanded="true"] span:nth-child(3) { transform: translateY(-6px) rotate(-45deg); }

/* language switch is now a server-side <form> (Django i18n) instead of a <button> */
.lang-toggle { display: inline-flex; align-items: center; gap: .15em; }
.lang-toggle button.lang-opt {
  font: inherit; letter-spacing: inherit; color: inherit;
  background: none; border: 0; padding: 0; margin: 0; cursor: pointer;
}
.lang-toggle button.lang-opt:hover { color: var(--cyan); }

/* contact form + Django messages */
.messages { list-style: none; margin: 0 0 1.4rem; padding: 0; display: grid; gap: .5rem; }
.msg { padding: .7rem 1rem; border-radius: var(--r); font-size: .9rem; border: 1px solid var(--line); color: var(--ink); }
.msg.success { border-color: var(--cyan); }
.msg.error { border-color: var(--danger); }
.contact-form { display: grid; gap: 1rem; max-width: 40rem; }
.form-row { display: grid; gap: .35rem; margin: 0; }
.form-row label { font-size: .75rem; font-weight: 700; letter-spacing: .01em; color: var(--ink); }
.form-row .req { color: var(--cyan); }
.contact-form input,
.contact-form textarea {
  font: inherit; color: var(--ink); background: transparent;
  border: 1px solid var(--line); border-radius: var(--r);
  padding: .6rem .7rem; width: 100%; box-sizing: border-box;
}
.contact-form input:focus,
.contact-form textarea:focus { outline: none; border-color: var(--cyan); }
.contact-form textarea { resize: vertical; min-height: 7rem; }
.form-err { color: var(--danger); font-size: .8rem; }

/* =========================================================================
   HERO / LOG PAGE
   ========================================================================= */
.hero { padding: clamp(1.5rem, 4vw, 3rem) clamp(1rem, 4vw, 3rem); }

.sheet {
  position: relative;
  max-width: var(--maxw);
  margin: 0 auto;
  border: 1px solid var(--line);
  border-radius: var(--r);
  padding: clamp(1.6rem, 4vw, 3.4rem);
  /* Opaque: the hatch belongs in the margins, never behind the text. The glow sits
     on top of the board colour, so it tints rather than showing the tile through. */
  background:
    radial-gradient(120% 90% at 80% 0%, var(--sheet-glow), transparent 60%),
    var(--board);
  box-shadow: 0 1px 3px rgba(16, 40, 78, 0.06);
}

/* bound-page motif: three punch holes and the margin rule */
.punch {
  position: absolute; left: 11px; width: 9px; height: 9px;
  border-radius: 50%; border: 1px solid var(--line);
  background: var(--bg); opacity: .75; pointer-events: none;
}
.punch-1 { top: 14%; } .punch-2 { top: 50%; } .punch-3 { top: 86%; }
.sheet::before {
  content: ""; position: absolute; top: 0; bottom: 0; left: 30px;
  width: 1px; background: var(--line-dim);
}

.hero-grid {
  display: grid;
  grid-template-columns: 1.05fr .95fr;
  gap: clamp(1.5rem, 4vw, 3rem);
  align-items: center;
}

/* The hero heading: the name is carried by the title block, not a display line. */
.hero-role {
  font-family: var(--sans);
  font-size: clamp(1.3rem, 3.2vw, 2rem);
  font-weight: 700;
  line-height: 1.25;
  color: var(--cyan);
  letter-spacing: .01em;
  margin: .1rem 0 1.4rem;
}
/* A bold lead-in plus a supporting line. Both used to sit at 1.05rem, so the support
   read as loud as the lead. The support drops to .95rem -- the step .callout and
   .principle p already use -- and the lead takes 1.1em of that, which lands within
   0.1px of the 16.8px it had before. Only the regular text moves. */
.hero-tagline { color: var(--ink-dim); max-width: 42ch; font-size: .95rem; }
.hero-tagline strong, .hero-tagline b { font-size: 1.1em; }

.stats {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 1.6rem 2.2rem;
  margin: 2rem 0;
}
.stats li { display: flex; flex-direction: column; }
.stat-num {
  font-family: var(--sans);
  font-size: 1.7rem;
  font-weight: 700;
  color: var(--ink);
  letter-spacing: -.01em;
}
.stat-num .plus, .stat-num .arrow { color: var(--cyan); }
.stat-lbl {
  font-size: .78rem;
  font-weight: 700;
  letter-spacing: .01em;
  color: var(--ink-faint);
  margin-top: .25rem;
}

.cta-row { display: flex; flex-wrap: wrap; gap: .9rem; margin-top: .5rem; }
.btn {
  font-family: var(--sans);
  font-size: .88rem;
  letter-spacing: .04em;
  padding: .75rem 1.3rem;
  border-radius: var(--r);
  border: 1px solid var(--line);
  display: inline-flex;
  align-items: center;
  gap: .5em;
  transition: transform .15s, border-color .18s, background .18s, color .18s;
}
.btn-primary { background: var(--cyan); color: var(--btn-primary-fg); border-color: var(--cyan); font-weight: 700; }
.btn-primary:hover { background: var(--cyan-br); border-color: var(--cyan-br); transform: translateY(-2px); }
.btn-ghost { color: var(--ink); background: transparent; }
.btn-ghost:hover { border-color: var(--cyan); color: var(--cyan); transform: translateY(-2px); }
.btn-arrow { transition: transform .2s; }
.btn:hover .btn-arrow { transform: translateY(2px); }

/* ---- hero SVG (chip) ---------------------------------------------------- */
.hero-art { display: flex; justify-content: center; }
.chip { width: 100%; max-width: 520px; height: auto; overflow: visible; }

.chip .pkg  { fill: var(--chip-pkg-fill); stroke: var(--cyan); stroke-width: 1.5; }
.chip .die  { fill: var(--chip-die-fill); stroke: var(--line); stroke-width: 1; }
.chip .pin1 { fill: var(--amber); }
.chip .pins rect { fill: var(--line); }

.chip .block rect { fill: var(--chip-block-fill); stroke: var(--line); stroke-width: 1; }
/* 10.5px is what MIDDLEWARE needs to sit inside an 80-unit block. */
.chip .block text {
  font-family: var(--sans); font-size: 10.5px; fill: var(--ink);
  letter-spacing: .02em; font-weight: 600;
}

.chip .leader { stroke: var(--cyan); stroke-width: 1; fill: none; opacity: .55; stroke-dasharray: 2 3; }
.chip .pad    { fill: var(--cyan); }
.chip .lbl    { font-family: var(--sans); font-size: 12px; fill: var(--cyan); letter-spacing: .06em; }

.chip .dim-line, .chip .dim-tick { stroke: var(--ink-faint); stroke-width: 1; }
.chip .dim-plate { fill: var(--bg); stroke: var(--line); stroke-width: 1; }
.chip .dim-text  { font-family: var(--sans); font-size: 11px; fill: var(--ink-dim); letter-spacing: .08em; }

/* draw-in animation for outlines */
.chip .draw-outline {
  stroke-dasharray: 900;
  stroke-dashoffset: 900;
  animation: drawIn 1.6s ease-out forwards;
}
.chip .die.draw-outline { animation-delay: .35s; }
@keyframes drawIn { to { stroke-dashoffset: 0; } }

/* ---- front matter (logbook) --------------------------------------------- */
.titleblock {
  position: absolute;
  right: -1px; bottom: -1px;
  display: grid;
  grid-template-columns: repeat(2, auto);
  border-top: 1px solid var(--line);
  border-left: 1px solid var(--line);
  background: var(--topbar-bg);
  border-bottom-right-radius: var(--r);
  overflow: hidden;
}
.tb-cell {
  display: flex; flex-direction: column;
  padding: .4rem .7rem;
  border-right: 1px solid var(--line-dim);
  border-bottom: 1px solid var(--line-dim);
}
.tb-wide { grid-column: 1 / -1; }
.tb-k { font-size: .58rem; font-weight: 700; letter-spacing: .1em; color: var(--ink-faint); }
.tb-v { font-family: var(--sans); font-size: .78rem; color: var(--cyan); letter-spacing: .04em; }

/* =========================================================================
   CAREER TIMELINE
   ========================================================================= */
.track { list-style: none; position: relative; display: grid; gap: 1px; }
/* vertical spine trace */
.track::before {
  content: "";
  position: absolute;
  left: 7px; top: 8px; bottom: 8px;
  width: 2px;
  background: linear-gradient(var(--cyan), var(--line-dim));
  opacity: .5;
}

.node { position: relative; display: grid; grid-template-columns: 32px 1fr; gap: 1.2rem; padding: 1.1rem 0; }
.node-mark { position: relative; padding-top: .4rem; }
.node-dot {
  display: block; width: 16px; height: 16px; margin-left: -1px;
  border: 2px solid var(--cyan);
  border-radius: 3px;
  background: var(--bg);
  transform: rotate(45deg);
}
.node-current .node-dot { background: var(--cyan); box-shadow: 0 0 0 4px var(--accent-glow); }

.node-body {
  border: 1px solid var(--line-dim);
  border-radius: var(--r);
  padding: 1.2rem 1.4rem;
  background: var(--panel-fill);
  transition: border-color .2s, transform .2s, background .2s;
}
.node:hover .node-body { border-color: var(--cyan); transform: translateX(4px); background: var(--panel-fill-hover); }

.node-top { display: flex; flex-wrap: wrap; align-items: baseline; gap: .5rem 1rem; margin-bottom: .3rem; }
.node-year { font-family: var(--sans); font-size: .8rem; color: var(--cyan); letter-spacing: .05em; }
.node-role { font-family: var(--sans); font-size: .72rem; color: var(--ink-faint); letter-spacing: .06em; text-transform: uppercase; }
.node-co { font-size: 1.35rem; font-weight: 700; letter-spacing: .01em; margin-bottom: .5rem; }
.node-co-ko { font-weight: 400; font-size: .95rem; color: var(--ink-dim); margin-left: .4rem; }
.node-desc { color: var(--ink-dim); }

.tags { list-style: none; display: flex; flex-wrap: wrap; gap: .5rem; margin-top: 1rem; }
.tags li {
  font-family: var(--sans);
  font-size: .72rem;
  letter-spacing: .03em;
  color: var(--ink-dim);
  border: 1px solid var(--line-dim);
  border-radius: 4px;
  padding: .22rem .55rem;
  background: var(--tag-bg);
}
.node-current .tags li { border-color: var(--line); color: var(--ink); }

/* =========================================================================
   SKILLS / SPEC SHEET
   ========================================================================= */
.skills {
  max-width: var(--maxw);
  margin: 0 auto;
  padding: clamp(2.5rem, 7vw, 5rem) clamp(1rem, 4vw, 3rem);
  border-top: 1px solid var(--line-dim);
}
.spec-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  gap: 1rem;
  margin-top: clamp(1.5rem, 4vw, 2.5rem);
  align-items: start;
}
.spec-card {
  position: relative;
  border: 1px solid var(--line-dim);
  border-radius: var(--r);
  padding: 1.3rem 1.4rem;
  background: var(--panel-fill);
  transition: border-color .2s, background .2s, transform .2s;
}
.spec-card::before {
  /* dog-ear: a folded page corner */
  content: "";
  position: absolute; top: 0; right: 0;
  width: 14px; height: 14px;
  background: linear-gradient(225deg, var(--line-dim) 0 50%, transparent 50%);
  border-top-right-radius: var(--r);
  opacity: .9;
}
.spec-card:hover { border-color: var(--cyan); background: var(--panel-fill-hover); transform: translateY(-3px); }
.spec-head { display: flex; align-items: baseline; gap: .7rem; margin-bottom: 1rem; }
.spec-idx { font-family: var(--sans); font-size: .72rem; letter-spacing: .12em; color: var(--cyan); }
.spec-cat { font-size: 1.05rem; font-weight: 700; color: var(--ink); letter-spacing: .01em; }

/* wireless card: RF-family sub-groups */
.spec-card-wide { grid-column: span 2; }
.spec-groups { display: grid; gap: 1rem; }
.spec-sub {
  display: block;
  font-size: .76rem;
  font-weight: 700;
  letter-spacing: .01em;
  color: var(--ink-faint);
  margin-bottom: .5rem;
  border-left: 3px solid var(--cyan);
  padding-left: .55rem;
}
.spec-group .tags { margin-top: 0; }

/* =========================================================================
   COLOPHON (page foot) + email link
   ========================================================================= */
/* On the board, not on the tile: no text anywhere sits directly on the hatch, so
   the hatch never has to carry a contrast ratio. */
.colophon { border-top: 1px solid var(--line-dim); background: var(--board); }
.colophon-inner {
  max-width: var(--maxw);
  margin: 0 auto;
  padding: clamp(2rem, 5vw, 3rem) clamp(1rem, 4vw, 3rem);
  text-align: center;
}
.mail {
  display: inline-block;
  font-family: var(--sans);
  font-size: clamp(1.1rem, 3.5vw, 1.9rem);
  color: var(--cyan);
  border-bottom: 1px solid var(--line);
  margin: 1.4rem 0 2rem;
  padding-bottom: .2rem;
  transition: color .18s, border-color .18s;
}
.mail:hover { color: var(--cyan-br); border-color: var(--cyan); }

.foot-note {
  font-size: .78rem;
  font-weight: 700;
  letter-spacing: .01em;
  color: var(--ink-faint);
}
.dot-sep { margin: 0 .6em; }

/* =========================================================================
   INTERIOR PAGES (about / experience / projects / skills / contact)
   ========================================================================= */
/* The interior-page board. Opaque like .sheet, so the tile only shows in the
   margins either side -- a notice posted on a board rather than printed on it. */
.page-wrap {
  max-width: var(--maxw);
  margin: 0 auto;
  padding: clamp(2rem, 5vw, 3.5rem) clamp(1.2rem, 4vw, 3rem);
  background: var(--board);
  border-left: 1px solid var(--line-dim);
  border-right: 1px solid var(--line-dim);
  box-shadow: 0 1px 3px rgba(16, 40, 78, 0.06);
}
.page-head { max-width: 820px; margin-bottom: clamp(2rem, 5vw, 3.2rem); }
/* About: the page head with a portrait to its right. Narrow screens keep the photo in
   that corner at a fixed width and wrap the head text around it, because a second column
   there costs the title more width than the photo needs. The figure leads the markup
   since only a float ahead of the text can be wrapped around; the h1 is still the first
   heading, and `order` keeps the wide layout reading text-then-photo. */
.head-row { display: flex; align-items: flex-start; gap: clamp(1.2rem, 4vw, 2.6rem); }
.head-row .page-head { flex: 1 1 auto; min-width: 0; }
.portrait {
  order: 2;
  flex: 0 0 auto;
  margin: 0;
  padding: .45rem;
  width: clamp(136px, 18vw, 208px);
  background: var(--panel-fill);
  border: 1px solid var(--line-dim);
  border-radius: var(--r);
}
.portrait img { display: block; width: 100%; height: auto; border-radius: 3px; }
@media (max-width: 860px) {
  /* flow-root, not block: the row has to contain the float or a tall photo spills into
     section 01. 148px picks up where the wide layout's clamp leaves off at this width. */
  .head-row { display: flow-root; }
  .portrait { float: right; width: 148px; margin: 0 0 .8rem 1.2rem; }
}
@media (max-width: 480px) {
  .portrait { width: 116px; margin-left: 1rem; }
}
.page-title { font-size: clamp(1.9rem, 5vw, 3rem); font-weight: 800; line-height: 1.1; letter-spacing: -.02em; color: var(--ink); margin: .2rem 0 .9rem; }
/* No lead carries bold, so nothing to rank here -- just the largest body
   text on the site, sized on Latin. 1rem still reads as a lead against 14.4px body. */
.page-lead { color: var(--ink-dim); font-size: 1rem; }

/* prose */
.prose { color: var(--ink); }
.prose p { margin-bottom: 1rem; }
.prose strong, .prose b { color: var(--ink); font-weight: 700; }

/* generic panel + grids */
.panel { border: 1px solid var(--line-dim); border-radius: var(--r); background: var(--panel-fill); padding: clamp(1.2rem, 3vw, 1.8rem); }
.grid-2 { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 1.2rem; }

/* section heading with rule */
.block-head { display: flex; align-items: baseline; gap: 1rem; margin: clamp(2.5rem, 6vw, 4rem) 0 1.4rem; }
.block-head .idx { font-family: var(--sans); font-size: .78rem; color: var(--cyan); letter-spacing: .12em; }
.block-head h2 { font-size: clamp(1.3rem, 3vw, 1.8rem); font-weight: 700; color: var(--ink); white-space: nowrap; min-width: 0; }
.block-head .rule { flex: 1; height: 1px; background: var(--line-dim); }

/* principles / feature cards */
.principles { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 1rem; margin-top: 1.5rem; }
.principle { border: 1px solid var(--line-dim); border-radius: var(--r); padding: 1.2rem 1.3rem; background: var(--panel-fill); transition: border-color .2s, transform .2s; }
.principle:hover { border-color: var(--cyan); transform: translateY(-3px); }
.principle .p-idx { font-family: var(--sans); font-size: .72rem; color: var(--cyan); letter-spacing: .12em; }
.principle h3 { font-size: 1.02rem; color: var(--ink); margin: .4rem 0 .5rem; }
/* Headingless principle: the body takes over the gap the h3 used to hold */
.principle .p-idx + p { margin-top: .5rem; }
.principle p { color: var(--ink-dim); font-size: .95rem; }

/* project detail */
.project { position: relative; border: 1px solid var(--line-dim); border-radius: var(--r); background: var(--panel-fill); padding: clamp(1.4rem, 3.5vw, 2.2rem); margin-bottom: 1.4rem; transition: border-color .2s; }
.project:hover { border-color: var(--cyan); }
.project-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: .5rem 1rem; margin-bottom: .35rem; }
.project-idx { font-family: var(--sans); font-size: .78rem; color: var(--cyan); letter-spacing: .14em; }
.project-title { font-size: clamp(1.2rem, 3vw, 1.6rem); font-weight: 700; color: var(--ink); }
.project-meta { font-size: .8rem; font-weight: 700; color: var(--ink-faint); letter-spacing: .01em; margin-bottom: 1rem; }
/* Same shape as .hero-tagline: a standalone bold lead line, then the prose. It carried
   no size at all, so it inherited 16px and was the largest body text on the page --
   larger than the kv-list and spec blocks it introduces. It takes the body step now, and
   the lead keeps roughly the size it already had.
   .callout is deliberately left alone: its bold is an inline label on the same line
   ("<b>Design rationale</b> — …"), so enlarging it would bulge mid-sentence. */
.project-overview { color: var(--ink-dim); margin-bottom: 1.3rem; font-size: var(--md-size); }
.project-overview strong, .project-overview b { font-size: 1.15em; }
/* two borderless cards side by side; the right one (Platform / Stack / Context)
   drops below the left one once the pair can no longer sit comfortably */
.project-cols { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 1rem; }
.project-cols > div {
  min-width: 0;                       /* let a wide table shrink instead of overflowing */
  background: var(--tag-bg);
  border-radius: var(--r);
  padding: 1rem 1.1rem 1.1rem;
}
.kv-title { font-size: .8rem; font-weight: 700; letter-spacing: .01em; color: var(--ink); margin-bottom: .7rem; border-left: 3px solid var(--cyan); padding-left: .55rem; }
.project-cols .kv-title { margin-top: 0; }
.project-cols .spec-table,
.project-cols .md-spec-table dl { margin-top: 0; }
.project-cols .tags { margin-top: 0; }
/* The fallback twin of a plain markdown list -- same GitHub treatment, because a
   page renders this markup whenever the block is missing or unpublished. */
.kv-list { padding-left: 2em; list-style: disc; }
.kv-list li + li { margin-top: .25em; }
.kv-note { font-size: .8rem; color: var(--ink-faint); line-height: 1.55; margin-top: .7rem; }
/* Citation links in a note are covered by the content-link rules below; they must
   not also get a border-bottom, or the underline doubles. */

/* Spec block: the datasheet layout lives with the markdown rules further down, so one
   rule drives both this <dl class="spec-table"> fallback and the generated <dl>. */

/* callout / note */
.callout { border: 1px solid var(--line); border-left: 3px solid var(--cyan); border-radius: var(--r); background: var(--tag-bg); padding: 1rem 1.2rem; color: var(--ink-dim); font-size: .95rem; margin-top: 1.2rem; }
.callout b { color: var(--ink); }

/* at-a-glance strip (home) */
.glance { display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); gap: 1rem; margin-top: 2rem; }
.glance-card { position: relative; border: 1px solid var(--line); border-radius: var(--r); background: var(--panel-fill); padding: 1.2rem 1.3rem; transition: border-color .2s, transform .2s, box-shadow .2s; }
.glance-card:hover { border-color: var(--cyan); transform: translateY(-3px); box-shadow: 0 4px 14px var(--accent-glow); }
.glance-yr { font-family: var(--sans); font-size: .74rem; color: var(--cyan); letter-spacing: .05em; }
.glance-co { font-weight: 700; color: var(--ink); font-size: 1.1rem; margin: .3rem 0 .2rem; }
.glance-role { font-size: .78rem; font-weight: 700; color: var(--ink-faint); letter-spacing: .01em; }

/* teaser cards (home projects) */
.teasers { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 1rem; margin-top: 1.5rem; }
.teaser { position: relative; border: 1px solid var(--line); border-radius: var(--r); background: var(--panel-fill); padding: 1.3rem 1.4rem 2.6rem; transition: border-color .2s, transform .2s, box-shadow .2s; }
.teaser:hover { border-color: var(--cyan); transform: translateY(-3px); box-shadow: 0 4px 14px var(--accent-glow); }
.teaser h3 { color: var(--ink); font-size: 1.08rem; margin: .5rem 0 .4rem; }
.teaser p { color: var(--ink-dim); font-size: .93rem; }
.teaser .t-tag { font-size: .72rem; font-weight: 700; letter-spacing: .02em; color: var(--cyan); }

/* card link affordance: say the card opens a page, do not make the reader guess */
.teaser::after,
.glance-card::after {
  content: attr(data-cue) " \2192";
  position: absolute;
  left: 1.4rem; bottom: 1rem;
  font-size: .76rem;
  font-weight: 700;
  letter-spacing: .01em;
  color: var(--cyan);
  transition: transform .2s;
}
.glance-card { padding-bottom: 2.6rem; }
.glance-card::after { left: 1.3rem; }
.teaser:hover::after,
.glance-card:hover::after { transform: translateX(4px); }

/* =========================================================================
   LINKS
   The global reset strips link styling (a { color: inherit; text-decoration:
   none }) because most links here are whole cards or nav items. That left links
   *inside content* invisible -- including any Markdown link written through the
   inline editor. Content areas therefore opt back in explicitly.
   ========================================================================= */
.prose a,
.page-lead a,
.callout a,
.kv-list a,
.grid-table a,
.spec-table a,
.md-b a,
.md-i a,
.project-overview a,
.arch-cap a,
.kv-note a {
  color: var(--cyan);
  text-decoration: underline;
  text-decoration-color: var(--line);
  text-decoration-thickness: 1px;
  text-underline-offset: .18em;
  transition: color .18s, text-decoration-color .18s;
}
.prose a:hover,
.page-lead a:hover,
.callout a:hover,
.kv-list a:hover,
.grid-table a:hover,
.spec-table a:hover,
.md-b a:hover,
.md-i a:hover,
.project-overview a:hover,
.arch-cap a:hover,
.kv-note a:hover {
  color: var(--cyan-br);
  text-decoration-color: var(--cyan);
  text-decoration-thickness: 2px;
}

/* "there is a page about this" -- a door, not a hint.
   The fill is --panel-fill, not the accent-tinted --tag-bg: over the background
   tile the tinted version put the accent text at 3.66:1, under WCAG AA for 13.6px
   text. --panel-fill lifts it to 4.64:1 in the worst case (over a tile line px). */
.more-link {
  display: inline-flex;
  align-items: center;
  gap: .5em;
  font-family: var(--sans);
  font-size: .85rem;
  color: var(--cyan);
  margin-top: 1.4rem;
  padding: .5rem .9rem;
  border: 1px solid var(--line);
  border-radius: var(--r);
  background: var(--panel-fill);
  transition: gap .2s, background .18s, border-color .18s, color .18s, transform .15s;
}
.more-link:hover {
  gap: .8em;
  background: var(--cyan);
  border-color: var(--cyan);
  color: var(--btn-primary-fg);
  transform: translateY(-2px);
}

/* several chips in a row: a flex row, so a wrapped chip does not keep a stray
   left margin the way an adjacent-sibling rule would leave it */
.link-row { display: flex; flex-wrap: wrap; gap: .6rem; margin-top: 1.4rem; }
.link-row .more-link { margin-top: 0; }

/* per-page review pager */
.pager { display: flex; justify-content: space-between; gap: 1rem; margin-top: clamp(2.5rem, 6vw, 4rem); padding-top: 1.4rem; border-top: 1px solid var(--line-dim); font-family: var(--sans); font-size: .85rem; }
.pager a {
  color: var(--ink);
  padding: .6rem .9rem;
  border: 1px solid var(--line);
  border-radius: var(--r);
  background: var(--panel-fill);
  transition: border-color .18s, background .18s, color .18s, transform .15s;
}
.pager a:hover { color: var(--cyan); border-color: var(--cyan); background: var(--panel-fill-hover); transform: translateY(-2px); }
.pager .nxt { margin-left: auto; text-align: right; }
.pager small { display: block; color: var(--cyan); font-size: .72rem; font-weight: 700; letter-spacing: .01em; }

/* =========================================================================
   AX / PIPELINE + DATASHEET TABLE (leadership page)
   ========================================================================= */
.pipeline { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem .3rem; margin: 1.6rem 0; }
.pipe-node {
  font-family: var(--sans); font-size: .78rem; letter-spacing: .03em; color: var(--ink);
  border: 1px solid var(--line); border-radius: var(--r); padding: .4rem .7rem;
  background: var(--panel-fill); white-space: nowrap;
}
.pipe-node.last { border-color: var(--cyan); color: var(--cyan); }
.pipe-arrow { color: var(--cyan); font-family: var(--sans); }

.table-wrap { margin-top: .6rem; }
/* The fallback twin of a markdown table. GitHub's table rules every cell and shades
   even rows; the table itself scrolls, so a wide one never widens the page. */
.grid-table {
  display: block; width: max-content; max-width: 100%; overflow: auto;
  border-spacing: 0; border-collapse: collapse;
}
.grid-table th, .grid-table td {
  padding: .375rem .8125rem; border: 1px solid var(--line-dim);
  text-align: left; vertical-align: top;
}
.grid-table th { font-weight: 700; color: var(--ink); }
.grid-table td { color: var(--ink); }
.grid-table tr { border-top: 1px solid var(--line-dim); }
.grid-table tr:nth-child(2n) { background: var(--md-fill); }

/* =========================================================================
   PRODUCT PHOTOS
   ========================================================================= */
.proj-shots { display: flex; flex-wrap: wrap; gap: .8rem; margin-top: 1rem; }
.product-shot {
  margin: 0; padding: .6rem;
  background: #f5f8fd; border: 1px solid #cddaeb; border-radius: var(--r);
  width: clamp(122px, 17vw, 176px);
  display: flex; align-items: center; justify-content: center;
}
.product-shot img { display: block; width: 100%; height: auto; border-radius: 3px; }
.product-shot figcaption { font-family: var(--sans); font-size: .62rem; letter-spacing: .04em; color: var(--ink-faint); text-align: center; margin-top: .4rem; }
.product-shot:has(figcaption) { flex-direction: column; align-items: stretch; }

/* =========================================================================
   MARKDOWN CONTENT BLOCKS  ({% md_block %} -> apps.content)
   GitHub's markdown-body metrics -- 16px block rhythm, ruled h2, 2em list indent,
   fully bordered zebra tables, 85% monospace code -- painted with this site's
   colour tokens, so one look serves every block in both themes.
   Two deliberate departures. GitHub underlines a link only on hover, which is exactly
   the "cannot tell whether there is a link" the owner reported -- content links here
   stay underlined at rest (see the LINKS section). And bold is 700, not GitHub's 600:
   Malgun Gothic ships only 400 and 700, so a 600 request resolves up to Bold, while
   Segoe UI does have a 600 Semibold -- one mixed Korean/Latin sentence then renders at
   two different weights. 700 is Bold in both, and it is what the rest of the site uses.
   Every rule is shared with the hand-written fallback the block replaces
   (.grid-table / .spec-table / .kv-list): a page renders that markup whenever the
   row is missing or unpublished, and the two must not drift apart.
   style="…" on the tag no longer changes the look -- GitHub has one -- but it
   still files the block on the board.
   ========================================================================= */
.md-b { display: block; color: var(--ink); }
.md-i { display: inline; }
/* One size for markdown and the twins, so the pair cannot drift. .md-i is absent on
   purpose: an inline block must take the size of the .page-lead / .callout /
   .kv-note it sits inside, and 77 blocks do exactly that. */
.md-b, .grid-table, .spec-table, .kv-list { font-size: var(--md-size); }

/* One bottom gap for every block element, and none on the wrapper's own edges */
.md-b p, .md-b blockquote, .md-b ul, .md-b ol, .md-b dl,
.md-b table, .md-b pre { margin: 0 0 1rem; }
.md-b > :first-child { margin-top: 0; }
.md-b > :last-child { margin-bottom: 0; }

.md-b h2, .md-b h3, .md-b h4, .md-b h5, .md-b h6 {
  margin: 1.5rem 0 1rem; font-weight: 700; line-height: 1.25; color: var(--ink);
}
.md-b h2 { font-size: 1.5em; padding-bottom: .3em; border-bottom: 1px solid var(--line-dim); }
.md-b h3 { font-size: 1.25em; }
.md-b h4 { font-size: 1em; }
.md-b h5 { font-size: .875em; }
.md-b h6 { font-size: .85em; color: var(--ink-dim); }
.md-b hr { height: .25em; margin: 1.5rem 0; background: var(--line-dim); border: 0; }

/* Lists keep the browser's own markers at GitHub's 2em indent. `* { padding: 0 }`
   removed that indent, so it is re-stated here rather than inherited. */
.md-b ul, .md-b ol, .md-i ul, .md-i ol { padding-left: 2em; }
.md-b ul, .md-i ul { list-style: disc; }
.md-b ol, .md-i ol { list-style: decimal; }
.md-b ul ul, .md-i ul ul { list-style: circle; }
.md-b ul ul ul, .md-i ul ul ul { list-style: square; }
.md-b li + li, .md-i li + li { margin-top: .25em; }
.md-b li > p { margin-top: 1rem; }
/* A nested list closes up against its parent item. python-markdown puts the child
   list inside the parent <li>, which is what makes `li > ul` match. */
.md-b li > ul, .md-b li > ol,
.md-i li > ul, .md-i li > ol { margin-top: 0; margin-bottom: 0; }

/* Definition lists (the "spec" blocks): italic 600 term, indented definition */
.md-b dl { padding: 0; }
.md-b dt { padding: 0; margin-top: 1rem; font-size: 1em; font-style: italic; font-weight: 700; color: var(--ink); }
.md-b dd { padding: 0 1rem; margin: 0 0 1rem; color: var(--ink); overflow-wrap: anywhere; }
.md-b dl > :first-child { margin-top: 0; }
.md-b dl > :last-child { margin-bottom: 0; }

/* style="spec-table" -- the same GitHub <dl>, laid out as a two-column datasheet.
   The element, the Markdown and the renderer stay GitHub's; only the presentation
   changes, which is what style="…" is for. The owner chose this over the stacked
   definition list (2026-08-19): at nine rows the stacked form ran three screens and
   the label/value pairing was carried only by indentation.
   Equal specificity with the .md-b dl rules above, so source order does the
   overriding -- these must stay after them. The template fallback is a
   <dl class="spec-table">, so the same rule keeps the pair identical. */
.md-spec-table dl,
.spec-table {
  display: grid;
  grid-template-columns: 34% minmax(0, 1fr);
  font-size: .8rem;
  margin: .4rem 0 0;
  padding: 0;
}
.md-spec-table dt, .md-spec-table dd,
.spec-table dt, .spec-table dd {
  padding: .5rem .7rem;
  margin: 0;
  border-bottom: 1px solid var(--line-dim);
}
/* A quiet key rather than GitHub's italic term: in a 34% column the italic reads as an
   error, and the tracking is what makes a three-letter key like "SoC" legible. */
.md-spec-table dt, .spec-table dt {
  color: var(--ink-faint);
  font-style: normal;
  font-weight: 400;
  letter-spacing: .06em;
}
.md-spec-table dd, .spec-table dd { color: var(--ink); overflow-wrap: anywhere; }

/* Tables: every cell ruled, even rows shaded, and the table -- not the page --
   is what scrolls when it is too wide for the screen. */
.md-b table {
  display: block; width: max-content; max-width: 100%; overflow: auto;
  border-spacing: 0; border-collapse: collapse;
}
.md-b table th, .md-b table td {
  padding: .375rem .8125rem; border: 1px solid var(--line-dim);
  text-align: left; vertical-align: top;
}
.md-b table th { font-weight: 700; }
.md-b table tr { border-top: 1px solid var(--line-dim); }
.md-b table tr:nth-child(2n) { background: var(--md-fill); }
.md-b table td > :last-child { margin-bottom: 0; }

/* Code. --mono lives here and nowhere else; see the font tokens at the top. */
.md-b code, .md-i code, .md-b kbd, .md-b samp {
  font-family: var(--mono);
  font-size: 85%;
  padding: .2em .4em;
  background: var(--md-code-fill);
  border-radius: var(--r);
  white-space: break-spaces;
}
.md-b pre {
  font-family: var(--mono);
  font-size: 85%;
  line-height: 1.45;
  padding: 1rem;
  overflow: auto;
  background: var(--md-fill);
  border-radius: var(--r);
}
.md-b pre code {
  font-size: 100%; padding: 0;
  background: none; border-radius: 0;
  white-space: pre; word-break: normal;
}
.md-b kbd {
  border: 1px solid var(--line-dim);
  box-shadow: inset 0 -1px 0 var(--line-dim);
  vertical-align: middle;
}

.md-b blockquote {
  padding: 0 1em; color: var(--ink-dim);
  border-left: .25em solid var(--line);
}
.md-b blockquote > :first-child { margin-top: 0; }
.md-b blockquote > :last-child { margin-bottom: 0; }

.md-b strong, .md-i strong, .md-b b, .md-i b { font-weight: 700; color: var(--ink); }

/* =========================================================================
   OWNER TOOLS (superuser only) — edit links + the content board
   Editing lives on the board; a page only links to it, so there is never a
   second, diverging way to change content.
   ========================================================================= */
.md-board-link,
.md-edit-toggle {
  font-size: .72rem; font-weight: 700; letter-spacing: .01em;
  color: var(--cyan); background: var(--panel-fill);
  border: 1px solid var(--line); border-radius: var(--r);
  padding: .3rem .55rem; cursor: pointer; flex: none;
}
.md-board-link { border-color: var(--cyan); white-space: nowrap; }
.md-board-link:hover { background: var(--cyan); color: var(--btn-primary-fg); }
.md-edit-toggle:hover { border-color: var(--cyan); }
.md-edit-toggle.is-on { background: var(--cyan); color: var(--btn-primary-fg); border-color: var(--cyan); }

/* per-block link, hidden until edit mode is on */
.md-edit-link { display: none; }
.md-editing .md-edit-link {
  display: inline-flex; align-items: center; gap: .3em;
  margin-left: .5em; padding: .05rem .4rem;
  font-size: .72rem; font-weight: 700; white-space: nowrap;
  color: var(--cyan); background: var(--panel-fill);
  border: 1px dashed var(--cyan); border-radius: 4px;
  vertical-align: baseline;
}
.md-editing .md-edit-link:hover { background: var(--cyan); color: var(--btn-primary-fg); border-style: solid; }
/* make it obvious which region a link belongs to */
.md-editing [data-md-key] { outline: 1px dashed var(--line); outline-offset: 3px; }

.md-bar {
  position: fixed; z-index: 80; left: 50%; transform: translateX(-50%); bottom: 1rem;
  display: flex; align-items: center; gap: .7rem; flex-wrap: wrap; justify-content: center;
  max-width: calc(100vw - 1.5rem);
  padding: .55rem .8rem;
  background: var(--panel-fill-hover); border: 1px solid var(--line); border-radius: var(--r);
  box-shadow: 0 6px 20px var(--accent-glow);
}
.md-bar form { display: flex; margin: 0; }
.md-bar-title { font-size: .72rem; font-weight: 700; color: var(--cyan); }
.md-bar-status { font-size: .74rem; color: var(--ink-dim); }
.md-bar button, .md-bar-board {
  font: inherit; font-size: .74rem; font-weight: 700;
  color: var(--ink-dim); background: var(--panel-fill);
  border: 1px solid var(--line-dim); border-radius: var(--r);
  padding: .25rem .55rem; cursor: pointer;
}
.md-bar button:hover, .md-bar-board:hover { border-color: var(--cyan); color: var(--cyan); }
.md-bar-board { border-color: var(--cyan); color: var(--cyan); }

/* ---- the board ---------------------------------------------------------- */
/* Head runs full width so the admin link reaches the page edge; every text child, the
   title included, keeps the 820px measure the rest of the site reads at. */
.board-head { max-width: none; }
.board-head .eyebrow, .board-head .page-title,
.board-head .page-lead, .board-head .kv-note { max-width: 820px; }
.board-title-row {
  display: flex; align-items: center; justify-content: space-between;
  gap: 1rem; flex-wrap: wrap; margin-bottom: .9rem;
}
.board-title-row .page-title { margin-bottom: 0; }
/* .more-link carries the chip and the sliding arrow; only the placement differs here. */
.board-admin-link { flex: none; white-space: nowrap; margin-top: 0; }
.board-group { margin-top: 1rem; }
.board-count { font-weight: 400; color: var(--ink-faint); margin-left: .4em; }
.board-table { width: 100%; border-collapse: collapse; font-size: .82rem; margin-top: .5rem; }
.board-table th, .board-table td {
  text-align: left; padding: .45rem .6rem;
  border-bottom: 1px solid var(--line-dim); vertical-align: top;
}
.board-table thead th { font-size: .76rem; font-weight: 700; color: var(--cyan); border-bottom: 1px solid var(--line); }
.board-table tbody tr:hover { background: var(--board); }
.board-link { font-weight: 700; color: var(--ink); border-bottom: 1px solid var(--line); }
.board-link:hover { color: var(--cyan); border-color: var(--cyan); }
.board-key { color: var(--ink-faint); font-size: .76rem; overflow-wrap: anywhere; }
.board-lang { width: 3.2rem; text-align: center; }
.board-when { color: var(--ink-faint); font-size: .76rem; white-space: nowrap; }
.board-yes { color: var(--cyan); font-weight: 700; }
.board-no { color: var(--ink-faint); }
.board-unpublished { opacity: .6; }
.board-unpublished .board-link::after { content: " (unpublished)"; font-weight: 400; color: var(--ink-faint); }
.board-crumb { color: var(--cyan); border-bottom: 1px solid var(--line); }
.board-flag {
  font-size: .68rem; font-weight: 700; color: var(--cyan);
  border: 1px solid var(--line-dim); border-radius: 3px; padding: .05rem .3rem; margin-left: .3em;
}

/* ---- the board entry (write / viewer) ---------------------------------- */
.board-form { display: grid; gap: 1.2rem; margin-top: 1.4rem; }
.board-editor { border: 1px solid var(--line); border-radius: var(--r); background: var(--panel-fill); }
.board-editor-head {
  display: flex; align-items: center; justify-content: space-between; gap: .6rem;
  flex-wrap: wrap; padding: .5rem .7rem; border-bottom: 1px solid var(--line-dim);
}
.board-editor-head .kv-title { margin: 0; }
.board-tabs { display: flex; gap: .35rem; }
.board-tab {
  font: inherit; font-size: .74rem; font-weight: 700;
  color: var(--ink-dim); background: transparent;
  border: 1px solid var(--line-dim); border-radius: var(--r);
  padding: .2rem .5rem; cursor: pointer;
}
.board-tab.is-on { border-color: var(--cyan); color: var(--cyan); background: var(--tag-bg); }
.board-text {
  display: block; width: 100%; box-sizing: border-box; resize: vertical;
  min-height: 9rem; padding: .8rem .9rem; border: 0;
  border-radius: 0 0 var(--r) var(--r);
  font-family: var(--sans); font-size: .86rem; line-height: 1.7;
  color: var(--ink); background: transparent;
}
.board-text:focus { outline: 2px solid var(--accent-glow); outline-offset: -2px; }
/* No colour of its own: .md-b carries it, so preview cannot differ from the page */
.board-view { padding: .8rem .9rem; }
.board-actions { display: flex; align-items: center; gap: 1rem; flex-wrap: wrap; }
.board-actions .more-link { margin-top: 0; }
.board-check { display: inline-flex; align-items: center; gap: .4em; font-size: .82rem; font-weight: 700; color: var(--ink); }
.md-ed-wait { font-size: .78rem; color: var(--ink-faint); }

.md-login { max-width: 420px; }

@media (max-width: 860px) {
  .md-edit-toggle { order: 3; }
}

/* compact strip — the five feature-phone handsets stay on one line */
.proj-shots.shots-sm { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: .5rem; max-width: 520px; }
.shots-sm .product-shot { width: auto; padding: .4rem; }
.shots-sm figcaption { font-size: .56rem; margin-top: .3rem; }

@media (max-width: 520px) {
  .proj-shots { gap: .6rem; }
  .product-shot { width: calc(50% - .3rem); padding: .5rem; }
}

/* =========================================================================
   ARCHITECTURE DIAGRAMS (case-study SVGs)
   ========================================================================= */
.arch-fig, .proj-fig {
  /* light "drafting-paper" schematic card — locked to the light palette so every diagram
     (incl. imported fixed-color SVGs) reads consistently in both site themes */
  --panel-fill: #ffffff; --line: #a9c2dd; --line-dim: #d3e0ee; --cyan: #0e6fbf;
  /* --ink-faint carries .arch-cap, so it needs AA on this card's own background
     (#f5f8fd): the old #8399b1 was 2.76:1. */
  --ink: #12293f; --ink-dim: #4c667f; --ink-faint: #556e88;
  --tag-bg: rgba(14,111,191,.05); --btn-primary-fg: #ffffff;
  --danger: #c62828;
  margin: 1.5rem 0 .4rem; padding: 1.2rem; overflow-x: auto;
  -webkit-overflow-scrolling: touch; overscroll-behavior-x: contain;
  background: #f5f8fd; border: 1px solid #cddaeb; border-radius: var(--r);
}
.arch-fig img, .proj-fig img { display: block; width: 100%; height: auto; }
.arch-cap { font-size: .78rem; font-weight: 700; color: var(--ink-faint); letter-spacing: .01em; margin: .6rem 0 0;
  position: sticky; left: 0; }

/* =========================================================================
   RESPONSIVE
   ========================================================================= */
@media (max-width: 860px) {
  .hero-grid { grid-template-columns: 1fr; }
  .hero-art { order: -1; }
  .chip { max-width: 380px; }
  .titleblock { display: none; }
  /* .sheet padding drops to 1.6rem here, so the 30px margin rule would cross the copy */
  .punch, .sheet::before { display: none; }
  .nav-toggle { display: flex; margin-left: auto; }
  .mainnav {
    display: none;
    position: absolute; top: 100%; left: 0; right: 0;
    flex-direction: column; gap: 0; margin-left: 0;
    background: var(--topbar-bg); backdrop-filter: blur(9px);
    border-bottom: 1px solid var(--line-dim);
    padding: .3rem clamp(1rem, 4vw, 3rem) .8rem;
  }
  .mainnav.open { display: flex; }
  .mainnav a { padding: .75rem 0; border-bottom: 1px solid var(--line-dim); }
  .mainnav a:last-child { border-bottom: none; }
  .spec-card-wide { grid-column: span 1; }
}

/* Diagrams are drawn on a 1200-unit canvas with 9.5px sub-labels. Fitting one to a
   360px screen scales that text to ~3px, so below 780px hold a legibility floor and
   let the card pan horizontally instead of shrinking the artwork. */
@media (max-width: 780px) {
  .arch-fig img, .proj-fig img { width: auto; max-width: none; min-width: 780px; }
  .arch-fig, .proj-fig { padding: .9rem; }
  .arch-fig::before, .proj-fig::before {
    content: "↔"; position: sticky; left: 0;
    display: block; width: 1.2em;
    font-family: var(--sans); font-size: .8rem; color: var(--ink-faint);
    margin-bottom: .35rem;
  }
}

@media (max-width: 760px) {
  /* Platform / Stack / Context moves under My role / Key work */
  .project-cols { grid-template-columns: 1fr; }
}

@media (max-width: 700px) {
  .block-head { flex-wrap: wrap; gap: .5rem 1rem; }
  .block-head h2 { white-space: normal; }
  .pager { flex-wrap: wrap; }
}

@media (max-width: 520px) {
  .stats { gap: 1.2rem 1.6rem; }
  .stat-num { font-size: 1.4rem; }
  .node { grid-template-columns: 24px 1fr; gap: .8rem; }
  .md-spec-table dl, .spec-table { grid-template-columns: 40% minmax(0, 1fr); }
  .project-meta, .kv-list li, .glance-co { overflow-wrap: anywhere; }
}

/* modern phones sit at 360–390px — keep gutters and mono type from crowding out content */
@media (max-width: 400px) {
  .shots-sm { gap: .3rem; }
  .shots-sm .product-shot { padding: .25rem; }
  .shots-sm figcaption { display: none; }
  .project-cols > div { padding: .85rem .9rem .95rem; }
  .sheet { padding: 1.2rem 1rem; }
  .project { padding: 1.1rem 1rem; }
  .tags li { font-size: .68rem; }
  .md-spec-table dl, .spec-table { font-size: .74rem; }
  .md-spec-table dt, .md-spec-table dd,
  .spec-table dt, .spec-table dd { padding: .45rem .5rem; }
  .arch-fig, .proj-fig { padding: .7rem; }
  .stats { gap: 1rem 1.2rem; }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .chip .draw-outline { animation: none; stroke-dashoffset: 0; }
  /* Hover lifts and slides are motion too. Colour and border feedback is kept, so
     every affordance still reads -- only the movement is dropped. */
  .btn:hover,
  .teaser:hover,
  .glance-card:hover,
  .more-link:hover,
  .pager a:hover,
  .node:hover .node-body,
  .spec-card:hover { transform: none; }
  .btn:hover .btn-arrow,
  .teaser:hover::after,
  .glance-card:hover::after { transform: none; }
}

/* =========================================================================
   UTILITY TOOLS (owner-only page)
   ========================================================================= */
.util-form { display: grid; gap: 1rem; }
.util-form input[type="url"] {
  width: 100%; padding: .6rem .8rem; font-family: var(--sans); font-size: .95rem;
  color: var(--ink); background: var(--bg); border: 1px solid var(--line); border-radius: var(--r);
}
.util-form input[type="url"]:focus { outline: none; border-color: var(--cyan); }
.util-kind { border: 0; padding: 0; margin: 0; display: grid; gap: .7rem; }
.util-kind legend { float: left; width: 100%; font-size: .8rem; color: var(--ink-faint); letter-spacing: .08em; margin-bottom: .5rem; }
/* Format and its one setting on the same line; the radio column is fixed so the two
   selects line up under each other. */
.util-kind-row { display: flex; align-items: center; gap: .9rem; flex-wrap: wrap; }
.util-kind-row .util-radio { flex: 0 0 5rem; }
.util-radio label, .util-radio { display: inline-flex; align-items: center; gap: .4rem; color: var(--ink-dim); font-size: .95rem; }
.util-note { margin-top: 1rem; font-size: .85rem; color: var(--ink-faint); }
/* The cap goes on the inner span, not the cell: a td's max-width is ignored in an
   auto-layout table, so a long title would keep pushing the other columns out. */
.util-title .js-title {
  display: block; max-width: 26rem;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.util-title .form-err { display: block; margin-top: .3rem; font-size: .8rem; }
.util-file { white-space: nowrap; }
.util-state { white-space: nowrap; }
/* Zero-width until a percentage arrives, so a queued row shows an empty track */
.util-bar { display: block; width: 7rem; height: 3px; margin-top: .4rem; background: var(--line-dim); border-radius: 2px; overflow: hidden; }
.util-bar i { display: block; height: 100%; width: 0; background: var(--cyan); transition: width .3s linear; }
.util-settled .util-bar { opacity: .35; }
.util-del {
  padding: 0 .45rem; font-size: 1.1rem; line-height: 1.4; cursor: pointer;
  color: var(--ink-faint); background: none; border: 1px solid var(--line-dim); border-radius: var(--r);
}
.util-del:hover { color: var(--cyan); border-color: var(--cyan); }
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
.util-opt { display: inline-flex; align-items: center; gap: .5rem; flex-wrap: wrap; }
.util-opt label { font-size: .8rem; color: var(--ink-faint); letter-spacing: .01em; font-weight: 700; }
.util-opt select {
  padding: .45rem .6rem; font-family: var(--sans); font-size: .92rem;
  color: var(--ink); background: var(--bg); border: 1px solid var(--line); border-radius: var(--r);
}
/* The setting that does not apply to the chosen format stays visible but inert,
   so the layout does not jump when the format changes. */
.util-opt.util-off { opacity: .4; }
.util-opt.util-off select { cursor: not-allowed; }
/* The help line explains both split modes, so it wraps under the controls rather
   than stretching the row. */
.util-opt small { flex: 1 0 100%; color: var(--ink-faint); font-weight: 400; font-size: .8rem; }
/* The split pieces hang off the whole file rather than crowding the row: shut by
   default, because most jobs have none and every job still has the one file. */
.util-tracks { margin-top: .45rem; font-size: .85rem; }
.util-tracks summary { cursor: pointer; color: var(--cyan); }
.util-tracks ol { margin: .4rem 0 0; padding-left: 1.6rem; display: grid; gap: .25rem; }
.util-tracks a { color: var(--cyan); text-decoration: underline; text-underline-offset: .2em; }
.util-tracksize { color: var(--ink-faint); }
.util-nochapters { display: block; margin-top: .35rem; font-size: .8rem; color: var(--ink-faint); }

/* =========================================================================
   WEB UTILITIES — a separate site sharing this stylesheet
   ========================================================================= */
.brand-util { cursor: default; }

/* The tool index: a numbered logbook list, not a card grid. Two entries in a grid
   would be mostly whitespace, and the number is what tells them apart. */
.util-list { list-style: none; display: grid; gap: .7rem; margin-top: 1.8rem; }
.util-item { display: grid; grid-template-columns: 3rem 1fr; align-items: center; gap: .4rem; }
.util-num {
  font-weight: 700; font-size: .95rem; letter-spacing: .06em;
  color: var(--ink-faint); text-align: right; padding-right: .9rem;
}
.util-open {
  display: grid; grid-template-columns: 1fr auto; align-items: center;
  gap: .1rem 1rem; padding: 1rem 1.2rem;
  border: 1px solid var(--line); border-radius: var(--r); background: var(--panel-fill);
  transition: border-color .2s, background .2s;
}
a.util-open:hover { border-color: var(--cyan); background: var(--panel-fill-hover); }
.util-name { grid-column: 1; font-weight: 700; color: var(--ink); }
.util-desc { grid-column: 1; font-size: .9rem; color: var(--ink-dim); }
.util-go {
  grid-column: 2; grid-row: 1 / span 2;
  color: var(--cyan); font-weight: 700; transition: transform .2s;
}
a.util-open:hover .util-go { transform: translateX(4px); }
/* Its own block, after the rule it cancels: the selector is identical, so the earlier
   reduced-motion section would lose on source order and silently do nothing. */
@media (prefers-reduced-motion: reduce) {
  a.util-open:hover .util-go { transform: none; }
}
/* Nothing to open yet, so the row must not read as a doorway: dashed edge, no arrow. */
.util-item-empty .util-open { border-style: dashed; background: none; }
.util-item-empty .util-name { color: var(--ink-faint); font-weight: 400; }
.util-back { font-size: .85rem; margin-bottom: .6rem; }
/* The tool has no masthead, so section 01 is the first thing on the page. */
.block-head.util-first { margin-top: 0; }
/* Reads out what the link offers, so it sits under the field it is talking about. */
.util-probe { display: block; margin-top: .4rem; font-size: .8rem; color: var(--ink-faint); }
.util-probe:empty { display: none; }
/* Held shut while the link is read; the note beside the field says why. */
.util-reading button[type="submit"] { opacity: .55; cursor: progress; }
.util-back a { color: var(--cyan); text-decoration: underline; text-underline-offset: .2em; }
.util-back a:hover { text-decoration-thickness: 2px; }

.util-signout { margin-left: .6rem; }
.util-signout button { font-family: var(--sans); }
.util-foot {
  margin-top: 3rem; padding: 1.4rem clamp(1rem, 4vw, 3rem);
  border-top: 1px solid var(--line-dim);
  font-size: .8rem; color: var(--ink-faint); text-align: center;
}

/* The AI disclosure. Rules to either side rather than a heavier weight, because it is an
   aside about how the page was made and not one of its claims. */
.lead-note {
  display: flex; align-items: center; gap: .7rem;
  margin-top: .9rem;
  font-size: .78rem; color: var(--ink-faint);
}
.lead-note::before, .lead-note::after {
  content: ""; flex: 1 1 auto; height: 1px; background: var(--line-dim);
}

/* =========================================================================
   ASK — the local-model question section on About (apps.ask)
   The notice is not decoration: it is the only thing standing between a model
   that can overstate a role and a reader who cannot tell. It stays visible.
   ========================================================================= */
.ask-notice {
  border: 1px solid var(--line); border-left: 3px solid var(--amber);
  border-radius: var(--r); background: var(--md-fill);
  padding: .9rem 1.1rem; margin: 1rem 0 1.4rem;
  font-size: .85rem; color: var(--ink-dim);
}
.ask-notice p { margin-bottom: .5rem; }
.ask-notice > :last-child { margin-bottom: 0; }
.ask-notice-title { font-weight: 700; color: var(--ink); }
/* --ink-dim for the same reason as .ask-status: this sits on --md-fill, which composites
   lighter than --panel-fill and puts --ink-faint under AA in dark mode. */
.ask-notice-model { font-size: .78rem; color: var(--ink-dim); }
.ask-notice-model code, .ask-src code, .ask-modal-run code { font-family: var(--mono); font-size: .95em; }

.ask-row {
  position: relative;
  border: 1px solid var(--line-dim); border-radius: var(--r);
  background: var(--panel-fill); padding: 1rem 1.1rem; margin-bottom: .9rem;
  font-size: var(--md-size);
}
/* Hides the row from this visitor; the question itself is kept. Quiet until hovered,
   because it sits over a row whose answer is the thing being read. */
.ask-del { position: absolute; top: .45rem; right: .5rem; }
.ask-del button {
  font-family: var(--sans); font-size: 1.15rem; line-height: 1;
  padding: .1rem .35rem; cursor: pointer;
  color: var(--ink-faint); background: transparent;
  border: 1px solid transparent; border-radius: var(--r);
  transition: color .18s, border-color .18s;
}
.ask-del button:hover { color: var(--ink); border-color: var(--line); }
/* Clears the button, which overlaps the line the question would otherwise run onto. */
.ask-q { font-weight: 700; color: var(--ink); margin-bottom: .6rem; padding-right: 1.8rem; }
/* pre-wrap while the text streams, because the poll writes it with textContent and a
   server-rendered <br> and a streamed newline would otherwise break differently. */
.ask-a { color: var(--ink); margin-bottom: .5rem; white-space: pre-wrap; }
/* Finished, and rendered as Markdown by the worker: the source's own newlines are now the
   renderer's business, so pre-wrap has to go or every block gains a blank line. */
.ask-a.is-md { white-space: normal; }
/* The answer is written into the page as the model produces it, so a caret rides the end
   of the text while more of it is still coming. */
.ask-a.is-typing::after {
  content: ""; display: inline-block;
  width: .45em; height: 1em; margin-left: .12em; vertical-align: -.15em;
  background: var(--cyan);
  animation: ask-caret 1s steps(1) infinite;
}
@keyframes ask-caret { 50% { opacity: 0; } }
@media (prefers-reduced-motion: reduce) {
  .ask-a.is-typing::after { animation: none; }
}
/* The English answer arrives second, so it stays quiet until it does. */
.ask-a-en { color: var(--ink-dim); }
.ask-a:empty, .ask-src:empty { display: none; }
.ask-flag {
  display: inline-block; font-size: .74rem; font-weight: 700; color: var(--amber);
  border: 1px solid var(--amber); border-radius: 3px;
  padding: .05rem .35rem; margin-bottom: .5rem;
}
.ask-src { font-size: .76rem; color: var(--ink-faint); }
.ask-err { font-size: .8rem; color: var(--danger); }

/* A status bar under the answer, in the cyan the site already uses for "in progress"
   (the amber left border means a warning, and this is not one). */
.ask-status {
  margin-top: .7rem; padding: .45rem .7rem;
  border-left: 3px solid var(--cyan); border-radius: var(--r);
  background: var(--md-fill);
  font-size: .78rem;
}
/* The message is its own progress track: the whole line sits in grey and the dark copy
   sweeps across it. Two inline spans, so both wrap in one box and cannot disagree. */
.ask-status [data-ask-typed] { color: var(--ink); font-weight: 700; }
/* --ink-dim, not --ink-faint: --md-fill over --panel-fill composites to a *lighter*
   surface than either, which is the premise --ink-faint's own comment rests on. */
.ask-status [data-ask-rest] { color: var(--ink-dim); }

/* One conversation. The turns are read down the left rule, so a follow-up belongs to the
   question above it rather than starting again. */
.ask-thread { margin-bottom: 1.4rem; }
.ask-thread .ask-row { margin-bottom: 0; }
.ask-thread .ask-row + .ask-row { border-top: 0; border-radius: 0; }
.ask-thread .ask-row:first-child { border-radius: var(--r) var(--r) 0 0; }
.ask-thread .ask-row:last-child { border-radius: 0 0 var(--r) var(--r); }
.ask-thread .ask-row:only-child { border-radius: var(--r); }
/* Indented, and the rule is what carries the nesting -- a second border would read as a
   separate card, which is the thing this replaces. */
.ask-row.is-follow-up { padding-left: 1.9rem; }
.ask-row.is-follow-up::before {
  content: ""; position: absolute; left: 1.1rem; top: 1rem; bottom: 1rem;
  border-left: 2px solid var(--line);
}

.ask-form {
  display: flex; flex-wrap: wrap; align-items: center; gap: .6rem;
  margin-bottom: 1.4rem;
}
/* The follow-up form lives inside the last turn, so it takes the card's spacing rather
   than the section's. */
.ask-more {
  margin: .9rem 0 0; padding-top: .8rem;
  border-top: 1px dashed var(--line-dim);
}
.ask-more .ask-label { color: var(--ink-dim); }
.ask-label { font-size: .8rem; font-weight: 700; color: var(--ink); }
.ask-form input[type="text"] {
  flex: 1 1 18rem; min-width: 0;
  padding: .6rem .8rem; font-family: var(--sans); font-size: .95rem;
  color: var(--ink); background: var(--panel-fill);
  border: 1px solid var(--line); border-radius: var(--r);
}

/* The confirmation every question passes through. Full-bleed and above both the sticky
   topbar (z-index 50) and the edit bar (80), so the page behind it cannot be reached
   while it is open. [hidden] closes it -- see the !important rule near the reset. */
.ask-modal {
  position: fixed; inset: 0; z-index: 100;
  display: flex; align-items: center; justify-content: center;
  padding: 1.2rem;
  background: rgba(6, 18, 34, .55);
}
.ask-modal-box {
  width: min(30rem, 100%); max-height: 100%; overflow-y: auto;
  padding: 1.3rem 1.4rem;
  background: var(--panel-fill);
  border: 1px solid var(--line); border-left: 3px solid var(--cyan);
  border-radius: var(--r);
  box-shadow: 0 10px 30px rgba(6, 18, 34, .3);
  font-size: .87rem; color: var(--ink-dim);
}
.ask-modal-box p { margin-bottom: .6rem; }
.ask-modal-title { font-size: 1rem; font-weight: 700; color: var(--ink); }
.ask-modal-run { color: var(--ink-faint); }
.ask-modal-act {
  display: flex; flex-wrap: wrap; gap: .6rem; justify-content: flex-end; margin-top: 1.1rem;
}

@media (max-width: 520px) {
  .ask-form input[type="text"] { flex: 1 1 100%; }
  .ask-modal-act .btn { flex: 1 1 auto; justify-content: center; }
}
