/*
Theme Name: Swing Light Style
Theme URI: https://swinglightstyle.com
Author: Beth Sea
Description: Custom theme for swinglightstyle.com — companion AI safety research platform. Hub-and-spoke architecture with multiple visual worlds. Every visual property is a CSS custom property scoped to page-level classes.
Version: 1.0.0
Requires at least: 6.4
Tested up to: 7.0
Requires PHP: 8.0
License: GNU General Public License v2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html
Text Domain: swinglightstyle
*/

/* ==========================================================================
   CSS VARIABLE ARCHITECTURE

   Every visual property is a custom property. Components reference variables,
   never raw values. Adding a new visual world = new variable overrides.

   Defaults = Homepage / Realistic-Material World
   ========================================================================== */

:root {
  /* --- Content Area --- */
  --content-bg: #BEE2DD;
  --text-color: #000000;
  --text-color-secondary: #333333;
  --link-color: #1a5c4f;
  --link-hover: #0d3b32;

  /* --- Border / Frame --- */
  --border-texture: url('assets/textures/marble.png');
  --border-width: 48px;
  --border-width-vertical: 1in;
  --border-radius: 14px;
  --border-line: none;

  /* --- Homepage Frame Stage --- */
  /* 45.33vh, not 32 (the original 5-frame value) — the homepage has
     dropped to 3 frames now (see front-page.php), and this is a fixed
     per-frame distance, so the total scrollable range (frame-count *
     this, minus one viewport) shrinks every time a frame is cut unless
     this is recalculated to compensate. Original budget: 60vh of scroll
     room across 5 frames = 12vh per transition. Solving frameCount *
     distance - 100 = 12 * frameCount for frameCount = 3 gives 45.33vh —
     recompute this the same way (12 * frameCount + 100) / frameCount if
     the frame count ever changes again. */
  --frame-scroll-distance: 45.33vh;
  --frame-dot-color: #FFFFFF;

  /* --- Homepage Easter Egg (sidebar aside) --- */
  --easter-egg-color: #B8B8B4;

  /* --- Homepage Frame Closing Line --- */
  --closing-line-color: #D3EBE8; /* ~6% lighter than --content-bg (#BEE2DD), same hue */

  /* --- Accent System --- */
  --accent-gradient: linear-gradient(
    135deg,
    #705A1E 0%,
    #C9A84C 25%,
    #FFD700 45%,
    #FFF2B2 50%,
    #FFD700 55%,
    #C9A84C 75%,
    #705A1E 100%
  );
  --accent-color: #C9A84C;
  --accent-flash-color: rgba(255, 235, 140, 0.6);

  /* --- Typography --- */
  --font-body: 'Atkinson Hyperlegible Next', Verdana, Calibri, system-ui, sans-serif;
  --font-heading: 'Roboto Serif', Georgia, Cambria, Charter, serif;
  --font-size-base: 1.125rem;
  --line-height-body: 1.7;

  /* --- Animation --- */
  --animation-easing: cubic-bezier(0.25, 0.1, 0.25, 1.0);
  --animation-duration: 400ms;
  --gold-flash-duration: 500ms;
  --fade-distance: 20px;
  --cascade-delay-step: 150ms;

  /* --- Emphasis Blocks --- */
  --emphasis-bar-gradient: var(--accent-gradient);
  --emphasis-bar-height: 3px;
  --emphasis-font-size: 1.75rem;
  --emphasis-max-width: 80%;

  /* --- Navigation --- */
  --nav-bg: rgba(255, 255, 255, 0.88);
  --nav-text: #000000;
  --nav-hover: var(--accent-color);
  --nav-width: 200px;
  --nav-height: 0px;
}


/* ==========================================================================
   SPOKE OVERRIDES — Each spoke defines its complete visual vocabulary
   ========================================================================== */

/* --- The Work: The Study (cranberry leather, dusty parchment) --- */
.spoke-work {
  --content-bg: #DDD5BE;
  --border-texture: url('assets/textures/leather.png');
  --accent-gradient: linear-gradient(
    135deg,
    #6B5A3E 0%,
    #9C8250 25%,
    #C4A265 50%,
    #9C8250 75%,
    #6B5A3E 100%
  );
  --accent-color: #9C8250;
  --accent-flash-color: rgba(196, 162, 101, 0.5);
  --link-color: #5C4A2E;
  --link-hover: #3D3120;
  --nav-bg: rgba(221, 213, 190, 0.92);
}

