/* =========================================================================
   Md Jahid Hasan — portfolio
   Visual direction C, "Paper & Signal" (confirmed 27 Jul 2026)

   Serif  (Newsreader)     -> the research voice: headings, metric numerals
   Sans   (Inter)          -> body copy
   Mono   (IBM Plex Mono)  -> the systems voice: eyebrows, labels, metadata

   The two-track narrative is encoded in the type, not just the words. When
   adding something new, ask which track it belongs to and pick the face.
   ========================================================================= */

/* ---------- tokens ---------- */

:root {
  --paper:        #f7f7f5;
  --surface:      #fffffe;
  --surface-sunk: #f1f1ee;
  --ink:          #161a19;
  --ink-soft:     #3d4442;
  --ink-muted:    #4f5653;   /* 7.0:1 on --paper */
  --ink-faint:    #5a615e;   /* 5.9:1 on --paper */
  --rule:         #e3e3de;
  --rule-strong:  #cfcfc9;
  /* Border for real controls (tabs, the theme toggle). Measured 3.2:1 against
     --paper so the control boundary itself clears WCAG 1.4.11, rather than
     leaning on the label to identify it. Decorative rules stay subtle. */
  --control-edge: #868c88;
  --accent:       #0f766e;
  --accent-soft:  #0f766e14;
  --accent-line:  #0f766e33;

  --serif: "Newsreader", "Newsreader Fallback", ui-serif, Georgia, serif;
  --sans:  "Inter", "Inter Fallback", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
  --mono:  "IBM Plex Mono", "Plex Mono Fallback", ui-monospace, Menlo, Consolas, monospace;

  --measure: 68ch;
  --gutter: clamp(1.25rem, 5vw, 3rem);
  --wrap: 1140px;

  --step: clamp(4.5rem, 9vw, 8rem);   /* vertical rhythm between sections */

  color-scheme: light;
}

:root[data-theme="dark"] {
  --paper:        #101211;
  --surface:      #171a19;
  --surface-sunk: #0b0d0c;
  --ink:          #e8eae9;
  --ink-soft:     #c3c9c7;
  --ink-muted:    #aab3b0;
  --ink-faint:    #929d99;   /* 6.7:1 on dark --paper */
  --rule:         #262a29;
  --rule-strong:  #363b3a;
  --control-edge: #626a66;
  --accent:       #5eead4;
  --accent-soft:  #5eead412;
  --accent-line:  #5eead43d;

  color-scheme: dark;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --paper:        #101211;
    --surface:      #171a19;
    --surface-sunk: #0b0d0c;
    --ink:          #e8eae9;
    --ink-soft:     #c3c9c7;
    --ink-muted:    #aab3b0;
    --ink-faint:    #929d99;   /* 6.7:1 on dark --paper */
    --rule:         #262a29;
    --rule-strong:  #363b3a;
    --control-edge: #626a66;
    --accent:       #5eead4;
    --accent-soft:  #5eead412;
    --accent-line:  #5eead43d;

    color-scheme: dark;
  }
}

/* ---------- metric-matched fallbacks ----------
   The real faces are declared in assets/fonts/fonts.css (generated). These are
   the stand-ins the browser uses during the font-display: swap window, tuned so
   the swap doesn't move the layout.

   Percentages are measured, not estimated: computed with fontTools from the
   actual WOFF2 files against the local fallback each rule names. Newsreader in
   particular is far tighter than Georgia (asc/desc 1470/-530 per 2000em vs
   1878/-449 per 2048em), so without these overrides every heading jumps on swap.
   Recompute if a face is ever swapped out. */

@font-face {
  font-family: "Newsreader Fallback";
  src: local("Times New Roman"), local("Times");
  size-adjust: 100.55%;
  ascent-override: 73.10%;
  descent-override: 26.36%;
  line-gap-override: 0%;
}
@font-face {
  font-family: "Inter Fallback";
  src: local("Arial"), local("Helvetica Neue"), local("Helvetica");
  size-adjust: 104.33%;
  ascent-override: 92.85%;
  descent-override: 23.12%;
  line-gap-override: 0%;
}
@font-face {
  font-family: "Plex Mono Fallback";
  src: local("Courier New"), local("Courier");
  size-adjust: 99.98%;
  ascent-override: 102.52%;
  descent-override: 27.50%;
  line-gap-override: 0%;
}

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

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

html {
  scroll-behavior: smooth;
  /* clears the sticky header; it grows to two rows below 880px */
  scroll-padding-top: 5.5rem;
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 1.09rem;
  line-height: 1.72;
  font-feature-settings: "kern", "liga";
  /* Deliberately NOT setting -webkit-font-smoothing: antialiased or
     text-rendering: optimizeLegibility. The first disables subpixel rendering
     and thins every stroke; the second buys nothing here and rasterises oddly.
     Together they made the whole page read blurry. Leave the browser alone. */
}

h1, h2, h3, h4 {
  font-family: var(--serif);
  font-weight: 400;
  line-height: 1.12;
  letter-spacing: -0.015em;
  margin: 0;
  text-wrap: balance;
}

p { margin: 0 0 1.15em; max-width: var(--measure); }
/* Long mono tokens -- DOIs, streamlit URLs, identifiers -- must break rather
   than force the page wider than the viewport. */
p, li, figcaption, dd, td, th { overflow-wrap: break-word; }
p:last-child { margin-bottom: 0; }

a {
  color: var(--accent);
  text-decoration: none;
  border-bottom: 1px solid var(--accent-line);
  transition: border-color .15s ease, color .15s ease, background-color .15s ease;
}
a:hover { border-bottom-color: var(--accent); }

/* No border-radius here: `outline` already follows the element's own radius,
   and setting one would square off every pill-shaped chip, tab and button the
   moment it takes keyboard focus. */
a:focus-visible,
button:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}

strong { font-weight: 600; color: var(--ink); }

