/* === Design tokens ===
 *
 * Brand colors are constant across themes. Everything below them
 * (surfaces, text, borders, header glass) is theme-sensitive and gets
 * overridden in [data-theme="dark"].
 */
:root {
  /* Tells the browser that the page supports both color schemes —
   * affects native form controls, scrollbars, and (importantly) the
   * default :visited link color, which would otherwise stay purple. */
  color-scheme: light;

  /* Typography rhythm — drives both line-height and grid cell size */
  --leading: 1.75rem; /* 28px @ 16px root */
  --leading-half: 0.875rem; /* 14px — for sub-multiple margins */

  /* Font cap-height ratios (capHeight / unitsPerEm, read straight from
   * each font file's OS/2 table). Used by .has-rhythm to push each text
   * block's first baseline onto the next grid line via
   *   margin-top: calc(var(--rhythm) * var(--leading) - var(--cap) * 1em);
   * Override --cap per-selector when the element uses a non-Fraunces face. */
  --cap-fraunces: 0.7;
  --cap-caveat: 0.61;
  --cap-jetbrains: 0.73;
  --cap: var(--cap-fraunces);

  /* Workshop Bench palette */
  --ws-bg: #f7f3e7;
  --ws-ink: #1f2433;
  --ws-grid: rgba(31, 36, 51, 0.07);
  --ws-amber: #c47a1a;
  --ws-cream: #fffdf7;
  /* Washi-tape palette (lesson body components) */
  --ws-tape: #fef4a8;
  --ws-tape-pink: #ffb8c1;
  --ws-tape-green: #c8e8c0;
  --ws-paper-soft: #faf6ee;
  --ws-paper: #ffffff;
  --ws-paper-header: #f0eada;
  --ws-border-soft: rgba(31, 36, 51, 0.2);
  --ws-border-dash: rgba(31, 36, 51, 0.3);
  --ws-syntax-kw: #c47a1a;
  --ws-syntax-num: #a6192e;
  --ws-syntax-str: #2a7a4f;
  --ws-syntax-com: #8a8275;

  /* Workshop layout cap. Wrappers that should stop growing on wide
   * monitors use max-width: var(--ws-max-width); margin-inline: auto.
   * --ws-wrap-offset is the gutter on either side of a centered wrap —
   * used by absolutely-positioned decorations (sidebar tape) to track
   * the centered layout instead of the viewport's left edge. */
  --ws-max-width: 1600px;
  --ws-wrap-offset: max(0px, calc((100vw - var(--ws-max-width)) / 2));

  /* z-index scale. Use these instead of raw numbers for any layer that
   * stacks across components. (Per-component internal stacking — z-index
   * 1/2/3 inside a card or strip — doesn't need this; just keep it
   * inside that component's own stacking context.) */
  --z-header: 100;
  --z-backdrop: 999; /* dimmed page behind the open drawer */
  --z-drawer: 1000; /* mobile sidebar drawer (above backdrop) */
  --z-skip-link: 9999; /* keyboard skip-to-content */
  --z-overlay: 10000; /* access-gate (above everything; blocks the page) */

  /* Brand palette */
  --color-primary: #33569b; /* Blue */
  --color-primary-hover: #264280; /* deeper blue, used on btn hover */
  --color-primary-deep: #254a8a; /* download-btn hover */
  --color-secondary: #a6192e; /* Red */
  --color-tertiary: #2a7a4f; /* Green */
  --color-quaternary: #c47a1a; /* Orange */

  /* Surfaces */
  --surface: #f4f7f6; /* page background */
  --surface-elevated: #ffffff; /* cards, articles, tables, sidebar */
  --surface-sunken: #f9f9f9; /* zebra rows */
  --surface-accent: #f0f4f8; /* subtle blue hover wash */
  --surface-accent-strong: #eef2fa; /* stronger blue active / inline code */

  /* Text */
  --text-main: #333333;
  --text-muted: #555555;
  --text-subtle: #888888;

  /* Borders */
  --border: #eeeeee;
  --border-input: #dddddd;
  --border-strong: #999999;

  /* Foreground for text/icons sitting on a solid brand-color fill (buttons,
   * table headers, hero, card titles). Stays white in both themes — the dark
   * brand blues are tuned to keep AA contrast against white. */
  --text-on-brand: #ffffff;

  /* Home-page hero gradient. Locked to the light-mode brand blues in both
   * themes so the hero reads as "brand blue" regardless of page theme. */
  --hero-bg-from: #3d6bb8;
  --hero-bg-mid: #33569b;
  --hero-bg-to: #264280;

  /* Header glass (translucent over surface) */
  --header-bg-scrolled: rgba(250, 250, 247, 0.9);
  --header-border-scrolled: rgba(51, 86, 155, 0.08);
  --header-shadow-scrolled: rgba(51, 86, 155, 0.1);

  /* Code action buttons (sit over code blocks) */
  --code-action-bg: rgba(255, 255, 255, 0.7);
  --code-action-bg-active: #ffffff;
  --code-action-border: rgba(0, 0, 0, 0.08);

  /* Subtle red wash behind hovered article links */
  --link-hover-bg: rgba(166, 25, 46, 0.05);
}

/* Dark theme overrides — applied via <html data-theme="dark"> */
[data-theme="dark"] {
  color-scheme: dark;

  --ws-bg: #252a33;
  --ws-ink: #e8e6e3;
  --ws-grid: rgba(232, 230, 227, 0.07);
  --ws-amber: #e09438;
  --ws-cream: #2d333f;
  --ws-paper: #1c180f;
  --ws-paper-soft: #1a1610;
  --ws-paper-header: #241e13;
  --ws-border-soft: rgba(237, 226, 200, 0.2);
  --ws-border-dash: rgba(237, 226, 200, 0.3);
  --ws-syntax-kw: #f5a45a;
  --ws-syntax-num: #e8595e;
  --ws-syntax-str: #58c285;
  --ws-syntax-com: #6e6450;

  /* Brand palette: brightened so the brand blue still reads as text on dark
   * surfaces while keeping enough saturation to look like the brand. Buttons
   * use these as solid fills, so values must keep AA contrast against #fff. */
  --color-primary: #6b8ed4;
  --color-primary-hover: #88a4dd; /* lighter on hover in dark mode */
  --color-primary-deep: #7c9ee0; /* download-btn hover */
  --color-secondary: #e25667;
  --color-tertiary: #5cb37a;
  --color-quaternary: #e09438;

  /* Surfaces — medium-dark blue-grey, à la VS Code "One Dark Pro".
   * Three tiers so cards still feel lifted off the page. */
  --surface: #252a33; /* page bg */
  --surface-elevated: #2d333f; /* cards, articles, sidebar */
  --surface-sunken: #1f242c; /* zebra rows, slightly recessed */
  --surface-accent: #364050; /* hover wash */
  --surface-accent-strong: #424f66; /* active / inline code */

  /* Text */
  --text-main: #e8e6e3;
  --text-muted: #a8a39d;
  --text-subtle: #6f6a64;

  /* Borders — bumped to stay visible against the lighter base */
  --border: #3a4150;
  --border-input: #444b5b;
  --border-strong: #5a6275;

  /* Header glass */
  --header-bg-scrolled: rgba(37, 42, 51, 0.85);
  --header-border-scrolled: rgba(107, 142, 212, 0.15);
  --header-shadow-scrolled: rgba(0, 0, 0, 0.35);

  /* Code action chips: invert the white-translucent to dark-translucent */
  --code-action-bg: rgba(0, 0, 0, 0.45);
  --code-action-bg-active: rgba(0, 0, 0, 0.7);
  --code-action-border: rgba(255, 255, 255, 0.12);

  /* Link hover wash — switch to the brightened red */
  --link-hover-bg: rgba(226, 86, 103, 0.12);
}

/* === Fonts === */
@font-face {
  font-family: sen;
  src: url("/fonts/sen.ttf");
}

@font-face {
  font-family: "Fraunces";
  src: url("/fonts/Fraunces-VariableFont_SOFT,WONK,opsz,wght.ttf")
    format("truetype");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Fraunces";
  src: url("/fonts/Fraunces-Italic-VariableFont_SOFT,WONK,opsz,wght.ttf")
    format("truetype");
  font-weight: 100 900;
  font-style: italic;
  font-display: swap;
}

@font-face {
  font-family: "Caveat";
  src: url("/fonts/Caveat-VariableFont_wght.ttf") format("truetype");
  font-weight: 400 700;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "JetBrains Mono";
  src: url("/fonts/JetBrainsMono-VariableFont_wght.ttf") format("truetype");
  font-weight: 100 800;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "JetBrains Mono";
  src: url("/fonts/JetBrainsMono-Italic-VariableFont_wght.ttf")
    format("truetype");
  font-weight: 100 800;
  font-style: italic;
  font-display: swap;
}

/* === Accessibility utilities === */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

.skip-link {
  position: absolute;
  top: -100%;
  left: 1rem;
  padding: 0.5rem 1rem;
  background: var(--color-primary);
  color: #fff;
  font-weight: 700;
  text-decoration: none;
  z-index: var(--z-skip-link);
  border-radius: 0 0 4px 4px;
}

.skip-link:focus {
  top: 0;
}

/* Global focus-visible baseline — keeps every interactive element keyboard-reachable */
:where(a, button, [role="button"], input, [tabindex]):focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 2px;
  border-radius: 2px;
}

/* === Base & layout === */
body {
  font-family:
    sen,
    -apple-system,
    BlinkMacSystemFont,
    avenir next,
    avenir,
    segoe ui,
    helvetica neue,
    Adwaita Sans,
    Cantarell,
    Ubuntu,
    roboto,
    noto,
    helvetica,
    arial,
    sans-serif;
  background-color: var(--surface);
  color: var(--text-main);
  margin: 0;
  padding: 0;
  line-height: 1.6;
  overflow-anchor: none;
}

/* Global link colors — keep links on the brand palette instead of
 * the browser default blue/purple. Components like .ws-article or
 * buttons can override locally as needed. */
a,
a:visited {
  color: var(--color-primary);
  font-weight: 600;
  text-decoration: underline;
  text-underline-offset: 2px;
  text-decoration-thickness: 2px;
}

a:hover {
  color: var(--color-secondary);
}

.container {
  max-width: 56.25rem;
  margin: 0 auto;
  padding: clamp(1rem, 4vw, 2rem);
}

/* === Header & navigation === */
header {
  position: sticky;
  top: 0;
  z-index: var(--z-header);
  background-color: transparent;
  backdrop-filter: blur(0px);
  border-bottom: 1px solid transparent;
  box-shadow: none;
  /* viewport-fit=cover lets the page extend under the notch / dynamic
   * island. Reserve space here so nav content doesn't sit under it. */
  padding-top: env(safe-area-inset-top);
  padding-left: env(safe-area-inset-left);
  padding-right: env(safe-area-inset-right);
  transition:
    background-color 0.3s ease,
    backdrop-filter 0.3s ease,
    border-color 0.3s ease,
    box-shadow 0.3s ease;
}

header.is-scrolled {
  background-color: var(--header-bg-scrolled);
  backdrop-filter: blur(8px);
  border-color: var(--header-border-scrolled);
  box-shadow: 0 2px 12px var(--header-shadow-scrolled);
}

nav {
  padding: 0 clamp(1rem, 4vw, 2.5rem);
  min-height: 72px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  position: relative;
  max-width: var(--ws-max-width);
  margin-inline: auto;
}

.nav-brand,
.nav-brand:visited {
  display: flex;
  align-items: center;
  gap: 0;
  text-decoration: none;
  color: var(--text-main);
  flex-shrink: 0;
}

.nav-logo {
  height: 52px;
  width: auto;
  display: block;
}

.nav-logo--dark {
  display: none;
}

[data-theme="dark"] .nav-logo--light {
  display: none;
}

[data-theme="dark"] .nav-logo--dark {
  display: block;
}

.nav-divider {
  width: 2px;
  height: 38px;
  background: currentColor;
  transform: rotate(12deg);
  opacity: 0.3;
  flex-shrink: 0;
  margin-left: -6px;
  margin-right: 10px;
}

