:root {
  --linen-50: #f5f2ea;
  --linen-100: #ece5d6;
  --stone-200: #ddd5c6;
  --stone-500: #6b6358;
  --stone-700: #4a443c;
  --stone-900: #23201b;
  --amber-400: #c99a4a;
  --amber-500: #b8863a;
  --amber-600: #96692a;
  --ink: var(--stone-900);
  --bg: var(--linen-50);
  --surface: #fffdf8;
  --surface-2: var(--linen-100);
  --border: var(--stone-200);
  --muted: var(--stone-500);
  --quote: var(--stone-700);
  --accent: var(--amber-500);
  --accent-strong: var(--amber-600);
  --accent-wash: rgba(184, 134, 58, 0.14);
  --shadow: 0 2px 10px rgba(35, 32, 27, 0.08);

  /* Spacing scale — available for later phases; not force-adopted here. */
  --s0: 0;
  --s1: 0.25rem;
  --s2: 0.5rem;
  --s3: 0.75rem;
  --s4: 1rem;
  --s5: 1.5rem;
  --s6: 2.5rem;

  /* Typography tokens */
  --serif: "Iowan Old Style", "Palatino Linotype", Palatino, "Book Antiqua", Georgia, serif;
  --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
}

/* Dark-mode palette — single source of truth for every dark override.
   :root:not([data-theme="light"]) lets the OS setting drive dark mode by
   default; :root[data-theme="dark"] lets a future manual toggle force it
   regardless of OS preference. Neither block builds the toggle itself. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --ink: #ece7dc;
    --bg: #1b1815;
    --surface: #262220;
    --surface-2: #2f2a25;
    --border: #3a352e;
    --muted: #a89f8e;
    --quote: var(--ink);
    --accent: var(--amber-400);
    --accent-strong: #d9ad63;
    --accent-wash: rgba(201, 154, 74, 0.18);
    --shadow: 0 2px 10px rgba(0, 0, 0, 0.4);
  }
}

:root[data-theme="dark"] {
  --ink: #ece7dc;
  --bg: #1b1815;
  --surface: #262220;
  --surface-2: #2f2a25;
  --border: #3a352e;
  --muted: #a89f8e;
  --quote: var(--ink);
  --accent: var(--amber-400);
  --accent-strong: #d9ad63;
  --accent-wash: rgba(201, 154, 74, 0.18);
  --shadow: 0 2px 10px rgba(0, 0, 0, 0.4);
}

* {
  box-sizing: border-box;
}

html,
body {
  margin: 0;
  padding: 0;
}

body {
  background: var(--bg);
  color: var(--ink);
  font-family: var(--serif);
  font-size: 18px;
  line-height: 1.6;
  min-height: 100vh;
  overflow-x: hidden;
}

button {
  font-family: inherit;
}

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* Sticky, not static: the header + tab bar used to scroll away with a long
   story, leaving no way back to another section (or to the Journey index —
   see the tap-active-tab-again idiom in app.js) short of scrolling all the
   way back to the top. Freezing this bar in place keeps navigation reachable
   at any scroll position while the story itself scrolls underneath it. */
.app-topbar {
  position: sticky;
  top: 0;
  z-index: 20;
  background: var(--bg);
}

.site-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  row-gap: 0.4rem;
  column-gap: 0.75rem;
  padding: max(0.6rem, env(safe-area-inset-top)) 1.1rem 0.5rem;
  border-bottom: 1px solid var(--border);
  background: var(--surface);
}

.app-name-heading {
  margin: 0;
}

/* A real button (tapping it returns to the Journey contents/index from
   anywhere), styled to read as plain heading text rather than a control. */
.app-name {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  font-size: 1.1rem;
  font-family: inherit;
  font-weight: 700;
  margin: 0;
  letter-spacing: 0.02em;
  color: var(--accent-strong);
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
}

.app-name:active {
  transform: scale(0.97);
}

.header-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 0.5rem;
  min-width: 0;
}

.icon-btn,
.tab-btn,
.action-btn {
  min-height: 44px;
  min-width: 44px;
  border-radius: 10px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--ink);
  font-family: var(--sans);
  cursor: pointer;
  transition: background-color 0.15s ease, border-color 0.15s ease, transform 0.1s ease;
}

.icon-btn:active,
.tab-btn:active,
.action-btn:active {
  transform: scale(0.97);
}

/* Header icon/status buttons (candle indicator, language toggle). Labels can
   still run long on some locales, so this lets text wrap to a couple of
   centered lines inside a capped-width pill rather than forcing a single
   long strip or overflowing the header on a ~390px phone. */
.icon-btn {
  font-size: 0.8rem;
  line-height: 1.2;
  padding: 0.4rem 0.65rem;
  white-space: normal;
  text-align: center;
  max-width: 9rem;
}

