@charset "utf-8";

/* vikram-portfolio — styles.css
 *
 * Single stylesheet, no build step, no external requests. The type stack is the
 * system monospace stack: a portfolio that waits on a font CDN looks broken on a
 * bad conference network, which is exactly where someone might open it.
 *
 * The page is a CRT terminal. That is a deliberate, whole-page choice, not a
 * decorative band — background, type, rules, cards and section headings all
 * follow from it. Two things keep it from being a costume:
 *
 *   1. The palette is Irulast's, not generic phosphor green. #70C5CA, #4A79BC
 *      and #3F4D98 are the brand's three circles; #F0C266 is Baryon's gold.
 *      Read against #06090f they land as a terminal that happens to be on-brand
 *      rather than a terminal skin dropped over someone else's colors.
 *   2. `html[data-crt="off"]` restores the previous light, sans-serif document
 *      wholesale — same tokens, same rules, nothing lost. The terminal's `theme`
 *      command flips it and localStorage remembers. Someone who cannot read
 *      light-on-dark, or who just wants to skim, is one command from a plain
 *      page. Print already resolves to that path unconditionally.
 *
 * Colors are defined once as custom properties on :root and overridden in that
 * single block. Nothing else in the file hardcodes a color.
 */

:root {
  /* CRT — the default. */
  --bg:        #06090f;
  --bg-sunk:   #090e16;
  --bg-card:   #0a1018;
  --bg-chip:   #eef3f6;
  --fg:        #cfe9e4;
  --fg-muted:  #93b7b6;
  --fg-faint:  #6f9397;
  --rule:      #17242e;
  --rule-soft: #101a22;
  --accent:    #70c5ca;   /* Irulast teal — prompts, links, focus */
  --accent-hot:#a8f0ee;   /* glow / hover */
  --accent-2:  #6f9ae0;   /* Irulast blue, lifted for dark ground */
  --amber:     #f0c266;   /* Baryon gold — tags, qualifiers */
  --accent-fg: #04070b;
  --tag-bg:    #0e1a22;
  --tag-fg:    #9dc6c4;
  --code-bg:   #101c24;

  --glow: 0 0 14px rgba(112, 197, 202, 0.30);

  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas,
          "DejaVu Sans Mono", "Liberation Mono", monospace;
  --sans: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto,
          "Helvetica Neue", Arial, sans-serif;

  --face: var(--mono);
  --measure: 76ch;
  --wrap: 64rem;
  --radius: 8px;
}

/* The escape hatch: the original light document, unchanged. */
html[data-crt="off"] {
  --bg:        #ffffff;
  --bg-sunk:   #f6f7f9;
  --bg-card:   #ffffff;
  --bg-chip:   #ffffff;
  --fg:        #16191d;
  --fg-muted:  #565d68;
  --fg-faint:  #646b76;   /* 5.0:1 on white; #7b828d was 3.87 and this is
                              the palette offered to people who need contrast */
  --rule:      #e2e5ea;
  --rule-soft: #eceef2;
  --accent:    #1f5f8b;
  --accent-hot:#1f5f8b;
  --accent-2:  #3f4d98;
  --amber:     #8a6a1f;
  --accent-fg: #ffffff;
  --tag-bg:    #eef2f6;
  --tag-fg:    #3d4650;
  --code-bg:   #eef1f4;
  --glow: none;
  --face: var(--sans);
  --measure: 68ch;
}

/* HIG: "If your app doesn't provide this minimum contrast by default, ensure
   it at least provides a higher contrast color scheme when the system setting
   Increase Contrast is turned on." The default palette already clears 4.5:1
   everywhere (the weakest pair is fg-faint on bg-card at 5.72:1), so this is
   headroom rather than a rescue: dim text is lifted, rules are strengthened,
   and the scanlines — which cost real contrast for the sake of the effect —
   are dropped entirely. */
@media (prefers-contrast: more) {
  :root {
    --fg:        #ffffff;
    --fg-muted:  #cfe6e4;
    --fg-faint:  #a9c9cb;
    --rule:      #40525c;
    --rule-soft: #2b3841;
    --accent:    #9adfe2;
    --accent-hot:#e2fffd;
    --amber:     #ffd98a;
    --tag-fg:    #d4eceb;
  }
  .crt-scan { display: none; }
  .crt-vig { opacity: 0.4; }
}

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

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