.nav-section {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.nav-title {
  font-family: "Caveat", cursive;
  font-weight: 700;
  font-size: 1.6875rem;
  line-height: 0.95;
  color: var(--text-main);
  display: block;
}

.nav-sub {
  font-family: "JetBrains Mono", monospace;
  font-size: 0.75rem;
  color: var(--text-subtle);
  text-transform: uppercase;
  letter-spacing: 1.5px;
  display: block;
}

/* Wattson mascot trigger button — resets default button chrome */
.wattson-trigger {
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
  display: block;
  line-height: 0;
}

/* === Wattson mascot animations === */
@keyframes wattson-wave {
  0% {
    transform: rotate(0deg) translateY(0);
  }
  8% {
    transform: rotate(-10deg) translateY(-4px);
  }
  16% {
    transform: rotate(10deg) translateY(-8px);
  }
  24% {
    transform: rotate(-9deg) translateY(-5px);
  }
  32% {
    transform: rotate(9deg) translateY(-9px);
  }
  42% {
    transform: rotate(-4deg) translateY(-6px);
  }
  52% {
    transform: rotate(0deg) translateY(-4px);
  }
  68% {
    transform: rotate(0deg) translateY(-7px);
  }
  84% {
    transform: rotate(0deg) translateY(-2px);
  }
  100% {
    transform: rotate(0deg) translateY(0);
  }
}

@keyframes wattson-bob {
  0%,
  100% {
    transform: translateY(0);
  }
  50% {
    transform: translateY(-6px);
  }
}

@keyframes wattson-pulse {
  0%,
  100% {
    filter: drop-shadow(0 4px 16px rgba(0, 0, 0, 0.25));
  }
  50% {
    filter: drop-shadow(0 4px 22px rgba(245, 200, 0, 0.45))
      drop-shadow(0 0 8px rgba(245, 200, 0, 0.3));
  }
}

.wattson-hero {
  transform-origin: bottom center;
  cursor: pointer;
  animation-name: wattson-wave;
  animation-duration: 3.8s;
  animation-timing-function: ease-in-out;
  animation-iteration-count: infinite;
}

/* Hover speeds the wave up and adds a pulsing glow. Timing function and
 * iteration count cascade from the base rule. */
.wattson-hero:hover {
  animation-name: wattson-wave, wattson-pulse;
  animation-duration: 1.4s, 0.9s;
}

.wattson-header {
  animation: wattson-bob 2.2s ease-in-out infinite;
  transform-origin: bottom center;
}

@media (prefers-reduced-motion: reduce) {
  .wattson-hero,
  .wattson-hero:hover,
  .wattson-header {
    animation: none;
  }
}

/* === Typography === */
h1 {
  color: var(--color-primary);
  border-bottom: none;
  padding-bottom: 0;
  margin-bottom: 1.5rem;
  font-weight: 800;
  letter-spacing: -0.01em;
}

/* === Workshop Landing Page === */

body.workshop {
  background-color: var(--ws-bg);
  --header-bg-scrolled: rgba(247, 243, 231, 0.93);
}

[data-theme="dark"] body.workshop,
body.workshop[data-theme="dark"] {
  --header-bg-scrolled: rgba(37, 42, 51, 0.92);
}

.workshop-landing {
  min-height: 100vh;
  min-height: 100dvh;
  color: var(--ws-ink);
  position: relative;
  overflow-x: hidden;
}

.workshop-grid-bg {
  position: absolute;
  inset: 0;
  background-image:
    linear-gradient(var(--ws-grid) 1px, transparent 1px),
    linear-gradient(90deg, var(--ws-grid) 1px, transparent 1px);
  background-size: var(--leading) var(--leading);
  /* Grid origin is the top-left of this element. Each `.has-rhythm`
   * container snaps its own first baseline to a grid line via
   * --grid-snap (set by JS in base.njk), so no manual offset is needed. */
  background-position: 0 0;
  pointer-events: none;
  z-index: 0;
}

/* Hero */
.workshop-hero {
  position: relative;
  z-index: 1;
  display: grid;
  grid-template-columns: 260px 1fr;
  gap: 48px;
  padding: 28px clamp(28px, 4vw, 56px) 56px;
  align-items: start;
  max-width: var(--ws-max-width);
  margin-inline: auto;
}

.workshop-hero__wattson-wrap {
  position: relative;
  padding-top: 0;
  padding-bottom: 4px;
  display: flex;
  justify-content: center;
}

.workshop-hero__wattson {
  /* Stays at 220 through normal laptop widths; grows on big monitors so
   * Wattson doesn't read as a thumbnail next to the giant hero title.
   * 14vw kicks in past ~1570px viewport and tops out at 280px around 2000px.
   * The single-column mobile override at ≤720px below sets 160px and wins. */
  height: clamp(220px, 14vw, 280px);
  width: auto;
  display: block;
  filter: drop-shadow(3px 6px 0 rgba(31, 36, 51, 0.15));
}

.workshop-hero__wattson-shadow {
  position: absolute;
  left: 50%;
  bottom: -8px;
  width: 140px;
  height: 14px;
  background: rgba(31, 36, 51, 0.12);
  border-radius: 50%;
  transform: translateX(-50%);
  filter: blur(6px);
}

.workshop-hero__copy {
  display: flex;
  flex-direction: column;
  /* Note: gap intentionally omitted — `.has-rhythm > * + *` provides
   * the cap-corrected margin-top that keeps baselines on the grid. */
  margin-top: 0;
}

.workshop-hero__kicker {
  font-family: "Caveat", cursive;
  font-size: 1.5rem;
  line-height: var(--leading);
  font-weight: 600;
  color: var(--ws-amber);
  display: block;
  --cap: var(--cap-caveat);
}

.workshop-hero__title {
  font-family: "Fraunces", serif;
  font-size: clamp(3rem, 7.5vw, 5rem);
  font-weight: 800;
  letter-spacing: -0.025em;
  line-height: calc(var(--leading) * 3);
  color: var(--ws-ink);
  border: none;
  padding: 0;
  /* At max font-size (5rem = 80px), cap × 1em = 56px = 2P, so --rhythm: 3
   * keeps margin = 1P. At min (3rem = 48px), margin ≈ 50px (~1.8P). */
  --rhythm: 3;
}

.workshop-hero__underline {
  text-decoration: underline;
  text-decoration-style: wavy;
  text-decoration-thickness: 0.2rem;
  text-decoration-color: #fcc902;
  text-underline-offset: 8px;
  color: var(--color-primary);
}

.workshop-hero__deck {
  font-size: 1.0625rem;
  line-height: var(--leading);
  min-height: calc(var(--leading) * 2);
  max-width: 520px;
  color: var(--ws-ink);
  opacity: 0.78;
  --rhythm: 2;
}

.workshop-hero__aside {
  font-family: "Caveat", cursive;
  font-size: 1.3125rem;
  color: var(--color-secondary);
}

.workshop-hero__deck-msg,
.workshop-hero__aside {
  transition: opacity 180ms ease;
}

.workshop-hero__deck-msg.is-fading,
.workshop-hero__aside.is-fading {
  opacity: 0;
}

@media (prefers-reduced-motion: reduce) {
  .workshop-hero__deck-msg,
  .workshop-hero__aside {
    transition: none;
  }
}

/* Message separator */
.workshop-message {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 12px;
  padding: 0 clamp(28px, 4vw, 56px) var(--leading);
  max-width: var(--ws-max-width);
  margin-inline: auto;
}

.workshop-message__rule {
  flex: 1;
  height: 0;
  border-top: 1.5px dotted rgba(31, 36, 51, 0.28);
}

[data-theme="dark"] .workshop-message__rule {
  border-top-color: rgba(232, 230, 227, 0.22);
}

.workshop-message__text {
  margin: 0;
  font-family: "Fraunces", serif;
  font-style: oblique 10deg;
  font-size: 1.0625rem;
  line-height: var(--leading);
  color: var(--ws-ink);
  opacity: 0.65;
  text-align: left;
  letter-spacing: 0.1px;
  padding-top: 12px;
  border-top: 1.5px dotted rgba(31, 36, 51, 0.28);
  max-width: 640px;
}

[data-theme="dark"] .workshop-message__text {
  border-top-color: rgba(232, 230, 227, 0.22);
}

.workshop-message__sep {
  opacity: 0.6;
}

.workshop-message__accent {
  color: var(--color-secondary);
  font-weight: 500;
  opacity: 1;
}

/* Board section */
.workshop-board {
  position: relative;
  z-index: 1;
  padding: 0 clamp(1.25rem, 4vw, 2.5rem) 48px;
  max-width: var(--ws-max-width);
  margin-inline: auto;
}

.workshop-board__head {
  margin-bottom: var(--leading);
  display: flex;
  flex-direction: column;
  gap: 28px;
}

.workshop-board__kick {
  font-family: "JetBrains Mono", monospace;
  font-size: 0.75rem;
  line-height: 2.5;
  text-transform: uppercase;
  letter-spacing: 3px;
  color: var(--color-secondary);
  font-weight: 700;
  display: block;
}

.workshop-board__title {
  margin: 0;
  font-family: "Fraunces", serif;
  font-size: clamp(1.75rem, 4vw, 2.4rem);
  font-weight: 800;
  letter-spacing: -0.02em;
  line-height: 1.15;
  color: var(--ws-ink);
  border: none;
  padding: 0;
  position: relative;
  top: calc(var(--leading) * 0.4);
}

.workshop-board__title em {
  font-style: oblique 10deg;
  color: var(--color-primary);
}

.workshop-board__grid {
  list-style: none;
  padding: 16px 0 0;
  margin: 0;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 40px 24px;
}

@media (max-width: 900px) {
  .workshop-board__grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (max-width: 560px) {
  .workshop-board__grid {
    grid-template-columns: 1fr;
  }
}

/* Big-desktop / 4K: the board caps at --ws-max-width (1600px), so 3-col
 * blows each polaroid out to ~485px wide — they stop reading as polaroids
 * and look like banner ads. Bump to 4-col above 1400px so cards stay in
 * the 280-360px sweet spot. */
@media (min-width: 1400px) {
  .workshop-board__grid {
    grid-template-columns: repeat(4, 1fr);
  }
}

/* Polaroid cards */
.workshop-polaroid {
  position: relative;
  transform: rotate(var(--rot, -1deg));
  transition: transform 0.25s cubic-bezier(0.2, 0.7, 0.2, 1);
}

.workshop-polaroid:nth-child(6n + 1) {
  --rot: -2.5deg;
  --tape: #fef4a8;
}
.workshop-polaroid:nth-child(6n + 2) {
  --rot: 1.5deg;
  --tape: #c8e8c0;
}
.workshop-polaroid:nth-child(6n + 3) {
  --rot: -1deg;
  --tape: #ffb8c1;
}
.workshop-polaroid:nth-child(6n + 4) {
  --rot: 2.2deg;
  --tape: #fef4a8;
}
.workshop-polaroid:nth-child(6n + 5) {
  --rot: -2deg;
  --tape: #bdd6ff;
}
.workshop-polaroid:nth-child(6n) {
  --rot: 1deg;
  --tape: #ffd7a5;
}

.workshop-polaroid:hover,
.workshop-polaroid:focus-within {
  transform: rotate(0deg) scale(1.05) translateY(-6px);
  z-index: 2;
}

.workshop-polaroid__tape {
  position: absolute;
  top: -16px;
  left: 50%;
  transform: translateX(-50%) rotate(var(--tape-rot, 0deg));
  width: 82px;
  height: 30px;
  background:
    linear-gradient(
      180deg,
      rgba(255, 255, 255, 0.25) 0%,
      transparent 35%,
      transparent 65%,
      rgba(0, 0, 0, 0.06) 100%
    ),
    var(--tape, #fef4a8);
  opacity: 0.9;
  z-index: 2;
  filter: drop-shadow(0 1px 1.5px rgba(0, 0, 0, 0.12));
  clip-path: polygon(
    3% 0%,
    97% 0%,
    100% 22%,
    96% 45%,
    99% 68%,
    95% 88%,
    98% 100%,
    2% 100%,
    0% 88%,
    4% 68%,
    1% 45%,
    5% 22%
  );
}

.workshop-polaroid:nth-child(6n + 1) .workshop-polaroid__tape {
  --tape-rot: -2deg;
}
.workshop-polaroid:nth-child(6n + 2) .workshop-polaroid__tape {
  --tape-rot: 3deg;
  clip-path: polygon(
    4% 0%,
    96% 0%,
    98% 18%,
    100% 40%,
    95% 62%,
    99% 82%,
    96% 100%,
    4% 100%,
    1% 82%,
    5% 62%,
    0% 40%,
    3% 18%
  );
}
.workshop-polaroid:nth-child(6n + 3) .workshop-polaroid__tape {
  --tape-rot: -4deg;
  clip-path: polygon(
    2% 0%,
    98% 0%,
    95% 24%,
    100% 48%,
    97% 70%,
    100% 86%,
    95% 100%,
    5% 100%,
    0% 86%,
    4% 70%,
    1% 48%,
    5% 24%
  );
}
.workshop-polaroid:nth-child(6n + 4) .workshop-polaroid__tape {
  --tape-rot: 2deg;
  clip-path: polygon(
    5% 0%,
    95% 0%,
    100% 20%,
    97% 42%,
    100% 64%,
    96% 84%,
    98% 100%,
    3% 100%,
    1% 84%,
    5% 64%,
    2% 42%,
    4% 20%
  );
}
.workshop-polaroid:nth-child(6n + 5) .workshop-polaroid__tape {
  --tape-rot: -3deg;
}
.workshop-polaroid:nth-child(6n) .workshop-polaroid__tape {
  --tape-rot: 4deg;
  clip-path: polygon(
    3% 0%,
    97% 0%,
    96% 25%,
    100% 50%,
    96% 72%,
    99% 90%,
    97% 100%,
    4% 100%,
    0% 90%,
    4% 72%,
    1% 50%,
    4% 25%
  );
}

.workshop-polaroid__card {
  background: #fff;
  border: 1.5px solid var(--ws-ink);
  padding: 9px;
  box-shadow:
    4px 6px 0 rgba(31, 36, 51, 0.1),
    0 1px 3px rgba(0, 0, 0, 0.05);
  display: flex;
  flex-direction: column;
  position: relative;
  transition: box-shadow 0.25s ease;
}

[data-theme="dark"] .workshop-polaroid__card {
  background: var(--surface-elevated);
  border-color: var(--border-strong);
  box-shadow: 4px 6px 0 rgba(0, 0, 0, 0.35);
}

.workshop-polaroid:hover .workshop-polaroid__card,
.workshop-polaroid:focus-within .workshop-polaroid__card {
  box-shadow:
    8px 18px 22px -6px rgba(31, 36, 51, 0.22),
    0 2px 4px rgba(0, 0, 0, 0.08);
}

[data-theme="dark"] .workshop-polaroid:hover .workshop-polaroid__card,
[data-theme="dark"] .workshop-polaroid:focus-within .workshop-polaroid__card {
  box-shadow:
    8px 18px 22px -6px rgba(0, 0, 0, 0.5),
    0 2px 4px rgba(0, 0, 0, 0.25);
}

.workshop-polaroid__image {
  position: relative;
  height: 130px;
  overflow: hidden;
  background-size: cover;
  background-position: center;
  border: 1px solid rgba(31, 36, 51, 0.18);
  display: flex;
  align-items: flex-end;
  justify-content: flex-end;
  padding: 10px;
}

.workshop-polaroid__badge {
  font-family: "JetBrains Mono", monospace;
  font-size: 0.75rem;
  font-weight: 700;
  background: rgba(255, 255, 255, 0.95);
  padding: 3px 8px;
  border-radius: 999px;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  text-transform: uppercase;
  letter-spacing: 0.8px;
}

.workshop-polaroid__badge--open {
  color: #2a7a4f;
}

.workshop-polaroid__live-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: #2a7a4f;
  flex-shrink: 0;
  animation: live-pulse 2.2s ease-in-out infinite;
}

@keyframes live-pulse {
  0%,
  100% {
    opacity: 1;
  }
  50% {
    opacity: 0.25;
  }
}

.workshop-polaroid__body {
  padding: 10px 4px 5px;
  display: flex;
  flex-direction: column;
  gap: 6px;
  flex: 1;
}

.workshop-polaroid__num {
  font-family: "JetBrains Mono", monospace;
  font-size: 0.75rem;
  color: var(--text-subtle);
  font-weight: 700;
  letter-spacing: 1px;
}

.workshop-polaroid__title {
  margin: 0;
  font-family: "Caveat", cursive;
  font-weight: 700;
  font-size: 1.625rem;
  line-height: 1;
  color: var(--ws-ink);
  /* Keep long program titles inside the card at narrow widths (280px
   * polaroids in 2-col tablet, 4-col big-desktop). Wraps at spaces first
   * and only breaks inside a word if a single token is wider than the card. */
  overflow-wrap: break-word;
}

.workshop-polaroid__link,
.workshop-polaroid__link:visited {
  color: inherit;
  text-decoration: none;
}

.workshop-polaroid__link::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
}

.workshop-polaroid__link:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 2px;
}

