/* ============================================================
   cld-toys — Guided Exercises Shared Stylesheet
   Inspired by Tufte CSS. Designed for readability and print.
   ============================================================ */

:root {
  --text: #1a1a1a;
  --muted: #555;
  --faint: #999;
  --border: #ddd;
  --bg: #fafaf8;
  --surface: #fff;
  --accent: #1a56db;
  --accent-bg: #eff6ff;
  --warn: #b45309;
  --warn-bg: #fffbeb;
  --success: #166534;
  --success-bg: #f0fdf4;
  --danger: #991b1b;
  --danger-bg: #fef2f2;
  --code-bg: #f4f4f0;
  --session-a: #1a56db;
  --session-a-bg: #eff6ff;
  --session-b: #7c3aed;
  --session-b-bg: #f5f3ff;
  --session-c: #047857;
  --session-c-bg: #ecfdf5;
  --max-width: 760px;
  --font-serif: Georgia, 'Times New Roman', serif;
  --font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  --font-mono: 'SF Mono', 'Fira Code', 'Consolas', monospace;
}

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

html { font-size: 18px; background: var(--bg); color: var(--text); }

body {
  font-family: var(--font-serif);
  line-height: 1.7;
  max-width: var(--max-width);
  margin: 0 auto;
  padding: 3rem 1.5rem 6rem;
}

/* ---- Typography ---- */

h1 { font-size: 2rem; line-height: 1.2; font-weight: 700; margin-bottom: 0.25rem; }
h2 { font-size: 1.3rem; font-weight: 700; margin: 2.5rem 0 0.75rem; }
h3 { font-size: 1.05rem; font-weight: 700; font-family: var(--font-sans); margin: 1.75rem 0 0.5rem; }
p  { margin-bottom: 1rem; }
p:last-child { margin-bottom: 0; }

a { color: var(--accent); text-decoration: underline; text-underline-offset: 2px; }
a:hover { opacity: 0.8; }

strong { font-weight: 700; }
em { font-style: italic; }

ul, ol { padding-left: 1.4rem; margin-bottom: 1rem; }
li { margin-bottom: 0.3rem; }

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

/* ---- Page header ---- */

.exercise-meta {
  font-family: var(--font-sans);
  font-size: 0.8rem;
  color: var(--faint);
  letter-spacing: 0.05em;
  text-transform: uppercase;
  margin-bottom: 1rem;
}

.exercise-meta a { color: var(--faint); text-decoration: none; }
.exercise-meta a:hover { color: var(--muted); text-decoration: underline; }

.exercise-title { margin-bottom: 0.5rem; }

.exercise-subtitle {
  font-size: 1.1rem;
  color: var(--muted);
  font-style: italic;
  margin-bottom: 2rem;
  font-family: var(--font-serif);
}

/* ---- Callout boxes ---- */

.callout {
  border-left: 3px solid;
  padding: 0.9rem 1.1rem;
  margin: 1.5rem 0;
  border-radius: 0 4px 4px 0;
  font-family: var(--font-sans);
  font-size: 0.92rem;
}

.callout-label {
  font-weight: 700;
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  display: block;
  margin-bottom: 0.3rem;
}