body {
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  font-family: var(--face);
  /* rem, not px: a px body size silently overrides the reader's own font-size
     setting, which is the web's equivalent of ignoring Dynamic Type. 0.97rem
     is the same 15.5px at the default 16px root — a notch down from the 17px
     the sans-serif draft used, because monospace sets wider — but it now
     scales with whatever the reader has chosen. */
  font-size: 0.97rem;
  line-height: 1.72;
  -webkit-font-smoothing: antialiased;
}

/* ---------------------------- CRT overlays ----------------------------
   Two fixed, non-interactive layers over the whole viewport: scanlines and
   a vignette. Both are suppressed in the light theme and in print. The
   scanline alpha is deliberately low — at the strength that reads best in a
   screenshot it costs real contrast on body text. */

.crt-scan, .crt-vig {
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 9;
}

.crt-scan {
  background: repeating-linear-gradient(
    to bottom,
    rgba(0, 0, 0, 0.20) 0px,
    rgba(0, 0, 0, 0.20) 1px,
    transparent 1px,
    transparent 3px
  );
  opacity: 0.55;
  animation: flicker 5.5s steps(2, end) infinite;
}

/* Bloom in the middle, falloff at the edges — a tube, not a flat panel. */
.crt-vig {
  background:
    radial-gradient(120% 100% at 50% 0%,
      rgba(112, 197, 202, 0.07), transparent 55%),
    radial-gradient(140% 120% at 50% 50%,
      transparent 55%, rgba(0, 0, 0, 0.55) 100%);
}

@keyframes flicker {
  0%, 96%, 100% { opacity: 0.55; }
  97%           { opacity: 0.44; }
  98%           { opacity: 0.62; }
}

html[data-crt="off"] .crt-scan,
html[data-crt="off"] .crt-vig { display: none; }

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

a {
  color: var(--accent);
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
}
a:hover { color: var(--accent-hot); text-decoration-thickness: 2px; }

:focus-visible {
  outline: 2px solid var(--accent-hot);
  outline-offset: 2px;
}

code {
  font-family: var(--mono);
  font-size: 0.92em;
  background: var(--code-bg);
  padding: 0.1em 0.38em;
  border-radius: 4px;
}
html[data-crt="off"] code { font-size: 0.86em; }

strong { color: var(--accent-hot); font-weight: 700; }
html[data-crt="off"] strong { color: inherit; }

/* Visible only when tabbed to — keyboard users can jump the header. */
.skip { position: absolute; left: -9999px; }
.skip:focus {
  left: 1rem; top: 1rem;
  position: fixed;
  z-index: 20;
  background: var(--accent);
  color: var(--accent-fg);
  padding: 0.6rem 1rem;
  border-radius: 6px;
  text-decoration: none;
}

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

.hero {
  position: relative;
  background: var(--bg-sunk);
  border-bottom: 1px solid var(--rule);
  padding-block: clamp(2.5rem, 7vw, 4rem) clamp(2rem, 6vw, 3rem);
}

/* Faint dot grid, sized off the brand teal. Sits under the content. */
.hero::before {
  content: "";
  position: absolute;
  inset: 0;
  background-image: radial-gradient(rgba(112, 197, 202, 0.13) 1px, transparent 1px);
  background-size: 22px 22px;
  mask-image: linear-gradient(to bottom, #000 0%, transparent 85%);
  -webkit-mask-image: linear-gradient(to bottom, #000 0%, transparent 85%);
  pointer-events: none;
}
html[data-crt="off"] .hero::before { opacity: 0.5; }

.hero .wrap { position: relative; }

/* Portrait left, identity right; stacks under ~48rem. */
.hero-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: clamp(1.5rem, 4vw, 2.5rem);
  align-items: start;
}

@media (min-width: 48rem) {
  .hero-grid { grid-template-columns: 13.5rem minmax(0, 1fr); }
}

.portrait {
  position: relative;
  margin: 0;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--bg-card);
  max-width: 13.5rem;
  box-shadow: var(--glow);
}