code {
  font-family: var(--mono);
  font-size: .85em;
  color: var(--ink-soft);
  background: var(--surface-sunk);
  border: 1px solid var(--rule);
  border-radius: 3px;
  padding: .08em .35em;
  white-space: nowrap;
}
@media (max-width: 640px) {
  /* let long identifiers break on a phone rather than widen the page */
  code { white-space: normal; overflow-wrap: anywhere; }
}

caption { caption-side: top; }
ul { margin: 0 0 1.15em; padding-left: 1.15em; max-width: var(--measure); }
li { margin-bottom: .5em; }
li::marker { color: var(--ink-faint); }

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

::selection { background: var(--accent); color: var(--paper); }

/* ---------- layout ---------- */

.wrap {
  width: 100%;
  max-width: var(--wrap);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.section {
  padding-block: var(--step);
  border-top: 1px solid var(--rule);
}
.section--flush { border-top: 0; }

/* Section headers: mono eyebrow over serif title — the core Paper & Signal pattern. */
.eyebrow {
  font-family: var(--mono);
  font-size: .78rem;
  font-weight: 500;
  letter-spacing: .115em;
  text-transform: uppercase;
  color: var(--accent);
  margin: 0 0 1rem;
  display: block;
}
.eyebrow--muted { color: var(--ink-faint); }

.section-head { margin-bottom: 3.25rem; }
.section-head h2 {
  font-size: clamp(2rem, 4.2vw, 2.9rem);
}
.section-head .lede {
  margin-top: 1.15rem;
  font-size: 1.125rem;
  color: var(--ink-soft);
  max-width: 60ch;
}

.lede { color: var(--ink-soft); }
.muted { color: var(--ink-muted); }

/* mono metadata line, e.g. "IEEE ACCESS · 2024 · Q1" */
.meta {
  font-family: var(--mono);
  font-size: .80rem;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin: 0;
  max-width: none;
}

/* ---------- header / nav ---------- */

.site-header {
  position: sticky;
  top: 0;
  z-index: 50;
  /* Solid, not a translucent blur. backdrop-filter forces a full-width
     compositing layer over the scrolling content, which is the other classic
     cause of blurry text mid-scroll. A solid bar is also simply easier to read. */
  background: var(--paper);
  border-bottom: 1px solid transparent;
  transition: border-color .2s ease;
}
.site-header.is-stuck { border-bottom-color: var(--rule); }

.site-header .wrap {
  display: flex;
  align-items: center;
  gap: 1.5rem;
  height: 4.25rem;
}

.brand {
  font-family: var(--serif);
  font-size: 1.0625rem;
  color: var(--ink);
  border: 0;
  white-space: nowrap;
  letter-spacing: -0.01em;
}
.brand:hover { color: var(--accent); }

.nav {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: 1.6rem;
}
.nav a {
  font-family: var(--mono);
  font-size: .79rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ink-muted);
  border: 0;
  padding-block: .35rem;
  position: relative;
}
.nav a::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 1px;
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform .18s ease;
}
.nav a:hover { color: var(--ink); }
.nav a:hover::after,
.nav a.is-active::after { transform: scaleX(1); }
.nav a.is-active { color: var(--accent); }

/* The nav is never hidden. It used to `display: none` below 1010px, which meant
   navigation simply vanished on laptops at scaled DPI, on tablets and on every
   phone, with nothing offered in its place.

   Measured against the real font metrics, the full seven-item bar fits down to
   ~875px. Below that it moves to its own row and scrolls horizontally, so every
   link stays reachable and no JavaScript is involved. */
@media (max-width: 880px) {
  html { scroll-padding-top: 6.5rem; }
  .site-header .wrap {
    flex-wrap: wrap;
    height: auto;
    padding-block: .5rem;
    gap: .5rem 1rem;
  }
  .theme-toggle { margin-left: auto; }

  .nav {
    order: 3;
    width: 100%;
    margin-left: 0;
    gap: 1.3rem;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    padding-bottom: .15rem;
  }
  .nav::-webkit-scrollbar { display: none; }
  .nav a {
    font-size: .75rem;
    white-space: nowrap;
    padding-block: .2rem;
  }
}

.theme-toggle {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.15rem;
  height: 2.15rem;
  padding: 0;
  background: transparent;
  border: 1px solid var(--control-edge);
  border-radius: 50%;
  color: var(--ink-muted);
  cursor: pointer;
  transition: color .15s ease, border-color .15s ease;
  flex: none;
}
.nav + .theme-toggle { margin-left: 0; }
.theme-toggle:hover { color: var(--accent); border-color: var(--accent); }
.theme-toggle svg { width: 1rem; height: 1rem; }
.theme-toggle .icon-sun { display: none; }
:root[data-theme="dark"] .theme-toggle .icon-sun { display: block; }
:root[data-theme="dark"] .theme-toggle .icon-moon { display: none; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .theme-toggle .icon-sun { display: block; }
  :root:not([data-theme="light"]) .theme-toggle .icon-moon { display: none; }
}

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  background: var(--accent);
  color: var(--paper);
  padding: .6rem 1rem;
  z-index: 100;
  border: 0;
  font-family: var(--mono);
  font-size: .8rem;
}
.skip-link:focus { left: .5rem; top: .5rem; }

/* ---------- hero ---------- */

.hero {
  padding-top: clamp(3.5rem, 9vw, 6.5rem);
  padding-bottom: clamp(3.5rem, 7vw, 5.5rem);
}

.hero h1 {
  font-size: clamp(2.9rem, 8vw, 5.25rem);
  letter-spacing: -0.03em;
  margin-bottom: 1.5rem;
}

.hero .role {
  font-family: var(--mono);
  font-size: .84rem;
  letter-spacing: .13em;
  text-transform: uppercase;
  color: var(--ink-muted);
  margin: 0 0 2.25rem;
  max-width: none;
}
.hero .role .sep { color: var(--rule-strong); margin-inline: .55rem; }