.callout.key      { border-color: var(--accent); background: var(--accent-bg); }
.callout.key      .callout-label { color: var(--accent); }
.callout.warn     { border-color: var(--warn); background: var(--warn-bg); }
.callout.warn     .callout-label { color: var(--warn); }
.callout.example  { border-color: #6b7280; background: #f9fafb; }
.callout.example  .callout-label { color: #374151; }

/* ---- Code ---- */

code {
  font-family: var(--font-mono);
  font-size: 0.83em;
  background: var(--code-bg);
  padding: 0.1em 0.35em;
  border-radius: 3px;
  border: 1px solid var(--border);
}

pre {
  background: #1e1e2e;
  color: #cdd6f4;
  padding: 1.2rem 1.4rem;
  border-radius: 6px;
  overflow-x: auto;
  margin: 1.5rem 0;
  font-family: var(--font-mono);
  font-size: 0.82rem;
  line-height: 1.6;
}

pre code {
  background: none;
  border: none;
  padding: 0;
  font-size: 1em;
  color: inherit;
}

/* Syntax highlighting (injected by copy.js) for the dark command blocks.
   Palette is Catppuccin Mocha, to match the #1e1e2e <pre> background. */
pre code .hl-comment { color: #7f849c; font-style: italic; }
pre code .hl-string  { color: #a6e3a1; }
pre code .hl-keyword { color: #cba6f7; }
pre code .hl-number  { color: #fab387; }
pre code .hl-builtin { color: #89b4fa; }

/* SQL / shell syntax */
.kw  { color: #89b4fa; font-weight: 600; }
.fn  { color: #cba6f7; }
.str { color: #a6e3a1; }
.cmt { color: #6c7086; font-style: italic; }
.num { color: #fab387; }
.op  { color: #89dceb; }

/* ---- Tables ---- */

table {
  width: 100%;
  border-collapse: collapse;
  font-family: var(--font-sans);
  font-size: 0.88rem;
  margin: 1.5rem 0;
}

th {
  text-align: left;
  font-weight: 700;
  padding: 0.6rem 0.8rem;
  border-bottom: 2px solid var(--border);
  color: var(--muted);
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

td {
  padding: 0.6rem 0.8rem;
  border-bottom: 1px solid var(--border);
  vertical-align: top;
}

tr:last-child td { border-bottom: none; }
tr:hover td { background: #f8f8f6; }

/* ---- Session tags (which psql session a step belongs to) ---- */

.session-tag {
  display: inline-block;
  font-family: var(--font-sans);
  font-size: 0.72rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  padding: 0.15em 0.6em;
  border-radius: 999px;
  margin-bottom: 0.5rem;
}

.session-tag.a { color: var(--session-a); background: var(--session-a-bg); }
.session-tag.b { color: var(--session-b); background: var(--session-b-bg); }
.session-tag.c { color: var(--session-c); background: var(--session-c-bg); }

/* Not a session — a plain shell on the machine you're sitting at, or a
   different vantage point entirely (macOS host vs the Linux VM vs the
   container). Deliberately grey so it doesn't compete with the coloured
   session tags, which by convention mean concurrent connections. */
.session-tag.host {
  color: var(--muted);
  background: #eeeeec;
  border: 1px solid var(--border);
}

/* ---- Comparing two runs ----
   Two shapes, one job: showing what changed between run A and run B.
   `table.compare` for a metric-by-metric breakdown (label | before | after);
   `.compare-pair` for two raw transcripts side by side. */

table.compare td:first-child {
  font-weight: 700;
  color: var(--muted);
  white-space: nowrap;
}

table.compare tbody td:nth-child(2) { background: var(--danger-bg); }
table.compare tbody td:nth-child(3) { background: var(--success-bg); }
table.compare tbody tr:hover td:nth-child(2) { background: #fde8e8; }
table.compare tbody tr:hover td:nth-child(3) { background: #dcfce7; }

table.compare thead th:nth-child(2) { color: var(--danger); }
table.compare thead th:nth-child(3) { color: var(--success); }

/* Opt out of the good/bad reading when the two columns are merely
   different rather than worse/better. */
table.compare.neutral tbody td:nth-child(2),
table.compare.neutral tbody td:nth-child(3),
table.compare.neutral tbody tr:hover td:nth-child(2),
table.compare.neutral tbody tr:hover td:nth-child(3) { background: transparent; }
table.compare.neutral thead th:nth-child(2),
table.compare.neutral thead th:nth-child(3) { color: var(--muted); }

/* A cell spanning both comparison columns — for rows where the two sides
   are identical and the point is that they're identical. */
table.compare td.span-both {
  text-align: center;
  font-style: italic;
  color: var(--muted);
}

table.compare tbody td.span-both,
table.compare tbody tr:hover td.span-both { background: var(--surface); }

.compare-pair {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1rem;
  margin: 1.5rem 0;
  align-items: start;
}

.compare-pair .pane-label {
  display: block;
  font-family: var(--font-sans);
  font-size: 0.76rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--faint);
  margin-bottom: 0.35rem;
}

.compare-pair .terminal-output,
.compare-pair pre { margin: 0; }

/* At half width, wrapping mangles aligned psql output — scroll each pane
   independently instead. */
.compare-pair .terminal-output {
  white-space: pre;
  overflow-x: auto;
}

@media (max-width: 700px) {
  .compare-pair { grid-template-columns: 1fr; gap: 1.2rem; }
}

/* ---- Exercise step ---- */

.step {
  margin: 1.75rem 0;
  padding: 1rem 1.3rem 1.15rem;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--surface);
}

/* The step's title reads as a proper card header: an accent eyebrow (matching
   the callout labels) with a full-width rule under it. */
.step-number {
  font-family: var(--font-sans);
  font-size: 0.75rem;
  font-weight: 700;
  color: var(--accent);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  margin: 0 -1.3rem 0.95rem;
  padding: 0 1.3rem 0.6rem;
  border-bottom: 1px solid var(--border);
}

.step pre { margin: 0.75rem 0; }
.step p:last-child { margin-bottom: 0; }

/* ---- Predict/reveal (native <details>) ---- */

details.reveal {
  margin: 1.1rem 0;
}

/* The summary reads as a clickable "reveal the answer" button: a tinted,
   bordered pill with a chevron that rotates open. */
details.reveal summary {
  cursor: pointer;
  list-style: none;
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.6rem 0.9rem;
  background: var(--accent-bg);
  border: 1px solid #cbdcf8;
  border-radius: 8px;
  color: var(--accent);
  font-family: var(--font-sans);
  font-weight: 600;
  font-size: 0.9rem;
  line-height: 1.35;
  user-select: none;
  transition: background 0.12s, border-color 0.12s;
}

details.reveal summary:hover {
  background: #e3edfd;
  border-color: var(--accent);
}

details.reveal summary::-webkit-details-marker { display: none; }

/* CSS triangle marker, separated from the text by the flex gap */
details.reveal summary::before {
  content: "";
  flex: none;
  width: 0;
  height: 0;
  border-left: 6px solid currentColor;
  border-top: 5px solid transparent;
  border-bottom: 5px solid transparent;
  transition: transform 0.15s ease;
}

details.reveal[open] summary {
  border-radius: 8px 8px 0 0;
}
details.reveal[open] summary::before {
  transform: rotate(90deg);
}

/* When open, the revealed content is a card body joined to the summary. */
details.reveal .observed {
  margin-top: 0;
  padding: 0.9rem 0.9rem 0.4rem;
  border: 1px solid #cbdcf8;
  border-top: none;
  border-radius: 0 0 8px 8px;
}
details.reveal .observed > p:last-child { margin-bottom: 0.5rem; }

.terminal-output {
  background: #1e1e2e;
  color: #cdd6f4;
  padding: 0.9rem 1.1rem;
  border-radius: 6px;
  font-family: var(--font-mono);
  font-size: 0.8rem;
  line-height: 1.55;
  white-space: pre-wrap;
  margin: 0.6rem 0;
}

/* ---- A session that is just sitting there ----
   For steps whose correct observation is *no output*: a blocked COMMIT, a
   synchronous replica that never confirms, a statement waiting on a lock. */

.terminal-output.waiting {
  color: var(--faint);
  font-style: italic;
  border-left: 3px solid var(--warn);
  white-space: normal;
}

/* inline-block, not a flex item — the text must stay one flowing line */
.terminal-output.waiting::before {
  content: "";
  display: inline-block;
  width: 0.5rem;
  height: 0.9rem;
  background: var(--warn);
  margin-right: 0.5rem;
  vertical-align: -0.12rem;
  animation: cursor-blink 1.1s step-end infinite;
}

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

@media (prefers-reduced-motion: reduce) {
  .terminal-output.waiting::before { animation: none; }
}

/* ---- Click-to-copy (progressive enhancement; see assets/copy.js) ----
   The button is injected by JS, so pages render fine with JS disabled. */

pre { position: relative; }

.copy-btn {
  position: absolute;
  top: 0.5rem;
  right: 0.5rem;
  font-family: var(--font-sans);
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: #cdd6f4;
  background: rgba(205, 214, 244, 0.12);
  border: 1px solid rgba(205, 214, 244, 0.25);
  border-radius: 4px;
  padding: 0.25em 0.6em;
  cursor: pointer;
  opacity: 0;
  transition: opacity 0.15s, background 0.15s;
}

pre:hover .copy-btn,
.copy-btn:focus-visible { opacity: 1; }

.copy-btn:hover { background: rgba(205, 214, 244, 0.22); }
.copy-btn.copied { color: #a6e3a1; border-color: #a6e3a1; opacity: 1; }

/* Touch devices have no hover — keep the button permanently visible. */
@media (hover: none) {
  .copy-btn { opacity: 0.75; }
}

/* An editorial cut inside a transcript — lines removed for length.
   Styled so it reads as *our* elision, not as something the program
   printed. Use as: <span class="elide">40 more lines</span> */
.terminal-output .elide {
  /* inline, not block: the parent is white-space: pre-wrap, so the real
     newlines around this span already break the line. display:block would
     add a second break and double-space every marker. */
  display: inline;
  color: var(--faint);
  font-style: italic;
  font-size: 0.9em;
  opacity: 0.75;
}

.terminal-output .elide::before { content: "⋮  "; font-style: normal; }

/* ---- Diagram / visual blocks ---- */

.diagram {
  border: 1px solid var(--border);
  background: var(--surface);
  border-radius: 6px;
  padding: 1.2rem;
  margin: 1.5rem 0;
  font-family: var(--font-mono);
  font-size: 0.82rem;
  line-height: 1.5;
  overflow-x: auto;
  white-space: pre;
}

/* ---- Citation ---- */

.cite {
  font-family: var(--font-sans);
  font-size: 0.78rem;
  color: var(--faint);
}

.cite a { color: var(--faint); }

/* ---- Footer ---- */

.exercise-footer {
  margin-top: 3rem;
  padding-top: 1.5rem;
  border-top: 1px solid var(--border);
  font-family: var(--font-sans);
  font-size: 0.82rem;
  color: var(--faint);
}

/* ---- Index / landing page ---- */

/* Collapsible source sections. With three sources and ~90 exercises the page
   was a long scroll before you learned what was on it; collapsed, the first
   screen is the table of contents.

   Anchors matter here: every exercise's breadcrumb links back to /#chNN or
   /#postgres, and those targets live inside these sections. The inline script
   at the foot of index.html opens the containing section on load and on
   hashchange. It is scoped to details.source so it can never pop open a
   .reveal on an exercise page. */
details.source {
  border-top: 1px solid var(--border);
}

details.source:last-of-type {
  border-bottom: 1px solid var(--border);
}

details.source > summary {
  cursor: pointer;
  list-style: none;
  padding: 1.35rem 0;
  display: flex;
  align-items: baseline;
  gap: 0.6rem;
}

/* Safari draws its own marker unless both of these are suppressed. */
details.source > summary::-webkit-details-marker { display: none; }

details.source > summary:hover .source-name { color: var(--accent); }

/* Our own disclosure caret, so it can sit tight against the title and rotate.
   A border triangle rather than a glyph: no font-dependent baseline drift. */
details.source > summary::before {
  content: "";
  flex: none;
  width: 0;
  height: 0;
  border-left: 5px solid var(--faint);
  border-top: 4px solid transparent;
  border-bottom: 4px solid transparent;
  transform: translateY(-0.15em);
  transition: transform 0.15s ease;
}

details.source[open] > summary::before {
  transform: translateY(-0.15em) rotate(90deg);
}

.source-name {
  font-size: 1.3rem;
  font-weight: 700;
  margin: 0;
  transition: color 0.15s;
}

/* Pushed to the right so the three counts line up as a column you can scan. */
.source-count {
  margin-left: auto;
  font-family: var(--font-sans);
  font-size: 0.8rem;
  color: var(--muted);
  white-space: nowrap;
}

/* The h3 inside a section already carries its own top margin; without this the
   first chapter heading sits too far from the summary it just opened. */
details.source[open] > .source-body > h3:first-of-type {
  margin-top: 0.75rem;
}

details.source > .source-body {
  padding-bottom: 1.25rem;
}

@media (max-width: 480px) {
  .source-count { display: none; }
}

.index-card {
  display: block;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--surface);
  padding: 1.1rem 1.3rem;
  margin: 1rem 0;
  text-decoration: none;
  color: inherit;
  transition: border-color 0.15s, box-shadow 0.15s;
}

.index-card:hover {
  opacity: 1;
  border-color: var(--accent);
  box-shadow: 0 1px 6px rgba(26, 86, 219, 0.08);
}

.card-tag {
  display: inline-block;
  font-family: var(--font-sans);
  font-size: 0.72rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--accent);
  background: var(--accent-bg);
  padding: 0.15em 0.6em;
  border-radius: 999px;
  margin-bottom: 0.6rem;
}

.card-title {
  font-size: 1.15rem;
  font-weight: 700;
  margin-bottom: 0.35rem;
  color: var(--accent);
  text-decoration: underline;
  text-underline-offset: 2px;
}

.card-aha {
  color: var(--muted);
  font-size: 0.95rem;
  margin-bottom: 0.6rem;
}

.card-meta {
  font-family: var(--font-sans);
  font-size: 0.76rem;
  color: var(--faint);
}

.upcoming {
  font-family: var(--font-sans);
  font-size: 0.88rem;
  color: var(--muted);
  list-style: none;
  padding-left: 0;
}

.upcoming li {
  padding: 0.4rem 0;
  border-bottom: 1px solid var(--border);
}

.upcoming li:last-child { border-bottom: none; }

.upcoming .system {
  display: inline-block;
  min-width: 7.5rem;
  color: var(--faint);
  font-size: 0.76rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

/* ---- Quotations ---- */

/* The global reset zeroes every margin, so blockquote needs its whole
   treatment restated. These are the source book's words, and on a page that
   mixes them with editorial prose they must never be mistakable for it:
   white card, neutral left rule (colored rules belong to callouts). */
blockquote {
  margin: 1.6rem 0;
  padding: 1rem 1.25rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-left: 4px solid var(--faint);
  border-radius: 0 4px 4px 0;
  color: var(--text);
}

blockquote p:last-child { margin-bottom: 0; }

/* An editorial interpolation inside a quotation: the bracketed words are the
   companion author's, supplying a referent the excerpt leaves dangling. Styled
   distinctly so it never reads as the book's own words. */
blockquote .interp {
  font-family: var(--font-sans);
  font-size: 0.9em;
  font-style: normal;
  color: var(--muted);
}

/* The book's words used mid-sentence. Italics can't carry this -- the companion
   italicizes its own emphasis too -- so these get real quotation marks and a
   faint underline, and the checker verifies them like any other quote. */
q.book {
  font-style: normal;
  /* Dotted, not solid: a solid underline next to real links reads as clickable. */
  border-bottom: 1px dotted var(--faint);
}

/* Companion pages only. Their quotes are machine-verified verbatim against the
   source epub; an exercise's Provenance quote may be a tight paraphrase, so it
   doesn't get this mark.

   A mark, not a caption: the page carries a dozen quotes, and a repeated line
   of words reads as noise by the third one. The legend up top explains it once;
   this glyph is the recurring cue. */
.companion blockquote {
  position: relative;
  padding-left: 2.5rem;
}

.companion blockquote::before {
  content: "\201C";
  position: absolute;
  left: 0.75rem;
  top: 0.3rem;
  font-family: var(--font-serif);
  font-size: 2.1rem;
  line-height: 1;
  color: var(--border);
}

/* ---- Reading companion ---- */

/* Sits between a chapter heading and its exercise list on the landing page. */
.companion-link {
  font-family: var(--font-sans);
  font-size: 0.88rem;
  color: var(--muted);
  margin: -0.2rem 0 0.9rem;
  padding-left: 0.75rem;
  border-left: 2px solid var(--border);
}

/* ---- Print ---- */

@media print {
  body { padding: 0; max-width: 100%; }
  pre, .terminal-output { background: #f4f4f0; color: var(--text); border: 1px solid var(--border); }
  details.reveal summary { display: none; }
  details.reveal .observed { display: block !important; }
}