.workshop-polaroid__summary {
  margin: 0;
  font-size: 0.8125rem;
  line-height: 1.4;
  color: var(--ws-ink);
  opacity: 0.78;
}

.workshop-polaroid__foot {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding-top: 8px;
  border-top: 1.5px dashed rgba(31, 36, 51, 0.14);
  margin-top: auto;
}

[data-theme="dark"] .workshop-polaroid__foot {
  border-top-color: var(--border);
}

.workshop-polaroid__meta {
  font-family: "JetBrains Mono", monospace;
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--text-muted);
}

.workshop-polaroid__go {
  font-size: 1.125rem;
  color: var(--ws-ink);
  font-weight: 700;
  position: relative;
  z-index: 2;
  pointer-events: none;
}

/* Up-next strip — upcoming workshops, secondary to the board */
.workshop-strip {
  position: relative;
  z-index: 1;
  padding: 0 clamp(1.25rem, 4vw, 2.5rem) 48px;
}

.workshop-strip__label {
  display: flex;
  align-items: center;
  gap: 14px;
  margin-bottom: 16px;
}

.workshop-strip__label-text {
  font-family: "Caveat", cursive;
  font-size: 1.375rem;
  font-weight: 700;
  color: var(--ws-ink);
  white-space: nowrap;
  opacity: 0.75;
}

.workshop-strip__line {
  flex: 1;
  border-top: 2px dotted var(--ws-ink);
  opacity: 0.18;
}

.workshop-strip__list {
  list-style: none;
  padding: 0;
  margin: 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(220px, 100%), 1fr));
  gap: 10px;
}

.workshop-strip__item {
  display: flex;
  align-items: center;
  gap: 10px;
  background: var(--ws-cream);
  border: 1.5px solid var(--ws-ink);
  padding: 8px 12px;
  box-shadow: 2px 2px 0 rgba(31, 36, 51, 0.07);
  min-width: 0;
  transition:
    transform 0.15s ease,
    box-shadow 0.15s ease;
}

.workshop-strip__item:hover,
.workshop-strip__item:focus-within {
  transform: translate(-1px, -1px);
  box-shadow: 3px 3px 0 rgba(31, 36, 51, 0.12);
}

[data-theme="dark"] .workshop-strip__item {
  background: var(--surface-elevated);
  border-color: var(--border);
}

.workshop-strip__dot {
  flex-shrink: 0;
  width: 9px;
  height: 9px;
  border-radius: 50%;
}

.workshop-strip__title {
  font-size: 0.875rem;
  font-weight: 700;
  line-height: 1.3;
  color: var(--ws-ink);
  text-decoration: none;
  /* No manifest defines shortTitle, and the longest current program title
   * ("Wattson Power Clicker", 21 chars) ellipsized at the default cell
   * width. Allow a 2-line wrap instead — break inside a word only if the
   * token itself exceeds the cell. */
  overflow-wrap: break-word;
  min-width: 0;
  flex: 1;
}

.workshop-strip__title:hover,
.workshop-strip__title:focus-visible {
  text-decoration: underline;
  text-underline-offset: 3px;
}

.workshop-strip__meta {
  margin-left: auto;
  font-family: "JetBrains Mono", monospace;
  font-size: 0.75rem;
  color: var(--text-subtle);
  text-transform: uppercase;
  letter-spacing: 1px;
  flex-shrink: 0;
}

/* Empty state — no workshop currently running */
.workshop-empty {
  position: relative;
  z-index: 1;
  margin: 0 auto;
  max-width: 520px;
  padding: 28px clamp(1.25rem, 4vw, 2.5rem) 56px;
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
}

.workshop-empty__tape {
  width: 88px;
  height: 30px;
  background:
    linear-gradient(
      180deg,
      rgba(255, 255, 255, 0.3) 0%,
      transparent 35%,
      transparent 65%,
      rgba(0, 0, 0, 0.07) 100%
    ),
    rgba(255, 200, 87, 0.7);
  transform: rotate(-3deg);
  margin-bottom: 4px;
  filter: drop-shadow(0 1px 1.5px rgba(0, 0, 0, 0.12));
  clip-path: polygon(
    3% 0%,
    97% 0%,
    100% 22%,
    96% 45%,
    99% 68%,
    95% 88%,
    98% 100%,
    2% 100%,
    0% 88%,
    4% 68%,
    1% 45%,
    5% 22%
  );
}

[data-theme="dark"] .workshop-empty__tape {
  background:
    linear-gradient(
      180deg,
      rgba(255, 255, 255, 0.12) 0%,
      transparent 35%,
      transparent 65%,
      rgba(0, 0, 0, 0.18) 100%
    ),
    rgba(255, 200, 87, 0.32);
}

.workshop-empty__note {
  margin: 0;
  font-family: "Caveat", cursive;
  font-size: 1.625rem;
  font-weight: 700;
  color: var(--ws-ink);
  opacity: 0.85;
}

.workshop-empty__link {
  font-family: "JetBrains Mono", monospace;
  font-size: 0.8125rem;
  font-weight: 700;
  letter-spacing: 0.8px;
  text-transform: uppercase;
  color: var(--ws-ink);
  text-decoration: none;
  border-bottom: 2px solid var(--ws-ink);
  padding-bottom: 2px;
  transition: opacity 0.18s ease;
}

.workshop-empty__link:hover,
.workshop-empty__link:focus-visible {
  opacity: 0.65;
}

/* Phone + landscape-phone hero treatment. The 260px mascot column + 48px
 * gap eats ~308px, so anything below ~720px viewport leaves the text in a
 * cramped sliver. Single-column kicks in up to 720, then iPad portrait
 * (768) and above get the two-column layout where there's room. */
@media (max-width: 720px) {
  .workshop-hero {
    grid-template-columns: 1fr;
    text-align: center;
    padding-top: 28px;
    gap: 28px;
  }

  .workshop-hero__wattson-wrap {
    padding-top: 0;
  }

  .workshop-hero__wattson {
    height: 160px;
  }

  .workshop-hero__copy {
    margin-top: 0;
    align-items: center;
  }

  .workshop-hero__deck {
    text-align: left;
  }

  .workshop-message__text {
    font-size: 0.9375rem;
  }
}

/* === Tables === */
/* Wrapper prevents wide tables from breaking mobile layouts */
.table-wrapper {
  overflow-x: auto;
  margin: 2rem 0;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
}

/* Generic table defaults (outside ws-article) */
table {
  width: 100%;
  border-collapse: collapse;
  /* Cap min-width at the viewport so small-phone (<500px) layouts
     don't horizontally overflow their wrapper. The 31rem target only
     kicks in once the parent has room for it. */
  min-width: min(100%, 31rem);
  background: var(--surface-elevated);
}

thead th {
  background-color: var(--color-primary);
  color: var(--text-on-brand);
  padding: 1rem;
  text-align: left;
  font-weight: 600;
  text-transform: uppercase;
  font-size: 0.9rem;
  letter-spacing: 0.5px;
}

tbody td {
  padding: 1rem;
  border-bottom: 1px solid var(--border);
  color: var(--text-main);
  vertical-align: top;
}

tbody tr:nth-child(even) {
  background-color: var(--surface-sunken);
}

tbody tr:hover {
  background-color: var(--surface-accent);
}

/* === Workshop Bench lesson reference table ===
 * Scoped to .ws-article so generic tables elsewhere are unaffected.
 * Washi tape sits above the dark header bar; the tape is rendered as a
 * ::before on a .ws-table-wrap sentinel div that markdown-it wraps around
 * each table via the tableWrapper option below. If the wrapper is absent,
 * the table still looks right — just without the tape. */
.ws-article .ws-table-wrap {
  position: relative;
  margin: calc(var(--leading) * 2) 0;
  background: #fff;
  border: 1.5px solid var(--ws-ink);
  box-shadow: 4px 4px 0 rgba(31, 36, 51, 0.15);
  max-width: 760px;
}

/* When a paragraph immediately precedes a table (e.g. a bold caption like
   "**Structure** — …"), tighten the gap so the label visually pairs with
   its table instead of floating between two big air pockets. */
.ws-article p:has(+ .ws-table-wrap) {
  margin-bottom: 0;
}
.ws-article p + .ws-table-wrap {
  margin-top: var(--leading-half);
}

.ws-article .ws-table-scroll {
  overflow-x: auto;
  /* Desktop affordance: thin always-on scrollbar so users see the table
   * is scrollable without having to scroll it first. iOS hides scrollbars
   * regardless — the right-edge shadow below (::after on the wrap) covers
   * that case. */
  scrollbar-width: thin;
  scrollbar-color: rgba(31, 36, 51, 0.35) transparent;
}
.ws-article .ws-table-scroll::-webkit-scrollbar {
  height: 8px;
}
.ws-article .ws-table-scroll::-webkit-scrollbar-thumb {
  background: rgba(31, 36, 51, 0.35);
  border-radius: 4px;
}
.ws-article .ws-table-scroll::-webkit-scrollbar-track {
  background: transparent;
}

/* Right-edge shadow — subtle visual hint that the table extends past the
 * visible area. Always present (cheap to render); only "reads" as a cue
 * when the table is actually scrollable. */
.ws-article .ws-table-wrap::after {
  content: "";
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  width: 18px;
  background: linear-gradient(to left, rgba(31, 36, 51, 0.08), transparent);
  pointer-events: none;
  z-index: 1;
}

.ws-article .ws-table-wrap::before {
  content: "";
  position: absolute;
  top: -10px;
  left: 50%;
  width: var(--tape-w, 100px);
  height: 18px;
  background: var(--ws-tape);
  transform: translate(calc(-50% + var(--tape-x, 0px)), var(--tape-y, 0px))
    rotate(var(--tape-rot, -2deg));
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.12);
  opacity: 0.92;
  z-index: 2;
  pointer-events: none;
}

.ws-article .ws-table-scroll table {
  width: 100%;
  border-collapse: collapse;
  min-width: 0;
  background: transparent;
  font-family: "Inter", system-ui, sans-serif;
  color: var(--ws-ink);
}

.ws-article .ws-table-scroll thead th {
  background: var(--ws-ink);
  color: #fff;
  font-family: "JetBrains Mono", monospace;
  font-size: 0.68rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  padding: 12px 14px;
  text-align: left;
  border-right: 1px solid rgba(255, 255, 255, 0.1);
}

.ws-article .ws-table-scroll thead th:last-child {
  border-right: none;
}

.ws-article .ws-table-scroll tbody td {
  padding: 11px 14px;
  font-size: 0.9375rem;
  line-height: 1.55;
  color: #26211a;
  vertical-align: top;
  border-bottom: none;
  border-top: 1px dashed rgba(31, 36, 51, 0.18);
  border-right: 1px dashed rgba(31, 36, 51, 0.18);
  /* Let long prose wrap inside cells instead of expanding the table.
   * Inline code in cells keeps `white-space: nowrap` (see rule below)
   * so code stays legible; long code rows scroll horizontally instead. */
  overflow-wrap: break-word;
}

.ws-article .ws-table-scroll tbody td:last-child {
  border-right: none;
}

.ws-article .ws-table-scroll tbody tr:first-child td {
  border-top: none;
}

/* Zebra */
.ws-article .ws-table-scroll tbody tr:nth-child(even) {
  background: var(--ws-paper-soft);
}

/* Hover row */
.ws-article .ws-table-scroll tbody tr:hover {
  background: #fff8d4;
  position: relative;
}

.ws-article .ws-table-scroll tbody tr:hover td:first-child::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 3px;
  background: #fcc902;
  box-shadow: 0 0 6px #fcc902;
}

/* Inline code inside table cells */
.ws-article .ws-table-scroll td code,
.ws-article .ws-table-scroll th code {
  font-family: "JetBrains Mono", monospace;
  font-size: 0.78rem;
  background: var(--ws-paper-soft);
  color: var(--ws-amber);
  padding: 1px 5px;
  border: 1px solid rgba(31, 36, 51, 0.2);
  border-radius: 3px;
  white-space: nowrap;
  text-shadow: none;
}

/* === Sidebar note layout === */
/* Full-width main wrapper used by the sidebar layout */
.main-full {
  width: 100%;
  margin: 0;
  padding: 0;
}

/* === Access gate — Workshop Bench style ===
 *
 * Sealed-envelope card sitting over a cream-paper scrim. Wattson stands
 * behind the card on the upper-left; the card overlaps his lower body
 * for a clean peek with no clip mask. Three states (default, error,
 * success) flip the offset shadow color + the per-character slot ink.
 */
.access-gate {
  position: fixed;
  inset: 0;
  z-index: var(--z-overlay);
  display: flex;
  align-items: safe center;
  justify-content: center;
  /* Hold a 60×24 minimum but grow past it to clear the notch / dynamic
   * island / home indicator when viewport-fit=cover puts the page edge
   * at the device edge. */
  padding-top: max(60px, env(safe-area-inset-top));
  padding-right: max(24px, env(safe-area-inset-right));
  padding-bottom: max(60px, env(safe-area-inset-bottom));
  padding-left: max(24px, env(safe-area-inset-left));
  background: linear-gradient(
    180deg,
    rgba(247, 243, 231, 0.78),
    rgba(247, 243, 231, 0.92)
  );
  backdrop-filter: blur(3px);
  -webkit-backdrop-filter: blur(3px);
  font-family: "Inter", system-ui, sans-serif;
  overflow-x: hidden;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
}