/* the binding positioning line — set in serif, large, its own moment */
.thesis {
  font-family: var(--serif);
  font-size: clamp(1.35rem, 2.9vw, 1.85rem);
  line-height: 1.42;
  color: var(--ink);
  max-width: 34ch;
  margin: 0 0 1.75rem;
  letter-spacing: -0.011em;
}
.thesis em { font-style: italic; color: var(--accent); }

.hero .sub { max-width: 56ch; color: var(--ink-soft); }

.hero-links {
  display: flex;
  flex-wrap: wrap;
  gap: .6rem;
  margin-top: 2.5rem;
}

/* mono "chips" — the systems voice, used for every outbound link */
.chip {
  font-family: var(--mono);
  font-size: .80rem;
  letter-spacing: .04em;
  color: var(--ink-soft);
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: 999px;
  padding: .42rem .95rem;
  display: inline-flex;
  align-items: center;
  gap: .4rem;
  white-space: nowrap;
}
.chip:hover {
  color: var(--accent);
  border-color: var(--accent);
  background: var(--accent-soft);
}
.chip .arw { color: var(--ink-faint); font-size: .8em; }
.chip:hover .arw { color: var(--accent); }
.chip--accent { color: var(--accent); border-color: var(--accent-line); }

/* two or more chips in a row need real gaps, not collapsing inline whitespace */
.chiprow {
  display: flex;
  flex-wrap: wrap;
  gap: .5rem;
  max-width: none;
}

/* ---------- "what I'm building": three tracks converging ----------
   The convergence funnel is drawn with absolutely-positioned 1px elements
   rather than ASCII art, so it reflows and collapses to a single stem on
   narrow screens. Column centres are at 1/6, 3/6 and 5/6 of the width. */

.building {
  margin-top: clamp(3.5rem, 7vw, 5.5rem);
  padding-top: clamp(2.5rem, 5vw, 3.5rem);
  border-top: 1px solid var(--rule);
}
.building-lede {
  font-size: clamp(1.25rem, 2.5vw, 1.65rem);
  line-height: 1.4;
  max-width: 38ch;
  margin: 0 0 3rem;
}

.tri {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 0;
}
.tri-col {
  padding-inline: 1.75rem;
  border-left: 1px solid var(--rule);
}
.tri-col:first-child { padding-left: 0; border-left: 0; }
.tri-col:last-child { padding-right: 0; }

.tri-k {
  font-family: var(--mono);
  font-size: .79rem;
  font-weight: 500;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--ink);
  margin: 0 0 .85rem;
  max-width: none;
}
.tri-n { color: var(--accent); margin-right: .45rem; }

.tri-where {
  font-family: var(--serif);
  font-size: 1.05rem;
  line-height: 1.4;
  color: var(--ink-soft);
  margin: 0 0 1rem;
}

.chain {
  font-family: var(--mono);
  font-size: .80rem;
  color: var(--ink-soft);
  letter-spacing: .01em;
  line-height: 1.9;
  margin: 0;
  max-width: none;
}
.chain .arrow { color: var(--accent); padding-inline: .25rem; }
.chain--sub { color: var(--ink-faint); margin-top: .15rem; }

.tri-status {
  display: flex;
  align-items: center;
  gap: .5rem;
  font-family: var(--mono);
  font-size: .78rem;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ink-muted);
  margin: 1.15rem 0 0;
  max-width: none;
}
.tri-status .dot {
  width: .4rem;
  height: .4rem;
  border-radius: 50%;
  background: var(--accent);
  flex: none;
}

.tri-go { margin: .7rem 0 0; max-width: none; }
.tri-go a {
  font-family: var(--mono);
  font-size: .78rem;
  letter-spacing: .06em;
  text-transform: uppercase;
}

.converge {
  position: relative;
  height: 5.25rem;
  margin-top: .25rem;
}
.converge .stem,
.converge .bar,
.converge .out {
  position: absolute;
  background: var(--rule-strong);
}
.converge .stem { top: 0; width: 1px; height: 2.4rem; }
.converge .s1 { left: 16.6667%; }
.converge .s2 { left: 50%; }
.converge .s3 { left: 83.3333%; }
.converge .bar { top: 2.4rem; left: 16.6667%; right: 16.6667%; height: 1px; }
.converge .out {
  top: 2.4rem;
  left: 50%;
  width: 1px;
  height: 2.85rem;
  background: var(--accent);
}
.converge .node {
  position: absolute;
  top: 2.4rem;
  left: 50%;
  transform: translate(-50%, -50%);
  width: .5rem;
  height: .5rem;
  border-radius: 50%;
  background: var(--accent);
}

.headed {
  max-width: 58ch;
  margin-inline: auto;
  text-align: center;
}
.headed .eyebrow { text-align: center; }
.headed-statement {
  font-family: var(--serif);
  font-size: clamp(1.1rem, 2.2vw, 1.4rem);
  line-height: 1.55;
  color: var(--ink);
  max-width: none;
  margin: 0;
}
.headed-statement b { color: var(--accent); font-weight: 400; }

@media (max-width: 780px) {
  .tri { grid-template-columns: minmax(0, 1fr); }
  .tri-col {
    padding: 1.6rem 0;
    border-left: 0;
    border-top: 1px solid var(--rule);
  }
  .tri-col:first-child { border-top: 0; padding-top: 0; }
  .tri-col:last-child { padding-bottom: 0; }

  /* Funnel collapses to a single stem on the left edge once the columns
     stack, so it lines up with the page's left-aligned text rather than
     stranding a centred graphic above a left-aligned statement. */
  .converge { height: 3.25rem; margin-top: 1.5rem; }
  .converge .s1,
  .converge .s3,
  .converge .bar { display: none; }
  .converge .s2 { left: 0; height: 1.4rem; }
  .converge .node { left: 0; top: 1.4rem; }
  .converge .out { left: 0; top: 1.4rem; height: 1.85rem; }

  .headed { text-align: left; margin-inline: 0; }
  .headed .eyebrow { text-align: left; }
}