.portrait img {
  display: block;
  width: 100%;
  height: auto;
  /* The source is a neutral grayscale frame; the tint is applied here so the
     asset itself stays honest and the light theme can drop it in one line. */
  filter: contrast(1.14) brightness(0.97) sepia(0.42) hue-rotate(132deg) saturate(1.1);
}
html[data-crt="off"] .portrait img { filter: none; }

/* Scanlines continue across the portrait, so it reads as part of the tube. */
.portrait::after {
  content: "";
  position: absolute;
  inset: 0;
  background: repeating-linear-gradient(
    to bottom,
    rgba(0, 0, 0, 0.28) 0px,
    rgba(0, 0, 0, 0.28) 1px,
    transparent 1px,
    transparent 3px
  );
  pointer-events: none;
}
html[data-crt="off"] .portrait::after { display: none; }

.portrait figcaption {
  font-size: 0.72rem;
  letter-spacing: 0.04em;
  color: var(--fg-faint);
  padding: 0.4rem 0.6rem;
  border-top: 1px solid var(--rule);
  background: var(--bg-sunk);
}

.eyebrow {
  margin: 0 0 0.6rem;
  font-size: 0.76rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--accent);
}
.eyebrow::before { content: "// " / ""; opacity: 0.6; }
html[data-crt="off"] .eyebrow { color: var(--fg-faint); }
html[data-crt="off"] .eyebrow::before { content: none; }

.hero h1 {
  margin: 0 0 0.9rem;
  font-size: clamp(2rem, 5.5vw, 3rem);
  line-height: 1.1;
  letter-spacing: -0.015em;
  font-weight: 700;
  color: var(--accent-hot);
  text-shadow: var(--glow);
}
html[data-crt="off"] .hero h1 { color: var(--fg); }

.lede {
  margin: 0 0 1.5rem;
  max-width: var(--measure);
  font-size: clamp(0.95rem, 2vw, 1.02rem);
  color: var(--fg-muted);
}

.contact {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem 1.25rem;
  margin: 0;
  padding: 0;
  list-style: none;
  font-size: 0.86rem;
  color: var(--fg-muted);
}

/* Separator dots between items, but not trailing the last one. */
.contact li { display: flex; align-items: center; gap: 1.25rem; }
.contact li:not(:last-child)::after {
  content: "";
  width: 3px; height: 3px;
  border-radius: 50%;
  background: var(--fg-faint);
  opacity: 0.6;
}

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

main { padding-block: clamp(2.5rem, 6vw, 4rem); }

section + section { margin-top: clamp(3rem, 7vw, 4.5rem); }

/* Section headings read as commands. The "$ " is generated so the heading text
   in the HTML stays a plain phrase — but generated content IS exposed in the
   accessibility tree in every current browser, so every ::before and ::after
   that draws a glyph on this page carries the alternative-text form,
   `content: "…" / ""`. Without it a screen reader announces "dollar shell",
   "black circle black circle black circle" before each card, and "black
   right-pointing small triangle" before some sixty bullets. */
h2 {
  margin: 0 0 1.5rem;
  font-size: 0.82rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: lowercase;
  color: var(--accent);
  padding-bottom: 0.7rem;
  border-bottom: 1px solid var(--rule);
  text-shadow: var(--glow);
}
h2::before {
  content: "$ " / "";
  color: var(--amber);
  text-shadow: none;
}
html[data-crt="off"] h2 {
  color: var(--fg-faint);
  text-transform: uppercase;
  letter-spacing: 0.11em;
}
html[data-crt="off"] h2::before { content: none; }

/* --------------------------- focus / capability grids --------------------------- */

.focus-grid, .cap-grid {
  display: grid;
  gap: 1.5rem 2rem;
}

/* Six items. A 3-up track would need ~64rem of content box and only ~59rem exists,
   so this lands on 2x3 at full width — even rows, no orphan — and collapses to a
   single column below. Keep the count even if you add or remove a focus area. */
.focus-grid { grid-template-columns: repeat(auto-fit, minmax(21rem, 1fr)); }

/* Six items — 3-up divides evenly. */
.cap-grid { grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr)); }

