/* =========================================================
   Jack O'Connor — personal site
   Editorial, warm, minimal. Off-white paper aesthetic.
   ========================================================= */

:root {
  /* Color */
  --paper: #fbfaf6;
  --paper-warm: #f5f2ea;
  --surface: #ffffff;
  --ink: #15140f;
  --ink-soft: #3c3a35;
  --muted: #6e6a62;
  --hairline: #e5e0d5;
  --hairline-soft: #efeae0;
  --accent: #b85c2c;
  --accent-soft: #d8c4b3;

  /* Type */
  --serif: "Fraunces", "Cormorant Garamond", Georgia, serif;
  --sans: "Inter", -apple-system, BlinkMacSystemFont, "Helvetica Neue", Arial, sans-serif;
  --mono: "JetBrains Mono", ui-monospace, "SF Mono", Menlo, monospace;

  /* Layout */
  --measure: 38rem;        /* ideal reading width */
  --measure-wide: 64rem;   /* wider grids */
  --measure-full: 84rem;   /* full container */
  --gutter: clamp(1.25rem, 4vw, 2.5rem);
  --rhythm: 1.6rem;

  /* Motion */
  --ease: cubic-bezier(0.2, 0.7, 0.2, 1);
}

/* ---------- Reset-ish ---------- */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; text-size-adjust: 100%; }
body, h1, h2, h3, h4, h5, p, figure, blockquote, dl, dd { margin: 0; }
img, picture, video, canvas, svg { display: block; max-width: 100%; }
button, input, textarea, select { font: inherit; color: inherit; }
a { color: inherit; }

/* ---------- Base ---------- */
html { scroll-behavior: smooth; }

body {
  background: var(--paper);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 17px;
  line-height: 1.6;
  font-feature-settings: "ss01", "cv11";
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

/* ---------- Typography ---------- */
h1, h2, h3, h4 {
  font-family: var(--serif);
  font-weight: 400;
  color: var(--ink);
  letter-spacing: -0.01em;
  line-height: 1.1;
}

h1 {
  font-size: clamp(2.5rem, 6vw, 4.25rem);
  font-weight: 350;
  letter-spacing: -0.025em;
}

h2 {
  font-size: clamp(1.75rem, 3.5vw, 2.5rem);
  font-weight: 380;
  letter-spacing: -0.02em;
  margin-bottom: 0.75rem;
}

h3 {
  font-size: 1.4rem;
  font-weight: 420;
  letter-spacing: -0.01em;
}

p { color: var(--ink-soft); }
p + p { margin-top: 1em; }

em { font-style: italic; font-family: var(--serif); }

a {
  text-decoration: none;
  border-bottom: 1px solid var(--hairline);
  transition: border-color 200ms var(--ease), color 200ms var(--ease);
}
a:hover { border-color: var(--accent); }

/* ---------- Layout containers ---------- */
.container {
  width: 100%;
  max-width: var(--measure-full);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.measure { max-width: var(--measure); }
.measure-wide { max-width: var(--measure-wide); }

main { flex: 1; }

/* ---------- Header / nav ---------- */
.site-header {
  position: sticky;
  top: 0;
  z-index: 50;
  background: color-mix(in srgb, var(--paper) 90%, transparent);
  backdrop-filter: saturate(160%) blur(12px);
  -webkit-backdrop-filter: saturate(160%) blur(12px);
  border-bottom: 1px solid var(--hairline-soft);
}

.site-header .container {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 2rem;
  padding-block: 1rem;
}

.brand {
  font-family: var(--serif);
  font-size: 1.25rem;
  font-weight: 420;
  letter-spacing: -0.01em;
  border: 0;
}

.brand .dot {
  display: inline-block;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--accent);
  margin-left: 0.4em;
  vertical-align: 0.15em;
}

.nav {
  display: flex;
  gap: clamp(1rem, 2.5vw, 2rem);
  font-size: 0.92rem;
  color: var(--muted);
}

.nav a {
  border: 0;
  position: relative;
  padding-block: 0.25rem;
  transition: color 200ms var(--ease);
}

.nav a::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: -2px;
  height: 1px;
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 250ms var(--ease);
}

.nav a:hover { color: var(--ink); }
.nav a:hover::after,
.nav a[aria-current="page"]::after { transform: scaleX(1); }
.nav a[aria-current="page"] { color: var(--ink); }

.nav-toggle {
  display: none;
  background: none;
  border: 0;
  cursor: pointer;
  padding: 0.5rem;
}

@media (max-width: 720px) {
  .nav { display: none; }
  .nav.open {
    display: flex;
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    flex-direction: column;
    background: var(--paper);
    padding: 1rem var(--gutter) 1.5rem;
    border-bottom: 1px solid var(--hairline);
    gap: 1rem;
  }
  .nav-toggle { display: block; }
}

/* ---------- Hero ---------- */
.hero {
  padding-block: clamp(4rem, 12vw, 8rem) clamp(3rem, 8vw, 5rem);
}

.eyebrow {
  font-family: var(--sans);
  font-size: 0.78rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 1.5rem;
}

.hero h1 em { color: var(--accent); }

.lede {
  font-family: var(--serif);
  font-size: clamp(1.15rem, 2vw, 1.4rem);
  line-height: 1.55;
  color: var(--ink-soft);
  font-weight: 350;
  margin-top: 2rem;
  max-width: 36rem;
}

/* ---------- Section building blocks ---------- */
section { padding-block: clamp(3rem, 7vw, 5.5rem); }

.section-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 2rem;
  margin-bottom: 2.5rem;
  border-bottom: 1px solid var(--hairline);
  padding-bottom: 1.25rem;
}

.section-head .label {
  font-size: 0.78rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--muted);
}

.section-head h2 { margin: 0; }

.section-head a { color: var(--muted); border: 0; font-size: 0.9rem; }
.section-head a:hover { color: var(--accent); }

/* ---------- Index grid of sections ---------- */
.index-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 1px;
  background: var(--hairline);
  border-block: 1px solid var(--hairline);
}

.index-card {
  background: var(--paper);
  padding: 2.5rem 2rem;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  text-decoration: none;
  border: 0;
  transition: background 250ms var(--ease);
  min-height: 220px;
  justify-content: space-between;
}

.index-card:hover { background: var(--paper-warm); }

.index-card .num {
  font-family: var(--mono);
  font-size: 0.78rem;
  color: var(--muted);
  letter-spacing: 0.05em;
}

.index-card h3 {
  font-size: 1.6rem;
  font-weight: 380;
}

.index-card p {
  font-size: 0.95rem;
  color: var(--muted);
  margin-top: 0.5rem;
}

.index-card .arrow {
  font-family: var(--serif);
  color: var(--accent);
  font-size: 1.25rem;
  margin-top: 1rem;
}

/* ---------- Posts list ---------- */
.posts {
  list-style: none;
  padding: 0;
  margin: 0;
  border-top: 1px solid var(--hairline);
}

.post-item {
  border-bottom: 1px solid var(--hairline);
}

.post-item a {
  display: grid;
  grid-template-columns: 8rem 1fr auto;
  gap: 2rem;
  padding-block: 1.5rem;
  border: 0;
  align-items: baseline;
}

.post-item .date {
  font-family: var(--mono);
  font-size: 0.82rem;
  color: var(--muted);
}

.post-item h3 {
  font-size: 1.35rem;
  font-weight: 400;
  transition: color 200ms var(--ease);
}

.post-item .read {
  font-size: 0.85rem;
  color: var(--muted);
}

.post-item a:hover h3 { color: var(--accent); }

@media (max-width: 640px) {
  .post-item a { grid-template-columns: 1fr; gap: 0.4rem; }
  .post-item .read { display: none; }
}