/* ---------- metric callout ---------- */

.metric {
  display: flex;
  align-items: baseline;
  gap: 1.15rem;
  flex-wrap: wrap;
  padding: 1.75rem 0 1.75rem 1.75rem;
  border-left: 2px solid var(--accent);
  background: var(--accent-soft);
  margin: 2.25rem 0;
}
.metric .figure {
  font-family: var(--serif);
  font-size: clamp(3rem, 7vw, 4.5rem);
  line-height: .92;
  color: var(--accent);
  letter-spacing: -0.035em;
  font-variant-numeric: lining-nums tabular-nums;
}
.metric .label {
  font-family: var(--mono);
  font-size: .84rem;
  line-height: 1.6;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ink-soft);
  max-width: 26ch;
}
.metric .label small {
  display: block;
  font-size: .95em;
  color: var(--ink-faint);
  text-transform: none;
  letter-spacing: .02em;
  margin-top: .25rem;
}

.metric--pair { gap: 2.75rem; }

/* Outcome strip for the case studies that have no published number.
   The design brief wants every case study to call its outcome out visually, but
   docs/ contains no metrics for AssessAuto or CICDAgent and none will be
   invented. This occupies the same visual slot as .metric and keeps the four
   case studies on one rhythm, using documented facts instead of figures. */
.metric--facts {
  /* A three-up grid, not flex-wrap. With flex the third fact dropped to its own
     row and the strip read as broken rather than as a set. */
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  padding-block: 1.5rem;
  gap: 0;
}
.metric--facts .fact {
  padding-inline: 1.15rem;
  border-left: 1px solid var(--accent-line);
}
.metric--facts .fact:first-child {
  padding-left: 0;
  border-left: 0;
}
.metric--facts .fact:last-child { padding-right: 0; }

.fact .k {
  display: block;
  font-family: var(--serif);
  font-size: 1.55rem;
  line-height: 1.1;
  color: var(--accent);
  letter-spacing: -0.02em;
}
.fact .v {
  display: block;
  font-family: var(--mono);
  font-size: .76rem;
  letter-spacing: .05em;
  text-transform: uppercase;
  color: var(--ink-muted);
  margin-top: .45rem;
  max-width: none;
  line-height: 1.6;
}

@media (max-width: 700px) {
  .metric--facts { grid-template-columns: minmax(0, 1fr); gap: 1.25rem; }
  .metric--facts .fact {
    padding: 0 0 1.25rem;
    border-left: 0;
    border-bottom: 1px solid var(--accent-line);
  }
  .metric--facts .fact:last-child { padding-bottom: 0; border-bottom: 0; }
}

/* ---------- case-study figures ----------
   Diagrams sit on a light card in both themes, because every supplied figure is
   drawn on white. Inverting them in dark mode would wreck the colour coding
   they rely on; a light mat is honest and keeps them legible.

   These are direct children of the .case-body grid and span both columns, so a
   diagram gets the full ~1044px rather than the ~555px text column. Inside the
   text column, detailed diagrams rendered too small to read. */
.case-body > .case-figure {
  grid-column: 1 / -1;
}
.case-figure {
  margin: 2.75rem 0 0;
  padding: 0;
}
.case-figure + .case-figure { margin-top: 2.25rem; }

/* Height cap, not width fill. The figures range from 0.58 to 2.14 in aspect
   ratio; letting them all fill the available width meant the portraits rendered
   over 1000px tall and stopped the read dead. Capping the height and centring
   gives every figure roughly the same visual weight whatever its shape, and the
   detail lost to the cap is one click away via .fig-zoom. */
.fig-zoom {
  display: block;
  width: fit-content;
  max-width: 100%;
  margin-inline: auto;
  position: relative;
  border: 0;
  line-height: 0;
}
.fig-zoom img {
  display: block;
  width: auto;
  height: auto;
  max-width: 100%;
  max-height: min(72vh, 660px);
  border: 1px solid var(--rule);
  border-radius: 4px;
  background: #fff;
}
.fig-zoom::after {
  content: "Full size";
  position: absolute;
  right: .55rem;
  bottom: .55rem;
  font-family: var(--mono);
  font-size: .7rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  line-height: 1;
  color: #fff;
  background: rgba(22, 26, 25, .82);
  padding: .38rem .6rem;
  border-radius: 3px;
  opacity: 0;
  transition: opacity .15s ease;
  pointer-events: none;
}
.fig-zoom:hover::after,
.fig-zoom:focus-visible::after { opacity: 1; }
.fig-zoom:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; }

.case-figure figcaption {
  font-family: var(--mono);
  font-size: .82rem;
  line-height: 1.72;
  color: var(--ink-faint);
  margin-top: .9rem;
  max-width: 68ch;
  margin-inline: auto;
}
.case-figure figcaption b { color: var(--ink-muted); font-weight: 500; }

@media (max-width: 640px) {
  .fig-zoom img { max-height: 420px; }
}

/* ---------- asset slot ----------
   A visible, labelled placeholder wherever an image belongs but hasn't been
   supplied yet. Deliberately obvious: it names the file path, the dimensions
   and what the image should show, so it reads as a content request rather than
   a broken layout. Delete the whole <figure> once the real image lands. */
.asset-slot {
  display: grid;
  gap: .55rem;
  margin: 2.25rem 0;
  padding: 1.5rem 1.6rem;
  border: 1px dashed var(--control-edge);
  border-radius: 4px;
  background: var(--surface-sunk);
  max-width: 62ch;
}
/* the noscript variant is a real message, not a pending request */
.asset-slot--solid { border-style: solid; }