/* --- The Conversation: The Bar (worn oak, olive) --- */
.spoke-conversation {
  --content-bg: #CACDA5;
  --border-texture: url('assets/textures/wood.png');
  --accent-gradient: linear-gradient(
    135deg,
    #5C4A2E 0%,
    #8B7355 50%,
    #5C4A2E 100%
  );
  --accent-color: #8B7355;
  --accent-flash-color: rgba(139, 115, 85, 0.4);
  --link-color: #3D3120;
  --link-hover: #2A2115;
  --nav-bg: rgba(202, 205, 165, 0.92);
}

/* --- The Blueprint: The Lab (circuit board, clean white) --- */
.spoke-blueprint {
  --content-bg: #FFFFFF;
  --border-texture: url('assets/textures/circuit-board.png');
  --accent-gradient: linear-gradient(
    135deg,
    #6B3A2A 0%,
    #B87333 25%,
    #DA8A67 50%,
    #B87333 75%,
    #6B3A2A 100%
  );
  --accent-color: #B87333;
  --accent-flash-color: rgba(184, 115, 51, 0.5);
  --link-color: #8B4726;
  --link-hover: #6B3A2A;
  --nav-bg: rgba(255, 255, 255, 0.92);
}

/* --- About: The Fractal (Mandelbrot, fairy lights, black) --- */
.spoke-about {
  --content-bg: #000000;
  --text-color: #FFFFFF;
  --text-color-secondary: #CCCCCC;
  --border-texture: none;
  --border-line: none;
  --accent-color: #FFFFFF;
  --link-color: #88DDFF;
  --link-hover: #BBEFFF;
  --nav-bg: rgba(0, 0, 0, 0.85);
  --nav-text: #FFFFFF;
}

/* --- Field Guide: Placeholder (warmer sea-green) --- */
.spoke-field-guide {
  --content-bg: #C8E6D8;
  --link-color: #1a5c4f;
  --link-hover: #0d3b32;
  --nav-bg: rgba(200, 230, 216, 0.92);
}

/* --- The Method: gingham QR code, black text ---
   --text-color is already #000000 in :root, so it's not restated here.
   The border (--border-texture) uses the gingham/QR image tiled 2x2 across
   the viewport (see .spoke-method .frame-texture above). The content panel
   itself has no image of its own — --content-bg is a translucent white, so
   the fixed border layer shows through it directly. Since .frame-texture is
   position: fixed and .content-area scrolls normally, this means the QR
   pattern visible through the panel actually shifts as the page scrolls,
   rather than being baked into the panel as its own static background.
   Accent/link colors below still inherit :root's teal defaults until Beth
   picks something intentional for this spoke. */
.spoke-method {
  --content-bg: rgba(255, 255, 255, 0.9);
  --border-texture: url('assets/textures/gingham-qr.png');
  --nav-bg: rgba(255, 255, 255, 0.92);
}

/* Back-to-top button on The Method uses The Work's bronze/brass gradient
   (same values as .spoke-work's --accent-gradient) rather than :root's
   shinier gold — Beth's call, scoped to just this button since Method's
   own accent palette is still an open decision (see README). Duplicated
   literally rather than shared via a variable, matching how every other
   spoke's colors are self-contained rather than cross-referenced. */
.spoke-method .back-to-top {
  background: linear-gradient(
    135deg,
    #6B5A3E 0%,
    #9C8250 25%,
    #C4A265 50%,
    #9C8250 75%,
    #6B5A3E 100%
  );
}

/* --- LOology: plain white/black, inverted nav ---
   --text-color is already #000000 in :root, so it's not restated here.
   No border texture — plain white content, black text, matching Beth's
   spec exactly. --nav-bg/--nav-text reuse the same black-nav values as
   .spoke-about (only spoke besides this one with a dark nav) rather than
   inventing a new black. Accent/link colors still inherit :root's teal
   defaults — not specified, so left alone rather than guessed. */
.spoke-loology {
  --content-bg: #FFFFFF;
  --border-texture: none;
  --nav-bg: rgba(0, 0, 0, 0.85);
  --nav-text: #FFFFFF;
}


/* ==========================================================================
   TYPOGRAPHY
   ========================================================================== */