.action-btn {
  font-size: 0.95rem;
  padding: 0.5rem 0.9rem;
}

.tab-bar {
  display: flex;
  gap: 0.35rem;
  padding: 0.4rem 0.75rem;
  background: var(--bg);
}

/* Icon + one short plain-language word, one line — replaces the old
   evocative-name-over-subtitle stack (which cost a whole extra line of
   height per tab). The evocative names ("The Barefoot Journey", "The Peace
   Office") aren't gone, just moved off the persistent chrome: they still
   open the app via the gate/hero screen, and now live in the accessible
   name alongside the plain word (setTabLabel in app.js), so a screen reader
   still hears both. */
.tab-btn {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.35rem;
  font-size: 0.85rem;
  line-height: 1.2;
  padding: 0.5rem 0.3rem;
  white-space: nowrap;
}

.tab-icon {
  display: inline-flex;
  flex: 0 0 auto;
}

.tab-icon svg {
  width: 15px;
  height: 15px;
  display: block;
}

/* Tinted wash + accent text, not a solid fill: reads as "selected" without
   the heavier block-color look, and holds up the same way in dark mode
   since --accent-wash is defined per theme rather than derived on the fly. */
.tab-btn[aria-selected="true"] {
  background: var(--accent-wash);
  border-color: transparent;
  color: var(--accent-strong);
  font-weight: 600;
}

main#app {
  width: min(38rem, 100% - 2rem);
  margin-inline: auto;
  padding-block: 0.5rem calc(3rem + env(safe-area-inset-bottom, 0px));
}

.noscript-msg {
  padding: 2rem;
  text-align: center;
  color: var(--muted);
}

/* Entrance gate */

#gate {
  position: fixed;
  inset: 0;
  z-index: 100;
  background: #1b1815;
  overflow: hidden;
  display: flex;
  /* Anchored to the top, not the bottom: the background is a portrait fresco
     taller than any phone viewport, so background:cover always shows its
     full height and crops only the sides — meaning Francis's figure (lower
     half of the painting) lands in the lower half of every phone screen
     regardless of aspect ratio. Bottom-anchored text sat directly on top of
     him; the open sky/treetops at the top of the painting is the one zone
     that stays clear across every portrait phone size. */
  align-items: flex-start;
  justify-content: center;
  transition: opacity 320ms ease;
}

#gate::before {
  content: '';
  position: absolute;
  inset: -10%;
  background: center/cover no-repeat;
  background-image: url('./art/sermon-to-the-birds.webp');
}

#gate[hidden] {
  display: none;
}

#gate.leaving {
  opacity: 0;
  pointer-events: none;
}

.gate-scrim {
  position: absolute;
  inset: 0;
  /* Darkest where the text now sits (top), fading out toward the bottom so
     the painting itself — including Francis, no longer behind any text —
     reads clearly. */
  background: linear-gradient(180deg, rgba(27, 24, 21, 0.82), rgba(27, 24, 21, 0.15));
}

.gate-content {
  position: relative;
  z-index: 1;
  text-align: center;
  color: #f5f2ea;
  padding: 3.5rem 1.5rem 0;
  padding-top: calc(3.5rem + env(safe-area-inset-top, 0px));
  max-width: 32rem;
}

.gate-content h1 {
  font-family: var(--serif);
  font-size: 2rem;
  margin: 0 0 0.5rem;
}

.gate-content p {
  font-size: 1rem;
  opacity: 0.92;
  margin: 0 0 1.5rem;
}

#gate-enter {
  background: var(--accent);
  color: #1b1815;
  border: none;
  padding: 0.75rem 2rem;
  font-size: 1rem;
  border-radius: 999px;
}

.gate-lang-toggle {
  position: absolute;
  top: calc(0.75rem + env(safe-area-inset-top, 0px));
  right: 0.75rem;
  z-index: 2;
  min-height: 44px;
  min-width: 44px;
  padding: 0.4rem 0.75rem;
  border: 1px solid rgba(245, 242, 234, 0.4);
  border-radius: 999px;
  background: rgba(27, 24, 21, 0.35);
  color: #f5f2ea;
  font-family: var(--sans);
  font-size: 0.8rem;
  cursor: pointer;
}

.gate-lang-toggle:active {
  transform: scale(0.97);
}

.gate-secondary {
  display: block;
  margin: 1rem auto 0;
  background: none;
  border: none;
  color: #f5f2ea;
  opacity: 0.85;
  font-family: var(--sans);
  font-size: 0.85rem;
  text-decoration: underline;
  cursor: pointer;
  padding: 0.5rem;
  min-height: 44px;
}

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

/* Journey view */

.journey-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 16px;
  padding: 1.25rem;
  box-shadow: var(--shadow);
}