.asset-slot .tag {
  font-family: var(--mono);
  font-size: .80rem;
  font-weight: 500;
  letter-spacing: .115em;
  text-transform: uppercase;
  color: var(--accent);
  margin: 0;
  max-width: none;
}
.asset-slot .what {
  font-size: .95rem;
  color: var(--ink-soft);
  margin: 0;
  max-width: none;
}
.asset-slot .spec {
  font-family: var(--mono);
  font-size: .78rem;
  line-height: 1.7;
  color: var(--ink-faint);
  margin: 0;
  max-width: none;
}
.asset-slot .spec code {
  background: transparent;
  border: 0;
  padding: 0;
  color: var(--ink-muted);
}

/* ---------- spacing utilities ----------
   These replace ten one-off inline style="" attributes. Anything reaching for
   an inline style again probably wants a class here instead. */
.mt-1 { margin-top: 1.5rem; }
.mt-2 { margin-top: 2rem; }
.lede--narrow { max-width: 62ch; margin-bottom: 2.5rem; }
.record--plain { margin-top: 1.5rem; padding-top: 0; border-top: 0; }
.stat--action { display: flex; align-items: flex-end; }
.tbl-caption { text-align: left; padding-bottom: .9rem; }
.pub-note {
  font-size: .9rem;
  color: var(--ink-muted);
  margin-bottom: .9rem;
}

/* ---------- case studies ---------- */

.case + .case { margin-top: var(--step); padding-top: var(--step); border-top: 1px solid var(--rule); }

.case-head {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: .5rem;
  margin-bottom: 2rem;
}
.case-head h3 {
  font-size: clamp(1.9rem, 4vw, 2.6rem);
  margin-bottom: .25rem;
}
.case-head .hook {
  font-family: var(--serif);
  font-style: italic;
  font-size: 1.2rem;
  color: var(--ink-muted);
  max-width: 48ch;
  margin: 0;
}

.case-body {
  display: grid;
  grid-template-columns: minmax(0, 1.55fr) minmax(0, 1fr);
  gap: clamp(2rem, 5vw, 4.5rem);
  align-items: start;
}
@media (max-width: 900px) {
  .case-body { grid-template-columns: minmax(0, 1fr); }
}

/* Not sticky. It used to be `position: sticky; top: 6rem`, which made it the
   last scroll-coupled compositing layer on the page -- and it was leaving
   painted residue over the figures below it after a fast scroll. The aside is
   three short link lists; it does not need to follow the reader down a long
   case study, and losing it removes the last thing that can smear. */
.case-aside {
  font-size: .9rem;
}

.aside-block + .aside-block { margin-top: 1.9rem; padding-top: 1.9rem; border-top: 1px solid var(--rule); }
.aside-block h4 {
  font-family: var(--mono);
  font-size: .76rem;
  font-weight: 500;
  letter-spacing: .115em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin: 0 0 .85rem;
}
.aside-block ul { list-style: none; padding: 0; margin: 0; }
.aside-block li { margin-bottom: .5rem; }

.stack-line {
  font-family: var(--mono);
  font-size: .80rem;
  line-height: 1.95;
  color: var(--ink-muted);
  letter-spacing: .01em;
}

/* numbered chapters within a case study */
.chapter { padding-top: 2rem; }
.chapter + .chapter { margin-top: 2rem; border-top: 1px dashed var(--rule); }
.chapter-label {
  font-family: var(--mono);
  font-size: .78rem;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin: 0 0 .5rem;
  max-width: none;
}
.chapter-label .n { color: var(--accent); }
.chapter h4 {
  font-family: var(--serif);
  font-size: 1.5rem;
  font-weight: 400;
  margin: 0 0 .3rem;
}
.chapter .q {
  font-family: var(--mono);
  font-size: .8rem;
  color: var(--ink-muted);
  margin: 0 0 1rem;
  max-width: none;
}

.linkrow {
  display: flex;
  flex-wrap: wrap;
  gap: .5rem;
  margin-top: 1.25rem;
}

/* footnote (e.g. the Agentic-AI study notes under PromptProof) */
.footnote {
  font-size: .875rem;
  color: var(--ink-muted);
  border-left: 2px solid var(--rule-strong);
  padding-left: 1rem;
  margin-top: 1.75rem;
  max-width: 62ch;
}

/* availability note — used where there is no public repo */
.note {
  font-family: var(--mono);
  font-size: .80rem;
  line-height: 1.75;
  color: var(--ink-muted);
  background: var(--surface-sunk);
  border: 1px solid var(--rule);
  padding: .9rem 1.1rem;
  margin-top: 1.75rem;
  max-width: 62ch;
}
.note b { color: var(--ink-soft); font-weight: 500; }

/* ---------- comparison table ---------- */

/* min-width: 0 so this can always shrink below the table's 34rem and scroll
   internally, whatever layout context it lands in. Without it the table's
   min-content leaks out to the ancestor and widens the page. */
.table-scroll { overflow-x: auto; min-width: 0; margin: 2rem 0; }
table {
  border-collapse: collapse;
  width: 100%;
  min-width: 34rem;
  font-size: .92rem;
}
th, td {
  text-align: left;
  padding: .85rem 1.1rem .85rem 0;
  border-bottom: 1px solid var(--rule);
  vertical-align: top;
}
thead th {
  font-family: var(--mono);
  font-size: .76rem;
  font-weight: 500;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--ink-faint);
  border-bottom-color: var(--rule-strong);
  padding-bottom: .7rem;
}
tbody th {
  font-family: var(--sans);
  font-weight: 500;
  color: var(--ink-soft);
  font-size: .88rem;
  padding-right: 2rem;
}
tbody td { color: var(--ink-muted); }
tbody tr:last-child th, tbody tr:last-child td { border-bottom: 0; }

/* ---------- live demo band ---------- */

.live {
  background: var(--surface-sunk);
  border-block: 1px solid var(--rule);
}

.live-head {
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  gap: 2rem;
  flex-wrap: wrap;
  margin-bottom: 2.5rem;
}
.live-head h2 { font-size: clamp(1.9rem, 4vw, 2.6rem); }