body {
  font-family: var(--font-body);
  font-size: var(--font-size-base);
  line-height: var(--line-height-body);
  color: var(--text-color);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

h1, h2, h3, h4, h5, h6 {
  font-family: var(--font-heading);
  font-weight: 500;
  color: var(--text-color);
}

h1 {
  font-size: 2.625rem;
  line-height: 1.2;
  margin-bottom: 0.5em;
}

h2 {
  font-size: 2rem;
  line-height: 1.25;
  margin-bottom: 0.5em;
}

h3 {
  font-size: 1.5rem;
  line-height: 1.3;
  margin-bottom: 0.5em;
}

h4 {
  font-size: 1.25rem;
  line-height: 1.35;
  margin-bottom: 0.5em;
}

.hero-text {
  font-family: var(--font-heading);
  font-size: 3.5rem;
  font-weight: 500;
  line-height: 1.1;
}

.hero-parenthetical {
  font-family: var(--font-body);
  font-size: 1.25rem;
  font-weight: 400;
  line-height: 1.5;
  cursor: default;
}

a {
  color: var(--link-color);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.15em;
  transition: color var(--animation-duration) var(--animation-easing);
}

a:hover,
a:focus-visible {
  color: var(--link-hover);
}

p {
  margin-bottom: 1em;
}

.small-text,
.caption,
.paper-meta {
  font-size: 0.875rem;
  line-height: 1.5;
}

.paper-meta {
  color: var(--text-color-secondary);
}

/* Deliberate break from the site's normal left-aligned text and paragraph
   flow — Beth's call. Pinned near the bottom-right corner of whichever
   .frame-slide it's in (position: absolute, so it's pulled out of the
   flex column that centers the rest of the frame's content), in a color
   only barely lighter than the background — meant to read as a quiet
   aside, not a headline. First use: homepage Frame 2. */
.frame-closing-line {
  position: absolute;
  right: 4rem;
  bottom: 30px;
  text-align: right;
  color: var(--closing-line-color);
}


/* ==========================================================================
   LAYOUT — Frame + Content Area
   ========================================================================== */

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

html {
  scroll-behavior: smooth;
}

body {
  margin: 0;
  padding: 0;
  background-color: var(--content-bg);
}

.site-frame {
  position: relative;
  min-height: 100vh;
  padding: var(--border-width);
}

.frame-texture {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-image: var(--border-texture);
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  z-index: 0;
  pointer-events: none;
}

.spoke-about .frame-texture {
  background-image: none;
}

/* The Fractal's animated border — replaces the static .frame-texture
   photo with a computed one. Same fixed-full-viewport footprint as
   every other spoke's border, just rendered instead of photographed. */
.frame-mandelbrot,
.frame-fairy-lights {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  pointer-events: none;
}

.frame-mandelbrot {
  z-index: 0;
}

.frame-fairy-lights {
  /* Above .content-area (z-index: 1), not below — the bloom is meant to
     bleed softly onto the black content edge (spec), which only reads
     correctly if the glow paints over that edge rather than being
     clipped flush by it. .content-area's own padding (3rem/4rem) keeps
     all real text well clear of the 12px bloom radius, so nothing gets
     covered. */
  z-index: 2;
}

/* .content-area is one continuous ~6-viewport-tall box (all six beats
   stacked, for scroll-snap) — full-bleed and edge-to-edge is right for
   every other spoke, but on The Fractal it means the black interior
   never actually leaves any room for the Mandelbrot border to show
   around it. Each beat's own .fade-reveal wrapper becomes the framed
   black box instead — bounded on all sides so the border is always
   visible around it, moved off the (unbounded) .content-area. */
.spoke-about .content-area {
  background: none;
  padding: 0;
  max-width: none;
  margin: 0;
  border-radius: 0;
  min-height: auto;
}

.spoke-about .snap-section {
  /* The base .snap-section rule adds its own 4rem/0 padding, which
     fights with centering the boxed .fade-reveal cleanly within the
     full viewport height — pure flex centering handles it here. */
  padding: 0;
}

.spoke-about .snap-section > .fade-reveal {
  width: 100%;
  max-width: 1200px;
  max-height: calc(100vh - 2 * var(--border-width));
  margin: 0 auto;
  padding: 3rem 4rem;
  box-sizing: border-box;
  /* Translucent, not solid — lets the Mandelbrot show through behind
     the text instead of hiding it. Text shadow keeps white text
     legible against a busy, brightly-colored moving background. */
  background: rgba(0, 0, 0, 0.82);
  text-shadow: 0 1px 8px rgba(0, 0, 0, 0.8);
  color: var(--text-color);
  border-radius: var(--border-radius);
  overflow: auto;
}

/* The Method's border tiles the gingham/QR image at a fixed 540px square
   (Beth's number — roughly half of 1080p) so each tile renders at a
   designated, undistorted size (single value = same width and height,
   no stretch). background-repeat: repeat then populates as many 540px
   tiles as fit right and down — the tile size stays constant, the count
   varies with screen size. */
.spoke-method .frame-texture {
  background-size: 540px;
  background-repeat: repeat;
}

.content-area {
  position: relative;
  z-index: 1;
  background-color: var(--content-bg);
  color: var(--text-color);
  /* Rounded on top only — .site-footer immediately below it rounds only
     its bottom corners (0 0 var(--border-radius) var(--border-radius)).
     Together they read as one continuous box instead of two stacked
     rounded boxes with a mismatched seam between them. */
  border-radius: var(--border-radius) var(--border-radius) 0 0;
  border: var(--border-line);
  min-height: calc(100vh - var(--border-width) * 2);
  padding: 3rem 4rem;
  max-width: 1200px;
  margin: 0 auto;
}

.content-area > *:first-child {
  margin-top: 0;
}

.content-area > *:last-child {
  margin-bottom: 0;
}


/* ==========================================================================
   NAVIGATION — Fixed left sidebar, the one constant across all visual worlds
   ========================================================================== */

.site-header {
  position: fixed;
  top: 0;
  left: 0;
  bottom: 0;
  z-index: 100;
  width: var(--nav-width);
  background: var(--nav-bg);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  display: flex;
  flex-direction: column;
  padding: 2rem 1.5rem;
  transition: background var(--animation-duration) var(--animation-easing);
}

.site-header .site-title {
  font-family: var(--font-heading);
  font-size: 1.125rem;
  font-weight: 500;
  color: var(--nav-text);
  text-decoration: none;
  margin-bottom: 2rem;
}

.site-header .site-title:hover {
  color: var(--nav-hover);
}

.primary-nav {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  list-style: none;
  margin: 0;
  padding: 0;
}

.primary-nav a {
  font-family: var(--font-body);
  font-size: 1rem;
  font-weight: 500;
  line-height: 1.4;
  color: var(--nav-text);
  text-decoration: none;
  display: block;
  padding: 0.5rem 0;
  transition: color var(--animation-duration) var(--animation-easing);
}

.primary-nav a:hover,
.primary-nav a:focus-visible {
  color: var(--nav-hover);
}

.primary-nav .current-menu-item a,
.primary-nav .current_page_item a {
  color: var(--nav-hover);
}

/* Mobile hamburger */
.nav-toggle {
  display: none;
  background: none;
  border: none;
  cursor: pointer;
  padding: 0.5rem;
  color: var(--nav-text);
}

.nav-toggle span {
  display: block;
  width: 24px;
  height: 2px;
  background: currentColor;
  margin: 5px 0;
  transition: transform 0.3s ease;
}

/* Homepage-only: quiet aside revealed on hover, text only, no container */
.sidebar-easter-egg {
  margin-top: auto;
  color: var(--easter-egg-color);
  font-family: var(--font-body);
  font-size: 0.875rem;
  line-height: 1.5;
  opacity: 0;
  transform: translateY(6px);
  pointer-events: none;
  transition:
    opacity var(--animation-duration) var(--animation-easing),
    transform var(--animation-duration) var(--animation-easing);
}

.sidebar-easter-egg.is-visible {
  opacity: 1;
  transform: translateY(0);
}

/* Second homepage easter egg: hovering .frame-closing-line reveals this in
   the top right of Frame 2 (see animations.js). Asymmetric timing is
   deliberate, not the shared --animation-duration — fades in slowly
   (1.5s), out quickly (0.2s). The two durations live on different rules
   (base vs. .is-visible) because a CSS transition reads its duration from
   whichever rule is active at the moment the change starts — the fast
   duration governs the fade-out precisely because it's the one declared
   on the state being transitioned OUT of. */
.hand-popup {
  position: absolute;
  top: 2rem;
  right: 2rem;
  z-index: 5;
  max-width: 180px;
  height: auto;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.2s ease-out;
}

.hand-popup.is-visible {
  opacity: 0.5;
  transition: opacity 1.5s ease-in;
}

@media (max-width: 768px) {
  .site-header {
    top: 0;
    left: 0;
    right: 0;
    bottom: auto;
    width: auto;
    height: var(--nav-height);
    flex-direction: row;
    align-items: center;
    padding: 0 2rem;
  }

  .site-header .site-title {
    margin-bottom: 0;
    margin-right: auto;
  }

  .sidebar-easter-egg {
    display: none;
  }

  .nav-toggle {
    display: block;
  }

  .primary-nav {
    display: none;
    position: absolute;
    top: var(--nav-height);
    left: 0;
    right: 0;
    flex-direction: column;
    gap: 0;
    background: var(--nav-bg);
    backdrop-filter: blur(12px);
    padding: 1rem 0;
  }

  .primary-nav.is-open {
    display: flex;
  }

  .primary-nav a {
    padding: 0.75rem 2rem;
  }

  .content-area {
    padding: 2rem 1.5rem;
  }
}

/* Account for fixed sidebar (desktop) / top bar (mobile) */
.site-frame {
  padding-top: calc(var(--nav-height) + var(--border-width));
  padding-left: calc(var(--nav-width) + var(--border-width));
}


/* ==========================================================================
   EMPHASIS BLOCK — "Key Point" (gold bars framing a statement)
   ========================================================================== */

.emphasis-block {
  max-width: var(--emphasis-max-width);
  margin: 3rem auto;
  padding: 2rem 0;
  text-align: center;
}

.emphasis-bar {
  height: var(--emphasis-bar-height);
  background: var(--emphasis-bar-gradient);
  border-radius: 1px;
  position: relative;
  overflow: hidden;
}

.emphasis-bar::after {
  content: '';
  position: absolute;
  top: 0;
  left: -50%;
  width: 50%;
  height: 100%;
  background: linear-gradient(
    90deg,
    transparent 0%,
    var(--accent-flash-color) 50%,
    transparent 100%
  );
  opacity: 0;
}

.emphasis-block.is-visible .emphasis-bar::after {
  opacity: 1;
  animation: flash-sweep var(--gold-flash-duration) var(--animation-easing) forwards;
  animation-delay: calc(var(--cascade-delay-step) * 3);
}

.emphasis-text {
  font-family: var(--font-heading);
  font-size: var(--emphasis-font-size);
  font-weight: 400;
  line-height: 1.4;
  color: var(--text-color);
  padding: 1.5rem 1rem;
  margin: 0;
}


/* ==========================================================================
   PULL QUOTE — magazine-style large quote, distinct from .emphasis-text
   (which uses a top/bottom bar pair). This one uses a single left rule
   instead, closer to how a print magazine sets a pull quote. The rule is
   the same metallic --accent-gradient as .accent-line (a plain border
   can't take a gradient, so it's a ::before bar instead — same technique
   as .emphasis-bar, just vertical instead of horizontal).
   First use of this pattern: homepage Frame 2 ("The Credibility Gap").
   ========================================================================== */

.pull-quote {
  font-family: var(--font-heading);
  font-style: italic;
  font-size: 2.25rem;
  font-weight: 400;
  line-height: 1.35;
  color: var(--text-color);
  max-width: 85%;
  margin: 2rem 0 0;
  padding-left: 1.5rem;
  position: relative;
}

.pull-quote::before {
  content: '';
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 4px;
  background: var(--accent-gradient);
}

.pull-quote p {
  margin: 0;
}

@media (max-width: 992px) {
  .pull-quote {
    font-size: 1.75rem;
    max-width: 100%;
  }
}


/* ==========================================================================
   METALLIC ACCENT SYSTEM
   ========================================================================== */

.accent-gold {
  background: var(--accent-gradient);
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  background-clip: text;
}

.accent-line {
  height: 2px;
  background: var(--accent-gradient);
  border: none;
  margin: 2rem 0;
  position: relative;
  overflow: hidden;
}

.accent-underline {
  text-decoration: none;
  background-image: var(--accent-gradient);
  background-size: 100% 2px;
  background-position: 0 100%;
  background-repeat: no-repeat;
  padding-bottom: 2px;
}


/* ==========================================================================
   ANIMATIONS — Fade-and-reveal + Gold flash
   ========================================================================== */

@keyframes flash-sweep {
  0% { left: -50%; }
  100% { left: 150%; }
}

@keyframes ambient-shimmer {
  0% { background-position: 0% center; }
  100% { background-position: 200% center; }
}

/* Fade-and-reveal: elements start hidden, JS adds .is-visible */
.fade-reveal {
  opacity: 0;
  transform: translateY(var(--fade-distance));
  transition:
    opacity var(--animation-duration) var(--animation-easing),
    transform var(--animation-duration) var(--animation-easing);
}

.fade-reveal.is-visible {
  opacity: 1;
  transform: translateY(0);
}

/* Cascade delays for sequential reveal within a section */
.fade-reveal.delay-1 { transition-delay: calc(var(--cascade-delay-step) * 1); }
.fade-reveal.delay-2 { transition-delay: calc(var(--cascade-delay-step) * 2); }
.fade-reveal.delay-3 { transition-delay: calc(var(--cascade-delay-step) * 3); }
.fade-reveal.delay-4 { transition-delay: calc(var(--cascade-delay-step) * 4); }
.fade-reveal.delay-5 { transition-delay: calc(var(--cascade-delay-step) * 5); }


/* ==========================================================================
   SCROLL-SNAP SECTIONS
   ========================================================================== */

html:has(.snap-container) {
  scroll-snap-type: y mandatory;
}

.snap-section {
  scroll-snap-align: start;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 4rem 0;
}

.snap-section--full {
  align-items: center;
  text-align: center;
}


/* ==========================================================================
   HOMEPAGE FRAME STAGE — the box stays fixed on screen; a tall scroll
   track behind it (.frame-track) drives which .frame-slide is active as
   the visitor scrolls. Native scrolling throughout (keyboard/trackpad/
   screen-reader friendly) — nothing is scroll-hijacked.
   ========================================================================== */

.frame-track {
  position: relative;
  height: calc(var(--frame-count, 5) * var(--frame-scroll-distance));
}

.frame-stage {
  position: sticky;
  top: var(--border-width-vertical);
  height: calc(100vh - var(--border-width-vertical) * 2);
  max-width: 1200px;
  margin: 0 auto;
  overflow: hidden;
  border-radius: var(--border-radius);
  border: var(--border-line);
  background-color: var(--content-bg);
  color: var(--text-color);
}

.frame-slide {
  position: absolute;
  inset: 0;
  z-index: 1;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 3rem 4rem;
  overflow-y: auto;
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--animation-duration) var(--animation-easing);
}

