/* ==========================================================================
   Coded Aesthetics — public stylesheet

   Two visual modes are toggled by a class on <body>:
     .cae-landing → dark, full-bleed canvas (overflow: hidden)
     .cae-pages   → warm taupe page surface, scrollable
   The transition between them is run from landing.js (noise dissolve);
   CSS only animates background-color and color so the canvas pixels
   are not interpolated through grey. Don't add filter transitions to
   the canvas itself.
   ========================================================================== */

:root {
  --bg-landing: #04050b;
  --fg-landing: #e6ecff;
  --bg-pages:   #b8a497;
  --fg-pages:   #6a00a6;          /* deep violet — −60° hue from --accent-pink-muted */

  --accent-pink:        #ff3d80;
  --accent-pink-muted:  #c93168;

  --rule:        rgba(201, 49, 104, 0.22);     /* hairline pink @ low alpha */
  --rule-faint:  rgba(201, 49, 104, 0.10);

  --mono:  "Kode Mono", "JetBrains Mono", "IBM Plex Mono", "Menlo", ui-monospace, monospace;
  --sans:  "Athiti", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;

  --nav-w: 220px;        /* fixed left rail */
  --gutter: 24px;        /* page-edge gutter */
  --measure: 760px;      /* reading column */
}

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

/* Screen-reader-only utility — visually removes content from layout
   without hiding it from assistive tech or crawlers. Used on the
   landing page so the canvas-only hero still has a real <h1>. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

html, body {
  min-height: 100vh;
  font-family: var(--sans);
  font-weight: 400;
  font-size: 17px;
  line-height: 1.55;
  /* Slow handover from dark landing to light pages mode. */
  transition: background-color 4000ms ease, color 4000ms ease;
}

body.cae-landing {
  background: var(--bg-landing);
  color: var(--fg-landing);
  overflow: hidden;
  font-family: var(--mono);    /* landing is mono territory */
}

body.cae-pages {
  background-color: var(--bg-pages);
  color: var(--fg-pages);
  overflow-x: hidden;
}

/* Canvas: full-viewport backdrop. The dissolve operates on its pixels
   in JS — no CSS filter or opacity animation here.
   No pointer-events override: the landing canvas needs to receive
   clicks (for the ripple-ignition effect) and pointer-move (for the
   letter-glow hover). On pages-mode the canvas sits at z-index 0 and
   <main> at z-index 5, so main naturally captures events first. */
canvas#net {
  position: fixed;
  inset: 0;
  width: 100vw;
  height: 100vh;
  z-index: 0;
}

/* On the landing page <main> is mostly empty (only the overlay-text
   appears if the home Page has body content). Make main transparent
   to pointer events so clicks pass through to canvas#net, but
   re-enable interaction on its children so the overlay-text and any
   inner controls still work. */
body.cae-landing main { pointer-events: none; }
body.cae-landing main > * { pointer-events: auto; }

/* Static grain canvas — sits below canvas#net so on SPA-arrived pages
   the live (dissolved + glitching) canvas overlays it. On direct visits
   canvas#net isn't loaded, so this is the visible bg. */
canvas#cae-grain {
  position: fixed;
  inset: 0;
  width: 100vw;
  height: 100vh;
  z-index: -1;
  pointer-events: none;
}
body.cae-landing canvas#cae-grain { display: none; }

/* ==========================================================================
   Nav — vertical "Inspect class" rail (top-left, fixed)

   C.AE sits at the top in pink as a brand anchor; each route reads as
   a method on it, indented under a hairline rail. Below the 760px
   breakpoint the methods collapse behind a hamburger toggle. The whole
   shape stays in mono so it reads as a code API.
   ========================================================================== */

.cae-nav {
  position: fixed;
  top: 22px;
  left: var(--gutter);
  z-index: 10;
  font-family: var(--mono);
  font-size: 14px;
  letter-spacing: 0.02em;
  line-height: 1.75;
  user-select: none;
  /* Soft halo on landing so mono text reads against the canvas. */
  text-shadow: 0 0 0 transparent;
  transition: text-shadow 600ms ease;
}
body.cae-landing .cae-nav { text-shadow: 0 0 8px rgba(0, 0, 0, 0.85); }

.cae-nav__bar {
  display: flex;
  align-items: center;
  gap: 14px;
  margin-bottom: 6px;
}