[data-theme="dark"] .access-gate {
  background: linear-gradient(
    180deg,
    rgba(37, 42, 51, 0.78),
    rgba(37, 42, 51, 0.94)
  );
}

.access-gate[hidden] {
  display: none;
}

/* Wraps card so Wattson can stand BEHIND it */
.access-gate__wrap {
  position: relative;
  display: inline-block;
  max-width: 100%;
}

.access-gate__peek {
  position: absolute;
  left: -88px;
  top: -42px;
  height: 210px;
  width: auto;
  z-index: 0;
  transform: rotate(-6deg);
  filter: drop-shadow(4px 8px 0 rgba(31, 36, 51, 0.12));
  pointer-events: none;
}

[data-theme="dark"] .access-gate__peek {
  filter: drop-shadow(4px 8px 0 rgba(0, 0, 0, 0.3));
}

@media (max-width: 540px) {
  .access-gate__peek {
    height: 150px;
    left: -36px;
    top: -68px;
  }
}

/* Envelope card */
.access-gate__card {
  position: relative;
  z-index: 1;
  width: 100%;
  max-width: 480px;
  background: #fff;
  border: 1.5px solid #1f2433;
  box-shadow:
    8px 10px 0 #1f2433,
    0 24px 60px rgba(31, 36, 51, 0.18);
  padding: 44px 38px 26px;
  transform: rotate(-1deg);
  transition:
    box-shadow 0.25s ease,
    transform 0.25s ease;
}

.access-gate__card::before {
  content: "";
  position: absolute;
  top: -12px;
  left: 50%;
  width: 132px;
  height: 22px;
  background: #fef4a8;
  transform: translateX(-50%) rotate(-3deg);
  opacity: 0.9;
  box-shadow: 0 1px 0 rgba(31, 36, 51, 0.08);
  z-index: 2;
  transition: background 0.25s ease;
}
.access-gate.is-success .access-gate__card::before {
  background: #c8e8c0;
}
.access-gate.is-error .access-gate__card::before {
  background: #ffb8c1;
}

[data-theme="dark"] .access-gate__card {
  background: var(--surface-elevated);
  border-color: rgba(232, 230, 227, 0.5);
  box-shadow:
    8px 10px 0 rgba(232, 230, 227, 0.85),
    0 24px 60px rgba(0, 0, 0, 0.4);
}

.access-gate.is-error .access-gate__card {
  box-shadow:
    8px 10px 0 #a6192e,
    0 24px 60px rgba(166, 25, 46, 0.18);
}

.access-gate.is-success .access-gate__card {
  box-shadow:
    8px 10px 0 #2a7a4f,
    0 24px 60px rgba(42, 122, 79, 0.2);
}

@media (max-width: 540px) {
  .access-gate__card {
    padding: 36px 22px 22px;
    transform: none;
  }
}

/* Stamp slammed across the access-code slots — only shown in success state */
.access-gate__stamp {
  position: absolute;
  top: 50%;
  left: 50%;
  z-index: 3;
  display: none;
  flex-direction: column;
  align-items: center;
  padding: 14px 26px 12px;
  border: 4px solid #2a7a4f;
  border-radius: 6px;
  background: rgba(255, 255, 255, 0.78);
  box-shadow:
    inset 0 0 0 2px rgba(42, 122, 79, 0.28),
    0 6px 18px -8px rgba(42, 122, 79, 0.45);
  color: #2a7a4f;
  font-family: "JetBrains Mono", monospace;
  font-weight: 700;
  font-size: 22px;
  letter-spacing: 5px;
  pointer-events: none;
  transform: translate(-50%, -50%) rotate(8deg);
}
.access-gate.is-success .access-gate__stamp {
  display: flex;
}
.access-gate__stamp-star {
  font-size: 1.25rem;
  line-height: 1;
}
.access-gate__stamp-sub {
  font-size: 0.95rem;
  letter-spacing: 2px;
  margin-top: 2px;
  opacity: 0.7;
}

/* Eyebrow ("★ ACCESS REQUIRED") + headline */
.access-gate__head {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-bottom: 22px;
}

.access-gate__eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-family: "JetBrains Mono", monospace;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 2px;
  color: #a6192e;
  font-weight: 700;
}
.access-gate__eyebrow-key {
  color: #fcc902;
  font-size: 16px;
}
.access-gate.is-success .access-gate__eyebrow {
  color: #2a7a4f;
}
.access-gate.is-success .access-gate__eyebrow-key {
  color: #2a7a4f;
}

[data-theme="dark"] .access-gate__eyebrow {
  color: var(--color-secondary);
}
[data-theme="dark"] .access-gate.is-success .access-gate__eyebrow,
[data-theme="dark"] .access-gate.is-success .access-gate__eyebrow-key {
  color: var(--color-tertiary);
}

.access-gate__title {
  margin: 0;
  margin-bottom: 1rem;
  font-family: "Fraunces", serif;
  font-size: clamp(2.25rem, 6vw, 3.375rem);
  font-weight: 800;
  letter-spacing: -0.025em;
  line-height: 0.95;
  color: #1f2433;
}

[data-theme="dark"] .access-gate__title {
  color: var(--ws-ink);
}

.access-gate__title-state--default {
  display: inline;
}
.access-gate__title-state--success {
  display: none;
}
.access-gate.is-success .access-gate__title-state--default {
  display: none;
}
.access-gate.is-success .access-gate__title-state--success {
  display: inline;
}

.access-gate__title-em {
  text-decoration: wavy underline;
  text-decoration-color: #fcc902;
  text-underline-offset: 8px;
  color: #33569b;
}
.access-gate.is-success .access-gate__title-em {
  color: #2a7a4f;
}

[data-theme="dark"] .access-gate__title-em {
  color: var(--color-primary);
}
[data-theme="dark"] .access-gate.is-success .access-gate__title-em {
  color: var(--color-tertiary);
}

.access-gate__deck {
  margin: 0;
  font-family: "Fraunces", serif;
  font-size: 1rem;
  line-height: 1.5;
  color: #3d3c30;
}
.access-gate__deck-prog {
  font-style: oblique 10deg;
  color: #1f2433;
  font-weight: 600;
}

[data-theme="dark"] .access-gate__deck {
  color: var(--text-muted);
}
[data-theme="dark"] .access-gate__deck-prog {
  color: var(--ws-ink);
}