.focus, .cap {
  padding-left: 0.9rem;
  border-left: 1px solid var(--rule);
}

.focus h3, .cap h3 {
  margin: 0 0 0.45rem;
  font-size: 0.95rem;
  font-weight: 700;
  letter-spacing: -0.005em;
  color: var(--accent-hot);
}
html[data-crt="off"] .focus h3, html[data-crt="off"] .cap h3 {
  font-weight: 650;
  color: inherit;
}

.focus p, .cap p {
  margin: 0;
  color: var(--fg-muted);
  font-size: 0.9rem;
}

.cap p { font-size: 0.88rem; }

/* ------------------------------- work cards -------------------------------
   Each card is a terminal window: chrome bar, three dots, and a path that
   names the card. The path is supplied per-card from the HTML via data-path. */

.card {
  position: relative;
  background: var(--bg-card);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  padding: clamp(1.25rem, 3vw, 1.9rem);
  padding-top: clamp(2.6rem, 4vw, 3.1rem);
}

/* The chrome bar. Purely decorative, hence aria-hidden content in CSS only. */
.card::before {
  /* attr(), not a custom property set in a style attribute: an inline style is
     the one thing that would force 'unsafe-inline' into the style-src of the
     CSP, and a decorative path is not worth that. */
  content: "● ● ●  " attr(data-path) / "";
  position: absolute;
  inset: 0 0 auto 0;
  height: 2rem;
  display: flex;
  align-items: center;
  padding-inline: 0.9rem;
  font-size: 0.7rem;
  letter-spacing: 0.06em;
  color: var(--fg-faint);
  background: var(--bg-sunk);
  border-bottom: 1px solid var(--rule);
  border-radius: var(--radius) var(--radius) 0 0;
  white-space: nowrap;
  overflow: hidden;
}

.card + .card { margin-top: 1.25rem; }

/* ------------------------- collapsed cards -------------------------
   Each card is a <details> and its head is the <summary>, so the toggle is
   the browser's: click target, Enter/Space, focus ring and expanded state are
   all native, and there is no open/closed flag in JavaScript that could end up
   disagreeing with what is on the screen.

   Closed, a card still has to earn the click, so the summary carries the value
   line — what the work was worth — not just the title. Title, org and value:
   enough to decide, and the problem, the bullets and the tags are behind the
   click. */

.work-hint {
  margin: -0.85rem 0 1.35rem;
  font-size: 0.85rem;
  color: var(--fg-faint);
}

.card-sum {
  display: block;          /* summary defaults to list-item */
  list-style: none;        /* kills the marker in Firefox */
  cursor: pointer;
}
.card-sum::-webkit-details-marker { display: none; }

/* The chevron is a third grid column of the head, so a long title wraps
   against it instead of running underneath it. */
.card-sum .card-head {
  grid-template-columns: auto minmax(0, 1fr) auto;
  margin-bottom: 0.5rem;
}
.card-sum .card-head::after {
  content: "▸" / "";
  color: var(--accent);
  font-size: 1.05rem;
  line-height: 1;
  transition: transform 140ms ease;
}
.card[open] > .card-sum .card-head::after { transform: rotate(90deg); }
.card-sum:hover .card-head::after { color: var(--accent-hot); }
.card-sum:hover .card-head h3 { text-decoration: underline; text-underline-offset: 3px; }
html[data-crt="off"] .card-sum .card-head::after { color: var(--fg-faint); }

/* The one line that says what the work was worth. */
.value {
  display: block;
  margin: 0;
  color: var(--fg-muted);
  font-size: 0.9rem;
}

.card[open] > .card-sum {
  margin-bottom: 1.15rem;
  padding-bottom: 0.95rem;
  border-bottom: 1px solid var(--rule-soft);
}


/* Head: org mark chip beside the title block. */
.card-head {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: center;
  gap: 0.85rem;
  margin-bottom: 0.9rem;
}

/* Logos keep their own brand colors, so they sit on a light chip rather than
   being recolored to survive the dark ground. */