.cae-nav__home {
  display: inline-block;
  font-family: var(--mono);
  font-size: 18px;
  font-weight: 600;
  letter-spacing: 0.08em;
  color: var(--accent-pink);
  text-decoration: none;
  transition: color 160ms ease, transform 240ms ease;
}
.cae-nav__home:hover { color: var(--accent-pink); filter: brightness(1.15); }
.cae-nav__home.is-active { color: var(--accent-pink); }

/* Hamburger — visible only at narrow viewports. */
.cae-nav__toggle {
  display: none;
  width: 28px;
  height: 22px;
  padding: 0;
  background: none;
  border: 0;
  cursor: pointer;
  position: relative;
  -webkit-tap-highlight-color: transparent;
}
.cae-nav__toggle-icon,
.cae-nav__toggle-icon::before,
.cae-nav__toggle-icon::after {
  content: "";
  display: block;
  position: absolute;
  left: 0;
  width: 24px;
  height: 2px;
  background: var(--accent-pink);
  border-radius: 1px;
  transition: transform 220ms ease, opacity 160ms ease, top 220ms ease;
}
.cae-nav__toggle-icon { top: 10px; }
.cae-nav__toggle-icon::before { top: -8px; }
.cae-nav__toggle-icon::after  { top: 8px; }
.cae-nav.is-open .cae-nav__toggle-icon { background: transparent; }
.cae-nav.is-open .cae-nav__toggle-icon::before {
  top: 0; transform: rotate(45deg);
}
.cae-nav.is-open .cae-nav__toggle-icon::after {
  top: 0; transform: rotate(-45deg);
}

.cae-nav__methods {
  list-style: none;
  padding-left: 1.4ch;
  border-left: 1px solid var(--rule-faint);
  margin-left: 0.4ch;
}
body.cae-landing .cae-nav__methods {
  border-color: rgba(230, 236, 255, 0.20);
}

.cae-nav__method {
  position: relative;
  padding-left: 1.4ch;
}
.cae-nav__method::before {
  content: "";
  position: absolute;
  left: -0.4ch;
  top: 0.95em;
  width: 1.4ch;
  height: 1px;
  background: currentColor;
  opacity: 0.22;
}
body.cae-landing .cae-nav__method::before { opacity: 0.3; }

.cae-nav__method a {
  display: inline-block;
  color: inherit;
  text-decoration: none;
  font-weight: 500;
  opacity: 0.78;
  transition: opacity 160ms ease, color 160ms ease, transform 240ms ease;
}
.cae-nav__method a:hover { opacity: 1; color: var(--accent-pink); }
.cae-nav__method.is-active a {
  opacity: 1;
  color: var(--accent-pink);
  font-weight: 700;
}
.cae-nav__method.is-active::before {
  background: var(--accent-pink);
  opacity: 0.9;
  width: 2ch;
  left: -1ch;
}
/* The body.cae-pages li rule below adds margin-bottom for prose list
   items; reset it on the nav so pages-mode spacing matches the tighter
   landing-mode layout (line-height handles the spacing here). */
.cae-nav .cae-nav__method { margin-bottom: 0; }

/* Narrow-viewport: collapse vertical menu behind a hamburger toggle.
   Methods list is hidden until the toggle flips aria-expanded. */
@media (max-width: 759px) {
  .cae-nav__toggle { display: block; }
  .cae-nav__methods {
    display: none;
    margin-top: 6px;
    padding: 8px 0 8px 1.6ch;
    background: rgba(184, 164, 151, 0.92);
    backdrop-filter: blur(6px);
    border-radius: 4px;
    box-shadow: 0 6px 24px rgba(0, 0, 0, 0.18);
  }
  body.cae-landing .cae-nav__methods {
    background: rgba(4, 5, 11, 0.86);
    box-shadow: 0 6px 24px rgba(0, 0, 0, 0.5);
  }
  .cae-nav.is-open .cae-nav__methods { display: block; }
  .cae-nav__method { padding-left: 1.4ch; padding-right: 14px; }
}

/* ==========================================================================
   Main column

   Centred in the viewport on viewports ≥ 760px; the fixed nav floats on
   top of the leftmost ~220px without inset-pushing the content. On
   narrow viewports the nav collapses to a hamburger and main gets a
   bumped top padding so content clears the toggle row.
   ========================================================================== */