.pulse {
  display: inline-block;
  width: .5rem; height: .5rem;
  border-radius: 50%;
  background: var(--accent);
  margin-right: .55rem;
  vertical-align: middle;
  box-shadow: 0 0 0 0 var(--accent);
  animation: pulse 2.4s ease-out infinite;
}
@keyframes pulse {
  0%   { box-shadow: 0 0 0 0 var(--accent-line); }
  70%  { box-shadow: 0 0 0 .5rem transparent; }
  100% { box-shadow: 0 0 0 0 transparent; }
}

.tabs {
  display: flex;
  gap: .5rem;
  flex-wrap: wrap;
  margin-bottom: 1.5rem;
  border-bottom: 1px solid var(--rule);
  padding-bottom: 1.25rem;
}
.tab {
  font-family: var(--mono);
  font-size: .80rem;
  letter-spacing: .05em;
  color: var(--ink-muted);
  background: transparent;
  border: 1px solid var(--control-edge);
  border-radius: 999px;
  padding: .5rem 1.05rem;
  cursor: pointer;
  transition: all .15s ease;
  display: inline-flex;
  align-items: center;
  gap: .5rem;
}
.tab:hover { color: var(--ink); border-color: var(--ink-faint); }
.tab[aria-selected="true"] {
  color: var(--paper);
  background: var(--accent);
  border-color: var(--accent);
}
:root[data-theme="dark"] .tab[aria-selected="true"] { color: #0b0d0c; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .tab[aria-selected="true"] { color: #0b0d0c; }
}
.tab .flag {
  font-size: .85em;
  opacity: .75;
  letter-spacing: .08em;
}

.panel[hidden] { display: none; }

.panel-meta {
  display: flex;
  justify-content: space-between;
  gap: 1.5rem;
  flex-wrap: wrap;
  align-items: flex-start;
  margin-bottom: 1.25rem;
}
.panel-meta .desc { max-width: 60ch; font-size: .95rem; color: var(--ink-soft); }
.panel-meta .desc h3 {
  font-size: 1.3rem;
  margin-bottom: .4rem;
}
.panel-meta .desc .meta { margin-bottom: .75rem; }

.frame {
  position: relative;
  background: var(--surface);
  border: 1px solid var(--rule-strong);
  border-radius: 4px;
  overflow: hidden;
}
.frame-bar {
  display: flex;
  align-items: center;
  gap: .55rem;
  padding: .6rem .9rem;
  border-bottom: 1px solid var(--rule);
  background: var(--surface-sunk);
}
.frame-bar .dot {
  width: .55rem; height: .55rem; border-radius: 50%;
  background: var(--rule-strong);
  flex: none;
}
.frame-bar .url {
  flex: 1 1 auto;
  min-width: 0;
  font-family: var(--mono);
  font-size: .76rem;
  color: var(--ink-faint);
  margin-left: .5rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  border: 0;
}
.frame-bar .url:hover { color: var(--accent); }

/* One shared height for the stub and the iframe that replaces it -- they used to
   differ, so every demo load shifted the page.

   Compact by default: at 78vh the embed swallowed the whole screen and pushed
   the rest of the band out of view. 480px is enough to work the app's controls
   and read a chart, and the Expand control below gives back the full height for
   anyone who actually wants to explore. */
.frame { --embed-h: min(52vh, 480px); }
.frame.is-expanded { --embed-h: min(88vh, 900px); }

.frame iframe {
  display: block;
  width: 100%;
  height: var(--embed-h);
  transition: height .28s cubic-bezier(.22,.61,.36,1);
  border: 0;
  background: var(--surface);
}

/* click-to-load placeholder — keeps first paint fast and is the visible
   fallback surface if Streamlit refuses to be framed */
.frame-expand {
  flex: none;
  font-family: var(--mono);
  font-size: .79rem;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--ink-muted);
  background: transparent;
  border: 1px solid var(--control-edge);
  border-radius: 999px;
  padding: .28rem .75rem;
  cursor: pointer;
  transition: color .15s ease, border-color .15s ease;
}
.frame-expand:hover { color: var(--accent); border-color: var(--accent); }

.frame-stub {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1rem;
  height: var(--embed-h);
  padding: 2rem 1.5rem;
  text-align: center;
}
.frame-stub p {
  font-family: var(--mono);
  font-size: .82rem;
  color: var(--ink-faint);
  max-width: 44ch;
  line-height: 1.8;
  margin: 0;
}

/* Streamlit free-tier apps cold-start slowly. Without this the stub vanishes on
   click and the visitor watches an empty frame with no signal anything is
   happening. */
.frame-stub[data-state="loading"] .btn,
.frame-stub[data-state="loading"] .stub-idle { display: none; }
.frame-stub .stub-loading,
.frame-stub .stub-timeout { display: none; }
.frame-stub[data-state="loading"] .stub-loading { display: flex; }
.frame-stub[data-state="timeout"] .stub-timeout { display: block; }
.frame-stub[data-state="timeout"] .stub-loading,
.frame-stub[data-state="timeout"] .stub-idle { display: none; }

.stub-loading {
  align-items: center;
  gap: .7rem;
  font-family: var(--mono);
  font-size: .80rem;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ink-muted);
}
.spinner {
  width: .95rem;
  height: .95rem;
  border: 2px solid var(--rule-strong);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: spin .8s linear infinite;
  flex: none;
}
@keyframes spin { to { transform: rotate(360deg); } }

.stub-timeout {
  font-family: var(--mono);
  font-size: .82rem;
  line-height: 1.8;
  color: var(--ink-soft);
  max-width: 46ch;
}
.btn {
  font-family: var(--mono);
  font-size: .82rem;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--paper);
  background: var(--accent);
  border: 1px solid var(--accent);
  border-radius: 999px;
  padding: .65rem 1.5rem;
  cursor: pointer;
  transition: opacity .15s ease;
}
:root[data-theme="dark"] .btn { color: #0b0d0c; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .btn { color: #0b0d0c; }
}
.btn:hover { opacity: .86; }