.org-mark {
  width: 2.75rem;
  height: 2.75rem;
  flex: none;
  display: grid;
  place-items: center;
  background: var(--bg-chip);
  border: 1px solid var(--rule);
  border-radius: 7px;
  padding: 0.3rem;
  overflow: hidden;
}
.org-mark img { display: block; width: 100%; height: 100%; object-fit: contain; }

/* Cards with no logo get a monogram in the same footprint, so the column of
   marks stays even down the page. */
.org-mark.mono {
  background: var(--tag-bg);
  color: var(--accent);
  font-size: 0.85rem;
  font-weight: 700;
  letter-spacing: 0.02em;
  padding: 0;
}
html[data-crt="off"] .org-mark.mono { color: var(--accent-2); }

.card-head h3 {
  margin: 0 0 0.15rem;
  font-size: clamp(1rem, 2.4vw, 1.16rem);
  font-weight: 700;
  letter-spacing: -0.01em;
  line-height: 1.35;
  color: var(--accent-hot);
}
html[data-crt="off"] .card-head h3 { color: inherit; font-weight: 680; }

/* span, not p — the head lives inside a <summary>, whose content model is
   phrasing content, so a <p> or a <div> in there is invalid. */
.org {
  display: block;
  margin: 0;
  font-size: 0.8rem;
  color: var(--fg-faint);
}
.card-title { min-width: 0; }

/* The framing line: what was wrong before the work. Set apart so a skimming
   reader gets problem → outcome without reading the bullets. */
.problem {
  margin: 0 0 1rem;
  padding-left: 0.9rem;
  border-left: 2px solid var(--amber);
  color: var(--fg-muted);
  font-size: 0.9rem;
}
html[data-crt="off"] .problem { border-left-color: var(--rule); }

.did {
  margin: 0 0 1rem;
  padding-left: 1.15rem;
  display: grid;
  gap: 0.55rem;
  font-size: 0.9rem;
  list-style: none;
}

.did li { position: relative; padding-left: 0.35rem; }
.did li::before {
  content: "▸" / "";
  position: absolute;
  left: -1rem;
  color: var(--accent);
}
html[data-crt="off"] .did { list-style: disc; }
html[data-crt="off"] .did li::before { content: none; }

.outcome {
  margin: 0 0 1.1rem;
  padding: 0.75rem 0.95rem;
  background: var(--bg-sunk);
  border: 1px solid var(--rule-soft);
  border-radius: 7px;
  font-size: 0.89rem;
  color: var(--fg);
}
.outcome::before {
  content: "→ " / "";
  color: var(--accent);
  font-weight: 700;
}
html[data-crt="off"] .outcome::before { content: none; }

.tags {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

.tags li {
  background: var(--tag-bg);
  color: var(--tag-fg);
  font-size: 0.72rem;
  font-weight: 500;
  letter-spacing: 0.01em;
  padding: 0.2rem 0.55rem;
  border: 1px solid var(--rule);
  border-radius: 4px;
  white-space: nowrap;
}
.tags li::before { content: "[" / ""; opacity: 0.45; }
.tags li::after  { content: "]" / ""; opacity: 0.45; }
html[data-crt="off"] .tags li {
  border-color: transparent;
  border-radius: 100px;
  font-size: 0.775rem;
  padding: 0.22rem 0.6rem;
}
html[data-crt="off"] .tags li::before,
html[data-crt="off"] .tags li::after { content: none; }

/* ------------------------------ background ------------------------------ */

.bg-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr));
  gap: 1.75rem 2.5rem;
}

.bg-col h3 {
  margin: 0 0 0.9rem;
  font-size: 0.95rem;
  font-weight: 700;
  color: var(--accent-hot);
}
html[data-crt="off"] .bg-col h3 { color: inherit; font-weight: 650; }

.bg-col dl { margin: 0; }

.bg-col dt {
  font-weight: 600;
  font-size: 0.89rem;
}

.bg-col dt span {
  font-weight: 400;
  color: var(--fg-muted);
}

.bg-col dd {
  margin: 0.1rem 0 0.95rem;
  font-size: 0.82rem;
  color: var(--fg-faint);
}

.bg-col dd:last-child { margin-bottom: 0; }

/* ------------------------------- closing ------------------------------- */

.closing p { max-width: var(--measure); color: var(--fg-muted); }