main {
  position: relative;
  z-index: 5;
  /* Top padding clears the hamburger row at narrow viewports. */
  padding: 76px var(--gutter) 96px;
  max-width: var(--measure);
}
@media (min-width: 760px) {
  /* Symmetric body padding so main centres on the viewport, not in the
     area to the right of the nav rail. The fixed nav floats on top of
     the content area; on viewports where they'd overlap (narrow
     desktops) the nav's text-shadow/translucent backdrop keeps it
     legible. */
  body {
    padding-left: var(--gutter);
    padding-right: var(--gutter);
  }
  main {
    margin-inline: auto;
    padding-top: 64px;
  }
}

/* ==========================================================================
   Pages-mode typography

   Body in Athiti medium; all headings (h1/h2/h3) in Kode Mono. The
   h1 page titles ARE the API surface (.root(), .signals(), etc.) so
   keeping them in mono reinforces the "nav-as-code" conceit. h2 used
   as small-caps section labels, h3 as in-card titles.
   ========================================================================== */

body.cae-pages main {
  font-family: var(--sans);
  font-weight: 500;
  font-size: 18px;
  line-height: 1.6;
}

body.cae-pages h1 {
  font-family: var(--mono);
  font-weight: 600;
  font-size: 44px;
  letter-spacing: -0.01em;
  line-height: 1.1;
  margin-bottom: 32px;
  color: var(--accent-pink-muted);
  text-align: right;
  opacity: 0.25;
}
body.cae-pages h1 code,
body.cae-pages h1 .punct {
  color: var(--accent-pink-muted);
}

body.cae-pages h2 {
  font-family: var(--mono);
  font-weight: 600;
  font-size: 14px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--accent-pink-muted);
  margin: 40px 0 16px;
}

body.cae-pages h3 {
  font-family: var(--mono);
  font-weight: 500;
  font-size: 20px;
  letter-spacing: 0.01em;
  margin: 26px 0 10px;
  color: var(--accent-pink-muted);
}

/* Markdown-authored content (admin-edited page bodies, signal bodies) follows
   conventional heading hierarchy — h1 > h2 > h3 — so non-technical authors get
   the result they expect from `## Heading`. The template-level h2 small-caps
   section-label style still applies to <h2> outside of `.md-body`. */
body.cae-pages .md-body h2 {
  font-size: 28px;
  font-weight: 600;
  letter-spacing: -0.005em;
  text-transform: none;
  margin: 36px 0 12px;
}
body.cae-pages .md-body h3 {
  font-size: 20px;
  font-weight: 600;
  letter-spacing: 0;
  margin: 24px 0 8px;
}
body.cae-pages .md-body h4 {
  font-family: var(--mono);
  font-size: 14px;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--accent-pink-muted);
  margin: 22px 0 6px;
}

body.cae-pages p,
body.cae-pages li {
  margin-bottom: 14px;
  font-weight: 500;
}
/* The global * { padding: 0 } reset zeros <ul>/<ol>'s left padding so
   their markers fall outside the list box. Restore enough indent for
   the bullets to sit inside the reading column. */
body.cae-pages ul,
body.cae-pages ol {
  padding-left: 1.4em;
}
body.cae-pages li { padding-left: 0.2em; }
body.cae-pages strong { font-weight: 700; }
body.cae-pages em { font-style: italic; }

body.cae-pages a {
  color: var(--accent-pink-muted);
  text-decoration-color: rgba(201, 49, 104, 0.4);
  text-underline-offset: 3px;
  text-decoration-thickness: 1px;
  transition: color 160ms ease, text-decoration-color 160ms ease;
}
body.cae-pages a:hover {
  color: var(--accent-pink);
  text-decoration-color: var(--accent-pink);
}

body.cae-pages section { margin-bottom: 36px; }
body.cae-pages article {
  margin-bottom: 24px;
  padding-bottom: 18px;
  border-bottom: 1px solid var(--rule);
}

body.cae-pages code {
  font-family: var(--mono);
  font-size: 0.86em;
  background: rgba(20, 20, 20, 0.07);
  padding: 1px 6px;
  border-radius: 3px;
  letter-spacing: 0.02em;
}

body.cae-pages summary {
  cursor: pointer;
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--accent-pink-muted);
  margin-top: 6px;
}
body.cae-pages summary:hover { color: var(--accent-pink); }

/* In-page sub-navs (e.g. signals bucket filters) — distinct from the
   top-level vertical rail. */