/* ---------- Article (long-form) ---------- */
.article {
  padding-block: clamp(3rem, 7vw, 5rem);
}
.article-header {
  max-width: var(--measure);
  margin-inline: auto;
  margin-bottom: 3rem;
  text-align: left;
}
.article-header .meta {
  font-family: var(--mono);
  font-size: 0.82rem;
  color: var(--muted);
  margin-bottom: 1rem;
}
.article-body {
  max-width: var(--measure);
  margin-inline: auto;
  font-size: 1.05rem;
  line-height: 1.75;
}
.article-body p, .article-body ul, .article-body ol, .article-body blockquote {
  margin-bottom: 1.4em;
  color: var(--ink-soft);
}
.article-body h2 { margin-top: 2.5em; margin-bottom: 0.6em; font-size: 1.7rem; }
.article-body blockquote {
  border-left: 2px solid var(--accent);
  padding: 0.4em 1.2em;
  font-family: var(--serif);
  font-style: italic;
  font-size: 1.2rem;
  color: var(--ink);
}
.article-body figure {
  margin-block: 2.5em;
}
.article-body figure img {
  width: 100%;
  height: auto;
  display: block;
  border: 1px solid var(--hairline);
  background: var(--paper-warm);
}
.article-body figcaption {
  font-family: var(--serif);
  font-style: italic;
  font-size: 0.95rem;
  color: var(--muted);
  margin-top: 0.7em;
  text-align: center;
  line-height: 1.5;
}

/* ---------- Media gallery ---------- */
.gallery {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 280px), 1fr));
  gap: 1.5rem;
}

.media-card {
  background: var(--surface);
  border: 1px solid var(--hairline);
  overflow: hidden;
  transition: transform 300ms var(--ease), box-shadow 300ms var(--ease);
}
.media-card:hover {
  transform: translateY(-2px);
  box-shadow: 0 12px 30px -18px rgba(0, 0, 0, 0.18);
}

.media-thumb {
  aspect-ratio: 4 / 3;
  background: linear-gradient(135deg, var(--paper-warm), var(--hairline-soft));
  display: grid;
  place-items: center;
  color: var(--muted);
  font-family: var(--serif);
  font-style: italic;
  overflow: hidden;
  position: relative;
}
.media-thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  transition: transform 700ms var(--ease);
}
.media-card:hover .media-thumb img { transform: scale(1.04); }

.media-thumb.video {
  background: linear-gradient(135deg, #2b2620, #4a3f33);
  color: #e8e0cf;
}
.media-thumb.video::after {
  content: "▶";
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  color: rgba(255, 255, 255, 0.85);
  font-size: 2rem;
  text-shadow: 0 2px 8px rgba(0, 0, 0, 0.4);
  pointer-events: none;
}

.media-meta {
  padding: 1.1rem 1.25rem 1.3rem;
}
.media-meta .kind {
  font-size: 0.72rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--muted);
}
.media-meta h3 {
  font-size: 1.2rem;
  margin-top: 0.35rem;
}

/* ---------- Project list ---------- */
.projects {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 320px), 1fr));
  gap: 2rem;
}
.project {
  border-top: 2px solid var(--ink);
  padding-top: 1.25rem;
}
.project .status {
  font-family: var(--mono);
  font-size: 0.78rem;
  color: var(--accent);
  margin-bottom: 0.5rem;
  letter-spacing: 0.05em;
}
.project h3 { font-size: 1.35rem; }
.project p { margin-top: 0.5rem; color: var(--muted); }
.project .tags {
  margin-top: 1rem;
  display: flex;
  gap: 0.5rem;
  flex-wrap: wrap;
}
.project .tag {
  font-size: 0.75rem;
  color: var(--muted);
  border: 1px solid var(--hairline);
  padding: 0.15rem 0.55rem;
  border-radius: 999px;
}

/* ---------- Book page ---------- */
.book-hero {
  display: grid;
  grid-template-columns: minmax(200px, 320px) 1fr;
  gap: clamp(2rem, 5vw, 4rem);
  align-items: center;
  padding-block: clamp(3rem, 8vw, 6rem);
}
@media (max-width: 720px) {
  .book-hero { grid-template-columns: 1fr; }
}

.book-cover {
  aspect-ratio: 2 / 3;
  background:
    linear-gradient(180deg, rgba(0,0,0,0) 60%, rgba(0,0,0,0.25) 100%),
    linear-gradient(135deg, #d8c4b3, #b85c2c);
  border-radius: 4px;
  box-shadow:
    0 1px 0 rgba(255,255,255,0.6) inset,
    0 30px 60px -25px rgba(20, 18, 12, 0.35),
    0 10px 20px -10px rgba(20, 18, 12, 0.2);
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  padding: 1.75rem 1.5rem;
  color: #fbfaf6;
  font-family: var(--serif);
}
.book-cover .cover-title { font-size: 1.6rem; line-height: 1.1; font-weight: 400; }
.book-cover .cover-author { font-size: 0.85rem; letter-spacing: 0.2em; text-transform: uppercase; opacity: 0.85; }

/* ---------- Survey / play ---------- */
.survey {
  background: var(--surface);
  border: 1px solid var(--hairline);
  padding: clamp(1.5rem, 4vw, 3rem);
  max-width: 38rem;
  margin-inline: auto;
}
.survey .progress {
  height: 2px;
  background: var(--hairline);
  margin-bottom: 2rem;
  position: relative;
  overflow: hidden;
}
.survey .progress > span {
  position: absolute;
  inset: 0;
  background: var(--accent);
  transform-origin: left;
  transform: scaleX(0);
  transition: transform 400ms var(--ease);
}
.survey .step-meta {
  font-family: var(--mono);
  font-size: 0.8rem;
  color: var(--muted);
  margin-bottom: 0.75rem;
  letter-spacing: 0.05em;
}
.survey h3 {
  font-size: 1.6rem;
  font-weight: 380;
  margin-bottom: 1.5rem;
  line-height: 1.2;
}
.survey .options {
  display: grid;
  gap: 0.75rem;
}
.survey .option {
  text-align: left;
  background: var(--paper);
  border: 1px solid var(--hairline);
  padding: 1rem 1.25rem;
  cursor: pointer;
  border-radius: 2px;
  transition: all 200ms var(--ease);
  font-size: 1rem;
  color: var(--ink);
}
.survey .option:hover {
  background: var(--surface);
  border-color: var(--accent);
  transform: translateX(2px);
}
.survey .result {
  text-align: center;
}
.survey .result .archetype {
  font-family: var(--serif);
  font-size: 2rem;
  color: var(--accent);
  margin: 0.5rem 0 1rem;
}
.survey .result p { color: var(--ink-soft); }
.survey .result button {
  margin-top: 2rem;
  background: none;
  border: 1px solid var(--ink);
  padding: 0.7rem 1.5rem;
  cursor: pointer;
  font-size: 0.9rem;
  letter-spacing: 0.05em;
  transition: all 200ms var(--ease);
}
.survey .result button:hover {
  background: var(--ink);
  color: var(--paper);
}

/* ---------- About ---------- */
.about-grid {
  display: grid;
  grid-template-columns: 1fr 2fr;
  gap: clamp(2rem, 5vw, 4rem);
  align-items: start;
}
@media (max-width: 720px) { .about-grid { grid-template-columns: 1fr; } }

.about-photo {
  aspect-ratio: 4 / 5;
  background: linear-gradient(135deg, var(--paper-warm), var(--hairline));
  display: grid; place-items: center;
  color: var(--muted);
  font-family: var(--serif);
  font-style: italic;
}

/* ---------- Footer ---------- */
.site-footer {
  margin-top: clamp(4rem, 8vw, 7rem);
  border-top: 1px solid var(--hairline);
  padding-block: 2.5rem;
  font-size: 0.88rem;
  color: var(--muted);
}
.site-footer .container {
  display: flex;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 1rem;
  align-items: baseline;
}
.site-footer a { color: var(--muted); border: 0; }
.site-footer a:hover { color: var(--accent); }
.site-footer .footer-links {
  display: flex;
  gap: 1.5rem;
}

/* ---------- Utility ---------- */
.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;
}

.fade-in {
  animation: fadeIn 600ms var(--ease) both;
}
@keyframes fadeIn {
  from { opacity: 0; transform: translateY(8px); }
  to { opacity: 1; transform: translateY(0); }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}