/* Form */
.access-gate__form {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.access-gate__label {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  align-self: flex-start;
  position: relative;
}

.access-gate__label-text {
  position: relative;
  z-index: 1;
  font-family: "Caveat", cursive;
  font-size: 1.375rem;
  font-weight: 700;
  color: #1f2433;
}
[data-theme="dark"] .access-gate__label-text {
  color: var(--ws-ink);
}

/* Per-character code slots — six dashed-bottom-rule boxes that read
 * like a stamped form field. The hidden <input> below sits on top,
 * transparent, so native keyboard/paste flow still works. */
.access-gate__code {
  position: relative;
  padding: 6px 4px;
}
.access-gate__slots {
  display: flex;
  gap: 8px;
  pointer-events: none;
}
.access-gate__slot {
  flex: 1;
  height: 64px;
  background: #faf6ee;
  border: 1.5px dashed rgba(31, 36, 51, 0.35);
  border-bottom: 3px solid #1f2433;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: "JetBrains Mono", monospace;
  font-size: 1.625rem;
  font-weight: 700;
  color: #1f2433;
  transition:
    background 0.15s,
    border-color 0.15s,
    color 0.15s;
}
.access-gate__slot.is-filled {
  background: #fff;
  border-style: solid;
  border-color: #1f2433;
}
.access-gate.is-error .access-gate__slot.is-filled {
  border-color: #a6192e;
  background: #fdecee;
  color: #a6192e;
  border-bottom-color: #a6192e;
}
.access-gate.is-success .access-gate__slot.is-filled {
  border-color: #2a7a4f;
  background: #e8f3ec;
  color: #2a7a4f;
  border-bottom-color: #2a7a4f;
  border-style: solid;
}

[data-theme="dark"] .access-gate__slot {
  background: var(--surface-sunken);
  border-color: rgba(232, 230, 227, 0.3);
  border-bottom-color: var(--ws-ink);
  color: var(--ws-ink);
}
[data-theme="dark"] .access-gate__slot.is-filled {
  background: var(--surface);
  border-color: var(--ws-ink);
}

.access-gate__caret {
  display: inline-block;
  width: 2px;
  height: 32px;
  background: currentColor;
  opacity: 0.6;
  animation: ag-blink 1s steps(2, end) infinite;
}
.access-gate__slot.is-active {
  border-color: #33569b;
}
.access-gate__slot.is-active .access-gate__caret {
  color: #33569b;
}

/* Invisible input — captures all keyboard / paste / autofill flow.
 * Sits on top of the slots so focus styles work naturally. */
.access-gate__hidden-input {
  position: absolute;
  inset: 6px 4px;
  width: calc(100% - 8px);
  height: calc(100% - 12px);
  opacity: 0;
  border: 0;
  background: transparent;
  color: transparent;
  caret-color: transparent;
  font-size: 1.625rem;
  letter-spacing: calc((100% - 6 * 1ch) / 5);
  text-transform: uppercase;
  cursor: text;
}
.access-gate__hidden-input:focus {
  outline: none;
}

@keyframes ag-blink {
  50% {
    opacity: 0;
  }
}

/* Error / success message line */
.access-gate__msg-slot {
  min-height: 28px;
  margin-top: 2px;
}
.access-gate__msg {
  display: none;
  align-items: center;
  gap: 8px;
  font-family: "Caveat", cursive;
  font-size: 1.375rem;
  font-weight: 700;
}
.access-gate__msg-icon {
  width: 22px;
  height: 22px;
  border-radius: 999px;
  color: #fff;
  font-family: "JetBrains Mono", monospace;
  font-weight: 700;
  font-size: 13px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}
.access-gate.is-error .access-gate__msg--error,
.access-gate.is-success .access-gate__msg--success {
  display: inline-flex;
}
.access-gate__msg--error {
  color: #a6192e;
}
.access-gate__msg--error .access-gate__msg-icon {
  background: #a6192e;
}
.access-gate__msg--success {
  color: #2a7a4f;
}
.access-gate__msg--success .access-gate__msg-icon {
  background: #2a7a4f;
}

[data-theme="dark"] .access-gate__msg--error {
  color: var(--color-secondary);
}
[data-theme="dark"] .access-gate__msg--error .access-gate__msg-icon {
  background: var(--color-secondary);
}
[data-theme="dark"] .access-gate__msg--success {
  color: var(--color-tertiary);
}
[data-theme="dark"] .access-gate__msg--success .access-gate__msg-icon {
  background: var(--color-tertiary);
}

.access-gate__dots {
  display: inline-flex;
  align-items: baseline;
  gap: 3px;
  margin-left: 4px;
}
.access-gate__dot {
  width: 5px;
  height: 5px;
  border-radius: 999px;
  background: currentColor;
  display: inline-block;
  animation: ag-dotpulse 1.1s ease-in-out infinite;
}
.access-gate__dot:nth-child(2) {
  animation-delay: 0.15s;
}
.access-gate__dot:nth-child(3) {
  animation-delay: 0.3s;
}

@keyframes ag-dotpulse {
  0%,
  80%,
  100% {
    transform: scale(0.6);
    opacity: 0.4;
  }
  40% {
    transform: scale(1);
    opacity: 1;
  }
}

/* Actions */
.access-gate__actions {
  display: flex;
  align-items: center;
  gap: 16px;
  margin-top: 8px;
  flex-wrap: wrap;
}

.access-gate__submit {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 14px 24px;
  background: #1f2433;
  color: #fff;
  border: 1.5px solid #1f2433;
  box-shadow: 4px 4px 0 #fcc902;
  font-family: "Inter", system-ui, sans-serif;
  font-size: 0.9375rem;
  font-weight: 700;
  letter-spacing: 0.4px;
  cursor: pointer;
  transition:
    transform 0.1s ease,
    box-shadow 0.1s ease;
}
.access-gate__submit:hover {
  transform: translate(-1px, -1px);
  box-shadow: 5px 5px 0 #fcc902;
}
.access-gate__submit:active {
  transform: translate(2px, 2px);
  box-shadow: 2px 2px 0 #fcc902;
}
.access-gate__submit-arrow {
  font-size: 18px;
}

.access-gate.is-success .access-gate__submit {
  background: #2a7a4f;
  border-color: #2a7a4f;
  box-shadow: 4px 4px 0 #1f2433;
}
.access-gate.is-success .access-gate__submit:hover {
  box-shadow: 5px 5px 0 #1f2433;
}
.access-gate.is-success .access-gate__submit:active {
  box-shadow: 2px 2px 0 #1f2433;
}

[data-theme="dark"] .access-gate__submit {
  background: var(--ws-ink);
  border-color: var(--ws-ink);
  color: var(--surface);
}

/* Footer hint */
.access-gate__foot {
  margin-top: 22px;
  padding-top: 16px;
  border-top: 1.5px dashed rgba(31, 36, 51, 0.22);
}
.access-gate__foot-hint {
  font-family: "Fraunces", serif;
  font-style: oblique 10deg;
  font-size: 0.84rem;
  color: #5a523f;
}
.access-gate__foot-strong {
  font-style: normal;
  font-weight: 600;
  color: #1f2433;
}
.access-gate__foot--default {
  display: inline;
}
.access-gate__foot--success {
  display: none;
}
.access-gate.is-success .access-gate__foot--default {
  display: none;
}
.access-gate.is-success .access-gate__foot--success {
  display: inline;
}

[data-theme="dark"] .access-gate__foot {
  border-top-color: rgba(232, 230, 227, 0.22);
}
[data-theme="dark"] .access-gate__foot-hint {
  color: var(--text-muted);
}
[data-theme="dark"] .access-gate__foot-strong {
  color: var(--ws-ink);
}

/* Reduced motion — kill blinking caret + dot pulse */
@media (prefers-reduced-motion: reduce) {
  .access-gate__caret,
  .access-gate__dot {
    animation: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  .workshop-polaroid {
    transition: none;
  }
  .workshop-polaroid:hover,
  .workshop-polaroid:focus-within {
    transform: rotate(var(--rot, -1deg));
  }
}

/* === Code blocks === */
code,
pre {
  font-family: "JetBrains Mono", "Fira Code", "Cascadia Code", monospace;
  font-size: 0.88rem;
}

/* Inline code */
:not(pre) > code {
  background: var(--surface-accent-strong);
  color: var(--color-primary);
  padding: 0.15em 0.45em;
  border-radius: 4px;
  font-size: 0.875em;
  /* Allow wrapping so long identifiers/URLs don't force horizontal page
   * scroll on mobile. `break-word` only breaks inside a token when no
   * other break point exists, so short tokens still render as one unit. */
  overflow-wrap: break-word;
}

pre {
  padding: 1.25rem 1.5rem;
  margin: 1.5rem 0;
  overflow-x: auto;
  line-height: 1.65;
  font-size: 0.88rem;
  background: var(--ws-paper);
  color: var(--ws-ink);
}

pre code {
  background: none;
  color: inherit;
  padding: 0;
  font-size: inherit;
  white-space: inherit;
}

/* === Washi-tape stagger ===
 * Assigns per-element rotation/offset/width via custom properties so each
 * tape lands in a slightly different "hand-placed" spot. Any tape that
 * reads `--tape-rot` / `--tape-x` / `--tape-y` / `--tape-w` picks these up
 * through inheritance — code blocks, tables, download buttons, pagination
 * cards, and day sheets. Combining 2n/3n/5n/7n gives a quasi-random mix
 * across mixed markdown bodies. */
.ws-article > *,
.ws-pagination > *,
.ws-day-stack > * {
  --tape-rot: -2deg;
  --tape-x: 0px;
  --tape-y: 0px;
}
.ws-article > *:nth-child(2n),
.ws-pagination > *:nth-child(2n),
.ws-day-stack > *:nth-child(2n) {
  --tape-rot: 2.5deg;
  --tape-x: 28px;
  --tape-w: 96px;
}
.ws-article > *:nth-child(3n),
.ws-day-stack > *:nth-child(3n) {
  --tape-rot: -3.5deg;
  --tape-x: -36px;
  --tape-y: -1px;
  --tape-w: 120px;
}
.ws-article > *:nth-child(5n),
.ws-day-stack > *:nth-child(5n) {
  --tape-rot: 1.5deg;
  --tape-x: 12px;
  --tape-y: 1px;
  --tape-w: 88px;
}
.ws-article > *:nth-child(7n) {
  --tape-rot: -4deg;
  --tape-x: -18px;
  --tape-w: 108px;
}

/* Code block wrapper: macOS-style chrome + copy/collapse controls */
.code-block {
  --code-chrome-h: 2.25rem;
  position: relative;
  margin: calc(var(--leading) * 2) 0;
  border: 1.5px solid var(--ws-ink);
  box-shadow: 5px 5px 0 var(--ws-ink);
  width: fit-content;
  max-width: 100%;
  min-width: min(100%, 280px);
}

/* Washi tape — top-center, floats above the block border.
 * Rotation/offset/width come from --tape-* vars set by the shared stagger
 * block (search "Washi-tape stagger"). */
.code-block::after {
  content: "";
  position: absolute;
  top: -10px;
  left: 50%;
  width: var(--tape-w, 100px);
  height: 18px;
  background: var(--ws-tape);
  transform: translate(calc(-50% + var(--tape-x, 0px)), var(--tape-y, 0px))
    rotate(var(--tape-rot, -2deg));
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.12);
  opacity: 0.92;
  z-index: 3;
  pointer-events: none;
}

/* Traffic-light header bar */
.code-block::before {
  content: "";
  display: block;
  height: var(--code-chrome-h);
  background-color: var(--ws-paper-header);
  background-image:
    radial-gradient(circle 5.5px at 14px 50%, #ff605c 100%, transparent 100%),
    radial-gradient(circle 5.5px at 35px 50%, #ffbd44 100%, transparent 100%),
    radial-gradient(circle 5.5px at 56px 50%, #00ca4e 100%, transparent 100%);
  background-repeat: no-repeat;
  border-bottom: 1.5px solid var(--ws-ink);
}

.code-block > pre {
  padding: 1rem 1.5rem;
  margin: 0;
  position: relative;
  transition: max-height 0.2s ease;
}

.code-block > pre.line-numbers {
  padding-left: 3.5rem;
}

.line-numbers-rows {
  position: absolute;
  top: 1rem;
  left: 0;
  width: 3rem;
  pointer-events: none;
  user-select: none;
  font-family: "JetBrains Mono", "Fira Code", "Cascadia Code", monospace;
  font-size: 0.88rem;
  line-height: 1.65;
  font-variant-numeric: tabular-nums;
  counter-reset: linenumber;
  border-right: 1px solid var(--ws-border-soft);
  color: var(--ws-syntax-com);
}

.line-numbers-rows > span {
  display: block;
  counter-increment: linenumber;
  text-align: right;
  padding-right: 0.5rem;
}

.line-numbers-rows > span::before {
  content: counter(linenumber);
}
.code-actions {
  position: absolute;
  top: calc(var(--code-chrome-h) / 2);
  right: 0.65rem;
  transform: translateY(-50%);
  display: flex;
  gap: 0.35rem;
}

/* Copy button — kraft-tag pill */
.code-copy {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 4px 11px 4px 9px;
  background: #fff;
  color: var(--ws-ink);
  border: 1.5px solid var(--ws-ink);
  box-shadow: 2px 2px 0 rgba(31, 36, 51, 0.35);
  font-family: "JetBrains Mono", monospace;
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  cursor: pointer;
  border-radius: 0;
  transition:
    color 0.12s,
    background 0.12s,
    box-shadow 0.12s,
    transform 0.12s;
}

/* Expand the hit area to ~44px tall × 66+px wide without changing the
 * visual pill. The chrome strip is only 36px tall so growing the button
 * itself isn't an option — this invisible pseudo extends the clickable
 * region into the chrome's empty space and ~4px into the top of the pre. */
.code-copy::before {
  content: "";
  position: absolute;
  inset: -11px -8px;
}
.code-copy:hover {
  background: var(--ws-ink);
  color: #fff;
  box-shadow: 1px 1px 0 rgba(31, 36, 51, 0.35);
  transform: translate(1px, 1px);
}
.code-copy.is-copied {
  background: #2a7a4f;
  color: #fff;
  border-color: #2a7a4f;
  box-shadow: 2px 2px 0 rgba(42, 122, 79, 0.4);
}

/* Collapse toggle — full-width washi tape strip at the bottom */
.code-toggle {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  position: relative;
  padding: 14px 16px;
  background: var(--ws-paper-soft);
  color: var(--ws-ink);
  border: none;
  border-top: 1.5px dashed var(--ws-border-dash);
  font-family: "Caveat", cursive;
  font-size: 1.35rem;
  font-weight: 700;
  cursor: pointer;
  transition: background 0.12s;
}
.code-toggle::before {
  content: "";
  position: absolute;
  top: -10px;
  left: 50%;
  width: var(--tape-w, 96px);
  height: 18px;
  background: var(--ws-tape);
  /* Mirror the parent code-block's tape so the bottom feels complementary,
   * not stamped identically. */
  transform: translate(calc(-50% - var(--tape-x, 0px)), 0)
    rotate(calc(-1 * var(--tape-rot, -2deg)));
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.12);
  opacity: 0.92;
  pointer-events: none;
}
.code-toggle:hover {
  background: var(--ws-paper-header);
}

/* Collapsed: show only the first few lines with a bottom fade. Keep
 * horizontal scroll so long lines aren't clipped — the shorthand
 * `overflow: hidden` would override the inherited `overflow-x: auto` on
 * <pre>. */
.code-block.is-collapsed > pre {
  max-height: calc(2rem + 5lh + 0.6rem);
  overflow-x: auto;
  overflow-y: hidden;
}
.code-block.is-collapsed > pre::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 48px;
  background: linear-gradient(180deg, transparent, var(--ws-paper));
  pointer-events: none;
}

/* Prism token palette — design tokens; CSS vars swap by [data-theme]. */
.token.comment,
.token.prolog,
.token.doctype,
.token.cdata {
  color: var(--ws-syntax-com);
  font-style: italic;
}

.token.keyword,
.token.builtin,
.token.atrule,
.token.tag,
.token.selector,
.token.attr-name,
.token.property {
  color: var(--ws-syntax-kw);
}

.token.number,
.token.boolean,
.token.constant,
.token.symbol,
.token.deleted {
  color: var(--ws-syntax-num);
}

.token.string,
.token.char,
.token.attr-value,
.token.regex,
.token.inserted {
  color: var(--ws-syntax-str);
}

.token.operator,
.token.punctuation,
.token.function,
.token.class-name,
.token.variable,
.token.entity,
.token.url,
.token.important {
  color: var(--ws-ink);
}

/* === Download button — kraft-paper claim ticket === */
.download-btn {
  position: relative;
  display: inline-flex;
  align-items: stretch;
  gap: 0;
  max-width: 480px;
  background: #fff;
  color: var(--ws-ink);
  border: 1.5px solid var(--ws-ink);
  box-shadow:
    4px 4px 0 var(--ws-ink),
    0 8px 18px rgba(31, 36, 51, 0.1);
  text-decoration: none;
  margin: calc(var(--leading) * 2) 0;
  font-family: "Inter", system-ui, sans-serif;
  transition:
    box-shadow 0.15s,
    transform 0.15s;
}

/* CTA variant: raw HTML external-link buttons (no icon/text structure) */
.download-btn:not(:has(.download-btn__icon)) {
  align-items: center;
  gap: 0.5em;
  padding: 0.6em 1.2em;
  font-family: "Fraunces", serif;
  font-weight: 700;
  font-size: 1rem;
}

/* External-link icon for CTA variant — rendered via mask so it inherits text color */
.download-btn:not(:has(.download-btn__icon))::after {
  content: "";
  width: 16px;
  height: 16px;
  flex-shrink: 0;
  background: currentColor;
  -webkit-mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6'/><polyline points='15 3 21 3 21 9'/><line x1='10' y1='14' x2='21' y2='3'/></svg>") no-repeat center / contain;
  mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6'/><polyline points='15 3 21 3 21 9'/><line x1='10' y1='14' x2='21' y2='3'/></svg>") no-repeat center / contain;
}

/* Washi tape — top-center */
.download-btn::before {
  content: "";
  position: absolute;
  top: -10px;
  left: 50%;
  width: var(--tape-w, 96px);
  height: 18px;
  background: var(--ws-tape);
  transform: translate(calc(-50% + var(--tape-x, 0px)), var(--tape-y, 0px))
    rotate(var(--tape-rot, -2deg));
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.12);
  opacity: 0.92;
  z-index: 2;
  pointer-events: none;
}

/* Perforation dashed divider — only on the structured download variant */
.download-btn:has(.download-btn__icon)::after {
  content: "";
  position: absolute;
  top: 8px;
  bottom: 8px;
  right: 52px;
  width: 0;
  border-left: 1.5px dashed rgba(31, 36, 51, 0.3);
}

.download-btn:hover {
  box-shadow:
    1px 1px 0 var(--ws-ink),
    0 12px 20px rgba(31, 36, 51, 0.15);
  transform: translate(2px, 1px) rotate(-0.4deg);
}

/* Left: dark icon block */
.download-btn__icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 56px;
  background: var(--ws-ink);
  color: #fff;
  flex-shrink: 0;
}
.download-btn__icon svg {
  display: block;
}