body.cae-pages main > nav {
  display: flex;
  gap: 20px;
  margin: 8px 0 36px;
  font-family: var(--mono);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
}
body.cae-pages main > nav a {
  text-decoration: none;
  opacity: 0.6;
  border-bottom: 1px solid transparent;
  padding-bottom: 2px;
}
body.cae-pages main > nav a:hover { opacity: 1; }
body.cae-pages main > nav a[aria-current="page"] {
  opacity: 1;
  color: var(--accent-pink);
  border-bottom-color: var(--accent-pink);
}

/* ==========================================================================
   Landing-only overlay text — stays mono, bottom-right
   ========================================================================== */
.overlay-text {
  position: fixed;
  bottom: 18px;
  right: 22px;
  z-index: 7;
  max-width: 360px;
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: 0.04em;
  text-shadow: 0 0 8px rgba(0, 0, 0, 0.85);
  pointer-events: none;
}

/* ==========================================================================
   Signals — vertical timeline / git-log inspired feed

   The temporal axis is rendered literally: a hairline rail runs down
   the left of the feed, with each signal attached as a marker. State
   reads from data-state on each .signal:
     future  → hollow ring marker, slightly translucent body
     present → filled accent-pink marker, full opacity
     past    → small dot marker, muted body
   The "now" line is a real divider between present and past.
   ========================================================================== */

.signals-feed {
  position: relative;
  margin-top: 12px;
  padding-left: 28px;
  border-left: 1px solid var(--rule);
}
.signals-bucket {
  position: relative;
  margin-bottom: 36px;
}
.signals-bucket > h2 {
  margin: 0 0 14px;
  /* Shift the bucket label slightly into the rail margin so it reads
     as an axis tick rather than an indented sub-heading. */
  margin-left: -28px;
  padding-left: 0;
  display: flex;
  align-items: baseline;
  gap: 12px;
}
.signals-bucket > h2::before {
  content: "";
  display: inline-block;
  width: 18px;
  height: 1px;
  background: var(--accent-pink-muted);
  opacity: 0.5;
}

.signal {
  position: relative;
  padding: 0 0 22px 0;
  margin-bottom: 14px;
  border-bottom: 1px solid var(--rule-faint);
}
.signal:last-child { border-bottom: 0; }

.signal::before {
  content: "";
  position: absolute;
  left: -34px;
  top: 0.55em;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--accent-pink);
  box-shadow: 0 0 0 3px var(--bg-pages);
  transition: transform 240ms ease;
}
.signal[data-state="future"]::before {
  background: transparent;
  border: 1px solid var(--accent-pink-muted);
  width: 9px; height: 9px;
}
.signal[data-state="present"]::before {
  background: var(--accent-pink);
  /* A subtle pulse ring for "now". */
  box-shadow:
    0 0 0 3px var(--bg-pages),
    0 0 0 5px rgba(255, 61, 128, 0.35);
}
.signal[data-state="past"]::before {
  background: var(--accent-pink-muted);
  width: 6px; height: 6px;
  opacity: 0.6;
  top: 0.7em;
  left: -32px;
}

.signal[data-state="future"]  { opacity: 0.78; }
.signal[data-state="present"] { opacity: 1; }
.signal[data-state="past"]    { opacity: 0.7; }

.signal__head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 10px 14px;
  margin-bottom: 6px;
}
.signal__title {
  font-family: var(--mono);
  font-weight: 500;
  font-size: 18px;
  margin: 0;
  letter-spacing: 0.01em;
  line-height: 1.3;
}
.signal__title a {
  color: var(--accent-pink-muted);
  text-decoration: none;
}
.signal__title a:hover { color: var(--accent-pink); }
.signal__date {
  font-family: var(--mono);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--fg-pages);
  opacity: 0.7;
  white-space: nowrap;
}
.signal__body {
  font-size: 17px;
  font-weight: 500;
}
.signal__body p:last-child { margin-bottom: 0; }

/* Now-line divider — between present and past buckets, mono caption. */
.signals-now {
  position: relative;
  margin: 8px -28px 28px;
  padding-left: 0;
  display: flex;
  align-items: center;
  gap: 12px;
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--accent-pink);
}
.signals-now::before,
.signals-now::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--accent-pink);
  opacity: 0.5;
}

/* Empty-bucket message */
.signals-bucket__empty {
  font-family: var(--mono);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--fg-pages);
  opacity: 0.45;
  margin-bottom: 6px;
}