.frame-slide.is-active {
  opacity: 1;
  visibility: visible;
}

.frame-slide--full {
  align-items: center;
  text-align: center;
}

.frame-dots {
  position: absolute;
  top: 50%;
  right: 1.5rem;
  z-index: 2;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  transform: translateY(-50%);
}

.frame-dot {
  width: 10px;
  height: 10px;
  padding: 0;
  border: 2px solid var(--frame-dot-color);
  border-radius: 50%;
  background: transparent;
  cursor: pointer;
  transition:
    background var(--animation-duration) var(--animation-easing),
    transform var(--animation-duration) var(--animation-easing);
}

.frame-dot:hover,
.frame-dot:focus-visible {
  transform: scale(1.3);
}

.frame-dot.is-active {
  background: var(--frame-dot-color);
}


/* ==========================================================================
   PAPER CARDS (The Work archive)
   ========================================================================== */

.paper-card {
  background: var(--content-bg);
  border-radius: 8px;
  padding: 1.5rem 2rem;
  margin-bottom: 1.5rem;
  border-left: 3px solid var(--accent-color);
  transition: border-color var(--animation-duration) var(--animation-easing);
}

.paper-card:hover {
  border-left-color: var(--link-hover);
}

.paper-card__title {
  font-family: var(--font-heading);
  font-size: 1.25rem;
  font-weight: 500;
  margin-bottom: 0.25rem;
}