.journey-card h2 {
  margin-top: 0;
  color: var(--accent-strong);
}

.plate {
  margin: 0 auto var(--s4);
  width: min(100%, 420px);
  background: var(--surface);
  padding: 6px;
  border: 1px solid var(--border);
  border-radius: 2px;
  box-shadow: 0 1px 2px rgba(35, 32, 27, 0.06), 0 10px 28px -14px rgba(35, 32, 27, 0.28);
}

.plate img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: 1px;
}

.plate figcaption {
  font: 400 0.72rem/1.4 var(--sans);
  color: var(--muted);
  text-align: center;
  margin-top: var(--s2);
  letter-spacing: 0.01em;
}

@media (max-height: 760px) {
  .plate img {
    max-height: 52svh;
    width: auto;
    /* Without this, height-constrained sizing drops the width cap entirely —
       any plate wider (relative to its height) than the frame allows spills
       past the card's border. This is the Android/Chrome "images overflowing
       their frame" report: short-viewport Chrome windows hit this query far
       more often than iOS Safari's fullscreen chrome does. */
    max-width: 100%;
    margin-inline: auto;
  }
}

@media (prefers-color-scheme: dark) {
  .plate img {
    filter: brightness(0.88) contrast(1.02);
  }
}

.vault-header {
  margin: 0 auto var(--s5);
  width: min(100%, 420px);
  border-radius: 8px;
  overflow: hidden;
  box-shadow: 0 1px 2px rgba(35, 32, 27, 0.06), 0 10px 28px -14px rgba(35, 32, 27, 0.28);
}

.vault-header svg {
  display: block;
  width: 100%;
  height: auto;
}

.vault-twinkle {
  animation: vault-twinkle 3.6s ease-in-out infinite;
  transform-origin: center;
}
.vault-twinkle:nth-child(3n) { animation-delay: -1.1s; }
.vault-twinkle:nth-child(4n) { animation-delay: -2.3s; }

@keyframes vault-twinkle {
  0%, 100% { opacity: 0.55; }
  50% { opacity: 1; }
}

.journey-pivot {
  border-left: 3px solid var(--accent);
  margin: 1.25rem 0;
  padding: 0.25rem 0 0.25rem 1rem;
  font-style: italic;
  color: var(--quote);
}

.journey-practice {
  background: var(--surface-2);
  border-radius: 10px;
  padding: 1rem;
  margin-top: 1.25rem;
}

.journey-practice .field-label {
  font-weight: 600;
  color: var(--accent-strong);
  display: block;
  margin-bottom: 0.35rem;
}

/* Fixed, not in-flow: this is the primary way to page through a story, so it
   stays reachable at any scroll depth instead of only appearing once you've
   scrolled all the way to the bottom of a long one — same reasoning as
   .app-topbar above, applied to the other edge of the screen. It's a DOM
   sibling of .journey-card, not a child (see the comment in app.js where
   it's appended), and .journey-card's own padding-bottom is set in JS to
   match this bar's real rendered height so the last paragraph always clears
   it. */
.journey-nav {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.4rem;
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 15;
  margin: 0 auto;
  width: min(38rem, 100% - 2rem);
  background: var(--surface);
  border: 1px solid var(--border);
  border-bottom: none;
  border-radius: 16px 16px 0 0;
  box-shadow: 0 -6px 18px -12px rgba(35, 32, 27, 0.25);
  padding: 0.5rem 1.25rem calc(0.5rem + env(safe-area-inset-bottom, 0px));
}

/* .journey-progress removed — the nav is now two stacked rows:
   .progress-dots (tappable dot buttons) above .journey-nav-row
   (Back / Contents-open / Next). See the "Barefoot Journey
   browsability" section near the bottom of this file for
   .journey-nav-row, .progress-dot and the .journey-contents panel. */

.progress-text {
  font-family: var(--sans);
  font-size: 0.85rem;
  color: var(--muted);
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  background: none;
  border: none;
  border-radius: 999px;
  padding: 0.4rem 0.9rem;
  min-height: 44px;
  cursor: pointer;
  text-decoration: underline;
  text-decoration-color: var(--border);
  text-underline-offset: 3px;
  transition: color 0.15s ease, text-decoration-color 0.15s ease, transform 0.1s ease;
}

.progress-text:hover {
  color: var(--accent-strong);
  text-decoration-color: var(--accent);
}

.progress-text:active {
  transform: scale(0.97);
}

.progress-dots {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.15rem;
}

/* Prayers view */

.prayer-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 16px;
  padding: 1.25rem;
  box-shadow: var(--shadow);
  margin-bottom: 1.25rem;
}

.prayer-card.current-time {
  border-color: var(--accent);
  box-shadow: 0 0 0 2px var(--accent) inset, var(--shadow);
}