/* Single-signal detail page */
body.cae-pages .signal-detail__back {
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
}
body.cae-pages .signal-detail__back a { text-decoration: none; }
body.cae-pages h1.signal-detail__title {
  font-family: var(--mono);
  font-weight: 500;
  font-size: 36px;
  letter-spacing: -0.005em;
  line-height: 1.18;
  color: var(--accent-pink-muted);
  margin: 28px 0 12px;
}
body.cae-pages .signal-detail__date {
  font-family: var(--mono);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--accent-pink-muted);
  margin-bottom: 32px;
}
body.cae-pages .signal-detail__body { font-size: 18px; }

/* ==========================================================================
   .public() — publications

   Each entry reads as a citation: title + year on one line, then the
   authors + venue, then a small meta row (type badge + DOI/PDF), then
   keywords as inline chips, then an expandable abstract. Filter form
   replaces default <select> chrome with a custom chevron in mono.
   ========================================================================== */

.pub-filters {
  display: flex;
  flex-wrap: wrap;
  gap: 18px 28px;
  margin: 4px 0 32px;
  padding: 14px 18px;
  border: 1px solid var(--rule);
  border-radius: 4px;
}
.pub-filter {
  display: flex;
  align-items: center;
  gap: 10px;
}
.pub-filter__label {
  font-family: var(--mono);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--accent-pink-muted);
}
.pub-filter__select {
  appearance: none;
  -webkit-appearance: none;
  background: transparent;
  border: 1px solid var(--rule);
  color: var(--fg-pages);
  font-family: var(--mono);
  font-size: 13px;
  font-weight: 500;
  padding: 6px 28px 6px 10px;
  border-radius: 3px;
  cursor: pointer;
  /* Custom chevron — pink, rotated to look like a small ▾. */
  background-image: linear-gradient(45deg, transparent 50%, var(--accent-pink-muted) 50%),
                    linear-gradient(135deg, var(--accent-pink-muted) 50%, transparent 50%);
  background-position: calc(100% - 14px) 50%, calc(100% - 9px) 50%;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
  transition: border-color 160ms ease, color 160ms ease;
}
.pub-filter__select:hover,
.pub-filter__select:focus-visible {
  border-color: var(--accent-pink);
  color: var(--accent-pink);
  outline: none;
}
.pub-filter__select option {
  /* Native option list — keep colors readable in the OS dropdown. */
  background: #f5ede5;
  color: #4a2030;
}

.pubs { margin-top: 4px; }

.pub {
  margin-bottom: 28px;
  padding-bottom: 22px;
  border-bottom: 1px solid var(--rule);
}
.pub:last-child { border-bottom: 0; }

.pub__head {
  display: flex;
  align-items: baseline;
  gap: 16px;
  margin-bottom: 6px;
}
.pub__title {
  font-family: var(--mono);
  font-size: 19px;
  font-weight: 500;
  margin: 0;
  flex: 1;
  color: var(--accent-pink-muted);
  line-height: 1.3;
  letter-spacing: 0.005em;
}
.pub__title a {
  color: inherit;
  text-decoration: none;
  border-bottom: 1px solid transparent;
  transition: border-color 160ms ease, color 160ms ease;
}
.pub__title a:hover {
  color: var(--accent-pink);
  border-bottom-color: var(--accent-pink);
}
.pub__year {
  font-family: var(--mono);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.18em;
  color: var(--fg-pages);
  opacity: 0.65;
  white-space: nowrap;
}

.pub__cite {
  margin: 0 0 10px;
  font-size: 16px;
  font-weight: 400;
  line-height: 1.5;
}

.pub__meta {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  align-items: center;
  margin-bottom: 8px;
}
.pub__badge {
  display: inline-block;
  font-family: var(--mono);
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--accent-pink-muted);
  background: rgba(201, 49, 104, 0.10);
  padding: 3px 8px;
  border-radius: 2px;
}
.pub__link {
  font-family: var(--mono);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--accent-pink-muted);
  border-bottom: 1px solid transparent;
  transition: color 160ms ease, border-color 160ms ease;
}
.pub__link:hover {
  color: var(--accent-pink);
  border-bottom-color: var(--accent-pink);
}