/* Middle + right: text block with padding-right for the perforated arrow area */
.download-btn__text {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 3px;
  padding: 14px 64px 14px 18px;
  min-width: 0;
  flex: 1;
}
.download-btn__title {
  font-family: "Fraunces", serif;
  font-size: 1.1rem;
  font-weight: 700;
  letter-spacing: -0.01em;
  color: var(--ws-ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.download-btn__file {
  font-family: "JetBrains Mono", monospace;
  font-size: 0.72rem;
  color: #5a523f;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* === Theme transition ===
 * Smooth color flip on body and themed components when toggling dark mode. */
body,
table {
  transition:
    background-color 0.25s ease,
    color 0.25s ease,
    border-color 0.25s ease;
}

/* === Workshop Program Overview === */

.ws-program-wrap {
  min-height: 100vh;
  min-height: 100dvh;
  background: var(--ws-bg);
  color: var(--ws-ink);
  position: relative;
  overflow-x: hidden;
}

.ws-program-lead {
  position: relative;
  z-index: 1;
  padding: calc(var(--leading) * 1.5) clamp(28px, 4vw, 56px) 0;
  max-width: var(--ws-max-width);
  margin-inline: auto;
}

.ws-days {
  position: relative;
  z-index: 1;
  padding: var(--leading) clamp(28px, 4vw, 56px) calc(var(--leading) * 1.5);
  max-width: var(--ws-max-width);
  margin-inline: auto;
}

.ws-backlink,
.ws-backlink:visited {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-family: "Caveat", cursive;
  font-size: 1.375rem;
  line-height: var(--leading);
  font-weight: 700;
  color: var(--ws-amber);
  text-decoration: none;
  margin-bottom: 0;
}

.ws-backlink:hover {
  color: var(--color-primary);
}

.ws-program-header {
  margin-bottom: calc(var(--leading) * 2);
}

.ws-program-header__kicker {
  display: block;
  font-family: "JetBrains Mono", monospace;
  font-size: 0.75rem;
  line-height: var(--leading);
  font-weight: 700;
  letter-spacing: 3px;
  text-transform: uppercase;
  color: var(--color-secondary);
  --cap: var(--cap-jetbrains);
}

.ws-program-header__title {
  font-family: "Fraunces", serif;
  font-size: clamp(2.75rem, 7vw, 4.75rem);
  font-weight: 800;
  letter-spacing: -0.025em;
  line-height: calc(var(--leading) * 3);
  color: var(--ws-ink);
  border: none;
  padding: 0;
  /* Fluid font: at max size cap × 1em ≈ 2P, so --rhythm: 2 keeps margin
   * non-negative; at the small end it gives ~1P of breathing room. */
  --rhythm: 2;
}

.ws-program-header__intro {
  font-family: "Fraunces", serif;
  font-size: 1.1rem;
  line-height: var(--leading);
  color: var(--ws-ink);
  opacity: 0.8;
  max-width: 640px;
  /* Two leading units below the title baseline. */
  --rhythm: 2;
}

/* Status callouts */
.ws-callout {
  background: var(--ws-cream);
  border: 2px solid var(--ws-ink);
  box-shadow: 4px 4px 0 rgba(31, 36, 51, 0.14);
  padding: 20px 24px;
  margin-bottom: 40px;
}

[data-theme="dark"] .ws-callout {
  background: var(--surface-elevated);
  border-color: var(--border-strong);
  box-shadow: 4px 4px 0 rgba(0, 0, 0, 0.3);
}

.ws-callout__label {
  display: inline-block;
  font-family: "JetBrains Mono", monospace;
  font-size: 0.75rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 2px;
  background: var(--ws-ink);
  color: var(--ws-cream);
  padding: 3px 8px;
  margin-bottom: 10px;
}

[data-theme="dark"] .ws-callout__label {
  background: var(--border-strong);
}

.ws-callout--past .ws-callout__label {
  background: var(--text-muted);
  color: #fff;
}

.ws-callout--soon .ws-callout__label {
  background: var(--ws-amber);
  color: #fff;
}

.ws-callout p {
  margin: 0;
  font-family: "Fraunces", serif;
  font-style: oblique 10deg;
  font-size: 1rem;
  opacity: 0.8;
}

/* Day stack */
.ws-day-stack {
  display: flex;
  flex-direction: column;
  gap: 20px;
  margin-bottom: 48px;
}

.ws-day-sheet {
  position: relative;
  background: var(--ws-cream);
  border: 2px solid var(--ws-ink);
  box-shadow: 5px 5px 0 rgba(31, 36, 51, 0.1);
}

[data-theme="dark"] .ws-day-sheet {
  background: var(--surface-elevated);
  border-color: var(--border-strong);
  box-shadow: 5px 5px 0 rgba(0, 0, 0, 0.3);
}

/* Washi tape — top-center, mirrors the code-block treatment. */
.ws-day-sheet::before {
  content: "";
  position: absolute;
  top: -10px;
  left: 50%;
  width: var(--tape-w, 110px);
  height: 18px;
  background: var(--ws-tape);
  transform: translate(calc(-50% + var(--tape-x, 0px)), var(--tape-y, 0px))
    rotate(var(--tape-rot, -2deg));
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.12);
  opacity: 0.92;
  z-index: 3;
  pointer-events: none;
}

.ws-day-sheet__head {
  padding: 18px 22px;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

/* Borders only appear when the day is open (lessons visible). */
details.ws-day-sheet[open] > .ws-day-sheet__head,
.ws-day-sheet--empty > .ws-day-sheet__head {
  border-bottom: 2px dashed rgba(31, 36, 51, 0.18);
}

[data-theme="dark"] details.ws-day-sheet[open] > .ws-day-sheet__head,
[data-theme="dark"] .ws-day-sheet--empty > .ws-day-sheet__head {
  border-bottom-color: var(--border);
}

/* <summary> reset: hide native disclosure triangle, allow click anywhere. */
summary.ws-day-sheet__head {
  cursor: pointer;
  list-style: none;
  user-select: none;
  transition: background 0.12s ease;
}
summary.ws-day-sheet__head::-webkit-details-marker {
  display: none;
}
summary.ws-day-sheet__head::marker {
  content: "";
}

summary.ws-day-sheet__head:hover {
  background: rgba(31, 36, 51, 0.025);
}
[data-theme="dark"] summary.ws-day-sheet__head:hover {
  background: var(--surface-accent);
}

.ws-day-sheet__meta {
  display: flex;
  align-items: center;
  gap: 12px;
}

.ws-day-sheet__count {
  margin-left: auto;
  font-family: "JetBrains Mono", monospace;
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--text-subtle);
  flex-shrink: 0;
}

.ws-day-sheet__chevron {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.25rem;
  height: 1.25rem;
  font-size: 0.95rem;
  line-height: 1;
  color: var(--ws-ink);
  opacity: 0.6;
  flex-shrink: 0;
  transition:
    transform 0.18s ease,
    opacity 0.12s ease;
}
details.ws-day-sheet[open]
  > summary
  > .ws-day-sheet__meta
  > .ws-day-sheet__chevron {
  transform: rotate(180deg);
  opacity: 0.9;
}
summary.ws-day-sheet__head:hover .ws-day-sheet__chevron {
  opacity: 1;
}

.ws-day-sheet__badge {
  display: inline-block;
  font-family: "JetBrains Mono", monospace;
  font-size: 0.8rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 1.5px;
  background: var(--ws-amber);
  color: #fff;
  padding: 5px 10px;
  flex-shrink: 0;
}

.ws-day-sheet__title {
  font-family: "Fraunces", serif;
  font-size: clamp(1.4rem, 2vw, 1.65rem);
  font-weight: 700;
  color: var(--ws-ink);
  margin: 0;
  border: none;
  padding: 0;
  letter-spacing: -0.01em;
}

.ws-day-sheet__summary {
  margin: 0;
  font-family: "Fraunces", serif;
  font-style: oblique 10deg;
  font-size: 0.95rem;
  color: var(--ws-ink);
  opacity: 0.65;
}

/* Lesson rows */
.ws-lesson-list {
  list-style: none;
  padding: 0;
  margin: 0;
}

.ws-lesson-row {
  display: flex;
  align-items: baseline;
  gap: 10px;
  padding: 10px 20px;
  border-bottom: 1px solid rgba(31, 36, 51, 0.07);
}

[data-theme="dark"] .ws-lesson-row {
  border-bottom-color: var(--border);
}

.ws-lesson-row:last-child {
  border-bottom: none;
}

.ws-lesson-row:hover {
  background: rgba(31, 36, 51, 0.03);
}

[data-theme="dark"] .ws-lesson-row:hover {
  background: var(--surface-accent);
}

.ws-lesson-row__idx {
  font-family: "JetBrains Mono", monospace;
  font-size: 0.75rem;
  font-weight: 700;
  color: var(--text-subtle);
  flex-shrink: 0;
  min-width: 18px;
}

.ws-lesson-row__title,
.ws-lesson-row__title:visited {
  font-weight: 600;
  font-size: 0.9rem;
  color: var(--ws-ink);
  text-decoration: none;
  flex-shrink: 0;
}

.ws-lesson-row__title:hover {
  color: var(--color-primary);
  text-decoration: underline;
  text-underline-offset: 3px;
}

.ws-lesson-row__leader {
  flex: 1;
  display: block;
  border-bottom: 1.5px dotted rgba(31, 36, 51, 0.2);
  margin-bottom: 4px;
  min-width: 20px;
}

[data-theme="dark"] .ws-lesson-row__leader {
  border-bottom-color: var(--border);
}

.ws-program-footer {
  margin-top: 48px;
  padding-bottom: 56px;
}

/* Deliverable callout */
.ws-deliverable {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 4px;
  width: 100%;
  max-width: 640px;
  min-height: calc(var(--leading) * 2.5);
  background: var(--ws-cream);
  border: 2px solid var(--ws-ink);
  box-shadow: 4px 4px 0 var(--ws-ink);
  padding: 14px 18px;
  margin: calc(var(--leading) * 1.5) 0 0;
  box-sizing: border-box;
  --tape-rot: 2.5deg;
  --tape-x: 24px;
  --tape-w: 104px;
}

.ws-deliverable::before {
  content: "";
  position: absolute;
  top: -10px;
  left: 50%;
  width: var(--tape-w, 100px);
  height: 18px;
  background: var(--ws-tape);
  transform: translate(calc(-50% + var(--tape-x, 0px)), var(--tape-y, 0px))
    rotate(var(--tape-rot, -2deg));
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.12);
  opacity: 0.92;
  z-index: 2;
  pointer-events: none;
}

[data-theme="dark"] .ws-deliverable {
  background: var(--surface-elevated);
  border-color: var(--border-strong);
  box-shadow: 4px 4px 0 var(--border-strong);
}

.ws-deliverable__label {
  font-family: "JetBrains Mono", monospace;
  font-size: 0.65rem;
  font-weight: 700;
  letter-spacing: 2px;
  text-transform: uppercase;
  color: var(--text-subtle);
}

.ws-deliverable__text {
  font-family: "Fraunces", serif;
  font-style: oblique 10deg;
  font-size: 1.125rem;
  font-weight: 500;
  color: var(--ws-ink);
  line-height: 1.4;
}

/* Days section heading */
.ws-days-head {
  margin-bottom: var(--leading);
}

.ws-days-head__kicker {
  font-family: "JetBrains Mono", monospace;
  font-size: 0.7rem;
  font-weight: 700;
  line-height: var(--leading);
  letter-spacing: 3px;
  text-transform: uppercase;
  color: var(--color-secondary);
  --cap: var(--cap-jetbrains);
}

.ws-days-head__title {
  font-family: "Fraunces", serif;
  font-size: clamp(1.6rem, 3.5vw, 2.375rem);
  font-weight: 800;
  line-height: calc(var(--leading) * 2);
  letter-spacing: -0.02em;
  color: var(--ws-ink);
  border: none;
  padding: 0;
  --rhythm: 2;
}

.ws-days-head__italic {
  font-style: oblique 10deg;
  color: var(--color-primary);
}

/* Lesson-row trailing arrow */
.ws-lesson-row__arrow {
  font-size: 1rem;
  color: var(--ws-ink);
  opacity: 0.55;
  flex-shrink: 0;
  transition:
    transform 120ms ease,
    opacity 120ms ease;
}

.ws-lesson-row:hover .ws-lesson-row__arrow {
  opacity: 1;
  transform: translateX(2px);
}

/* === Workshop Lesson Reader === */

.ws-note-wrap {
  display: flex;
  align-items: stretch;
  min-height: calc(100vh - var(--header-h, 72px));
  min-height: calc(100dvh - var(--header-h, 72px));
  background: var(--ws-bg);
  color: var(--ws-ink);
  position: relative;
  /* `clip` (not `hidden`) — `hidden` implies overflow-y: auto and would
     establish a scroll container that breaks .ws-sidebar's viewport sticky. */
  overflow-x: clip;
  max-width: var(--ws-max-width);
  margin-inline: auto;
}

/* Grid background sits inside the (potentially) capped wrap, but must
   still span the full viewport on wide monitors. Full-bleed via the
   absolute + 100vw trick; .ws-note-wrap's overflow-x: clip prevents
   any horizontal scroll. */
.ws-note-wrap > .workshop-grid-bg {
  left: 50%;
  right: auto;
  transform: translateX(-50%);
  width: 100vw;
}

/* Sidebar */
.ws-sidebar {
  flex: 0 0 268px;
  min-width: 0;
  background: var(--ws-cream);
  border-right: 2px solid rgba(31, 36, 51, 0.25);
  display: flex;
  flex-direction: column;
  position: sticky;
  top: var(--header-h, 72px);
  align-self: flex-start;
  max-height: calc(100vh - var(--header-h, 72px));
  max-height: calc(100dvh - var(--header-h, 72px));
  overflow-x: hidden;
  overflow-y: auto;
  z-index: 1;
}

[data-theme="dark"] .ws-sidebar {
  background: var(--surface-elevated);
  border-right-color: var(--border);
}

/* Washi tape "fastening" the sidebar to the header. Lives inside the
 * sticky <header> so it tracks the header during scroll (including
 * overscroll rubber-band at the top of the page, where a fixed-position
 * tape would visibly detach from the header). Positioned to straddle
 * the header/sidebar seam — half on the header bar, half on the
 * sidebar — and centered horizontally on the 268px sidebar column.
 * Behind the toggle button (lower z-index) so the toggle stays legible. */
.ws-sidebar-tape {
  position: absolute;
  top: calc(var(--header-h, 72px) - 11px);
  /* Sidebar sits at the left edge of .ws-note-wrap, which is itself
     centered at --ws-max-width on wide monitors. Add the wrap's left
     gutter so the tape tracks the sidebar instead of viewport-left. */
  left: calc(var(--ws-wrap-offset) + (268px - 110px) / 2);
  width: 110px;
  height: 22px;
  background:
    linear-gradient(
      180deg,
      rgba(255, 255, 255, 0.3) 0%,
      transparent 38%,
      transparent 62%,
      rgba(0, 0, 0, 0.08) 100%
    ),
    var(--ws-tape-pink, #ffb8c1);
  transform: rotate(-3.5deg);
  opacity: 0.92;
  filter: drop-shadow(0 1.5px 2px rgba(0, 0, 0, 0.14));
  clip-path: polygon(
    3% 0%,
    96% 0%,
    100% 25%,
    95% 50%,
    99% 75%,
    96% 100%,
    3% 100%,
    0% 78%,
    4% 52%,
    1% 26%
  );
  z-index: 1;
  pointer-events: none;
}

[data-theme="dark"] .ws-sidebar-tape {
  opacity: 0.78;
}

/* Hide the tape wherever there's no visible sidebar to fasten. */
body.sidebar-mode .ws-sidebar-tape,
:root[data-sidebar-collapsed] body:not(.sidebar-mode) .ws-sidebar-tape {
  display: none;
}

.ws-sidebar-back,
.ws-sidebar-back:visited {
  display: block;
  padding: 13px 16px 10px;
  font-family: "Caveat", cursive;
  font-size: 1.25rem;
  font-weight: 700;
  color: var(--ws-amber);
  text-decoration: none;
  border-bottom: 1px solid rgba(31, 36, 51, 0.1);
  flex-shrink: 0;
}

[data-theme="dark"] .ws-sidebar-back {
  border-bottom-color: var(--border);
}

.ws-sidebar-back:hover {
  color: var(--color-primary);
}

.ws-sidebar-sticker {
  margin: 14px 12px 6px;
  background: #fff;
  border: 2px solid rgba(31, 36, 51, 0.3);
  box-shadow: 3px 3px 0 rgba(31, 36, 51, 0.1);
  padding: 10px 12px;
  transform: rotate(-0.7deg);
  flex-shrink: 0;
}

[data-theme="dark"] .ws-sidebar-sticker {
  background: var(--surface);
  border-color: var(--border-strong);
  box-shadow: 3px 3px 0 rgba(0, 0, 0, 0.25);
}

.ws-sidebar-sticker__link,
.ws-sidebar-sticker__link:visited {
  text-decoration: none;
  display: block;
}

.ws-sidebar-sticker__kick {
  display: block;
  font-family: "JetBrains Mono", monospace;
  font-size: 0.625rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 2px;
  color: #a6192e;
  margin-bottom: 3px;
}

[data-theme="dark"] .ws-sidebar-sticker__kick {
  color: var(--ws-amber);
}

.ws-sidebar-sticker__title {
  display: block;
  font-family: "Fraunces", serif;
  font-size: 1.15rem;
  font-weight: 700;
  color: var(--ws-ink);
  line-height: 1.1;
  letter-spacing: -0.01em;
  margin-bottom: 4px;
  overflow-wrap: break-word;
}

.ws-sidebar-sticker__sub {
  display: block;
  font-family: "Caveat", cursive;
  font-size: 1.05rem;
  font-weight: 600;
  color: #5a523f;
  line-height: 1.1;
}

[data-theme="dark"] .ws-sidebar-sticker__sub {
  color: var(--text-subtle);
}

.ws-sidebar-nav {
  flex: 1;
  overflow-x: hidden;
  overflow-y: auto;
  padding: 8px 0 16px;
}

.ws-sidebar-day {
  margin-top: 10px;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.ws-sidebar-day__label {
  display: flex;
  align-items: center;
  padding: 6px 16px 4px;
}

.ws-sidebar-day__title {
  font-family: "Caveat", cursive;
  font-size: 1.25rem;
  font-weight: 700;
  color: var(--ws-ink);
  line-height: 1;
}

[data-theme="dark"] .ws-sidebar-day__title {
  color: var(--text-primary);
}

.ws-sidebar-item,
.ws-sidebar-item:visited {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 14px 8px 12px;
  text-decoration: none;
  transition: background 0.12s;
  position: relative;
  border-left: 3px solid transparent;
}

.ws-sidebar-item:hover {
  background: rgba(31, 36, 51, 0.05);
}

[data-theme="dark"] .ws-sidebar-item:hover {
  background: var(--surface-accent);
}

.ws-sidebar-item.is-active {
  background: #fef4a8;
  border-left-color: var(--ws-amber);
  flex-wrap: wrap;
}

[data-theme="dark"] .ws-sidebar-item.is-active {
  background: rgba(196, 122, 26, 0.18);
}

.ws-sidebar-item__idx {
  flex: 0 0 auto;
  width: 22px;
  font-family: "JetBrains Mono", monospace;
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.5px;
  color: #a6192e;
  text-align: left;
}

[data-theme="dark"] .ws-sidebar-item__idx {
  color: #e07a89;
}

.ws-sidebar-item.is-active .ws-sidebar-item__idx {
  color: var(--ws-amber);
}

.ws-sidebar-item__title {
  flex: 1;
  min-width: 0;
  font-family: "Fraunces", serif;
  font-size: 0.95rem;
  font-weight: 500;
  color: var(--ws-ink);
  line-height: 1.3;
}

[data-theme="dark"] .ws-sidebar-item__title {
  color: var(--text-primary);
}

.ws-sidebar-item.is-active .ws-sidebar-item__title {
  font-weight: 700;
}

.ws-sidebar-item__here {
  flex: 1 0 100%;
  margin-left: 32px;
  margin-top: 2px;
  font-family: "Caveat", cursive;
  font-size: 1rem;
  font-weight: 700;
  color: #a6192e;
  line-height: 1;
  transform: rotate(-1.5deg);
  transform-origin: left center;
}

[data-theme="dark"] .ws-sidebar-item__here {
  color: #e07a89;
}

/* Note main content */
.ws-note-main {
  flex: 1;
  min-width: 0;
  margin-inline: auto;
  padding: var(--leading) clamp(1.25rem, 4vw, 3rem);
  max-width: 820px;
  position: relative;
  z-index: 1;
}

/* Sidebar occupies a 268px flex column, so .ws-note-main's auto margins
   center its content inside the post-sidebar leftover area — visually
   right of center. Compensate with a right margin so the 820px content
   column re-centers on the viewport at every width.
     margin-right = half of the effective layout width − half of content
                  = min(100vw, --ws-max-width) / 2  −  820 / 2
   Clamped to 0 on narrow viewports where there isn't room. */
body.has-sidebar:not(.sidebar-mode) .ws-note-main {
  margin-right: max(0px, calc(min(100vw, var(--ws-max-width)) / 2 - 410px));
}

.ws-note-head {
  margin-bottom: var(--leading);
}

.ws-note-title {
  font-family: "Fraunces", serif;
  font-size: clamp(1.9rem, 4.5vw, 3rem);
  font-weight: 800;
  letter-spacing: -0.025em;
  line-height: calc(var(--leading) * 2);
  color: var(--ws-ink);
  border: none;
  padding: 0;
  --rhythm: 2;
}

.ws-note-title__accent {
  font-style: oblique 10deg;
  color: var(--color-secondary);
}

.ws-note-meta {
  display: inline-block;
  font-family: "JetBrains Mono", monospace;
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 1.5px;
  text-transform: uppercase;
  color: var(--text-subtle);
}

/* === Baseline-to-grid rhythm =========================================
 *
 * Goal: every text baseline lands on a horizontal grid line.
 *
 * Mechanism:
 *   1. text-box-trim collapses each block's box to (cap-height ↔ baseline),
 *      so block bottom = last baseline and (block-top → first-baseline)
 *      distance = cap × 1em.
 *   2. margin-top = N × leading − cap × 1em puts the next block's first
 *      baseline exactly N × leading below the previous baseline. The
 *      cap × 1em term self-adjusts per element font-size — same formula
 *      works for paragraphs, h1/h2/h3, and fluid clamp() titles.
 *   3. JS in base.njk snaps the very first baseline of each grid-bg
 *      container to a grid line via --grid-snap.
 *
 * Apply by adding `.has-rhythm` to a container; direct children get the
 * formula. Children pick their own --rhythm (default 1) and may override
 * --cap when they use a non-Fraunces face.
 */
.has-rhythm {
  /* Default rhythm: 1 leading unit between baselines. Inherits down so
   * per-element overrides (`.workshop-hero__title { --rhythm: 3 }`)
   * win by being a direct property set on the child — avoids the
   * specificity tie that would otherwise let this rule shadow them. */
  --rhythm: 1;
}

.has-rhythm > * {
  margin-block: 0;
  text-box-trim: trim-both;
  text-box-edge: cap alphabetic;
}

.has-rhythm > * + * {
  margin-top: calc(var(--rhythm) * var(--leading) - var(--cap) * 1em);
}

.has-rhythm > *:first-child {
  margin-top: var(--grid-snap, 0px);
}

/* Article — sits directly on the grid paper; special blocks (callout, code, wattson, wiring) retain their card chrome */
.ws-article {
  margin-bottom: calc(var(--leading) * 2);
  font-family: "Fraunces", serif;
  font-size: 1rem;
  line-height: var(--leading);
}

.ws-article p {
  max-width: 760px;
  color: #26211a;
}

[data-theme="dark"] .ws-article p {
  color: var(--text-main);
}

.ws-article ul,
.ws-article ol {
  padding-left: 1.25rem;
  max-width: 760px;
  display: flex;
  flex-direction: column;
  gap: 0;
  color: #26211a;
}

[data-theme="dark"] .ws-article ul,
[data-theme="dark"] .ws-article ol {
  color: var(--text-main);
}

.ws-article li {
  line-height: var(--leading);
}

.ws-article h1 {
  font-family: "Fraunces", serif;
  font-size: 1.9rem;
  font-weight: 800;
  line-height: calc(var(--leading) * 2);
  color: var(--ws-ink);
  letter-spacing: -0.02em;
  border: none;
  padding: 0;
  --rhythm: 2;
}

/* Sticky header sits ~72px from the top; anchor jumps to a heading need
   to clear it (plus a small visual gap). Applies to anything markdown
   might give an id. */
.ws-article :is(h1, h2, h3, h4, h5, h6) {
  scroll-margin-top: calc(var(--header-h, 72px) + 8px);
}

.ws-article h2 {
  font-family: "Fraunces", serif;
  font-size: 1.5rem;
  font-weight: 700;
  line-height: calc(var(--leading) * 2);
  color: var(--ws-ink);
  letter-spacing: -0.015em;
  border: none;
  padding: 0;
  display: flex;
  align-items: baseline;
  gap: 12px;
  --rhythm: 2;
}
.ws-article h2::before {
  content: "✦";
  color: var(--color-secondary);
  font-weight: 600;
  font-size: 1.5rem;
  flex: 0 0 auto;
}

.ws-article h3 {
  font-family: "Fraunces", serif;
  font-size: 1.2rem;
  font-weight: 700;
  line-height: var(--leading);
  color: var(--ws-ink);
}

.ws-article a,
.ws-article a:visited {
  color: var(--color-primary);
  text-decoration: underline;
  text-underline-offset: 3px;
}

.ws-article a:hover {
  color: var(--color-secondary);
}

.ws-article img {
  max-width: 100%;
  height: auto;
  display: block;
  margin: var(--leading) auto;
}

.ws-article em,
.ws-article i {
  font-style: oblique 10deg;
}

/* Margin-note blockquote. Lighter than .ws-callout-block so authors can
   drop `> ...` in markdown for a soft aside without it competing with
   the boxed callouts. Bold leads (e.g. `> **Why relative paths?**`)
   pick up the amber accent. */
.ws-article blockquote {
  margin: calc(var(--leading) * 1.5) 0;
  padding: 12px 18px 12px 20px;
  background: var(--ws-paper-soft);
  border-left: 4px solid var(--color-secondary);
  font-family: "Fraunces", serif;
  font-style: italic;
  color: var(--ws-ink);
  line-height: 1.55;
}
.ws-article blockquote > *:first-child {
  margin-top: 0;
}
.ws-article blockquote > *:last-child {
  margin-bottom: 0;
}
.ws-article blockquote strong {
  font-style: normal;
  color: var(--color-secondary);
  font-weight: 700;
}

/* Section break — three workshop stars in amber, well-spaced. Replaces
   the browser-default 2px gray rule for markdown `---`. */
.ws-article hr {
  border: 0;
  margin: calc(var(--leading) * 2) auto;
  text-align: center;
  height: auto;
  overflow: visible;
}
.ws-article hr::before {
  content: "✦  ✦  ✦";
  font-family: "Fraunces", serif;
  font-size: 1rem;
  letter-spacing: 0.6em;
  color: var(--color-secondary);
  opacity: 0.7;
}

.ws-article :not(pre) > code {
  background: var(--ws-paper-soft);
  color: var(--ws-syntax-kw);
  padding: 0.1em 0.45em;
  font-family: "JetBrains Mono", monospace;
  font-size: 0.875em;
  border: 1px solid var(--ws-border-soft);
  border-radius: 3px;
  /* See note on the global :not(pre) > code rule — wrap long tokens
   * instead of forcing horizontal page scroll on phones. */
  overflow-wrap: break-word;
}

/* Special blocks (cards / figures) inside .ws-article: opt out of cap-height
 * baseline correction. Their bottom is NOT a text baseline — it's the card
 * edge — so they use plain integer-leading margins. Setting --cap: 0 makes
 * the universal formula collapse to `--rhythm * --leading`. */
.has-rhythm > .code-block,
.has-rhythm > .ws-callout-block,
.has-rhythm > .ws-wattson-note,
.has-rhythm > .ws-wiring,
.has-rhythm > .ws-table-wrap,
.has-rhythm > figure,
.has-rhythm > blockquote {
  --cap: 0;
  text-box-trim: none;
  text-box-edge: auto;
}

/* Lede: bundles the title with the opening paragraph. Two selectors —
 * the generator-emitted .lesson-intro div, and the implicit first-child
 * paragraph when no explicit intro is set. */
.ws-article .lesson-intro,
.ws-article > p:first-child {
  font-family: "Fraunces", serif;
  font-style: oblique 10deg;
  font-size: 1.05rem;
  line-height: var(--leading);
  /* Fallback first for browsers that don't parse color-mix() (Safari
     < 16.2, Chrome < 111, Firefox < 113); modern browsers ignore the
     fallback and use the color-mix line below. */
  color: var(--ws-ink);
  opacity: 0.78;
  color: color-mix(in srgb, var(--ws-ink) 78%, transparent);
}

.ws-article .lesson-outro {
  font-family: "Fraunces", serif;
  font-style: oblique 10deg;
  font-size: 1rem;
  color: var(--ws-ink);
  opacity: 0.7;
  border-top: 1.5px dotted rgba(31, 36, 51, 0.2);
  padding-top: var(--leading);
}

[data-theme="dark"] .ws-article .lesson-outro {
  border-top-color: var(--border);
}

/* Pagination */
.ws-pagination {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 14px;
  margin-top: calc(var(--leading) * 2);
  margin-bottom: calc(var(--leading) * 2);
}

.ws-pagination__card,
.ws-pagination__card:visited {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 4px;
  /* Locks card height so prev/next pages with 1- vs 2-line titles don't
     swap heights and shift the surrounding layout. Fits a 2-line
     Fraunces title plus the dir label + padding. */
  min-height: 6rem;
  padding: 20px 20px 16px;
  background: #fff;
  border: 1.5px solid var(--ws-ink);
  box-shadow: 3px 3px 0 var(--ws-ink);
  text-decoration: none;
  transition:
    box-shadow 0.15s,
    transform 0.15s;
}
.ws-pagination__card::before {
  content: "";
  position: absolute;
  top: -10px;
  left: 50%;
  width: var(--tape-w, 100px);
  height: 18px;
  background: var(--ws-tape);
  transform: translate(calc(-50% + var(--tape-x, 0px)), var(--tape-y, 0px))
    rotate(var(--tape-rot, -2deg));
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.12);
  opacity: 0.92;
  z-index: 2;
  pointer-events: none;
}

[data-theme="dark"] .ws-pagination__card {
  background: var(--surface-elevated);
  border-color: var(--border-strong);
  box-shadow: 3px 3px 0 rgba(0, 0, 0, 0.25);
}

.ws-pagination__card:hover {
  box-shadow: 5px 5px 0 rgba(31, 36, 51, 0.12);
  transform: translateY(-2px);
}

.ws-pagination__card--next {
  text-align: right;
}

.ws-pagination__dir {
  display: block;
  font-family: "JetBrains Mono", monospace;
  font-size: 0.75rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 1.5px;
  color: var(--ws-amber);
}

.ws-pagination__title {
  display: block;
  font-family: "Fraunces", serif;
  font-size: 0.9375rem;
  font-weight: 700;
  color: var(--ws-ink);
  line-height: 1.3;
}

/* === Workshop sidebar toggle — paper card with washi-tape strips === */
/* Anchored as a small standalone tab at the top-left of the lesson layout
   (per the design's "Lesson reader · sidebar collapsed" artboard). Drives
   both the desktop collapse and the mobile drawer. */
.ws-sidebar-toggle {
  position: absolute;
  top: 50%;
  left: clamp(12px, 2vw, 24px);
  z-index: 2;
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 6px 14px 6px 6px;
  background: #fff;
  border: 1.5px solid var(--ws-ink);
  box-shadow: 3px 3px 0 var(--ws-ink);
  cursor: pointer;
  font-family: "Caveat", cursive;
  font-size: 1.25rem;
  font-weight: 700;
  color: var(--ws-ink);
  transform: translateY(-50%) rotate(-1deg);
  transition:
    transform 0.12s ease,
    box-shadow 0.12s ease,
    background 0.12s;
}
.ws-sidebar-toggle:hover {
  transform: translateY(-50%) rotate(-1deg) translate(-1px, -1px);
  box-shadow: 4px 4px 0 var(--ws-ink);
}
.ws-sidebar-toggle:focus-visible {
  outline: 2px solid var(--ws-amber);
  outline-offset: 3px;
}
.ws-sidebar-toggle[aria-expanded="true"] {
  background: var(--ws-tape);
  transform: translateY(-50%) rotate(-1.5deg);
}
.ws-sidebar-toggle[aria-expanded="true"]:hover {
  transform: translateY(-50%) rotate(-1.5deg) translate(-1px, -1px);
}

.ws-sidebar-toggle__icon {
  position: relative;
  display: inline-block;
  width: 32px;
  height: 30px;
  flex: 0 0 auto;
}
.ws-sidebar-toggle__strip {
  position: absolute;
  width: 24px;
  height: 5px;
  left: 4px;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.15);
  opacity: 0.92;
  transition:
    transform 0.18s ease,
    opacity 0.18s,
    width 0.18s,
    top 0.18s,
    left 0.18s;
}
.ws-sidebar-toggle__strip--1 {
  top: 6px;
  background: var(--ws-ink);
  transform: rotate(-2deg);
}
.ws-sidebar-toggle__strip--2 {
  top: 13px;
  background: var(--ws-ink);
  transform: rotate(1.5deg);
}
.ws-sidebar-toggle__strip--3 {
  top: 20px;
  background: var(--ws-ink);
  transform: rotate(-1deg);
}

/* Open state: cross strips 1 and 3, hide the middle strip. */
.ws-sidebar-toggle[aria-expanded="true"] .ws-sidebar-toggle__strip--1 {
  top: 50%;
  left: 50%;
  width: 26px;
  transform: translate(-50%, -50%) rotate(45deg);
}
.ws-sidebar-toggle[aria-expanded="true"] .ws-sidebar-toggle__strip--3 {
  top: 50%;
  left: 50%;
  width: 26px;
  transform: translate(-50%, -50%) rotate(-45deg);
}
.ws-sidebar-toggle[aria-expanded="true"] .ws-sidebar-toggle__strip--2 {
  opacity: 0;
}

.ws-sidebar-toggle__label {
  font-family: "Caveat", cursive;
  font-size: 1.25rem;
  font-weight: 700;
  line-height: 1;
  color: var(--ws-ink);
}

/* Dark mode: keep the card readable on the dark workbench. */
[data-theme="dark"] .ws-sidebar-toggle {
  background: var(--surface);
  border-color: var(--border-strong);
  box-shadow: 3px 3px 0 rgba(0, 0, 0, 0.5);
  color: var(--text-main);
}
[data-theme="dark"] .ws-sidebar-toggle__label {
  color: var(--text-main);
}
[data-theme="dark"] .ws-sidebar-toggle[aria-expanded="true"] {
  background: var(--ws-amber);
  color: var(--ws-ink);
}

/* Desktop collapsed state — sidebar contents go invisible but its 268px
   column stays in the flex layout, so .ws-note-main keeps its exact
   horizontal position (no shift). Scoped to non-mobile so a persisted
   collapse from desktop doesn't break the mobile drawer. */
:root[data-sidebar-collapsed] body:not(.sidebar-mode) .ws-sidebar {
  visibility: hidden;
}

/* No-JS / pre-JS fallback at narrow viewports. The lesson layout uses a
 * 268px flex column; without the JS-managed drawer mode, that column eats
 * the mobile viewport. If body.sidebar-mode is absent, collapse the
 * sidebar (and the dead toggle button) so the lesson body stays usable.
 * Once JS adds .sidebar-mode the drawer rules below take over, and since
 * the drawer starts off-screen via translateX there's no visible flicker. */
@media (max-width: 820px) {
  body:not(.sidebar-mode) .ws-sidebar,
  body:not(.sidebar-mode) .ws-sidebar-toggle,
  body:not(.sidebar-mode) .ws-sidebar-tape {
    display: none;
  }
}

/* Mobile drawer backdrop — only visible when the drawer is open. */
.sidebar-backdrop {
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(31, 36, 51, 0.45);
  z-index: var(--z-backdrop);
  opacity: 0;
  transition: opacity 0.2s ease;
}
:root[data-sidebar-open] body.sidebar-mode .sidebar-backdrop {
  display: block;
  opacity: 1;
}

/* Sidebar drawer for ws-sidebar (mirrors existing sidebar-nav rules).
   Mobile-only: at narrow widths the sidebar becomes a fixed slide-in panel
   driven by data-sidebar-open on the root element. */
body.sidebar-mode .ws-sidebar {
  position: fixed;
  inset: 0 auto 0 0;
  width: min(85vw, 20rem);
  max-height: none;
  transform: translateX(-100%);
  transition: transform 0.25s ease;
  z-index: var(--z-drawer);
  box-shadow: 4px 0 20px rgba(0, 0, 0, 0.15);
  /* Keep drawer content clear of notch / home indicator under
   * viewport-fit=cover. Left inset matters in landscape when the device
   * is rotated with the notch on the drawer side. */
  padding-top: env(safe-area-inset-top);
  padding-bottom: env(safe-area-inset-bottom);
  padding-left: env(safe-area-inset-left);
}

:root[data-sidebar-open] body.sidebar-mode .ws-sidebar {
  transform: translateX(0);
}

/* Body scroll lock while a full-screen overlay is open. Without this, tap-
 * scrolling the modal/drawer also scrolls the page underneath on mobile.
 * Two triggers: the sidebar drawer (root data attr) and the access gate
 * (uses [hidden] attribute, queried via :has on root). */
:root[data-sidebar-open],
:root:has(.access-gate:not([hidden])) {
  overflow: hidden;
}
:root[data-sidebar-open] body,
:root:has(.access-gate:not([hidden])) body {
  overflow: hidden;
}

/* Mobile */
@media (max-width: 600px) {
  .ws-pagination {
    grid-template-columns: 1fr;
  }
}

/* === Workshop lesson-body primitives: callout / wattson / wiring === */

.ws-callout-block {
  position: relative;
  display: flex;
  align-items: stretch;
  margin: calc(var(--leading) * 2) 0;
  max-width: 760px;
  background: #fff;
  border: 1.5px solid var(--ws-ink);
  box-shadow: 4px 4px 0 var(--ws-ink);
}
.ws-callout-block::before {
  content: "";
  position: absolute;
  top: -10px;
  left: 50%;
  width: 100px;
  height: 18px;
  background: var(--ws-tape);
  transform: translateX(-50%) rotate(-2deg);
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.12);
  opacity: 0.92;
  z-index: 2;
  pointer-events: none;
}
.ws-callout-block--stop::before {
  background: var(--ws-tape-pink);
}