/* ---------- Editor toolbar (local dev only — Theme / History / Edit) ----- */
#__editor_toolbar {
  position: fixed;
  bottom: 1.25rem;
  right: 1.25rem;
  z-index: 100;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-family: var(--sans);
}
.__tool_btn {
  background: var(--ink);
  color: var(--paper);
  border: 0;
  padding: 0.6rem 1.1rem;
  font-size: 0.85rem;
  letter-spacing: 0.04em;
  border-radius: 999px;
  cursor: pointer;
  box-shadow: 0 10px 30px -12px rgba(0, 0, 0, 0.35);
  transition: transform 200ms var(--ease), background 200ms var(--ease);
  font-family: var(--sans);
}
.__tool_btn:hover { transform: translateY(-1px); }
.__tool_theme, .__tool_history {
  background: var(--surface);
  color: var(--ink);
  border: 1px solid var(--hairline);
  box-shadow: 0 8px 22px -10px rgba(0, 0, 0, 0.2);
}
.__tool_theme:hover, .__tool_history:hover { background: var(--paper-warm); }
.__tool_theme.__tool_active {
  background: var(--accent);
  color: var(--paper);
  border-color: var(--accent);
}

#__edit_msg {
  position: fixed;
  bottom: 4.5rem;
  right: 1.25rem;
  z-index: 100;
  background: var(--surface);
  color: var(--ink-soft);
  padding: 0.55rem 1rem;
  border-radius: 999px;
  font-size: 0.85rem;
  border: 1px solid var(--hairline);
  box-shadow: 0 8px 20px -10px rgba(0, 0, 0, 0.15);
  max-width: 22rem;
  font-family: var(--sans);
}
#__edit_msg[data-kind="ok"]  { color: var(--accent); }
#__edit_msg[data-kind="err"] { color: #b8442a; border-color: #d8aa9d; }

body.__edit_mode_active [contenteditable="true"]:hover {
  outline: 1px dashed var(--accent);
  outline-offset: 4px;
  cursor: text;
  border-radius: 2px;
}
body.__edit_mode_active [contenteditable="true"]:focus {
  outline: 2px solid var(--accent);
  outline-offset: 4px;
  border-radius: 2px;
}

/* ---------- Block editor controls (article bodies, edit mode only) ------- */
#__block_controls {
  position: absolute;
  display: flex;
  flex-direction: column;
  gap: 2px;
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: 4px;
  padding: 3px;
  box-shadow: 0 4px 14px -6px rgba(0, 0, 0, 0.18);
  z-index: 90;
  font-family: var(--mono);
  user-select: none;
  opacity: 0;
  transition: opacity 150ms var(--ease);
  pointer-events: none;
}
#__block_controls[data-visible="true"] {
  opacity: 1;
  pointer-events: auto;
}
.__bc_btn {
  background: none;
  border: 0;
  color: var(--muted);
  width: 1.7rem;
  height: 1.7rem;
  cursor: pointer;
  border-radius: 3px;
  font-size: 0.95rem;
  line-height: 1;
  display: grid;
  place-items: center;
  transition: background 150ms, color 150ms;
  padding: 0;
}
.__bc_btn:hover { background: var(--paper-warm); color: var(--ink); }
.__bc_drag { cursor: grab; font-size: 1rem; }
.__bc_drag:active { cursor: grabbing; }
.__bc_del:hover { background: #fdf0ec; color: #b8442a; }

#__add_menu {
  position: absolute;
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: 4px;
  padding: 4px;
  z-index: 95;
  box-shadow: 0 8px 22px -8px rgba(0, 0, 0, 0.22);
  font-family: var(--sans);
  display: none;
  min-width: 9rem;
}
#__add_menu[data-visible="true"] { display: block; }
#__add_menu button {
  display: block;
  width: 100%;
  text-align: left;
  background: none;
  border: 0;
  padding: 0.5rem 0.75rem;
  font-size: 0.85rem;
  color: var(--ink-soft);
  cursor: pointer;
  border-radius: 3px;
}
#__add_menu button:hover { background: var(--paper-warm); color: var(--ink); }

#__drop_indicator {
  position: absolute;
  height: 2px;
  background: var(--accent);
  z-index: 95;
  pointer-events: none;
  border-radius: 2px;
  display: none;
}
#__drop_indicator[data-visible="true"] { display: block; }

body.__edit_mode_active .article-body > *.__dragging {
  opacity: 0.4;
}

/* ---------- Theme panel ---------- */
#__theme_panel {
  position: fixed;
  bottom: 4.5rem;
  right: 1.25rem;
  width: 18rem;
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: 8px;
  padding: 1rem 1.25rem 1.25rem;
  box-shadow: 0 18px 40px -18px rgba(0, 0, 0, 0.3);
  z-index: 110;
  font-family: var(--sans);
}
.__tp_header {
  font-family: var(--serif);
  font-size: 1.1rem;
  font-weight: 420;
  margin-bottom: 0.75rem;
  color: var(--ink);
}
.__tp_field {
  display: block;
  font-size: 0.78rem;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 1rem;
}
.__tp_field > select,
.__tp_field > .__tp_row,
.__tp_field > .__tp_swatches { margin-top: 0.4rem; }
.__tp_field select {
  width: 100%;
  padding: 0.4rem 0.5rem;
  border: 1px solid var(--hairline);
  border-radius: 4px;
  background: var(--paper);
  font-size: 0.9rem;
  text-transform: none;
  letter-spacing: 0;
  color: var(--ink);
  font-family: var(--sans);
}
.__tp_row {
  display: flex;
  gap: 0.5rem;
  align-items: center;
}
.__tp_row input[type="color"] {
  width: 2.4rem;
  height: 2.2rem;
  border: 1px solid var(--hairline);
  border-radius: 4px;
  background: none;
  padding: 2px;
  cursor: pointer;
}
.__tp_row input[type="text"] {
  flex: 1;
  padding: 0.4rem 0.5rem;
  border: 1px solid var(--hairline);
  border-radius: 4px;
  font-family: var(--mono);
  font-size: 0.85rem;
  text-transform: none;
  letter-spacing: 0;
  color: var(--ink);
}
.__tp_row input[type="range"] { flex: 1; }
.__tp_row span {
  font-family: var(--mono);
  font-size: 0.78rem;
  color: var(--muted);
  letter-spacing: 0;
  text-transform: none;
  min-width: 3rem;
  text-align: right;
}
.__tp_swatches {
  display: flex;
  gap: 0.4rem;
}
.__tp_swatch {
  width: 1.8rem;
  height: 1.8rem;
  border-radius: 50%;
  border: 1px solid var(--hairline);
  cursor: pointer;
  padding: 0;
  transition: transform 150ms;
}
.__tp_swatch:hover { transform: scale(1.1); }
.__tp_swatch.__tp_active {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.__tp_actions {
  display: flex;
  gap: 0.5rem;
  margin-top: 0.5rem;
}
.__tp_btn {
  flex: 1;
  padding: 0.5rem 0.75rem;
  border: 1px solid var(--hairline);
  background: var(--paper);
  color: var(--ink);
  font-size: 0.85rem;
  border-radius: 4px;
  cursor: pointer;
  font-family: var(--sans);
}
.__tp_btn:hover { background: var(--paper-warm); }
.__tp_save {
  background: var(--ink);
  color: var(--paper);
  border-color: var(--ink);
}
.__tp_save:hover { background: var(--ink-soft); }

/* ---------- History modal ---------- */
#__history_modal {
  position: fixed;
  inset: 0;
  z-index: 200;
  font-family: var(--sans);
}
.__hm_backdrop {
  position: absolute;
  inset: 0;
  background: rgba(20, 18, 12, 0.45);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
}
.__hm_panel {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: 8px;
  width: min(36rem, calc(100vw - 2rem));
  max-height: calc(100vh - 4rem);
  overflow: hidden;
  display: flex;
  flex-direction: column;
  box-shadow: 0 30px 80px -25px rgba(0, 0, 0, 0.5);
}
.__hm_header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 1rem 1.25rem;
  border-bottom: 1px solid var(--hairline);
  font-family: var(--serif);
  font-size: 1.1rem;
  color: var(--ink);
}
.__hm_path {
  font-family: var(--mono);
  font-size: 0.78rem;
  color: var(--muted);
  margin-left: 0.4em;
}
.__hm_close {
  background: none;
  border: 0;
  font-size: 1.4rem;
  cursor: pointer;
  color: var(--muted);
  padding: 0;
  line-height: 1;
  width: 1.6rem;
  height: 1.6rem;
}
.__hm_close:hover { color: var(--ink); }
.__hm_list {
  margin: 0;
  padding: 0;
  list-style: none;
  overflow-y: auto;
}
.__hm_list > li {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.7rem 1.25rem;
  border-bottom: 1px solid var(--hairline-soft);
  font-size: 0.9rem;
}
.__hm_list > li:last-child { border-bottom: 0; }
.__hm_ts {
  font-family: var(--mono);
  font-size: 0.85rem;
  color: var(--ink);
  flex: 1;
}
.__hm_size {
  font-family: var(--mono);
  font-size: 0.78rem;
  color: var(--muted);
}
.__hm_btn {
  background: none;
  border: 1px solid var(--hairline);
  padding: 0.35rem 0.7rem;
  border-radius: 4px;
  font-size: 0.78rem;
  cursor: pointer;
  color: var(--ink);
  font-family: var(--sans);
}
.__hm_btn:hover { background: var(--paper-warm); }
.__hm_restore {
  border-color: var(--accent);
  color: var(--accent);
}
.__hm_restore:hover {
  background: var(--accent);
  color: var(--paper);
}
.__hm_empty, .__hm_loading {
  padding: 1.5rem 1.25rem;
  color: var(--muted);
  font-style: italic;
  font-family: var(--serif);
  text-align: center;
}