.pub__keywords {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 4px 12px;
  padding: 0 !important;
  margin: 6px 0 8px;
  font-family: var(--mono);
  font-size: 12px;
  color: var(--fg-pages);
  opacity: 0.78;
}
.pub__keywords li {
  margin: 0;
  padding: 0;
}
.pub__keywords li::after {
  content: " ·";
  margin-left: 4px;
  opacity: 0.4;
}
.pub__keywords li:last-child::after { content: ""; }

.pub__abstract { margin-top: 8px; }
.pub__abstract summary {
  /* Re-style summary on top of the body.cae-pages summary base. */
  display: inline-block;
  list-style: none;
  margin: 0;
  font-size: 11px;
  letter-spacing: 0.18em;
  position: relative;
  padding-left: 14px;
}
.pub__abstract summary::-webkit-details-marker { display: none; }
.pub__abstract summary::before {
  content: "+";
  position: absolute;
  left: 0;
  top: 0;
  font-family: var(--mono);
  font-weight: 700;
  color: var(--accent-pink-muted);
  transition: transform 160ms ease;
  display: inline-block;
}
.pub__abstract[open] summary::before { content: "−"; }
.pub__abstract-body {
  margin-top: 10px;
  padding: 12px 16px;
  border-left: 1px solid var(--rule);
  font-size: 16px;
  font-weight: 400;
  color: var(--fg-pages);
}
.pub__abstract-body p:last-child { margin-bottom: 0; }

/* ==========================================================================
   Newsletter form — inline email + submit, mono labels, pink accents
   ========================================================================== */

.newsletter {
  display: flex;
  flex-wrap: wrap;
  align-items: stretch;
  gap: 10px;
  max-width: 480px;
  margin-top: 10px;
}
.newsletter__field {
  position: relative;
  flex: 1 1 240px;
  display: flex;
  flex-direction: column;
}
.newsletter__label {
  font-family: var(--mono);
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--accent-pink-muted);
  margin-bottom: 4px;
}
.newsletter__optional {
  font-weight: 400;
  letter-spacing: 0.12em;
  opacity: 0.55;
  margin-left: 4px;
}
.newsletter__input {
  font-family: var(--sans);
  font-weight: 500;
  font-size: 16px;
  color: var(--fg-pages);
  background: transparent;
  border: 1px solid var(--rule);
  border-radius: 3px;
  padding: 10px 12px;
  width: 100%;
  transition: border-color 160ms ease, box-shadow 160ms ease;
}
.newsletter__input::placeholder {
  color: var(--fg-pages);
  opacity: 0.4;
}
.newsletter__input:focus {
  outline: none;
  border-color: var(--accent-pink);
  box-shadow: 0 0 0 3px rgba(255, 61, 128, 0.18);
}

.newsletter__submit {
  align-self: flex-end;
  font-family: var(--mono);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--bg-pages);
  background: var(--accent-pink-muted);
  border: 0;
  border-radius: 3px;
  padding: 11px 22px;
  cursor: pointer;
  transition: background-color 160ms ease, transform 240ms ease;
}
.newsletter__submit:hover {
  background: var(--accent-pink);
}
.newsletter__submit:active {
  transform: translateY(1px);
}
.newsletter__submit:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px rgba(255, 61, 128, 0.35);
}

/* ==========================================================================
   Nodes — force-directed network graph

   Coherent with the landing canvas (same pink hairlines, small dots) but
   minimal: no drifting particles, no headline letters. Each node is a
   real <a> element absolutely positioned over the canvas; the canvas
   draws edges and node markers; the simulation keeps them in sync.
   ========================================================================== */

.nodes-lede {
  margin-bottom: 24px;
  max-width: 56ch;
  font-style: italic;
  opacity: 0.85;
}

.node-graph {
  /* Default (mobile): hidden in favour of the list-fallback, but if it
     ever rendered it should fit within main without breaking out. */
  position: relative;
  width: 100%;
  height: calc(100vh - 220px);
  min-height: 520px;
  max-height: 1100px;
  overflow: visible;
}
@media (min-width: 760px) {
  .node-graph {
    /* Span the full viewport. main is centred at 760px (with no nav-rail
       inset on body), so we shift left by half the empty space + main's
       own padding to land at viewport x=0. */
    width: 100vw;
    margin-left: calc(-1 * (
      max(0px, (100vw - var(--measure)) / 2) + var(--gutter)
    ));
  }
}
/* Visitor-controlled toggle between network and list view. The inline
   script in nodes.html sets data-view to "graph" or "list" on first
   paint and on every toggle click, including a forced switch to "list"
   on narrow viewports where the network is unreadable. The "auto"
   state (used only for the brief moment before the inline script
   runs, or if JS is disabled) falls back to list — safe on any
   viewport. */