.frame-foot {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
  align-items: center;
  margin-top: .85rem;
}
.frame-foot .fallback {
  font-family: var(--mono);
  font-size: .79rem;
  color: var(--ink-faint);
  max-width: 64ch;
  line-height: 1.7;
  margin: 0;
}

/* ---------- credentials ---------- */

.cred {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 0;
  border-top: 1px solid var(--rule);
}
.cred-item {
  padding: 1.9rem 2rem 1.9rem 0;
  border-bottom: 1px solid var(--rule);
}
.cred-item:nth-child(even) {
  padding-left: 2rem;
  padding-right: 0;
  border-left: 1px solid var(--rule);
}
.cred-item h3 { font-size: 1.2rem; margin-bottom: .5rem; }
.cred-item .covers { font-size: .9rem; color: var(--ink-muted); margin-bottom: 1rem; }
.cred-item .proof {
  font-family: var(--mono);
  font-size: .79rem;
  letter-spacing: .05em;
  color: var(--ink-faint);
  text-transform: uppercase;
  margin: 0;
  max-width: none;
}
.cred-item .proof a { border-bottom-color: var(--accent-line); }

@media (max-width: 780px) {
  .cred { grid-template-columns: minmax(0, 1fr); }
  .cred-item, .cred-item:nth-child(even) {
    padding: 1.6rem 0;
    border-left: 0;
  }
}

.coursework { margin-top: 2.75rem; }

/* ---------- publications ---------- */

.scholar-stats {
  display: flex;
  gap: clamp(2rem, 6vw, 4.5rem);
  flex-wrap: wrap;
  padding: 1.75rem 0;
  border-block: 1px solid var(--rule);
  margin-bottom: 3rem;
}
.stat .n {
  font-family: var(--serif);
  font-size: 2.35rem;
  line-height: 1;
  color: var(--ink);
  letter-spacing: -0.03em;
  font-variant-numeric: lining-nums tabular-nums;
}
.stat .k {
  font-family: var(--mono);
  font-size: .76rem;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin-top: .45rem;
  display: block;
}

.pub {
  display: grid;
  grid-template-columns: 5rem minmax(0, 1fr);
  gap: 1.5rem;
  padding: 1.6rem 0;
  border-bottom: 1px solid var(--rule);
}
.pub:first-of-type { border-top: 1px solid var(--rule); }
.pub .year {
  font-family: var(--mono);
  font-size: .8rem;
  color: var(--ink-faint);
  padding-top: .2rem;
}
.pub h3 {
  font-size: 1.18rem;
  line-height: 1.3;
  margin-bottom: .45rem;
}
.pub .authors { font-size: .875rem; color: var(--ink-muted); margin-bottom: .35rem; }
.pub .venue { font-size: .875rem; color: var(--ink-soft); margin-bottom: .75rem; }
.pub .venue .q {
  font-family: var(--mono);
  font-size: .76rem;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--accent);
  border: 1px solid var(--accent-line);
  border-radius: 3px;
  padding: .1rem .4rem;
  margin-left: .5rem;
  white-space: nowrap;
}
@media (max-width: 620px) {
  .pub { grid-template-columns: minmax(0, 1fr); gap: .5rem; }
}

.record {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  gap: 2.5rem;
  margin-top: 3.25rem;
  padding-top: 2.5rem;
  border-top: 1px solid var(--rule);
}
/* h3 for Education / Grants / Recognition (siblings of the teaching block),
   h4 for the nested coursework groups — same mono label treatment either way. */
.record h3,
.record h4 {
  font-family: var(--mono);
  font-size: .76rem;
  font-weight: 500;
  letter-spacing: .115em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin: 0 0 1rem;
}
.record ul { list-style: none; padding: 0; margin: 0; font-size: .9rem; }
.record li { margin-bottom: .85rem; color: var(--ink-muted); line-height: 1.6; }
.record li b { color: var(--ink-soft); font-weight: 500; }

/* Education / Grants / Recognition entries.
   These used to be run-on sentences inside a bullet-less <ul> — bold title, an
   em dash, then institution, dates and description all in one flowing line, and
   no separation between items. The dates, which are the most scannable fact,
   were buried mid-sentence. Now each entry leads with its date in mono (the
   metadata voice this design uses everywhere else), and items are ruled apart.
   Deliberately mirrors the .role-item pattern in the teaching block above. */
.rec + .rec {
  margin-top: 1.35rem;
  padding-top: 1.35rem;
  border-top: 1px solid var(--rule);
}
.rec .meta {
  color: var(--accent);
  margin-bottom: .4rem;
  font-size: .76rem;
  letter-spacing: .09em;
}
.rec-t {
  font-family: var(--serif);
  font-size: 1.12rem;
  line-height: 1.25;
  color: var(--ink);
  margin: 0 0 .25rem;
  max-width: none;
}
.rec-w {
  font-family: var(--mono);
  font-size: .78rem;
  line-height: 1.6;
  color: var(--ink-muted);
  margin: 0;
  max-width: none;
}
.rec-d {
  font-size: .9rem;
  line-height: 1.62;
  color: var(--ink-muted);
  margin: .5rem 0 0;
  max-width: none;
}

/* ---------- teaching & supervision ----------
   Pulled out of the 4-up record grid, where it read as a footnote. Each role
   gets a mono rail on the left and real room for its description on the right. */

.roles {
  margin-top: 3.25rem;
  padding-top: 2.75rem;
  border-top: 1px solid var(--rule);
}
.roles-h {
  font-size: clamp(1.45rem, 3vw, 1.9rem);
  margin-bottom: .9rem;
}
.roles-lede {
  font-size: 1.05rem;
  max-width: 56ch;
  margin-bottom: 2.5rem;
}