/* ---------- Restore-draft banner (page load) ---------- */
#__restore_banner {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 110;
  background: var(--accent);
  color: var(--paper);
  padding: 0.6rem 1rem;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 1rem;
  font-family: var(--sans);
  font-size: 0.9rem;
  box-shadow: 0 4px 14px -6px rgba(0, 0, 0, 0.3);
}
.__rb_btn {
  background: rgba(0, 0, 0, 0.18);
  color: var(--paper);
  border: 0;
  padding: 0.35rem 0.85rem;
  border-radius: 4px;
  font-size: 0.82rem;
  cursor: pointer;
  font-family: var(--sans);
}
.__rb_btn:hover { background: rgba(0, 0, 0, 0.32); }

/* ---------- New post button (blog index, edit mode) ---------- */
#__new_post_btn {
  display: block;
  margin: 0 0 1.5rem;
  background: none;
  border: 1px dashed var(--accent);
  color: var(--accent);
  padding: 0.7rem 1.25rem;
  font-family: var(--serif);
  font-style: italic;
  font-size: 1rem;
  cursor: pointer;
  border-radius: 4px;
  transition: all 200ms var(--ease);
}
#__new_post_btn:hover {
  background: var(--accent);
  color: var(--paper);
}

/* ---------- Lightbox (image viewer) ---------- */
.__lightbox {
  position: fixed;
  inset: 0;
  background: rgba(15, 14, 11, 0.92);
  z-index: 200;
  display: none;
  align-items: center;
  justify-content: center;
  padding: 2rem;
  cursor: zoom-out;
  font-family: var(--sans);
}
.__lightbox.__lb_open { display: flex; }
.__lb_img {
  max-width: 100%;
  max-height: 80vh;
  object-fit: contain;
  cursor: default;
  box-shadow: 0 20px 60px -20px rgba(0, 0, 0, 0.7);
}
.__lb_close, .__lb_prev, .__lb_next {
  position: absolute;
  background: rgba(255, 255, 255, 0.1);
  color: rgba(255, 255, 255, 0.9);
  border: 0;
  width: 2.6rem;
  height: 2.6rem;
  border-radius: 50%;
  font-size: 1.4rem;
  cursor: pointer;
  display: grid;
  place-items: center;
  line-height: 1;
  transition: background 200ms;
}
.__lb_close:hover, .__lb_prev:hover, .__lb_next:hover {
  background: rgba(255, 255, 255, 0.22);
}
.__lb_close { top: 1rem; right: 1rem; }
.__lb_prev  { left: 1rem; }
.__lb_next  { right: 1rem; }
.__lb_caption {
  position: absolute;
  bottom: 1.5rem;
  left: 50%;
  transform: translateX(-50%);
  color: rgba(255, 255, 255, 0.85);
  font-family: var(--serif);
  font-style: italic;
  font-size: 0.95rem;
  text-align: center;
  max-width: 80%;
  background: rgba(0, 0, 0, 0.4);
  padding: 0.4rem 1rem;
  border-radius: 4px;
}

/* ---------- Community visualization (Play page) ---------- */
.cviz { display: block; outline: none; }
.cviz-stage {
  background: var(--paper-warm);
  border: 1px solid var(--hairline);
  border-radius: 8px;
  overflow: hidden;
}
.cviz-stage svg {
  display: block;
  width: 100%;
  height: auto;
}

.cviz-dot {
  /* --dot-fill lets the wander slide tint individual dots via inline style
     (sustained life shifts ink → terracotta) without losing the fill
     transitions or being overridden by pulse/faded/stuck class rules. */
  fill: var(--dot-fill, var(--ink));
  opacity: 1;
  transition: fill 600ms var(--ease), opacity 600ms var(--ease),
              r 300ms cubic-bezier(0.34, 1.56, 0.64, 1);
}
.cviz-dot-faded {
  fill: var(--muted);
  opacity: 0.32;
}
.cviz-dot-stuck {
  fill: var(--muted);
  animation: cviz-decay 5s ease-in-out infinite alternate;
}
.cviz-dot-lukewarm {
  /* Lukewarm dots still move, but their color drifts toward muted and
     their opacity pulses — they're rotting in place even as they wander. */
  fill: var(--muted);
  animation: cviz-decay 6s ease-in-out infinite alternate;
}
@keyframes cviz-decay {
  0%   { opacity: 0.55; }
  100% { opacity: 0.15; }
}