.node-list { list-style: none; padding: 0; margin: 16px 0 0; }
[data-node-views][data-view="graph"] .node-graph { display: block; }
[data-node-views][data-view="graph"] .node-list  { display: none; }
[data-node-views][data-view="list"]  .node-graph { display: none; }
[data-node-views][data-view="list"]  .node-list  { display: block; }
[data-node-views][data-view="auto"]  .node-graph { display: none; }
[data-node-views][data-view="auto"]  .node-list  { display: block; }

/* View toggle — segmented pill. Both states are visible at all times,
   with the active cell highlighted, so the affordance reads as a
   switch on first glance. */
.view-toggle {
  display: flex;
  align-items: stretch;
  width: max-content;
  margin: 4px 0 16px auto; /* margin-left: auto pushes the pill to the right edge */
  padding: 2px;
  background: rgba(255, 61, 128, 0.05);
  border: 1px solid var(--rule-faint);
  border-radius: 999px;
  gap: 0;
}
.view-toggle__cell {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 12px 6px 10px;
  border: 0;
  background: transparent;
  border-radius: 999px;
  font-family: var(--mono);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--accent-pink-muted);
  opacity: 0.55;
  cursor: pointer;
  transition: background-color 160ms ease, color 160ms ease, opacity 160ms ease;
}
.view-toggle__cell:hover { opacity: 0.85; color: var(--accent-pink); }
.view-toggle__cell:focus-visible {
  outline: none;
  box-shadow: 0 0 0 2px rgba(255, 61, 128, 0.35);
}
.view-toggle__cell[aria-selected="true"] {
  background: var(--accent-pink-muted);
  color: var(--bg-pages);
  opacity: 1;
  cursor: default;
}
.view-toggle__icon { width: 16px; height: 16px; flex-shrink: 0; }
.view-toggle__label { line-height: 1; }

/* Floating newsletter affordance — fixed bottom-right on every page
   except the landing canvas and the newsletter page itself (those
   exclusions live in base.html). Selector scoped under body to win
   specificity over the global `body.cae-pages a` colour rule. */
body.cae-pages .newsletter-fab {
  position: fixed;
  right: 20px;
  bottom: 20px;
  z-index: 50;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 52px;
  height: 52px;
  border-radius: 999px;
  background: var(--accent-pink-muted);
  color: #ffffff;
  text-decoration: none;
  box-shadow:
    0 8px 22px rgba(122, 0, 50, 0.28),
    0 2px 6px rgba(0, 0, 0, 0.08);
  transition: background-color 160ms ease, transform 180ms ease,
              box-shadow 180ms ease;
}
body.cae-pages .newsletter-fab:hover {
  background: var(--accent-pink);
  color: #ffffff;
  transform: translateY(-2px);
  text-decoration: none;
  box-shadow:
    0 10px 26px rgba(122, 0, 50, 0.34),
    0 2px 6px rgba(0, 0, 0, 0.10);
}
body.cae-pages .newsletter-fab:focus-visible {
  outline: none;
  box-shadow:
    0 8px 22px rgba(122, 0, 50, 0.28),
    0 0 0 3px rgba(255, 61, 128, 0.35);
}
body.cae-pages .newsletter-fab svg { width: 24px; height: 24px; }
@media (max-width: 759px) {
  body .newsletter-fab { right: 14px; bottom: 14px; width: 48px; height: 48px; }
  body .newsletter-fab svg { width: 22px; height: 22px; }
}

.node-list__entry {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 14px 0;
  border-bottom: 1px solid var(--rule-faint);
}
.node-list__entry:last-child { border-bottom: 0; }
.node-list__entry--centre .node-list__name {
  color: var(--accent-pink);
  font-weight: 700;
  letter-spacing: 0.08em;
}
/* Magazine-style: image floats to the right of the header and body so
   the heading and bio wrap around it. The block's top edge — where the
   floated image starts — coincides with the heading's top, so name and
   image align at the top by default. */