.paper-card__title a {
  text-decoration: none;
  color: var(--text-color);
}

.paper-card__title a:hover {
  color: var(--link-color);
}

.paper-card__meta {
  font-size: 0.875rem;
  color: var(--text-color-secondary);
  margin-bottom: 0.75rem;
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 1.5rem;
}

.paper-card__status {
  display: inline-block;
  font-size: 0.75rem;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  padding: 0.15em 0.6em;
  border-radius: 3px;
}

.paper-card__status--published {
  background: #2D6A4F;
  color: #FFFFFF;
}

.paper-card__status--in-progress {
  background: #B8860B;
  color: #FFFFFF;
}

.paper-card__status--planned {
  background: #6C757D;
  color: #FFFFFF;
}

.paper-card__abstract {
  font-size: 0.9375rem;
  line-height: 1.6;
  color: var(--text-color-secondary);
}

.paper-card__links {
  margin-top: 1rem;
  display: flex;
  gap: 1rem;
  flex-wrap: wrap;
}


/* ==========================================================================
   LOOLOGY INDEX (LOology archive) — deliberately NOT card-styled like The
   Work's paper archive above. Beth's call: this should read as a plain
   repository listing, not a designed content archive.
   ========================================================================== */

.loology-index {
  list-style: none;
  margin: 0;
  padding: 0;
  font-family: monospace;
}