.cviz-ref-line {
  stroke: var(--ink);
  stroke-width: 1.5;
  stroke-opacity: 0.55;
}
.cviz-ref-label {
  fill: var(--muted);
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: 0.14em;
  /* text-anchor is set per-element by JS — don't override here */
}
.cviz-ref-target rect {
  fill: none;
  stroke: var(--accent);
  stroke-width: 2;
  stroke-dasharray: 5 5;
}
.cviz-ref-arrow {
  fill: none;
  stroke: var(--accent);
  stroke-width: 1;
  opacity: 0.4;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.cviz-ref-box {
  fill: none;
  stroke: var(--accent);
  stroke-width: 1.2;
  stroke-dasharray: 3 3;
  opacity: 0.55;
  transition: stroke-dasharray 350ms ease, opacity 350ms ease, stroke-width 350ms ease;
}
.cviz-ref-box-filled {
  stroke-dasharray: none;
  opacity: 1;
  stroke-width: 1.6;
}
.cviz-trail {
  fill: none;
  stroke: var(--accent);
  stroke-width: 1.4;
  stroke-opacity: 0.35;
  stroke-linecap: round;
  stroke-linejoin: round;
  /* Trails are drawn fresh each frame — disable the refs fade-in animation */
  animation: none !important;
}
.cviz-wind {
  /* Ambient terracotta "wind wisps" — short streaks drifting upward through
     the field. stroke-opacity is set per-frame in JS to fade in/out. */
  stroke: var(--accent);
  stroke-width: 1.2;
  stroke-linecap: round;
  pointer-events: none;
  animation: none !important;
}
.cviz-floor-line {
  stroke: var(--ink);
  stroke-width: 1.5;
  stroke-opacity: 0.55;
  /* JS animates y1/y2 — disable the refs fade-in so the whoosh isn't ghostly */
  animation: none !important;
}
.cviz-ladder-rail {
  stroke: var(--ink);
  stroke-width: 0.7;
  stroke-opacity: 0.22;
  pointer-events: none;
}
.cviz-ladder-rung {
  stroke: var(--accent);
  stroke-width: 1.1;
  stroke-opacity: 0.6;
  stroke-linecap: round;
  pointer-events: none;
}
.cviz-ladder-top-fade,
.cviz-floor-mask {
  pointer-events: none;
  animation: none !important;
}
/* Background warmth on the Reality slide — opacity is driven each frame
   by the collective average vitality of the dots. As the field thrives,
   the canvas glows. */
.cviz-warmth-bg {
  pointer-events: none;
  /* Disable the refs fade-in so JS opacity control is smooth */
  animation: none !important;
  transition: opacity 600ms ease;
}
/* Constellation memory — small terracotta dots placed at connection
   midpoints. They fade slowly over ~35s, so the field accumulates a
   visible record of where lives have crossed. */
.cviz-memory-dot {
  /* Faint parchment / sepia — the record of community, not loud. */
  fill: #a89684;
  pointer-events: none;
  animation: cviz-memory-fade 38s ease-out forwards;
}
@keyframes cviz-memory-fade {
  0%   { opacity: 0; }
  12%  { opacity: 0.18; }
  100% { opacity: 0; }
}
/* Encounter spark — a brief warm-white flash marks the moment two dots
   first come into proximity. Light, not flame. */
.cviz-spark {
  fill: none;
  stroke: #f5e6c8;
  stroke-width: 1.4;
  pointer-events: none;
  animation: cviz-spark-expand 650ms ease-out forwards;
}
@keyframes cviz-spark-expand {
  0%   { r: 2;  opacity: 0.85; }
  100% { r: 20; opacity: 0; }
}
/* Resurrection — a dying dot, encountered by a healthy one, gets a big
   terracotta burst. Larger and longer than a normal pulse. */
.cviz-dot-resurrect {
  fill: var(--accent);
}
/* Terracotta pulse on each dot when it's about to climb a rung */
.cviz-dot-pulse {
  fill: var(--accent);
}
/* Tendril: a slow squiggly path growing outward from each dot on the
   "Reality" slide. Drawn fresh each frame in JS, so the refs fade-in
   animation is disabled to avoid flicker. */
.cviz-tendril {
  fill: none;
  stroke: var(--accent);
  stroke-width: 1.4;
  stroke-opacity: 0.45;
  stroke-linecap: round;
  stroke-linejoin: round;
  pointer-events: none;
  animation: none !important;
}
.cviz-tendril-lukewarm {
  /* Breaks visually before the JS-driven fade kicks in */
  stroke-dasharray: 3 4;
  stroke-opacity: 0.35;
}
.cviz-connection {
  /* Sepia thread instead of terracotta — community as woven cloth, not fire.
     The stroke-opacity is set per-pair in JS based on proximity. */
  stroke: #7a6a55;
  stroke-width: 1;
  stroke-linecap: round;
  pointer-events: none;
  animation: none !important;
}
/* The orbit slide's morphing line — the slide-5 floor whooshes up and
   shrinks inward, then becomes the sun. */
.cviz-orbit-line {
  stroke: var(--ink);
  stroke-width: 1.5;
  stroke-opacity: 0.55;
  animation: none !important;
}
/* Sun core — radial gradient terracotta at the center of the orbit.
   Subtle opacity breath gives it gentle "alive" feel without changing r. */
.cviz-sun-core {
  pointer-events: none;
  animation: cviz-sun-pulse 6s ease-in-out infinite alternate;
}
@keyframes cviz-sun-pulse {
  0%   { opacity: 0.92; }
  100% { opacity: 1; }
}
/* Vortex swirl — a dashed ring around the sun. The dashes slide along
   the circle via stroke-dashoffset, so it reads as gentle spinning. */
.cviz-sun-swirl {
  stroke: #b85c2c;
  stroke-width: 1.2;
  stroke-opacity: 0.38;
  stroke-dasharray: 5 18;
  pointer-events: none;
  animation: cviz-sun-swirl-spin 22s linear infinite;
}
@keyframes cviz-sun-swirl-spin {
  to { stroke-dashoffset: -230; }
}
/* Tapestry line — a single connection drawn between two dots (or a dot
   and the sun) over ~1.1 sec, then stays. Sun connections get a slightly
   warmer, more prominent stroke so they read as spokes of light. */
.cviz-loom-line {
  stroke: #7a4828;
  stroke-width: 0.9;
  stroke-opacity: 0.5;
  stroke-linecap: round;
  pointer-events: none;
  animation: none !important;
}
.cviz-loom-line-sun {
  stroke: #b85c2c;
  stroke-width: 1.0;
  stroke-opacity: 0.55;
}
/* Holy huddle slide — the floor where they land, ZZZ overlay, faint
   stranger dots flowing across the upper canvas. */
.cviz-huddle-bg {
  pointer-events: none;
  animation: none !important;
}
.cviz-huddle-floor {
  stroke: var(--ink);
  stroke-width: 1.2;
  stroke-opacity: 0.40;
  pointer-events: none;
  animation: none !important;
}
.cviz-zzz {
  fill: var(--muted);
  font-family: var(--serif);
  font-style: italic;
  font-size: 11px;
  pointer-events: none;
  animation: cviz-zzz-drift 4s ease-in-out infinite alternate;
}
@keyframes cviz-zzz-drift {
  0%   { opacity: 0.10; transform: translateY(0); }
  100% { opacity: 0.45; transform: translateY(-3px); }
}
.cviz-stranger {
  fill: var(--ink);
  pointer-events: none;
  animation: none !important;
}
.cviz-refs > * {
  animation: cviz-fade-in 500ms var(--ease) both;
}
@keyframes cviz-fade-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}

.cviz-captions {
  position: relative;
  margin-top: 2rem;
  text-align: center;
  min-height: 11rem;
  max-width: 38rem;
  margin-inline: auto;
}
.cviz-caption {
  position: absolute;
  inset: 0;
  opacity: 0;
  pointer-events: none;
  transition: opacity 400ms var(--ease);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.6rem;
}
.cviz-caption.active {
  opacity: 1;
  pointer-events: auto;
}
.cviz-eyebrow .eyebrow { margin-bottom: 0; }
.cviz-title {
  font-family: var(--serif);
  font-size: clamp(1.25rem, 2.5vw, 1.7rem);
  font-weight: 400;
  color: var(--ink);
  line-height: 1.3;
  margin: 0;
  max-width: 34rem;
}
.cviz-title em {
  color: var(--accent);
  font-style: italic;
}
.cviz-body {
  font-family: var(--sans);
  font-size: 1rem;
  color: var(--ink-soft);
  line-height: 1.6;
  margin: 0;
  max-width: 32rem;
}

.cviz-controls {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 1.5rem;
  margin-top: 2rem;
}
.cviz-prev, .cviz-next {
  background: none;
  border: 1px solid var(--hairline);
  width: 2.4rem;
  height: 2.4rem;
  border-radius: 50%;
  font-size: 1.3rem;
  line-height: 1;
  cursor: pointer;
  color: var(--ink);
  transition: all 200ms var(--ease);
  display: grid;
  place-items: center;
  padding: 0;
  font-family: var(--sans);
}
.cviz-prev:hover:not(:disabled),
.cviz-next:hover:not(:disabled) {
  background: var(--accent);
  color: var(--paper);
  border-color: var(--accent);
}
.cviz-prev:disabled, .cviz-next:disabled {
  opacity: 0.3;
  cursor: not-allowed;
}
.cviz-progress {
  display: flex;
  gap: 0.5rem;
  align-items: center;
}
.cviz-progress-dot {
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 50%;
  background: var(--hairline);
  border: 0;
  padding: 0;
  cursor: pointer;
  transition: all 200ms var(--ease);
}
.cviz-progress-dot:hover { background: var(--muted); }
.cviz-progress-dot.active {
  background: var(--accent);
  width: 1.5rem;
  border-radius: 4px;
}

/* ---- Presentation mode (Google Slides clone) ------------------------- */
/* The cviz container becomes a 16:9 "slide" with stage + caption stacked
   inside it. Pre/post slides are slide-mode: dots hidden, title+content
   take over the whole slide area. Animation slides bring the dots back. */
.cviz-presentation {
  position: relative;
  display: block;
  margin: 0 auto;
  /* Default to comfortable in-page size; fullscreen overrides below. */
  max-width: min(100%, 70rem);
  aspect-ratio: 16 / 9;
  background: var(--paper);
  border: 1px solid var(--hairline);
  border-radius: 10px;
  overflow: hidden;
  box-shadow: 0 1px 2px rgba(21,20,15,0.04), 0 8px 32px rgba(21,20,15,0.08);
}
/* Animation slides: stage takes the top 70% of the slide, caption fills
   the bottom 30% on a solid paper backdrop. This prevents the canvas
   (dots/ladders/orbits) from colliding with the title and body text.
   The SVG (viewBox 2:1) letterboxes naturally into the 70% area. */