.cta {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  margin-top: 1.25rem;
}

.btn {
  display: inline-block;
  background: var(--accent);
  color: var(--accent-fg);
  padding: 0.55rem 1.1rem;
  border-radius: 6px;
  font-size: 0.88rem;
  font-weight: 700;
  text-decoration: none;
  border: 1px solid var(--accent);
}

.btn:hover { background: var(--accent-hot); border-color: var(--accent-hot); color: var(--accent-fg); }

.btn.ghost {
  background: transparent;
  color: var(--accent);
}
.btn.ghost:hover { background: transparent; color: var(--accent-hot); }

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

footer {
  margin-top: clamp(3rem, 7vw, 4.5rem);
  border-top: 1px solid var(--rule);
  padding-block: 1.75rem;
}

.footer-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.75rem 1.25rem;
  justify-content: space-between;
}

footer p {
  margin: 0;
  font-size: 0.8rem;
  color: var(--fg-faint);
}

/* Injected by the inline script in index.html, so it only exists with JS. The
   slot reserves the button's height so the footer does not grow one in after
   first paint and shift what is under it. */
#theme-slot { min-height: 44px; }

.theme-toggle {
  font: inherit;
  font-size: 0.78rem;
  color: var(--fg-faint);
  background: transparent;
  border: 1px solid var(--rule);
  border-radius: 5px;
  /* HIG puts the default hit target at 44x44pt with a 28x28pt floor. The
     visible chip stays small; the tappable area does not. */
  min-height: 44px;
  padding: 0.25rem 0.9rem;
  cursor: pointer;
}
.theme-toggle:hover { color: var(--accent); border-color: var(--accent); }

/* ------------------------------- print -------------------------------
   Someone will print this to PDF and hand it across a table. Print takes the
   light document unconditionally — the CRT is a screen affordance and a page
   of dark background is not something anyone wants coming out of a printer. */