.loology-index__row {
  padding: 0.4rem 0;
  border-bottom: 1px solid var(--text-color-secondary);
}

.loology-index__serial {
  display: inline-block;
  width: 13em;
  color: var(--text-color-secondary);
}

.loology-index__date {
  display: inline-block;
  width: 7em;
  color: var(--text-color-secondary);
}

.loology-index__row a {
  color: var(--text-color);
}

.loology-index__row a:hover {
  color: var(--link-color);
}


/* ==========================================================================
   SINGLE LOOLOGY POST
   ========================================================================== */

.loology-post__header {
  margin-bottom: 2rem;
  padding-bottom: 1.5rem;
  border-bottom: 1px solid var(--accent-color);
}

.loology-post__meta {
  font-size: 0.875rem;
  color: var(--text-color-secondary);
}


/* ==========================================================================
   SINGLE PAPER PAGE
   ========================================================================== */

.paper-header {
  margin-bottom: 2rem;
  padding-bottom: 1.5rem;
  border-bottom: 1px solid var(--accent-color);
}

.paper-header__title {
  font-size: 2rem;
  line-height: 1.2;
  margin-bottom: 0.5rem;
}

.paper-header__author {
  font-size: 1rem;
  margin-bottom: 0.25rem;
}

.paper-header__orcid {
  font-size: 0.875rem;
  color: var(--text-color-secondary);
}