.node-list__entry--with-image {
  display: block;
  padding: 18px 0;
}
.node-list__entry--with-image::after {
  content: "";
  display: block;
  clear: both;
}
.node-list__img {
  float: right;
  width: 132px;
  height: 132px;
  object-fit: cover;
  border-radius: 2px;
  margin: 0 0 8px 22px;
  shape-outside: margin-box;
}
.node-list__entry--with-image .node-list__name {
  display: block;
  margin-bottom: 6px;
}
.node-list__entry--with-image .node-list__desc {
  display: block;
  text-align: justify;
  hyphens: auto;
}
.node-list__name {
  font-family: var(--mono);
  font-size: 16px;
  font-weight: 500;
  letter-spacing: 0.02em;
  color: var(--accent-pink-muted);
  text-decoration: none;
}
a.node-list__name:hover { color: var(--accent-pink); }
.node-list__desc {
  font-family: var(--sans);
  font-size: 15px;
  font-weight: 500;
  color: var(--fg-pages);
  opacity: 0.92;
}

.node-graph__canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  overflow: hidden;
}

.node-graph__overlay {
  position: absolute;
  inset: 0;
  pointer-events: none;     /* labels re-enable per element */
}

.graph-node {
  position: absolute;
  top: 0;
  left: 0;
  /* Translate is set per-frame by JS. Anchor at the node's centre. */
  transform: translate(-9999px, -9999px);
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 8px 4px 0;
  font-family: var(--sans);
  font-size: 13px;
  font-weight: 500;
  color: var(--fg-pages);
  text-decoration: none;
  pointer-events: auto;
  cursor: pointer;
  white-space: nowrap;
  /* Anchor label so it sits to the right of the dot at the simulated point. */
  translate: 8px -50%;       /* additive offset on top of transform translate */
  transition: color 160ms ease;
  user-select: none;
}
.graph-node:not([href]) { cursor: default; }
.graph-node--no-url { color: var(--fg-pages); }
/* Hovered/focused node lifts above its neighbours so the popup card
   doesn't get cluttered by other labels poking through. */
.graph-node:hover,
.graph-node:focus-visible { z-index: 10; }

/* The on-canvas marker draws the dot; this one is a transparent
   placeholder that pads the label. */
.graph-node__dot {
  display: inline-block;
  width: 0;
  height: 0;
}

.graph-node__name {
  font-family: var(--mono);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.04em;
  color: var(--accent-pink-muted);
  background: rgba(184, 164, 151, 0.25);
  padding: 1px 6px;
  border-radius: 2px;
}
.graph-node--centre .graph-node__name {
  /* background inherited from .graph-node__name above */
  color: var(--accent-pink);
  font-weight: 700;
  font-size: 13px;
  letter-spacing: 0.08em;
  padding: 2px 8px;
}

.graph-node__desc {
  display: none;
}

.graph-node[href]:hover .graph-node__name,
.graph-node:focus-visible .graph-node__name {
  color: var(--accent-pink);
  background: rgba(184, 164, 151, 0.95);
}

/* On hover/focus, surface the description as a small caption under the
   label. Non-blocking — just ambient context.
   `flow-root` (not `block`) so the floated image is contained inside the
   popup box — text wraps around it magazine-style. */
.graph-node:hover .graph-node__desc,
.graph-node:focus-visible .graph-node__desc {
  display: flow-root;
  position: absolute;
  top: 100%;
  left: 0;
  margin-top: 6px;
  min-width: 240px;
  max-width: 320px;
  width: max-content;
  white-space: normal;
  font-family: var(--sans);
  font-size: 14px;
  font-weight: 500;
  line-height: 1.45;
  color: var(--fg-pages);
  background: var(--bg-pages);
  padding: 8px 12px;
  border-left: 2px solid var(--accent-pink-muted);
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.18);
  text-align: justify;
  hyphens: auto;
}

.graph-node__img {
  float: left;
  width: 96px;
  height: 96px;
  object-fit: cover;
  margin: 2px 12px 4px 0;
  border-radius: 2px;
  shape-outside: margin-box;
}
.graph-node__desc-text:empty { display: none; }

/* ==========================================================================
   Flash messages
   ========================================================================== */
.flash {
  list-style: none;
  max-width: var(--measure);
  margin: 60px auto 0;
  padding: 0 var(--gutter);
}
.flash li {
  padding: 8px 12px;
  margin-bottom: 6px;
  background: rgba(0, 0, 0, 0.05);
  border-left: 3px solid var(--accent-pink-muted);
}