@media print {
  :root {
    --bg: #fff; --bg-sunk: #fff; --bg-card: #fff; --bg-chip: #fff;
    --fg: #000; --fg-muted: #333; --fg-faint: #555;
    --rule: #ccc; --rule-soft: #ddd;
    --accent: #1f5f8b; --accent-hot: #000; --amber: #666;
    --tag-bg: #f2f2f2; --tag-fg: #333; --code-bg: #f2f2f2;
    --glow: none;
    --face: var(--sans);
  }
  /* #shell in full, not just .terminal — hiding the widget alone leaves its
     heading and rule stranded at the top of page one over empty space. */
  .crt-scan, .crt-vig, .skip, .cta, #shell, .hero::before,
  .theme-toggle { display: none; }
  .portrait::after { display: none; }
  .portrait img { filter: grayscale(1); }

  /* Print is ~700px of usable width, under the 48rem the two-column hero needs,
     so without this the portrait stacks above the name and costs most of the
     first page. Same arrangement as on screen, smaller. */
  .hero-grid { grid-template-columns: 9rem minmax(0, 1fr); gap: 1.25rem; }
  .portrait { max-width: 9rem; box-shadow: none; }

  /* The lowercase headings belong to the terminal; print takes the light
     document's uppercase treatment, and there is no data-crt attribute on the
     print path to pick it up from. */
  h2 { text-transform: uppercase; letter-spacing: 0.11em; }

  h2::before, .outcome::before, .eyebrow::before { content: none; }
  .tags li::before, .tags li::after { content: none; }
  /* Not break-inside: avoid on the card itself — several are taller than a page,
     and an unbreakable block that cannot fit forces a break before it and leaves
     the previous page half empty. Keep the head with what follows it and keep
     bullets whole; let the card break where it has to. */
  .card { border: 1px solid #ccc; padding-top: 1.25rem; }
  .card-sum { break-inside: avoid; break-after: avoid; }
  .did li, .outcome, .problem { break-inside: avoid; }
  /* Print gets every card open — see the script under the work section — so the
     affordances for opening them are noise on paper. */
  .card-sum { cursor: auto; }
  .card-sum .card-head { grid-template-columns: auto minmax(0, 1fr); }
  .card-sum .card-head::after { content: none; }
  .card[open] > .card-sum { border-bottom-color: #ddd; }
  .work-hint { display: none; }
  .card::before { display: none; }
  .did li::before { content: none; }
  .did { list-style: disc; }
}

@media (prefers-reduced-motion: reduce) {
  * { animation: none !important; transition: none !important; }
  .crt-scan { animation: none !important; }
}

/* ----------------------------- the terminal -----------------------------
   A real prompt, not a typing animation. Everything it prints is read out of
   the document at runtime (see terminal.js), so the terminal cannot drift out
   of agreement with the page the way a second hardcoded copy of the same
   claims would. If a card is edited, `work` changes with it.

   The whole block is progressive enhancement: the HTML ships a static list of
   commands and a link to the work section, and terminal.js replaces it only
   once it has successfully wired up. No JS, no broken widget. */

.terminal {
  /* Pinned to the monospace stack in both themes. The light theme swaps --face
     to the sans stack for the document, but the terminal draws boxes and pads
     columns with spaces — in a proportional face that alignment collapses. */
  font-family: var(--mono);
  margin-top: 1.25rem;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: var(--bg-card);
  overflow: hidden;
  box-shadow: var(--glow);
}
html[data-crt="off"] .terminal { box-shadow: none; }

.term-bar {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  height: 2rem;
  padding-inline: 0.8rem;
  background: var(--bg-sunk);
  border-bottom: 1px solid var(--rule);
  font-size: 0.72rem;
  letter-spacing: 0.05em;
  color: var(--fg-faint);
}

.term-dots { display: inline-flex; gap: 0.32rem; }
.term-dots i {
  width: 0.55rem;
  height: 0.55rem;
  border-radius: 50%;
  background: var(--rule);
  border: 1px solid var(--fg-faint);
  opacity: 0.55;
}

.term-title { margin-left: 0.35rem; }

/* Scrolls internally so a long `help` cannot push the rest of the page down. */
.term-body {
  height: clamp(19rem, 46vh, 27rem);
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 0.9rem 1rem 1rem;
  font-size: 0.84rem;
  line-height: 1.62;
  cursor: text;
}

.term-body::-webkit-scrollbar { width: 10px; }
.term-body::-webkit-scrollbar-thumb {
  background: var(--rule);
  border-radius: 6px;
}

.term-log { margin: 0; }

/* Output is written as pre-wrapped text so alignment in listings survives. */
.term-line {
  margin: 0;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}
/* Numbered rows hang: "10. Long title" wraps under the title, not back under
   the number. The -4ch pulls the first line out to the 2ch the rest of the
   output uses; " 1. " through "10. " are all four characters wide. */
.term-line.is-item   { padding-left: 6ch; text-indent: -4ch; }
.term-line.is-cmd    { color: var(--fg); }
.term-line.is-dim    { color: var(--fg-faint); }
.term-line.is-head   { color: var(--accent-hot); }
.term-line.is-warn   { color: var(--amber); }
.term-line.is-spacer { height: 0.62rem; }

.term-line a { color: var(--accent); }


/* The prompt row. A real <input>, so mobile keyboards, IME, autofill-off,
   and screen readers all behave. */
.term-row {
  display: flex;
  align-items: baseline;
  gap: 0.45rem;
  margin-top: 0.15rem;
}

/* An author `display` declaration outranks the UA stylesheet's
   `[hidden] { display: none }`, so without this the prompt row stays on screen
   whenever it is hidden — before the script boots, and behind the editor. */
.term-row[hidden] { display: none; }

.term-row label {
  color: var(--accent);
  flex: none;
}

.term-input {
  flex: 1 1 auto;
  min-width: 0;
  background: transparent;
  border: 0;
  padding: 0;
  color: var(--fg);
  font: inherit;
  caret-color: var(--accent-hot);
}
.term-input:focus { outline: none; }

/* iOS Safari zooms the whole layout when a field under 16px takes focus, so
   tapping the prompt — or any key on the vim touch bar, which focuses the
   hidden field — threw the page out of position. 16px on coarse pointers only;
   the fix is the font size, never maximum-scale, which breaks pinch zoom for
   everyone. */
@media (pointer: coarse) {
  .term-input, .vim-field { font-size: 16px; }
}

/* The prompt hides its own outline (the caret is the indicator), which leaves a
   keyboard user nothing to see. The row carries it instead. */
.term-row:focus-within {
  outline: 2px solid var(--accent-hot);
  outline-offset: 3px;
  border-radius: 4px;
}

/* The block cursor is drawn separately so it can blink while the caret sits
   where the real input caret is. Hidden once the field has focus. */
.term-caret {
  width: 0.55em;
  height: 1.05em;
  flex: none;
  background: var(--accent-hot);
  animation: blink 1.15s steps(1, end) infinite;
  transform: translateY(0.15em);
}
.term-row:focus-within .term-caret { display: none; }

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

/* Static content shown before terminal.js takes over, and forever without it. */
/* Lives inside .term-body, which already supplies the padding. */
.term-fallback { font-size: 0.84rem; }
.term-fallback p { margin: 0 0 0.5rem; color: var(--fg-muted); }
.term-fallback ul {
  margin: 0;
  padding-left: 1.2rem;
  color: var(--fg-muted);
  columns: 2;
  column-gap: 1.5rem;
}
@media (max-width: 34rem) { .term-fallback ul { columns: 1; } }

.term-hint {
  margin: 0.6rem 0 0;
  font-size: 0.76rem;
  color: var(--fg-faint);
}

/* ------------------------------- the editor -------------------------------
   vim.js renders into the terminal body. The buffer scrolls inside its own
   box so a long comment never grows the page, and the touch bar below it is
   sized to Apple's 44pt default hit target rather than to the glyphs. */

.vim {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}

.vim-head {
  font-size: 0.72rem;
  letter-spacing: 0.08em;
  color: var(--amber);
}
.vim-head::before { content: "\"" / "" ; }
.vim-head::after  { content: "\" [New]" / ""; opacity: 0.7; }

.vim-buf {
  min-height: 8.5rem;
  max-height: clamp(9rem, 34vh, 16rem);
  overflow-y: auto;
  overscroll-behavior: contain;
  border: 1px solid var(--rule);
  border-radius: 6px;
  padding: 0.45rem 0.55rem;
  background: var(--bg);
}

.vim-row { white-space: pre-wrap; overflow-wrap: anywhere; }

.vim-num {
  color: var(--fg-faint);
  opacity: 0.65;
  user-select: none;
}

/* Block cursor in normal mode, bar in insert — the same distinction vim makes,
   and it is carried by shape as well as colour. */
.vim-cursor {
  background: var(--accent-hot);
  color: var(--bg);
  border-radius: 1px;
}
.vim-cursor.is-bar {
  background: transparent;
  color: inherit;
  box-shadow: inset 2px 0 0 0 var(--accent-hot);
}

.vim-status {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  font-size: 0.78rem;
  color: var(--fg-muted);
}
.vim-mode { font-weight: 700; color: var(--accent); }
.vim-mode.is-err { color: var(--amber); }
.vim-pos { color: var(--fg-faint); white-space: nowrap; }

/* A phone keyboard has no Esc, so modal editing is unreachable without these.
   44px minimum, with gap — HIG treats spacing between controls as mattering
   as much as their size. */
.vim-touch {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
}

.vim-key {
  font: inherit;
  font-size: 0.8rem;
  min-width: 44px;
  min-height: 44px;
  padding: 0 0.6rem;
  color: var(--accent);
  background: var(--tag-bg);
  border: 1px solid var(--rule);
  border-radius: 6px;
  cursor: pointer;
}
.vim-key:hover { border-color: var(--accent); color: var(--accent-hot); }
.vim-key:active { background: var(--rule); }

/* The real input. Off-screen but focusable, so a phone raises its keyboard —
   never display:none, which would make it unfocusable and the editor unusable
   on touch. */
.vim-field {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  border: 0;
  opacity: 0;
  pointer-events: none;
}

/* Announced to screen readers; the painted buffer is aria-hidden because a
   character-cell cursor means nothing read aloud. */
.vim-live {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* ------------------------------ comments list ----------------------------- */

.term-line.is-author { color: var(--accent-hot); }
.term-line.is-quote  { color: var(--fg); }