.paper-header__orcid a {
  color: var(--link-color);
}

/* Divider before the repeated title block, ahead of the full-text body */
.paper-divider {
  height: var(--emphasis-bar-height);
  background: var(--accent-gradient);
  border-radius: 1px;
  margin: 3rem 0 2rem;
}

/* Repeated title/author/ORCID above the full text — reuses the .paper-header__*
   typography classes, but skips .paper-header's own border since .paper-divider
   already marks the break above it. */
.paper-header-repeat {
  margin-bottom: 2rem;
}

.paper-abstract {
  margin: 2rem 0;
  padding: 1.5rem 2rem;
  background: rgba(0, 0, 0, 0.03);
  border-radius: 8px;
  border-left: 3px solid var(--accent-color);
}

.paper-abstract__heading {
  font-size: 1.25rem;
  margin-bottom: 0.75rem;
}

.paper-body {
  margin: 2rem 0;
}

.paper-references {
  margin-top: 3rem;
  padding-top: 2rem;
  border-top: 1px solid rgba(0, 0, 0, 0.1);
}

.paper-references h2 {
  font-size: 1.5rem;
}

.paper-actions {
  display: flex;
  gap: 1rem;
  flex-wrap: wrap;
  margin: 2rem 0;
}

.btn {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.75rem 1.5rem;
  border-radius: 6px;
  font-family: var(--font-body);
  font-size: 0.9375rem;
  font-weight: 500;
  text-decoration: none;
  cursor: pointer;
  border: none;
  transition: all var(--animation-duration) var(--animation-easing);
}

.btn--primary {
  background: var(--accent-gradient);
  color: #000000;
}

.btn--primary:hover {
  filter: brightness(1.1);
  color: #000000;
}

.btn--secondary {
  background: transparent;
  color: var(--text-color);
  border: 1px solid var(--accent-color);
}

.btn--secondary:hover {
  background: rgba(0, 0, 0, 0.05);
  color: var(--text-color);
}

.paper-pdf-embed {
  width: 100%;
  height: 80vh;
  border: none;
  border-radius: 8px;
  margin: 2rem 0;
}

.video-embed {
  position: relative;
  width: 100%;
  max-width: 560px;
  aspect-ratio: 16 / 9;
  margin: 2rem 0;
}

.video-embed iframe {
  width: 100%;
  height: 100%;
  border: none;
  border-radius: 8px;
}


/* ==========================================================================
   SPOKE CARDS (Homepage Frame 4 — the four doors)
   ========================================================================== */

.spoke-cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  gap: 2rem;
  margin: 2rem 0;
}

.doors-copyright {
  text-align: center;
  color: #000000;
  margin-top: 1.5rem;
}

/* The Conversation's "under construction" banner — the joke is that it
   looks physically taped onto the page: tilted a couple degrees off
   axis like a hand applied it, lifted off the surface with a drop
   shadow, actual (fake) strips of tape at the top corners holding it
   down. Sits slightly on top of the H1 below it (negative margin) so it
   reads as layered on top of the page rather than just another block
   in the flow. */
.under-construction {
  position: relative;
  transform: rotate(-2deg);
  margin: 1rem auto 0.5rem;
  max-width: 480px;
  z-index: 2;
}

.under-construction__banner {
  background: #CC0000;
  color: #FFFFFF;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  text-align: center;
  padding: 1rem 1.5rem;
  margin: 0;
  border: 3px solid #FFFFFF;
  box-shadow: 0 10px 20px rgba(0, 0, 0, 0.35);
  font-size: 1.25rem;
}

.under-construction__banner span {
  color: #FFD700;
  font-size: 1.4em;
  vertical-align: middle;
}

/* Fake tape strips — semi-transparent, rotated opposite the banner
   itself so they read as separate pieces stuck across the corners
   rather than part of the banner's own rotation. */
.under-construction::before,
.under-construction::after {
  content: '';
  position: absolute;
  top: -14px;
  width: 70px;
  height: 28px;
  background: rgba(255, 255, 255, 0.55);
  border: 1px solid rgba(255, 255, 255, 0.7);
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}

.under-construction::before {
  left: -20px;
  transform: rotate(-40deg);
}

.under-construction::after {
  right: -20px;
  transform: rotate(40deg);
}

.under-construction__joke {
  text-align: center;
  color: var(--text-color-secondary);
  font-size: 0.9375rem;
  margin: 0 0 2rem;
}