.cviz-presentation .cviz-stage {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 30%;
  border: none;
  border-radius: 0;
  background: transparent;
}
.cviz-presentation .cviz-stage svg {
  width: 100%;
  height: 100%;
  /* Clip content to the SVG's viewBox bounds so off-canvas elements
     (e.g. strangers spawning at x=-40 to drift in) don't render in the
     letterbox area beside the viewBox. Without this, the stranger field
     appears to "pop in" before the visible canvas's left edge. */
  overflow: hidden;
}
/* Caption sits in the bottom 30% on a solid paper background — reads
   cleanly even when the canvas above is busy. The transitions only fire
   when the .slidemode class toggles (top + background flip), giving a
   smooth slide-down/up reveal between slide-mode and animation-mode.
   2880ms duration = 4× the original 720ms, so the schedule→intro
   reveal (and the meta→order re-engage at the end) plays slowly and
   deliberately. Slide-mode↔slide-mode swaps don't change `top` or
   `background` at all, so this rule never fires inside slide-mode —
   adjacent slidemode slides still swap instantly. */
.cviz-presentation .cviz-captions {
  position: absolute;
  top: 70%;
  left: 0;
  right: 0;
  bottom: 0;
  margin: 0;
  max-width: none;
  min-height: 0;
  text-align: center;
  pointer-events: none;
  background: var(--paper);
  border-top: 1px solid var(--hairline-soft);
  transition: top 2880ms cubic-bezier(0.4, 0, 0.2, 1),
              background-color 2880ms ease,
              border-top-color 2880ms ease;
}
.cviz-presentation .cviz-caption {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 1rem 5%;
  opacity: 0;
  transition: opacity 200ms linear;
  gap: 0.4rem;
}
.cviz-presentation .cviz-caption.active {
  opacity: 1;
}
.cviz-presentation .cviz-eyebrow {
  font-family: var(--sans);
  font-size: clamp(0.7rem, 1.1vw, 0.85rem);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 0.4rem;
}
.cviz-presentation .cviz-title {
  /* Compact size for animation-slide caption strip (bottom 30%).
     calc(clamp(...) * var(...)) lets caption-tools.js scale the type
     PER-SLIDE without breaking the responsive clamp — the inline style
     `--cap-title-scale: 1.2` simply multiplies the natural size, so
     it scales correctly across a laptop and a 65" TV. */
  font-family: var(--serif);
  font-weight: 400;
  line-height: 1.2;
  color: var(--ink);
  font-size: calc(clamp(1rem, 2.1vw, 1.55rem) * var(--cap-title-scale, 1));
  margin: 0;
  max-width: 56ch;
}
.cviz-presentation .cviz-body {
  font-family: var(--serif);
  font-size: calc(clamp(0.85rem, 1.4vw, 1.05rem) * var(--cap-body-scale, 1));
  color: var(--ink-soft);
  margin: 0.25rem 0 0;
  line-height: 1.35;
  max-width: 60ch;
}
/* Slide-mode (welcome/looking-back/order/icebreaker) — Google-Slides scale */
.cviz-presentation.slidemode .cviz-title {
  font-size: calc(clamp(1.6rem, 4.4vw, 3.4rem) * var(--cap-title-scale, 1));
  line-height: 1.15;
  max-width: none;
}
.cviz-presentation.slidemode .cviz-body {
  font-size: calc(clamp(1rem, 1.9vw, 1.4rem) * var(--cap-body-scale, 1));
  line-height: 1.4;
  max-width: 50ch;
}

/* SLIDE MODE: pre/post slides — Google Slides look. Dots hidden, the
   caption fills the WHOLE slide with title in the top-left (where it
   defaults in a normal Google Slides deck). The slide-mode background
   is a SOLID warm cream (var(--slide-bg), defined in :root via theme.css
   with a fallback) — distinctly non-white so the slides feel deliberate,
   and the warm tone complements the orange-tinted dot illustrations
   that take over once we leave slide-mode. NO fade transition between
   slide-mode slides — they appear instantly like clicking through a
   real slide deck. The cream-to-paper bg fade IS animated, but only
   fires when the .slidemode class toggles (the wrapper-level transition
   handles it). */