.prayer-eyebrow {
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-size: 0.75rem;
  color: var(--accent-strong);
  font-weight: 600;
  font-family: var(--sans);
}

.prayer-card h2 {
  margin: 0.25rem 0 0.75rem;
}

.prayer-lines p {
  margin: 0 0 0.5rem;
  line-height: 1.75;
}

.prayer-lines[lang="la"] {
  font-style: italic;
  font-size: 0.97em;
  letter-spacing: 0.01em;
}

.prayer-source {
  font-style: italic;
  font-size: 0.85rem;
  color: var(--muted);
  margin-top: 0.75rem;
}

.provenance {
  margin-top: 0.75rem;
}

.provenance summary {
  font-family: var(--sans);
  font-size: 0.85rem;
  color: var(--muted);
  cursor: pointer;
  list-style: none;
  display: flex;
  align-items: center;
  min-height: 44px;
  padding: 0.25rem 0;
}

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

.provenance summary::before {
  content: '▸';
  display: inline-block;
  width: 1em;
  color: var(--accent);
}

.provenance[open] summary::before {
  content: '▾';
}

.provenance summary:hover {
  color: var(--accent-strong);
}

.provenance .prayer-source {
  margin-top: 0.5rem;
  margin-left: 1em;
}

.prayer-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-top: 1rem;
}

/* --- Attributed prayer (not a writing of Francis) ---------------------
   Deliberately warm, not quarantined: a different paper laid on the same
   table, not a warning label. The real signal is structural — this card
   sits below a section rule and never joins the Office's time rotation. */

.office-appendix {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin: 2.5rem 0 1.25rem;
  color: var(--muted);
  font-size: 0.75rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.office-appendix::before,
.office-appendix::after {
  content: '';
  flex: 1 1 auto;
  height: 1px;
  background: var(--border);
}

.prayer-card.attributed {
  background: var(--surface-2);
  border-left: 3px solid var(--accent);
}

.prayer-card.attributed .prayer-eyebrow {
  color: var(--muted);
}

/* Belt-and-braces: this card must never read as "now" in the Office,
   even if a future edit hands it a time slot. */
.prayer-card.attributed.current-time {
  border-color: var(--border);
  border-left-color: var(--accent);
  box-shadow: var(--shadow);
}

.prayer-note {
  background: var(--surface);
  border-radius: 10px;
  padding: 1rem;
  margin: 1.25rem 0 0;
  font-size: 0.92rem;
  line-height: 1.55;
  color: var(--quote);
}

@media (prefers-reduced-motion: reduce) {
  * {
    transition: none !important;
    animation: none !important;
  }
}

@media print {
  .site-header,
  .tab-bar,
  .journey-nav,
  .journey-contents,
  .prayer-actions,
  .vault-header {
    display: none !important;
  }

  body {
    background: #fff;
    color: #000;
    font-size: 14pt;
  }

  main#app {
    width: 100%;
    padding: 0;
  }

  body.printing-one .prayer-card:not(.print-target) {
    display: none;
  }

  .journey-card,
  .prayer-card {
    box-shadow: none;
    border: none;
    page-break-inside: avoid;
    margin: 0 0 1.5rem;
    padding: 0;
  }

  .prayer-card.current-time {
    box-shadow: none;
  }

  .office-appendix {
    border-top: 1pt solid #000;
    padding-top: 0.4rem;
    margin: 1.5rem 0 0.75rem;
  }

  .prayer-card.attributed {
    background: none;
    border: none;
    border-left: 2pt solid #000;
    padding-left: 0.6rem;
  }

  .prayer-note {
    background: none;
    padding: 0;
    font-style: italic;
    font-size: 10pt;
  }
}

@media (min-width: 700px) {
  body {
    font-size: 19px;
  }

  /* A little roomier now that there's width to spare — the tighter sizing
     above exists for ~390-430px phones. */
  .tab-btn {
    font-size: 0.95rem;
    padding: 0.55rem 0.9rem;
  }

  .icon-btn {
    font-size: 0.85rem;
    max-width: none;
  }
}

/* Candle */

.candle-indicator {
  display: none;
  align-items: center;
  justify-content: center;
  padding: 0;
}

.candle-indicator[data-lit="true"] {
  display: inline-flex;
}