.ws-callout-block__label {
  flex: 0 0 auto;
  min-width: 78px;
  padding: 10px 12px;
  font-family: "JetBrains Mono", monospace;
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 1.5px;
  color: #fff;
  background: var(--ws-ink);
  display: flex;
  align-items: flex-start;
}

.ws-callout-block--stop .ws-callout-block__label {
  background: var(--color-secondary);
}

.ws-callout-block__body {
  flex: 1;
  padding: 14px 18px;
  font-family: "Fraunces", serif;
  font-size: 1rem;
  line-height: 1.55;
  color: var(--ws-ink);
}

.ws-callout-block__body > *:first-child {
  margin-top: 0;
}
.ws-callout-block__body > *:last-child {
  margin-bottom: 0;
}

[data-theme="dark"] .ws-callout-block {
  background: var(--surface-elevated);
  border-color: var(--border-strong);
  box-shadow: 4px 4px 0 rgba(0, 0, 0, 0.4);
}

[data-theme="dark"] .ws-callout-block__label {
  background: var(--border-strong);
}

[data-theme="dark"] .ws-callout-block--stop .ws-callout-block__label {
  background: var(--color-secondary);
}

[data-theme="dark"] .ws-callout-block__body {
  color: var(--text-main);
}

/* Wattson sticky-note */
.ws-wattson-note {
  position: relative;
  display: flex;
  align-items: stretch;
  gap: 14px;
  margin: calc(var(--leading) * 2) 0;
  max-width: 760px;
  padding: 20px 18px 14px;
  background: #fef4a8;
  border: 1.5px solid var(--ws-ink);
  box-shadow: 4px 4px 0 rgba(31, 36, 51, 0.15);
  transform: rotate(-0.5deg);
}
.ws-wattson-note::before {
  content: "";
  position: absolute;
  top: -10px;
  left: 50%;
  width: 100px;
  height: 18px;
  background: var(--ws-tape);
  transform: translateX(-50%) rotate(-2deg);
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.12);
  opacity: 0.92;
  z-index: 2;
  pointer-events: none;
}