.cviz-presentation.slidemode .cviz-dots,
.cviz-presentation.slidemode .cviz-refs {
  opacity: 0;
  pointer-events: none;
  transition: opacity 2400ms ease;
}
.cviz-presentation .cviz-dots,
.cviz-presentation .cviz-refs {
  /* Default (animation mode): full opacity. Matches the captions-wrap
     transition timing so dots fade in/out in lockstep with the caption
     strip sliding down/up. ~83% of the wrap duration so the dots are
     fully visible just before the wrap finishes sliding. */
  transition: opacity 2400ms ease;
}
.cviz-presentation.slidemode .cviz-stage {
  bottom: 0;
}
.cviz-presentation.slidemode .cviz-captions {
  top: 0;
  /* Warm cream-tan — picks up the dawn palette of the dot illustration
     without being so saturated it pulls focus from the type. */
  background: var(--slide-bg, #e8dcc4);
  border-top-color: transparent;
}
.cviz-presentation.slidemode .cviz-caption {
  padding: 7% 8%;
  gap: 1rem;
  align-items: flex-start;
  justify-content: flex-start;
  text-align: left;
  /* Instant slide swap — no fade between slide-mode slides */
  transition: none;
}
/* Title slide variant — the FIRST slide (Welcome) is centered like a
   classic title slide instead of top-left aligned. */
.cviz-presentation.slidemode .cviz-caption.cviz-title-slide {
  align-items: center;
  justify-content: center;
  text-align: center;
}
.cviz-presentation.slidemode .cviz-caption.cviz-title-slide .cviz-eyebrow,
.cviz-presentation.slidemode .cviz-caption.cviz-title-slide .cviz-title,
.cviz-presentation.slidemode .cviz-caption.cviz-title-slide .cviz-body,
.cviz-presentation.slidemode .cviz-caption.cviz-title-slide .cviz-reveals {
  align-self: center;
  text-align: center;
}
.cviz-presentation.slidemode .cviz-eyebrow {
  margin-bottom: 0.5rem;
  align-self: flex-start;
}
.cviz-presentation.slidemode .cviz-eyebrow .eyebrow {
  margin-bottom: 0;
}
.cviz-presentation.slidemode .cviz-title,
.cviz-presentation.slidemode .cviz-body,
.cviz-presentation.slidemode .cviz-reveals {
  text-align: left;
  align-self: flex-start;
}
.cviz-presentation.slidemode .cviz-reveals {
  margin-top: 1.5rem;
}

/* Reveals: appear INSTANTLY on click (Google Slides style, no fade). */
.cviz-reveals {
  list-style: none;
  padding: 0;
  margin: 1.5rem 0 0;
  display: flex;
  flex-direction: column;
  gap: 0.85rem;
  text-align: center;
  font-family: var(--serif);
  /* Reveals share the body scale — same content category. */
  font-size: calc(clamp(1.05rem, 2.2vw, 1.55rem) * var(--cap-body-scale, 1));
  line-height: 1.4;
  color: var(--ink-soft);
  max-width: 50ch;
}
.cviz-reveals li {
  display: none;
}
.cviz-reveals li.shown {
  display: block;
}
/* Generic reveal: any element with .cviz-reveal hides until .shown is
   added by the reveal-click handler. Used for non-list reveals like the
   icebreaker title (h3.cviz-title.cviz-reveal). Stays compatible with
   the existing list rules above — .cviz-reveals li.shown's higher
   specificity wins for <li> bullets, so their display: block doesn't
   regress. */
.cviz-reveal {
  display: none;
}
.cviz-reveal.shown {
  display: block;
}
.cviz-reveals strong {
  color: var(--ink);
  font-weight: 500;
}
.cviz-reveals em {
  color: var(--accent);
  font-style: italic;
}
/* Schedule reveals: in animation-mode contexts (other pages) the
   bullet list is a centered block of sans-serif data. In SLIDE-MODE on
   presentations.html, though, we want it to look exactly like the
   "Looking Back" slide — same font, same alignment, anchored at the
   same left edge as the title. Slide-mode overrides below clear the
   `margin-inline: auto` and the sans-serif font override so the
   schedule items inherit `.cviz-reveals` styling. */
.cviz-reveals-schedule {
  text-align: left;
  margin-inline: auto;
}
.cviz-reveals-schedule li {
  font-family: var(--sans);
  font-size: clamp(0.95rem, 1.7vw, 1.2rem);
}
.cviz-presentation.slidemode .cviz-reveals-schedule {
  /* Don't center the block — let `align-self: flex-start` from the
     parent caption pull it to the left edge alongside the title. */
  margin-inline: 0;
}
.cviz-presentation.slidemode .cviz-reveals-schedule li {
  /* Inherit the serif typography from `.cviz-reveals` so the schedule
     reads the same as Looking Back / Tonight's Order. */
  font-family: inherit;
  font-size: inherit;
}

/* Controls sit at the bottom of the slide, semi-transparent. */
.cviz-presentation .cviz-controls {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 1rem;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 1rem;
  z-index: 4;
  pointer-events: auto;
}
.cviz-presentation .cviz-prev,
.cviz-presentation .cviz-next,
.cviz-presentation .cviz-fullscreen {
  background: rgba(251,250,246,0.85);
  border: 1px solid var(--hairline);
  width: 2.25rem;
  height: 2.25rem;
  border-radius: 999px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 1.05rem;
  color: var(--ink-soft);
  cursor: pointer;
  transition: background 160ms, color 160ms;
}
.cviz-presentation .cviz-prev:hover:not(:disabled),
.cviz-presentation .cviz-next:hover:not(:disabled),
.cviz-presentation .cviz-fullscreen:hover {
  background: var(--paper);
  color: var(--ink);
}
.cviz-presentation .cviz-prev:disabled,
.cviz-presentation .cviz-next:disabled {
  opacity: 0.35;
  cursor: not-allowed;
}
.cviz-presentation .cviz-fullscreen {
  position: absolute;
  right: 1rem;
  bottom: auto;
  top: 1rem;
  z-index: 5;
}
/* Progress dots fade to soft so they don't compete with the slide. */
.cviz-presentation .cviz-progress {
  gap: 0.4rem;
}
.cviz-presentation .cviz-progress-dot {
  width: 0.45rem;
  height: 0.45rem;
  background: var(--hairline);
}
.cviz-presentation .cviz-progress-dot.active {
  background: var(--accent);
  width: 1.2rem;
}

/* Editable text: subtle hover/focus indication so Jack can see what's
   clickable in browser-edit-mode, but invisible in fullscreen so the
   audience sees a clean Google Slides deck. */
.cviz-presentation [contenteditable="true"] {
  cursor: text;
  border-radius: 4px;
  padding: 0 2px;
  margin: 0 -2px;
  transition: background 120ms;
}
.cviz-presentation [contenteditable="true"]:hover {
  background: rgba(184, 92, 44, 0.05);
}
.cviz-presentation [contenteditable="true"]:focus {
  outline: 1px dashed rgba(184, 92, 44, 0.4);
  outline-offset: 2px;
  background: rgba(184, 92, 44, 0.06);
}
.cviz-presentation:fullscreen [contenteditable="true"] {
  cursor: default;
}
.cviz-presentation:fullscreen [contenteditable="true"]:hover,
.cviz-presentation:fullscreen [contenteditable="true"]:focus {
  background: transparent;
  outline: none;
}

/* Fullscreen — the cviz container fills the entire viewport. In
   fullscreen we ALSO hide the navigation bar + fullscreen toggle to
   complete the Google Slides illusion. Audience navigates with arrow
   keys (or Esc to exit fullscreen). */
.cviz-presentation:fullscreen {
  max-width: none;
  width: 100vw;
  height: 100vh;
  aspect-ratio: auto;
  border-radius: 0;
  border: none;
  box-shadow: none;
  background: var(--paper);
}
.cviz-presentation:fullscreen .cviz-controls,
.cviz-presentation:fullscreen .cviz-fullscreen {
  display: none;
}
/* Slide-mode in fullscreen: title fills the screen Google Slides-style.
   Logic: viewport-based units (vw/vh) auto-scale to the actual screen
   size, so the same caps work on a laptop AND on a 65" TV — the type
   simply scales up until it hits the cap. The caps here are sized for
   comfortable 10-foot reading on a 1080p+ TV; on a laptop the fluid
   middle term (vw) takes over and keeps things proportionate.
   IMPORTANT: these rules only apply to .slidemode — illustration slides
   keep their compact strip-sized typography below. */
.cviz-presentation:fullscreen.slidemode .cviz-caption {
  padding: 6vh 8vw;
}
.cviz-presentation:fullscreen.slidemode .cviz-title {
  font-size: calc(clamp(2.8rem, 7vw, 6.5rem) * var(--cap-title-scale, 1));
  line-height: 1.1;
}
.cviz-presentation:fullscreen.slidemode .cviz-body,
.cviz-presentation:fullscreen.slidemode .cviz-reveals {
  font-size: calc(clamp(1.6rem, 3.2vw, 2.8rem) * var(--cap-body-scale, 1));
  line-height: 1.4;
}
/* Schedule reveals: more items per slide, so cap slightly lower than
   regular reveals to fit without crowding the slide. */
.cviz-presentation:fullscreen.slidemode .cviz-reveals-schedule {
  font-size: calc(clamp(1.4rem, 2.6vw, 2.2rem) * var(--cap-body-scale, 1));
}
/* Animation mode in fullscreen: caption strip stays at the bottom 30%
   with compact typography so it doesn't compete with the visualization.
   Caps DELIBERATELY left smaller than slidemode so the illustration
   stays the visual focus. Slight bump from previous values for TV
   legibility without crowding the 30% strip. */
.cviz-presentation:fullscreen:not(.slidemode) .cviz-title {
  font-size: calc(clamp(1.5rem, 2.6vw, 2.4rem) * var(--cap-title-scale, 1));
}
.cviz-presentation:fullscreen:not(.slidemode) .cviz-body {
  font-size: calc(clamp(1.05rem, 1.7vw, 1.5rem) * var(--cap-body-scale, 1));
}

/* ---------------- Caption tools (per-slide size adjuster) -----------
   Floating buttons that appear in the top-right of each .cviz-caption
   when edit mode is active. Click A− / A+ to nudge title / body type
   sizes. Stored as --cap-title-scale / --cap-body-scale inline on the
   caption, persisted by edit-mode.js's autosave.
   • !important on the default `display:none` to defeat any baked-in
     inline style="" leftovers from prior saves (belt + suspenders).
   • Only the explicit body.__edit_mode_active selector can show them.
   • Always hidden in fullscreen so the audience never sees controls. */
.__cap_tools {
  position: absolute;
  top: 0.5rem;
  right: 0.5rem;
  display: none !important;
  gap: 0.35rem;
  align-items: center;
  background: rgba(20, 19, 15, 0.86);
  color: #fbfaf6;
  padding: 0.3rem 0.5rem;
  border-radius: 6px;
  font-family: var(--sans);
  font-size: 0.7rem;
  z-index: 6;
  user-select: none;
  pointer-events: auto;
}
body.__edit_mode_active .__cap_tools {
  display: inline-flex !important;
}
.cviz-presentation:fullscreen .__cap_tools {
  display: none !important;
}
.__cap_tools .__cap_lbl {
  font-size: 0.62rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  opacity: 0.65;
  padding: 0 0.15rem;
}
.__cap_tools .__cap_sep {
  width: 1px;
  height: 14px;
  background: rgba(255, 255, 255, 0.18);
  margin: 0 0.1rem;
}
.__cap_tools button {
  background: rgba(255, 255, 255, 0.08);
  border: 1px solid rgba(255, 255, 255, 0.15);
  color: inherit;
  min-width: 1.7rem;
  height: 1.5rem;
  border-radius: 4px;
  cursor: pointer;
  font-size: 0.75rem;
  padding: 0 0.35rem;
  font-family: var(--sans);
  transition: background 120ms;
}
.__cap_tools button:hover {
  background: rgba(255, 255, 255, 0.18);
}
.__cap_tools button:active {
  background: rgba(255, 255, 255, 0.28);
}
/* Per-bullet delete button (× to the left of each reveal item).
   Visible only in edit mode. Hidden in fullscreen + on the live site.
   Same !important pattern as .__cap_tools so leftover baked-in markup
   can't possibly leak through to the audience view. */
.__cap_bullet_x {
  display: none !important;
}
body.__edit_mode_active .__cap_bullet_x {
  display: inline-flex !important;
  align-items: center;
  justify-content: center;
  width: 1.1rem;
  height: 1.1rem;
  margin-right: 0.4rem;
  vertical-align: middle;
  background: transparent;
  border: 1px solid rgba(184, 92, 44, 0.45);
  color: var(--accent);
  border-radius: 50%;
  font-size: 0.75rem;
  line-height: 1;
  cursor: pointer;
  padding: 0;
  opacity: 0.45;
  transition: opacity 120ms, background 120ms;
  user-select: none;
}
body.__edit_mode_active .__cap_bullet_x:hover {
  opacity: 1;
  background: rgba(184, 92, 44, 0.12);
}
.cviz-presentation:fullscreen .__cap_bullet_x {
  display: none !important;
}

/* "+ Add bullet" button — a ghost-style link directly under each
   reveals list. Same edit-mode-only visibility pattern as the other
   editor chrome. The dashed border + low opacity keeps it from
   competing visually with the actual content. */
.__cap_add {
  display: none !important;
}
body.__edit_mode_active .__cap_add {
  display: inline-flex !important;
  align-items: center;
  margin-top: 0.6rem;
  padding: 0.3rem 0.65rem;
  background: transparent;
  border: 1px dashed rgba(184, 92, 44, 0.45);
  color: var(--accent);
  border-radius: 999px;
  font-family: var(--sans);
  font-size: 0.75rem;
  letter-spacing: 0.02em;
  cursor: pointer;
  opacity: 0.55;
  transition: opacity 120ms, background 120ms, border-color 120ms;
}
body.__edit_mode_active .__cap_add:hover {
  opacity: 1;
  background: rgba(184, 92, 44, 0.08);
  border-color: rgba(184, 92, 44, 0.7);
}
.cviz-presentation:fullscreen .__cap_add {
  display: none !important;
}

/* ---------------- Icebreaker corner-wheel callback ----------------
   A quarter-visible flywheel anchored to the bottom-right of the
   icebreaker slide. The SVG's coordinate space puts the sun at its
   own bottom-right (200,200) so only the upper-left quadrant of the
   surrounding orbit shows inside the slide — the rest is clipped by
   the SVG's default overflow:hidden. The whole orbit rotates as one
   CSS animation; the sun is radially symmetric so it doesn't need to
   spin. Sized as a percentage of the caption box so it scales with
   laptop ↔ TV. */
.cviz-corner-wheel {
  position: absolute;
  bottom: 0;
  right: 0;
  width: 32%;
  aspect-ratio: 1;
  pointer-events: none;
  z-index: 1;
  /* SVG default overflow:hidden clips dots/spokes outside the viewBox
     — explicit declaration in case a browser ever changes the default. */
  overflow: hidden;
  /* HIDDEN by default. The wheel starts off-screen (translated past the
     bottom-right corner) and invisible. When its parent .cviz-caption
     gets .active, the emerge animation drifts it into final position.
     Setting non-active state to "hidden" here means navigating AWAY
     from the icebreaker snaps the wheel back instantly (no slow exit). */
  transform: translate(60%, 60%);
  opacity: 0;
}
.cviz-caption.active .cviz-corner-wheel {
  /* Slow surprise reveal. After a brief beat (1s) the wheel drifts up
     and left from off-screen into its corner, fading in as it arrives.
     forwards keeps the final state pinned so the wheel stays in place
     after the animation ends. */
  animation: cviz-corner-emerge 4500ms cubic-bezier(0.2, 0.7, 0.2, 1) 1000ms forwards;
}
@keyframes cviz-corner-emerge {
  from { transform: translate(60%, 60%); opacity: 0; }
  to   { transform: translate(0, 0);     opacity: 1; }
}
.cviz-corner-wheel line {
  stroke: rgba(184, 92, 44, 0.55);
  stroke-width: 1.2;
  stroke-linecap: round;
}
.cviz-corner-wheel circle:not([fill]) {
  fill: var(--ink, #15140f);
}
.cviz-corner-wheel-spin {
  transform-box: fill-box;
  transform-origin: center;
  animation: cviz-corner-spin 26s linear infinite;
}
@keyframes cviz-corner-spin {
  to { transform: rotate(360deg); }
}
/* Fullscreen note: stays visible — same coordinate space, same anchor
   to the slide's bottom-right. The aspect-ratio: 1 + width:32% rule
   keeps it square at any viewport size. */

/* Distant flywheels in the zoom-out finale spin gently around their own
   sun — same idea as the three foreground wheels, but autonomous (no
   meta-orbit). The rotation is CSS-driven so we don't have to track
   each wheel in the multiwheel motion loop. transform-box: fill-box
   makes the transform-origin relative to each <g>'s bounding box; since
   each wheel's geometry is symmetric around (cx, cy), `center` lands
   exactly on the sun. Individual `animation-duration` and
   `animation-direction` are set inline by JS for variety. */
.cviz-distant-wheel {
  transform-box: fill-box;
  transform-origin: center;
  animation-name: cviz-distant-spin;
  animation-timing-function: linear;
  animation-iteration-count: infinite;
}
@keyframes cviz-distant-spin {
  to { transform: rotate(360deg); }
}

/* =================================================================
   Touch + mobile responsive
   =================================================================
   Two media queries handle device-shape adaptations. Both work in
   tandem with the existing clamp()/vw-based type scaling, which
   already covers laptop ↔ TV well. */

/* Touch-primary devices (phone / tablet, no hover, coarse pointer):
   Bump the control buttons up so they hit Apple's 44pt minimum touch
   target. Desktop with mouse keeps the slimmer buttons. */
@media (hover: none) and (pointer: coarse) {
  .cviz-presentation .cviz-prev,
  .cviz-presentation .cviz-next,
  .cviz-presentation .cviz-fullscreen,
  .cviz .cviz-prev,
  .cviz .cviz-next {
    width: 3rem;
    height: 3rem;
    font-size: 1.4rem;
  }
  /* Bigger progress dots so they're tappable too */
  .cviz-presentation .cviz-progress-dot,
  .cviz .cviz-progress-dot {
    width: 0.85rem;
    height: 0.85rem;
  }
  .cviz-presentation .cviz-progress-dot.active,
  .cviz .cviz-progress-dot.active {
    width: 2rem;
  }
  /* Bigger fullscreen toggle on touch — it's the gateway to a usable
     mobile experience, so make it impossible to miss. */
  .cviz-presentation .cviz-fullscreen {
    top: 0.6rem;
    right: 0.6rem;
  }
}

/* Mobile portrait — narrow viewport:
   The default 16:9 aspect on .cviz-presentation produces a wide-and-
   short box on phones, cramping the captions. Use a portrait-friendly
   3:4 ratio there so the slide gets vertical breathing room. */
@media (max-width: 640px) and (orientation: portrait) {
  .cviz-presentation {
    aspect-ratio: 3 / 4;
  }
  /* Tighter slide padding so the type doesn't get squeezed at the
     edges on small screens (default was 7% 8% which eats real estate). */
  .cviz-presentation.slidemode .cviz-caption {
    padding: 5vh 6vw;
  }
  /* Animation-mode caption strip can creep taller on phones to give
     the title + body room to breathe (default is top:70% / 30% strip). */
  .cviz-presentation:not(.slidemode) .cviz-captions {
    top: 62%;
  }
  .cviz-presentation:not(.slidemode) .cviz-stage {
    bottom: 38%;
  }
  /* Corner wheel: scale down slightly on small screens so it doesn't
     dominate the icebreaker slide. */
  .cviz-corner-wheel {
    width: 26%;
  }
}

/* Fullscreen on small / portrait screens: the .cviz-presentation:fullscreen
   rule already removes max-width and uses 100vh/100vw, so the slide
   fills the device. We just tighten padding + bump base type sizes
   here so a phone in landscape OR portrait reads cleanly. */
@media (max-width: 640px) {
  .cviz-presentation:fullscreen.slidemode .cviz-caption {
    padding: 4vh 5vw;
  }
  .cviz-presentation:fullscreen.slidemode .cviz-title {
    /* Keep the clamp's vw term but tighten min so portrait phones don't
       force-grow the type beyond what the viewport can hold. */
    font-size: calc(clamp(1.9rem, 8vw, 5rem) * var(--cap-title-scale, 1));
  }
  .cviz-presentation:fullscreen.slidemode .cviz-body,
  .cviz-presentation:fullscreen.slidemode .cviz-reveals {
    font-size: calc(clamp(1.05rem, 4vw, 2.2rem) * var(--cap-body-scale, 1));
  }
}