.role-item {
  display: grid;
  grid-template-columns: 11rem minmax(0, 1fr);
  gap: 1.75rem;
  padding: 1.6rem 0;
  border-top: 1px solid var(--rule);
}
.role-item:last-child { border-bottom: 1px solid var(--rule); }

.role-rail { padding-top: .3rem; }
.role-rail .meta { color: var(--accent); }
.role-rail .role-when {
  color: var(--ink-faint);
  margin-top: .35rem;
  text-transform: none;
  letter-spacing: .04em;
}

.role-body h4 {
  font-size: 1.2rem;
  line-height: 1.25;
  margin-bottom: .3rem;
}
.role-where {
  font-family: var(--mono);
  font-size: .79rem;
  letter-spacing: .05em;
  color: var(--ink-muted);
  margin-bottom: .7rem;
  max-width: none;
}
.role-body > p:last-child {
  font-size: .95rem;
  color: var(--ink-muted);
  max-width: 62ch;
}

@media (max-width: 700px) {
  .role-item { grid-template-columns: minmax(0, 1fr); gap: .75rem; }
  .role-rail { display: flex; gap: 1rem; padding-top: 0; }
  .role-rail .role-when { margin-top: 0; }
}

/* ---------- writing ---------- */

.trail { position: relative; }
.post {
  display: grid;
  grid-template-columns: 3.25rem minmax(0, 1fr);
  gap: 1.25rem;
  padding: 1.5rem 0;
  border-bottom: 1px solid var(--rule);
}
.post:first-of-type { border-top: 1px solid var(--rule); }
.post .idx {
  font-family: var(--mono);
  font-size: .79rem;
  color: var(--accent);
  padding-top: .35rem;
  position: relative;
}
/* the connecting line that makes this a trail, not a blog roll */
.trail .post:not(:last-child) .idx::after {
  content: "";
  position: absolute;
  left: .55rem;
  top: 2.1rem;
  bottom: -1.9rem;
  width: 1px;
  background: var(--accent-line);
}
.post h3 { font-size: 1.15rem; line-height: 1.35; margin-bottom: .4rem; }
.post h3 a { color: var(--ink); border-bottom-color: var(--rule-strong); }
.post h3 a:hover { color: var(--accent); border-bottom-color: var(--accent); }
.post .post-meta {
  font-family: var(--mono);
  font-size: .78rem;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin: 0 0 .6rem;
  max-width: none;
}
.post .gloss { font-size: .93rem; color: var(--ink-muted); margin-bottom: .7rem; }
@media (max-width: 620px) {
  .post { grid-template-columns: 2.25rem minmax(0, 1fr); gap: .85rem; }
  .trail .post:not(:last-child) .idx::after { left: .35rem; }
}

.other-writing { margin-top: 3.25rem; padding-top: 2.5rem; border-top: 1px solid var(--rule); }

/* ---------- tech stack ---------- */

.stack {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
  gap: 2.5rem 3rem;
}
.stack-group h3 {
  font-family: var(--mono);
  font-size: .78rem;
  font-weight: 500;
  letter-spacing: .115em;
  text-transform: uppercase;
  color: var(--accent);
  margin: 0 0 1rem;
  padding-bottom: .75rem;
  border-bottom: 1px solid var(--rule);
}
.stack-group ul {
  list-style: none;
  padding: 0;
  margin: 0;
  font-family: var(--mono);
  font-size: .84rem;
  line-height: 2.15;
  color: var(--ink-muted);
}
.stack-group li { margin: 0; }

/* ---------- contact / footer ---------- */

.contact h2 { font-size: clamp(2.1rem, 5vw, 3.1rem); margin-bottom: 1.25rem; }
.contact .lede { font-size: 1.125rem; max-width: 52ch; margin-bottom: 2.25rem; }

.contact-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
  gap: 1.5rem 2rem;
  padding-top: 2.25rem;
  border-top: 1px solid var(--rule);
}
.contact-item .k {
  font-family: var(--mono);
  font-size: .80rem;
  letter-spacing: .115em;
  text-transform: uppercase;
  color: var(--ink-faint);
  display: block;
  margin-bottom: .4rem;
}
.contact-item .v { font-size: .95rem; word-break: break-word; }
.contact-item .v a { border-bottom-color: var(--accent-line); }

.site-footer {
  border-top: 1px solid var(--rule);
  padding-block: 2.5rem 3.5rem;
}
.site-footer .wrap {
  display: flex;
  justify-content: space-between;
  gap: 1.5rem;
  flex-wrap: wrap;
  align-items: center;
}
.site-footer p {
  font-family: var(--mono);
  font-size: .78rem;
  letter-spacing: .05em;
  color: var(--ink-faint);
  margin: 0;
  max-width: none;
}

/* ---------- scroll reveal: REMOVED, deliberately ----------
   There was a fade-and-rise on a view() timeline here. It caused text to render
   blurry after a fast scroll, and to stay blurry until something forced a
   repaint.

   Why: `transform` promotes each of the 26 .reveal elements to its own GPU
   layer, and a scroll-driven animation keeps that layer permanently active
   because the timeline never ends. During a fast scroll the compositor
   rasterises active layers at reduced resolution to keep up, then never
   re-rasterises them at full resolution because the animation is still
   "running". A fractional translateY mid-range also puts glyphs on sub-pixel
   offsets, which is blurry by definition.

   The fade was decoration on a site whose entire job is legibility, so it is
   gone rather than patched. The .reveal classes are left on the markup: they do
   nothing now, and they make it a six-line change to reinstate if it is ever
   wanted -- but if it comes back, animate opacity only and never transform.  */

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

/* ---------- print ---------- */

@media print {
  .site-header, .theme-toggle, .frame { display: none; }
  .reveal { opacity: 1; transform: none; }
  body { background: #fff; color: #000; font-size: 11pt; }
  a { color: #000; border: 0; }
  .section { page-break-inside: avoid; }
}