.ws-wattson-note__img-wrap {
  flex: 0 0 auto;
  align-self: flex-start;
  width: clamp(58px, 14vw, 80px);
  aspect-ratio: 68 / 98;
  overflow: visible;
}

.ws-article .ws-wattson-note__img {
  height: 115%;
  width: 115%;
  margin: 0;
  transform: translateY(-6%);
  object-fit: contain;
  object-position: left center;
  display: block;
}

.ws-wattson-note__body {
  flex: 1;
  margin: 0;
  padding-top: 8px;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.ws-wattson-note__label {
  font-family: "Caveat", cursive;
  font-size: 1.35rem;
  font-weight: 700;
  color: var(--color-secondary);
  margin: 0;
}

.ws-wattson-note__text {
  font-family: "Fraunces", serif;
  font-style: oblique 10deg;
  font-size: 1rem;
  line-height: 1.55;
  color: var(--ws-ink);
  margin: 0;
}

[data-theme="dark"] .ws-wattson-note {
  background: #4a4326;
  border-color: var(--border-strong);
  box-shadow: 4px 4px 0 rgba(0, 0, 0, 0.3);
}

[data-theme="dark"] .ws-wattson-note__text {
  color: #f3e8c0;
}

/* Wiring diagram chip-strip */
.ws-wiring {
  margin: calc(var(--leading) * 2) 0;
  max-width: 760px;
}

.ws-wiring__cap {
  font-family: "JetBrains Mono", monospace;
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 1.5px;
  color: var(--text-subtle);
  margin-bottom: 8px;
  display: block;
}

.ws-wiring__strip {
  position: relative;
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  padding: 28px 18px 20px;
  background: #fff;
  border: 1.5px solid var(--ws-ink);
  box-shadow: 4px 4px 0 var(--ws-ink);
}
.ws-wiring__strip::before {
  content: "";
  position: absolute;
  top: -10px;
  left: 50%;
  width: 100px;
  height: 18px;
  background: var(--ws-tape-green);
  transform: translateX(-50%) rotate(-2deg);
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.12);
  opacity: 0.92;
  z-index: 2;
  pointer-events: none;
}

.ws-wiring__chip {
  color: #fff;
  font-family: "JetBrains Mono", monospace;
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 1px;
  padding: 8px 12px;
  flex: 0 0 auto;
}

.ws-wiring__wire {
  flex: 1;
  min-width: 22px;
  height: 2px;
  background: var(--ws-ink);
}

[data-theme="dark"] .ws-wiring__strip {
  background: var(--surface-elevated);
  border-color: var(--border-strong);
  box-shadow: 4px 4px 0 rgba(0, 0, 0, 0.35);
}

[data-theme="dark"] .ws-wiring__wire {
  background: var(--text-muted);
}

/* On narrow screens, flex-wrap fragments the horizontal wires into
   stubby leftover pieces that no longer read as a circuit. Switch to
   a vertical layout so chips stack and the wires become connectors
   between them. */
@media (max-width: 600px) {
  .ws-wiring__strip {
    flex-direction: column;
    align-items: center;
    gap: 0;
    padding: 28px 18px 24px;
  }
  .ws-wiring__chip {
    text-align: center;
    min-width: 6rem;
  }
  .ws-wiring__wire {
    flex: 0 0 auto;
    width: 2px;
    height: 22px;
    margin: 4px 0;
  }
}

/* === Reduced-motion blanket ===
 * Covers animations not individually gated above (live-pulse, transitions,
 * header fade, polaroid hover, code-block collapse). */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}