.candle-indicator .mini-flame {
  width: 10px;
  height: 14px;
  border-radius: 50% 50% 50% 50% / 60% 60% 40% 40%;
  background: radial-gradient(circle at 50% 65%, #fff6d8 0%, #ffb43d 55%, #e2701c 100%);
}

.candle-view {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  padding: 2.5rem 1rem 3rem;
  min-height: 50vh;
  min-height: 50dvh;
  justify-content: center;
}

.candle-prompt,
.candle-status {
  font-family: var(--serif);
  font-size: 1.15rem;
  color: var(--ink);
  margin: 0 0 2.5rem;
}

.candle-hint {
  font-family: var(--sans);
  font-size: 0.8rem;
  color: var(--muted);
  margin: 1.5rem 0 0;
}

.candle-stand {
  position: relative;
  width: 88px;
  height: 150px;
  display: flex;
  align-items: flex-end;
  justify-content: center;
}

.candle-glow {
  position: absolute;
  bottom: 4px;
  left: 50%;
  transform: translateX(-50%);
  width: 140px;
  height: 60px;
  background: radial-gradient(ellipse, rgba(255, 180, 61, 0.35) 0%, rgba(255, 180, 61, 0) 70%);
  pointer-events: none;
}

.candle-body {
  position: relative;
  width: 44px;
  height: 150px;
  border-radius: 4px 4px 2px 2px;
  background: linear-gradient(180deg, var(--surface) 0%, var(--surface-2) 100%);
  border: 1px solid var(--border);
}

.candle-wick {
  position: absolute;
  top: -22px;
  left: 50%;
  transform: translateX(-50%);
  width: 44px;
  height: 44px;
  min-width: 44px;
  min-height: 44px;
  border: none;
  background: transparent;
  cursor: pointer;
  padding: 0;
  /* Without this, a phone's scroll/zoom gesture recognizer can steal a
     held touch mid-hold and fire pointercancel before wireHoldToLight's
     1200ms timer completes — the hold-to-light never finishes. */
  touch-action: none;
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  user-select: none;
}

.candle-wick::before {
  content: '';
  position: absolute;
  top: 22px;
  left: 50%;
  transform: translateX(-50%);
  width: 3px;
  height: 10px;
  background: var(--stone-700);
  border-radius: 1px;
}

.hold-ring {
  position: absolute;
  top: -28px;
  left: 50%;
  transform: translateX(-50%);
  width: 52px;
  height: 52px;
  pointer-events: none;
  opacity: 0;
  transition: opacity 120ms ease;
}

.hold-ring.active {
  opacity: 1;
}

.hold-ring svg {
  width: 100%;
  height: 100%;
  transform: rotate(-90deg);
}

.ring-bg {
  fill: none;
  stroke: var(--border);
  stroke-width: 2;
}

.ring-fill {
  fill: none;
  stroke: var(--accent);
  stroke-width: 2;
  stroke-linecap: round;
  stroke-dasharray: 125.6;
  stroke-dashoffset: 125.6;
}

.hold-ring.active .ring-fill {
  animation: hold-fill 1200ms linear forwards;
}

@keyframes hold-fill {
  to {
    stroke-dashoffset: 0;
  }
}

.candle-flame {
  position: absolute;
  top: -48px;
  left: 50%;
  transform: translateX(-50%);
  width: 30px;
  height: 46px;
}

.flame-glow,
.flame-core {
  position: absolute;
  top: 50%;
  left: 50%;
  transform-origin: bottom center;
}

.flame-glow {
  width: 54px;
  height: 54px;
  transform: translate(-50%, -46%);
  filter: blur(7px);
  opacity: 0.35;
}

.flame-core {
  width: 20px;
  height: 32px;
  transform: translate(-50%, -50%);
  animation: flicker 2.9s ease-in-out infinite, sway 4.3s ease-in-out infinite;
}

.flame-core.igniting {
  animation: ignite 600ms ease-out, flicker 2.9s ease-in-out infinite 600ms, sway 4.3s ease-in-out infinite 600ms;
}

.flame-glow svg,
.flame-core svg {
  width: 100%;
  height: 100%;
  display: block;
}

@keyframes ignite {
  0% {
    transform: translate(-50%, -50%) scaleY(0.1);
    opacity: 0;
  }
  60% {
    opacity: 1;
  }
  100% {
    transform: translate(-50%, -50%) scaleY(1);
    opacity: 1;
  }
}

@keyframes flicker {
  0%,
  100% {
    transform: translate(-50%, -50%) scaleY(1) translateX(0);
  }
  40% {
    transform: translate(-50%, -50%) scaleY(1.06) translateX(0.4px);
  }
  70% {
    transform: translate(-50%, -50%) scaleY(0.97) translateX(-0.4px);
  }
}

@keyframes sway {
  0%,
  100% {
    rotate: -1.2deg;
  }
  50% {
    rotate: 1.2deg;
  }
}

.candle-intention {
  font-family: var(--serif);
  font-style: italic;
  font-size: 0.95rem;
  color: var(--quote);
  margin: 1.75rem 0 0;
}

.candle-intention-prompt {
  margin-top: 1.75rem;
  animation: fade-in 400ms ease-out 700ms both;
}

@keyframes fade-in {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

.candle-intention-input {
  font-family: var(--serif);
  font-style: italic;
  font-size: 0.95rem;
  text-align: center;
  border: none;
  border-bottom: 1px solid var(--border);
  background: transparent;
  color: var(--ink);
  padding: 0.35rem 0.5rem;
  width: min(90vw, 20rem);
}

.candle-intention-input:focus-visible {
  outline: none;
  border-bottom-color: var(--accent);
}

.candle-privacy {
  font-family: var(--sans);
  font-size: 0.72rem;
  color: var(--muted);
  margin: 2rem 0 0;
}

.candle-thread {
  font-family: var(--sans);
  font-size: 0.78rem;
  color: var(--accent-strong);
  margin: 0 0 0.5rem;
}

body.has-candle::before {
  content: '';
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: -1;
  background: radial-gradient(circle at 50% 100%, rgba(255, 180, 61, 0.05), transparent 60%);
}

@media (prefers-reduced-motion: reduce) {
  .flame-core,
  .flame-core.igniting {
    animation: flame-pulse-reduced 4s ease-in-out infinite;
  }

  @keyframes flame-pulse-reduced {
    0%,
    100% {
      opacity: 1;
    }
    50% {
      opacity: 0.85;
    }
  }

  .hold-ring.active .ring-fill {
    animation: none;
    stroke-dashoffset: 0;
    transition: stroke-dashoffset 1200ms steps(3, end);
  }

  .candle-intention-prompt {
    animation: none;
  }
}

/* ===================================================================
   Interaction/motion polish additions (app.js UX pass) — additive only,
   new rules/classes appended here rather than edited in place above to
   avoid clashing with concurrent layout/spacing edits elsewhere in this
   file. All covered by the global `prefers-reduced-motion: reduce`
   override near the top of the file, which zeroes every transition and
   animation via `* { transition: none !important; animation: none !important; }`.
   =================================================================== */

/* Tab-switch view transition (app.js: playViewTransition via switchView) */
.view-transition {
  animation: view-fade-in 220ms cubic-bezier(0.22, 1, 0.36, 1);
}

@keyframes view-fade-in {
  from {
    opacity: 0;
    transform: translateY(6px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

/* Journey card paging direction (app.js: changeJourney) */
.journey-card.enter-next {
  animation: journey-slide-next 260ms cubic-bezier(0.22, 1, 0.36, 1);
}

.journey-card.enter-prev {
  animation: journey-slide-prev 260ms cubic-bezier(0.22, 1, 0.36, 1);
}

@keyframes journey-slide-next {
  from {
    opacity: 0;
    transform: translateX(18px);
  }
  to {
    opacity: 1;
    transform: translateX(0);
  }
}

@keyframes journey-slide-prev {
  from {
    opacity: 0;
    transform: translateX(-18px);
  }
  to {
    opacity: 1;
    transform: translateX(0);
  }
}

/* Onboarding gate entrance (app.js: initGate) — exit already handled by
   the existing #gate.leaving opacity transition */
.gate-content {
  animation: gate-content-in 420ms cubic-bezier(0.22, 1, 0.36, 1) both;
}

@keyframes gate-content-in {
  from {
    opacity: 0;
    transform: translateY(-14px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

/* Candle wick press feedback — immediate tactile response distinct from
   the hold-ring's 1200ms fill (app.js: wireHoldToLight) */
.candle-wick {
  transition: transform 150ms ease;
}

.candle-wick:active {
  transform: translateX(-50%) scale(0.94);
}

/* Smooth the hold-ring's recede back to empty when a hold is cancelled
   before completion, instead of an instant snap (app.js: wireHoldToLight
   removes .active, which also removes the .hold-fill animation) */
.ring-fill {
  transition: stroke-dashoffset 200ms ease;
}

/* ===================================================================
   Barefoot Journey browsability — tappable progress dots + Contents
   index panel (app.js: renderJourneyNav / renderContents / paging).
   New two-row nav markup: .progress-dots sits above .journey-nav-row
   inside .journey-nav (both edited in place above). .journey-contents
   replaces .journey-card entirely inside #app while the index is open.
   =================================================================== */

/* Swipe paging (app.js: wireJourneySwipe). Declaring the gesture we handle
   stops a phone's own recognizer claiming a horizontal drag — the same class
   of problem .candle-wick's touch-action solves. pan-y keeps vertical
   scrolling native, and pinch-zoom is kept explicitly so a reader can still
   zoom into a fresco; a bare `pan-y` would silently disable that. */
.journey-card {
  touch-action: pan-y pinch-zoom;
}

/* The numeric position read-out used to be a visible line of its own above
   the dots — dropped from sight in the 2026-09-16 nav redesign to reclaim
   that row's height, since the dots already show position at a glance (the
   current one is larger with a ring — see .progress-dot[aria-current] below)
   and Contents gives an exact count. Kept in the DOM as .sr-only (app.js)
   rather than deleted, so it's still announced to screen readers exactly as
   before. */

/* flex-wrap is the safety valve, not the intended layout: at 390px the three
   controls fit on one line in both languages, but on a ~320px screen they
   drop to a second row instead of overflowing the card. */
.journey-nav-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 0.5rem;
  width: 100%;
}

/* Progress dots: 24x24px hit area (WCAG 2.5.8 AA) around an 8px visual
   dot. 11 x 24px + gaps must fit inside a ~318px card interior on a
   390px phone — see .progress-dots gap above; do not widen the gap
   without re-checking that budget. */
.progress-dot {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  flex: 0 0 auto;
  padding: 0;
  border: none;
  background: none;
  cursor: pointer;
}

.progress-dot .dot {
  display: block;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--border);
  transition: background-color 0.15s ease, transform 0.15s ease, box-shadow 0.15s ease;
}

.progress-dot:hover .dot {
  background: var(--accent);
}

/* Current position: larger + a two-tone ring (surface, then accent) so
   it reads clearly without relying on hue alone. */
.progress-dot[aria-current="true"] .dot {
  background: var(--accent);
  transform: scale(1.5);
  box-shadow: 0 0 0 3px var(--surface), 0 0 0 5px var(--accent);
}

.contents-icon {
  display: inline-flex;
}

.contents-icon svg {
  width: 0.85em;
  height: 0.85em;
  fill: currentColor;
  display: block;
}

/* Contents / index panel — same card language as .journey-card /
   .prayer-card (surface, border, radius, shadow), quiet rather than a
   media-grid look. */

.journey-contents {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 16px;
  padding: 1.25rem;
  box-shadow: var(--shadow);
}

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

.contents-title {
  margin: 0;
  color: var(--accent-strong);
  font-size: 1.2rem;
}

.contents-grid {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 0.75rem;
}

@media (min-width: 700px) {
  .contents-grid {
    grid-template-columns: repeat(3, 1fr);
  }
}

.contents-item {
  width: 100%;
  display: flex;
  flex-direction: column;
  text-align: left;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 12px;
  overflow: hidden;
  padding: 0;
  cursor: pointer;
  font-family: inherit;
  color: var(--ink);
  transition: border-color 0.15s ease, background-color 0.15s ease, box-shadow 0.15s ease, transform 0.1s ease;
}

.contents-item:hover {
  border-color: var(--accent);
}

.contents-item:active {
  transform: scale(0.97);
}

/* "You are here": accent border (thicker, not just recoloured) plus a
   surface-tone shift back toward the panel's own --surface, so the
   current item reads distinctly even without perceiving amber. */
.contents-item[aria-current="true"] {
  background: var(--surface);
  border: 2px solid var(--accent);
  box-shadow: 0 0 0 1px var(--accent);
}

.contents-thumb {
  position: relative;
  width: 100%;
  aspect-ratio: 4 / 3;
  background: var(--surface-2);
  overflow: hidden;
  border-radius: 10px 10px 0 0;
}

.contents-thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Frescoes crop better biased toward the top than dead-centre — keeps
     figures' heads in frame. */
  object-position: center 28%;
  display: block;
}

/* Same scope/treatment as .plate img above: dim artwork under the OS
   dark preference only. */
@media (prefers-color-scheme: dark) {
  .contents-thumb img {
    filter: brightness(0.88) contrast(1.02);
  }
}

.contents-meta {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  padding: 0.65rem 0.75rem 0.8rem;
}

.contents-num {
  font-family: var(--sans);
  font-size: 0.75rem;
  color: var(--muted);
  flex: 0 0 auto;
}

.contents-name {
  font-family: var(--serif);
  font-size: 0.92rem;
  line-height: 1.3;
  color: var(--ink);
  overflow-wrap: break-word;
}

/* ===================================================================
   Candle legibility pass (app.js: renderCandleView / wireHoldToLight).
   Problem this solves: unlit, the whole feature was a pale rectangle on
   a pale background with a 3x10px wick stub drawn INSIDE its top edge,
   and the only tap target was a transparent 44x44 button floating above
   it with no resting affordance at all — the hold-ring was opacity:0
   until you had already guessed where to press. Nothing on screen said
   "this is a candle, press it here".
   Additive/override rules only; nothing above is edited in place.
   =================================================================== */

/* The candle now reads as a waxy cylinder rather than a flat panel: a
   left-to-right shading pass over the existing vertical gradient, plus a
   drawn wax rim at the top. */
.candle-body {
  background-image:
    linear-gradient(
      90deg,
      rgba(0, 0, 0, 0.11) 0%,
      rgba(255, 255, 255, 0.34) 33%,
      rgba(255, 255, 255, 0.1) 64%,
      rgba(0, 0, 0, 0.13) 100%
    ),
    linear-gradient(180deg, var(--surface) 0%, var(--surface-2) 100%);
  transition: transform 160ms ease, filter 160ms ease;
}

/* The wax top. pointer-events:none matters: without it this pseudo-element
   sits over the top of the press target and swallows the pointerdown that
   starts a hold. */
.candle-body::after {
  content: '';
  position: absolute;
  top: -4px;
  left: 0;
  right: 0;
  height: 8px;
  border-radius: 50%;
  background: var(--surface);
  border: 1px solid var(--border);
  pointer-events: none;
}

/* Press target = the whole candle, not a patch of air above it. */
.candle-wick {
  top: -34px;
  width: 64px;
  height: 190px;
  min-width: 44px;
  min-height: 44px;
}

/* A wick that actually sticks out of the wax (-8px to +6px around the
   body's top edge), instead of being buried inside it. */
.candle-wick::before {
  top: 26px;
  width: 3px;
  height: 14px;
  border-radius: 2px;
}

/* :active on a transparent button showed nothing. The press feedback now
   lands on the candle itself, driven by .holding on .candle-stand. */
.candle-wick:active {
  transform: translateX(-50%);
}

.candle-stand.holding .candle-body {
  transform: scale(0.97);
  filter: brightness(1.05);
}

/* renderCandleView sets an inline opacity:0 on the glow while unlit, so
   the warm-up glow during a hold has to override it. */
.candle-stand.holding .candle-glow {
  opacity: 0.45 !important;
  transition: opacity 400ms ease;
}

/* The ring is the affordance as well as the progress meter: a soft dashed
   target sitting at the wick from the moment the view loads. */
.hold-ring {
  opacity: 1;
  animation: hold-ring-breathe 3.2s ease-in-out infinite;
}

.ring-bg {
  stroke: var(--accent);
  stroke-width: 1.5;
  stroke-dasharray: 3 5;
  opacity: 0.4;
}

.hold-ring.active {
  animation: none;
  transform: translateX(-50%) scale(1);
}

.hold-ring.active .ring-bg {
  stroke-dasharray: none;
  opacity: 0.55;
}

@keyframes hold-ring-breathe {
  0%,
  100% {
    transform: translateX(-50%) scale(1);
    opacity: 0.75;
  }
  50% {
    transform: translateX(-50%) scale(1.08);
    opacity: 1;
  }
}

/* The instruction, now the caption of the candle rather than a footnote. */
.candle-hint {
  margin-top: 1.1rem;
  font-size: 0.85rem;
  max-width: 22rem;
  transition: color 160ms ease;
}

/* Shown when a press was released before the ring filled — the old
   behaviour was silence, which reads as "not a button". */
.candle-hint.urging {
  color: var(--accent-strong);
  font-weight: 600;
}

/* How much light is left. "Still burning" alone never said whether that
   meant one more hour or twenty. */
.candle-remaining {
  font-family: var(--sans);
  font-size: 0.78rem;
  color: var(--muted);
  margin: 1.1rem 0 0;
}

/* The intention is editable for as long as the candle burns, so the block
   only does its delayed reveal in the render right after lighting. */
.candle-intention-prompt {
  animation: none;
}

.candle-intention-prompt.revealing {
  animation: fade-in 400ms ease-out 700ms both;
}

.candle-intention-edit {
  display: inline-block;
  margin-top: 0.25rem;
  padding: 0.3rem 0.55rem;
  min-height: 32px;
  background: none;
  border: none;
  font-family: var(--sans);
  font-size: 0.75rem;
  color: var(--accent-strong);
  text-decoration: underline;
  text-underline-offset: 2px;
  cursor: pointer;
}

/* ===================================================================
   Contents index as a landing view (app.js: renderJourneyContents).
   The header's bare "Close" button is gone; this row replaces it and
   names its destination, so the index can open cold — on a first visit
   or after a break — and still say where the reader is.
   =================================================================== */

.contents-resume {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.1rem;
  width: 100%;
  margin-bottom: 1.25rem;
  padding: 0.7rem 0.9rem;
  text-align: left;
  background: var(--surface-2);
  border-color: var(--accent);
}

.contents-resume:hover {
  background: var(--surface);
}

.contents-resume-label {
  font-family: var(--sans);
  font-size: 0.68rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--accent-strong);
}

.contents-resume-title {
  font-family: var(--serif);
  font-size: 1rem;
  line-height: 1.3;
  color: var(--ink);
}

/* Superseded by the icon + single-line-label .tab-btn rule near .tab-bar
   above (2026-09-16 nav redesign) — the evocative-name-over-gloss two-line
   stack this used to style is gone from the visible tab, though the
   evocative name lives on in the accessible name (setTabLabel in app.js). */