.spoke-card {
  padding: 2rem;
  border-radius: var(--border-radius);
  background: rgba(0, 0, 0, 0.04);
  text-decoration: none;
  color: var(--text-color);
  transition: all var(--animation-duration) var(--animation-easing);
}

.spoke-card:hover {
  background: rgba(0, 0, 0, 0.08);
  transform: translateY(-2px);
  color: var(--text-color);
}

.spoke-card__title {
  font-family: var(--font-heading);
  font-size: 1.25rem;
  font-weight: 500;
  margin-bottom: 0.5rem;
}

.spoke-card__description {
  font-size: 0.9375rem;
  line-height: 1.6;
}


/* ==========================================================================
   APPEARANCE CARDS (The Conversation)
   ========================================================================== */

.appearance-card {
  padding: 1.5rem 0;
  border-bottom: 1px solid rgba(0, 0, 0, 0.1);
}

.appearance-card:last-child {
  border-bottom: none;
}

.appearance-card__show {
  font-family: var(--font-heading);
  font-size: 1.125rem;
  font-weight: 500;
}

.appearance-card__details {
  font-size: 0.875rem;
  color: var(--text-color-secondary);
  margin-top: 0.25rem;
}


/* ==========================================================================
   FOOTER
   ========================================================================== */

.site-footer {
  position: relative;
  z-index: 1;
  text-align: center;
  padding: 1.5rem 2rem;
  font-size: 0.8125rem;
  color: var(--text-color-secondary);
  background: var(--content-bg);
  border-radius: 0 0 var(--border-radius) var(--border-radius);
  max-width: 1200px;
  margin: 0 auto;
}

/* Third homepage easter egg: the (c) symbol links to /loology/, but must
   not read as a link at all — no color, underline, or cursor change.
   Same "stays visually inert on purpose" principle as the other two. */
.copyright-link,
.copyright-link:hover,
.copyright-link:visited {
  color: inherit;
  text-decoration: none;
  cursor: inherit;
}

.visit-counter {
  margin-top: 0.25rem;
  opacity: 0.7;
}


/* ==========================================================================
   BACK TO TOP — hidden until scrolled past a threshold (see animations.js),
   plain anchor to #top so it rides the existing global smooth-scroll
   instead of needing its own JS scroll handler. Skipped on the homepage
   (see footer.php) — its frame-dots already cover this role.
   ========================================================================== */

.back-to-top {
  position: fixed;
  bottom: 2rem;
  right: 2rem;
  z-index: 20;
  width: 48px;
  height: 48px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: var(--accent-gradient);
  color: var(--content-bg);
  font-size: 1.25rem;
  text-decoration: none;
  opacity: 0;
  pointer-events: none;
  transform: translateY(10px);
  transition:
    opacity var(--animation-duration) var(--animation-easing),
    transform var(--animation-duration) var(--animation-easing);
}

.back-to-top.is-visible {
  opacity: 1;
  pointer-events: auto;
  transform: translateY(0);
}

.back-to-top:hover {
  transform: translateY(-2px);
}

@media (max-width: 768px) {
  .back-to-top {
    bottom: 1rem;
    right: 1rem;
    width: 40px;
    height: 40px;
    font-size: 1.1rem;
  }
}


/* ==========================================================================
   ACCESSIBILITY — prefers-reduced-motion
   ========================================================================== */

@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;
  }

  .fade-reveal {
    opacity: 1;
    transform: none;
  }

  html:has(.snap-container) {
    scroll-snap-type: none;
  }
}


/* ==========================================================================
   RESPONSIVE
   ========================================================================== */

@media (max-width: 1024px) {
  :root {
    --border-width: 32px;
    --border-width-vertical: 32px;
  }

  .hero-text {
    font-size: 2.5rem;
  }

  h1 {
    font-size: 2rem;
  }

  h2 {
    font-size: 1.625rem;
  }
}

@media (max-width: 768px) {
  :root {
    --border-width: 20px;
    --border-width-vertical: 20px;
    --nav-width: 0px;
    --nav-height: 56px;
  }

  .hero-text {
    font-size: 2rem;
  }

  h1 {
    font-size: 1.75rem;
  }

  .emphasis-block {
    max-width: 95%;
  }

  .emphasis-text {
    font-size: 1.375rem;
  }

  .pull-quote {
    font-size: 1.5rem;
    padding-left: 1rem;
  }

  .paper-header__title {
    font-size: 1.5rem;
  }

  .spoke-cards {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 480px) {
  :root {
    --border-width: 12px;
    --border-width-vertical: 12px;
    --font-size-base: 1rem;
  }

  .content-area {
    padding: 1.5rem 1rem;
  }

  .frame-slide {
    padding: 1.5rem 1rem;
  }

  .hero-text {
    font-size: 1.625rem;
  }
}
