/* ============================================================
   components.css — log detail page components
   ============================================================ */

/* ---- Shared sticky-nav inner container ----
   Every page's top nav (log-breadcrumb / couple-nav / top-nav /
   chronicle-nav) wraps its content in `.nav-inner`. This guarantees:
     - same horizontal max-width across all pages (so the left edge
       lines up — "rena-log" / "← home" / breadcrumb start at the
       same column on wide viewports)
     - same flex layout, so the viewer-settings gear can always sit
       at the right edge as a sibling
     - same vertical alignment, so headers don't wobble in height
   Add a per-nav extra wrapper (.nav-inner-main) when you need
   ellipsis on long content (currently only log-breadcrumb does). */
.nav-inner {
  max-width: 1080px;
  margin: 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-3);
  min-width: 0;
}
/* Two slots inside every .nav-inner: main (left content — breadcrumb,
   back link, brand) and tools (right side — links group, viewer
   settings gear). Empty .nav-inner-tools still takes flex space so
   the gear lands in a predictable spot once injected. */
.nav-inner-main {
  flex: 1 1 auto;
  min-width: 0;
}
.nav-inner-tools {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: var(--s-3);
}

/* Bookmark icon link in nav-inner-tools — sits next to the ⚙ gear
   on every page (except the home top-nav, where the full "📑 책갈피"
   text link lives in .top-nav-links). Sized to match the ⚙ button
   so the two read as a pair. */
.nav-bookmark-link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.85);
  border: 1px solid rgba(0, 0, 0, 0.10);
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.06);
  font-size: 14px;
  text-decoration: none;
  color: var(--ink-sub);
  transition: transform var(--dur-fast) var(--ease),
              box-shadow var(--dur-fast) var(--ease),
              color var(--dur-fast) var(--ease);
}
.nav-bookmark-link:hover {
  transform: translateY(-2px);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
  color: var(--rose-deep);
}

/* Unified mobile padding for every page-top nav so headers don't
   wobble in height across page types. */
@media (max-width: 640px) {
  .log-breadcrumb,
  .couple-nav,
  .top-nav,
  .chronicle-nav {
    padding: var(--s-3) var(--s-3);
  }
}

/* ---- Breadcrumb (sticky) ---- */
.log-breadcrumb {
  position: sticky;
  top: 0;
  z-index: 40;
  margin: 0;
  padding: var(--s-3) var(--s-5);
  font-size: var(--text-xs);
  color: var(--ink-sub);
  letter-spacing: 0.04em;
  background: var(--nav-bg, rgba(253, 247, 245, 0.85));
  backdrop-filter: blur(10px) saturate(140%);
  -webkit-backdrop-filter: blur(10px) saturate(140%);
  border-bottom: 1px solid var(--line);
}
/* Breadcrumb keeps its long-title ellipsis on .nav-inner-main so the
   outer .log-breadcrumb has visible overflow — otherwise the
   viewer-settings dropdown that hangs below the nav gets clipped. */
.log-breadcrumb .nav-inner-main {
  flex: 1 1 auto;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.log-breadcrumb a {
  color: var(--ink-sub);
  text-decoration: none;
  transition: color var(--dur-fast) var(--ease);
  flex-shrink: 0;
}
.log-breadcrumb a:hover {
  color: var(--couple-primary, var(--rose-deep));
}
.log-breadcrumb .sep {
  margin: 0 8px;
  color: var(--ink-light);
  flex-shrink: 0;
}
.log-breadcrumb .current {
  color: var(--ink);
  font-weight: 700;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}

/* ---- Page container ---- */
.log-page {
  max-width: 880px;
  margin: 0 auto;
  padding: 0 var(--s-4) var(--s-8);
}

/* ---- Hero / cover ---- */
.log-hero {
  position: relative;
  height: clamp(360px, 55vh, 520px);
  overflow: hidden;
  margin: 0 calc(-1 * var(--s-4)) var(--s-7);
  border-radius: 0 0 var(--r-xl) var(--r-xl);
  isolation: isolate;
}
.log-hero-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: var(--hero-x, 50%) var(--hero-y, 35%);
  z-index: 1;
}
.log-hero-overlay {
  position: absolute;
  inset: 0;
  background:
    linear-gradient(180deg,
      rgba(74, 58, 68, 0)   0%,
      rgba(74, 58, 68, 0.05) 45%,
      rgba(74, 58, 68, 0.80) 100%);
  z-index: 2;
}
.log-hero-text {
  position: absolute;
  z-index: 3;
  bottom: var(--s-6);
  left: var(--s-6);
  right: var(--s-6);
  color: #fff;
}
.log-archive-no {
  font-family: var(--font-display);
  font-size: var(--text-xs);
  letter-spacing: 0.32em;
  text-transform: uppercase;
  opacity: 0.85;
  margin-bottom: var(--s-2);
}
.log-archive-no::before {
  content: "✦ ";
  color: var(--peach);
}
.log-title {
  font-family: var(--font-display);
  font-size: clamp(2rem, 6vw, 3rem);
  font-weight: 700;
  line-height: 1.15;
  margin-bottom: 6px;
  color: #fff;                /* override base.css h1 ink color */
  text-shadow: 0 2px 12px rgba(0, 0, 0, 0.45);
}
.log-subtitle {
  font-family: var(--font-serif);
  font-size: var(--text-md);
  font-style: italic;
  opacity: 0.96;
  margin-bottom: var(--s-4);
}
.log-tags {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2);
}
.log-tag {
  display: inline-block;
  background: rgba(255, 255, 255, 0.22);
  border: 1px solid rgba(255, 255, 255, 0.4);
  border-radius: var(--r-pill);
  padding: 4px 12px;
  font-size: var(--text-xs);
  font-weight: 600;
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
}

/* ---- Profile cards ---- */
.log-profiles {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--s-4);
  margin-bottom: var(--s-6);
}
.log-profile-card {
  background: var(--surface);
  border: 1.5px solid var(--line);
  border-radius: var(--r-lg);
  padding: var(--s-4);
  display: flex;
  align-items: center;
  gap: var(--s-3);
  box-shadow: var(--shadow);
  transition: transform var(--dur-base) var(--ease);
}
.log-profile-card:hover {
  transform: translateY(-2px);
  box-shadow: var(--shadow-up);
}
.log-profile-avatar {
  width: 52px;
  height: 52px;
  border-radius: 50%;
  background: linear-gradient(135deg, var(--peach), var(--rose));
  color: #fff;
  display: grid;
  place-items: center;
  font-family: var(--font-display);
  font-size: var(--text-lg);
  font-weight: 700;
  flex-shrink: 0;
}
.log-profile-card.role-user .log-profile-avatar {
  background: linear-gradient(135deg, var(--sky), var(--lilac));
}
/* Image variant — same circle silhouette but the speaker's couple-level
   image instead of the initial. Only rendered when initial is empty. */
.log-profile-avatar--img {
  background: var(--surface-2);
  object-fit: cover;
  object-position: center;
}
.log-profile-avatar--img.is-broken {
  /* Image 404'd at runtime — drop the silhouette to a soft block */
  background: var(--surface-2);
}
.log-profile-info { min-width: 0; }
.log-profile-role {
  font-size: 10px;
  color: var(--ink-sub);
  letter-spacing: 0.2em;
  text-transform: uppercase;
  font-weight: 800;
  margin-bottom: 2px;
}
.log-profile-name {
  font-family: var(--font-serif);
  font-weight: 700;
  font-size: var(--text-md);
  color: var(--ink);
}

/* ---- Meta bar (Play Bar style) ---- */
.log-metabar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-3);
  background: var(--surface);
  border: 1.5px solid var(--line);
  border-radius: var(--r-pill);
  padding: var(--s-3) var(--s-5);
  box-shadow: var(--shadow);
  margin-bottom: var(--s-7);
}
.log-metabar-icon {
  display: inline-grid;
  place-items: center;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: linear-gradient(135deg, var(--rose), var(--rose-deep));
  color: #fff;
  font-size: var(--text-xs);
  flex-shrink: 0;
}
.log-metabar-item {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: var(--text-xs);
  color: var(--ink-sub);
  font-weight: 600;
}
.log-metabar-item-label {
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--ink-light);
}
.log-metabar-item-value {
  color: var(--ink);
  font-weight: 700;
}
.log-metabar-item a {
  color: var(--rose-deep);
  text-decoration: underline;
  text-decoration-color: var(--line-strong);
  text-underline-offset: 3px;
}
.log-metabar-divider {
  color: var(--ink-light);
  user-select: none;
  opacity: 0.6;
}

/* ---- Storyline (story so far) ---- */
.log-storyline {
  background: var(--surface-2);
  border-left: 3px solid var(--lilac);
  padding: var(--s-4) var(--s-5);
  border-radius: 0 var(--r-md) var(--r-md) 0;
  margin-bottom: var(--s-7);
  font-family: var(--font-serif);
  font-style: italic;
  color: var(--ink-sub);
  line-height: var(--leading-loose);
}
.log-storyline::before {
  content: "✿ ";
  color: var(--lilac);
}

/* ---- Body — message stream ---- */
.log-body {
  display: flex;
  flex-direction: column;
  gap: var(--s-6);
}

.msg {
  display: flex;
  flex-direction: column;
}
.msg--char {
  align-items: flex-start;
}
.msg--user {
  align-items: flex-end;
}

/* Highlight pulse when the page is navigated to a specific message
   via URL fragment (e.g. /couple/log/#m3 from an excerpt card).
   Pure CSS via :target pseudo-class — no JS needed. The pulse
   fades over 1.8s so the user sees where they landed without
   committing to a permanent highlight. */
.msg:target {
  animation: msgTargetPulse 1.8s ease-out 200ms 1;
  scroll-margin-top: 80px;  /* leave room above for sticky nav */
}
@keyframes msgTargetPulse {
  0% {
    background: color-mix(in srgb, var(--rose, #ee7aa6) 18%, transparent);
    border-radius: 12px;
  }
  100% {
    background: transparent;
    border-radius: 12px;
  }
}

.msg-speaker {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 10px;
  color: var(--ink-sub);
  font-weight: 800;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  margin-bottom: var(--s-2);
  padding: 0 var(--s-2);
}
.msg-speaker::before {
  content: "";
  width: 18px;
  height: 1.5px;
  background: var(--rose);
  border-radius: 1px;
}
.msg--user .msg-speaker::before {
  background: var(--lilac);
}

.msg-num {
  font-family: var(--font-display);
  font-size: 11px;
  color: var(--ink-light);
  letter-spacing: 0.08em;
  font-weight: 700;
  margin-left: 6px;
  padding: 1px 8px;
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  text-transform: none;
}

/* Per-message bookmark button next to the #NN pill — tiny, ghost-y
   until hovered or activated. js/chat-bookmark.js handles the toggle. */
.msg-bookmark {
  appearance: none;
  background: transparent;
  border: none;
  color: var(--ink-light);
  font-size: 12px;
  line-height: 1;
  padding: 2px 6px;
  margin-left: 4px;
  border-radius: 999px;
  cursor: pointer;
  opacity: 0.35;
  transition: color 140ms ease, opacity 140ms ease, background 140ms ease;
}
.msg-bookmark:hover,
.msg-bookmark:focus-visible {
  opacity: 1;
  outline: none;
  color: var(--rose);
  background: rgba(238, 122, 166, 0.10);
}
.msg-bookmark.is-active {
  opacity: 1;
  color: var(--rose-deep);
  background: rgba(238, 122, 166, 0.16);
}
.msg-bookmark .msg-bookmark-glyph {
  display: inline-block;
}

.msg-bubble {
  max-width: 88%;
  padding: var(--s-5) var(--s-5);
  border: 1.5px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--surface);
  box-shadow: var(--shadow);
  font-family: var(--font-serif);
  font-size: var(--text-base);
  line-height: var(--leading-loose);
  color: var(--ink);
}
/* Editor-mode marker for messages whose translation cache lookup
   missed (parse_risu emits this only when RENALOG_EDITOR=1 — site
   builds never include it). */
.msg-untranslated {
  display: inline-block;
  margin: 0 0 8px;
  padding: 4px 10px;
  background: #fdf2e3;
  border: 1px solid #e6c89c;
  border-radius: 999px;
  color: #7d4d1a;
  font-family: var(--font-sans);
  font-size: 11px;
  letter-spacing: 0.02em;
}
/* Chat bubbles use color-mix on top of theme tokens so they stay
   readable across light / sepia / dark — char gets a faint rose
   wash, user gets a faint lilac wash. No more hardcoded #ffffff. */
.msg--char .msg-bubble {
  background: var(--bubble-char-bg,
    color-mix(in srgb, var(--surface) 92%, var(--rose) 8%));
  border-color: var(--bubble-char-border, rgba(238, 122, 166, 0.22));
  border-top-left-radius: var(--r-sm);
  color: var(--ink);
}
.msg--user .msg-bubble {
  background: var(--bubble-user-bg,
    color-mix(in srgb, var(--surface) 92%, var(--lilac) 8%));
  border-color: var(--bubble-user-border, rgba(196, 168, 224, 0.32));
  border-top-right-radius: var(--r-sm);
  max-width: 75%;
  color: var(--ink);
}
/* Dark theme — boost the wash a touch so the bubble stands out from
   the body background more clearly. */
body.is-theme-dark .msg--char .msg-bubble {
  background: color-mix(in srgb, var(--surface) 80%, var(--rose) 20%);
}
body.is-theme-dark .msg--user .msg-bubble {
  background: color-mix(in srgb, var(--surface) 80%, var(--lilac) 20%);
}
.msg-bubble p {
  margin-bottom: var(--s-3);
}
.msg-bubble p:last-child { margin-bottom: 0; }

/* ---- Section headers (## / ###) ---- */
.log-section-header {
  font-family: var(--font-display);
  font-size: var(--text-md);
  color: var(--rose-deep);
  letter-spacing: 0.05em;
  margin: var(--s-5) 0 var(--s-3);
  display: flex;
  align-items: center;
  gap: var(--s-2);
}
.log-section-header::before {
  content: "";
  flex: 0 0 24px;
  height: 1px;
  background: var(--line-strong);
}
.log-section-header::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--line);
}
.log-subheader {
  font-size: var(--text-xs);
  color: var(--lilac-deep);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  font-weight: 800;
  margin: var(--s-4) 0 var(--s-2);
}

/* ---- Dialogue (Korean curly double quote) ----
   Highlighter color now follows site theme (was hardcoded peach/sky).
   Each var() falls back to the legacy literal if the theme block
   isn't injected (e.g. a page that bypasses the build pipeline). */
.dialogue {
  font-weight: 700;
  background: linear-gradient(180deg,
    transparent 55%,
    var(--dialogue-char-bg, rgba(255, 213, 194, 0.62)) 55%,
    var(--dialogue-char-bg, rgba(255, 213, 194, 0.62)) 95%,
    transparent 95%);
  padding: 0 2px;
  border-radius: 2px;
  color: var(--ink);
}
.msg--user .dialogue {
  background: linear-gradient(180deg,
    transparent 55%,
    var(--dialogue-user-bg, rgba(206, 228, 245, 0.7)) 55%,
    var(--dialogue-user-bg, rgba(206, 228, 245, 0.7)) 95%,
    transparent 95%);
}
/* Dark theme — drop the dialogue highlighter opacity so it doesn't
   blow out the bubble. The default 62% peach reads as "bright yellow
   marker" against a dim purple-rose bubble background and the dialogue
   text becomes hard to read. */
body.is-theme-dark .dialogue {
  background: linear-gradient(180deg,
    transparent 55%,
    var(--dialogue-char-bg-dark, rgba(255, 213, 194, 0.22)) 55%,
    var(--dialogue-char-bg-dark, rgba(255, 213, 194, 0.22)) 95%,
    transparent 95%);
  color: var(--ink);
}
body.is-theme-dark .msg--user .dialogue {
  background: linear-gradient(180deg,
    transparent 55%,
    var(--dialogue-user-bg-dark, rgba(206, 228, 245, 0.22)) 55%,
    var(--dialogue-user-bg-dark, rgba(206, 228, 245, 0.22)) 95%,
    transparent 95%);
}

/* ---- Thought (Korean curly single quote) ---- */
.thought {
  font-style: italic;
  color: var(--lilac-deep);
  background: var(--thought-bg, rgba(232, 213, 245, 0.35));
  padding: 1px 4px;
  border-radius: 3px;
  font-size: 0.96em;
}

/* ---- Italic emphasis (*..*) ---- */
.msg-bubble em {
  font-style: italic;
  color: var(--ink-sub);
}

/* ---- GigaTrans block ---- */
.gigatrans {
  margin: var(--s-3) 0;
}
.gigatrans-toggle {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: var(--text-xs);
  color: var(--lilac-deep);
  background: rgba(196, 168, 224, 0.08);
  border: 1px dashed var(--lilac);
  border-radius: var(--r-pill);
  padding: 4px 12px;
  cursor: pointer;
  font-weight: 700;
  letter-spacing: 0.04em;
  transition: all var(--dur-fast) var(--ease);
}
.gigatrans-toggle::before {
  content: "🌐";
  font-size: 11px;
}
.gigatrans-toggle:hover {
  /* Lighter than the aria-expanded=true state so closing the panel
     visibly drops the button out of "active" — even if the mouse is
     still over it. Previously hover and expanded shared the full
     lilac fill, making the close click feel "stuck". */
  background: rgba(196, 168, 224, 0.20);
  color: var(--lilac-deep);
  transform: translateY(-1px);
}
.gigatrans-toggle[aria-expanded="true"] {
  background: var(--lilac);
  color: #fff;
}
/* When expanded AND hovered, keep the full fill (no lighter
   override), since there's no ambiguity about state. */
.gigatrans-toggle[aria-expanded="true"]:hover {
  background: var(--lilac-deep);
  color: #fff;
}
/* Drop the focus outline after a mouse click — the JS handler also
   blurs the button, so this is just belt-and-suspenders. Keyboard
   focus still gets a visible ring via :focus-visible. */
.gigatrans-toggle:focus { outline: none; }
.gigatrans-toggle:focus-visible {
  outline: 2px solid var(--lilac);
  outline-offset: 2px;
}
.gigatrans-content {
  margin-top: var(--s-3);
  padding: var(--s-3) var(--s-4);
  background: rgba(232, 213, 245, 0.18);
  border-left: 2px solid var(--lilac);
  border-radius: 0 var(--r-sm) var(--r-sm) 0;
  font-family: var(--font-body);
  font-size: var(--text-sm);
  font-style: italic;
  color: var(--ink-sub);
  line-height: 1.7;
}
.gigatrans-content[hidden] { display: none; }
.gigatrans-content p { margin-bottom: var(--s-2); }
.gigatrans-content p:last-child { margin-bottom: 0; }

/* ---- Chain-of-thought blocks (<Thoughts>...) ----
   Rendered only when meta.chainOfThought is "fold" or "show".
   Hidden mode strips them in the parser before HTML even appears. */
.log-thoughts {
  margin: var(--s-3) 0;
  font-size: var(--text-sm);
  color: var(--ink-sub);
  line-height: 1.7;
}

/* fold: collapsible <details> */
.log-thoughts--fold {
  background: rgba(255, 245, 230, 0.55);
  border: 1px dashed #d8c4a8;
  border-radius: var(--r-sm);
  padding: 0;
}
.log-thoughts--fold > summary {
  cursor: pointer;
  list-style: none;
  padding: 6px 14px;
  font-size: var(--text-xs);
  font-weight: 700;
  letter-spacing: 0.06em;
  color: #8a6438;
  display: flex;
  align-items: center;
  gap: 6px;
}
.log-thoughts--fold > summary::-webkit-details-marker { display: none; }
.log-thoughts--fold > summary::before {
  content: "✦";
  font-size: 11px;
  color: #b18654;
}
.log-thoughts--fold > summary::after {
  content: "▾";
  margin-left: auto;
  color: #b18654;
  transition: transform var(--dur-fast) var(--ease);
}
.log-thoughts--fold[open] > summary::after {
  transform: rotate(180deg);
}
.log-thoughts--fold .log-thoughts-content {
  padding: 0 14px 10px;
  font-style: italic;
  font-size: 12px;
  color: #6c5037;
}
.log-thoughts--fold .log-thoughts-content p {
  margin-bottom: var(--s-2);
}
.log-thoughts--fold .log-thoughts-content p:last-child {
  margin-bottom: 0;
}

/* show: always-visible aside */
.log-thoughts--show {
  background: rgba(255, 248, 235, 0.5);
  border-left: 2px solid #d8c4a8;
  border-radius: 0 var(--r-sm) var(--r-sm) 0;
  padding: 8px 14px;
}
.log-thoughts--show .log-thoughts-label {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: #b18654;
  margin-bottom: 4px;
}
.log-thoughts--show .log-thoughts-content {
  font-style: italic;
  font-size: 12px;
  color: #6c5037;
}
.log-thoughts--show .log-thoughts-content p {
  margin-bottom: var(--s-2);
}
.log-thoughts--show .log-thoughts-content p:last-child {
  margin-bottom: 0;
}

/* ---- Footer ---- */
.log-footer {
  margin-top: var(--s-8);
  padding-top: var(--s-6);
  border-top: 1px dashed var(--line-strong);
  text-align: center;
}
.log-footer-glyphs {
  font-size: var(--text-md);
  color: var(--rose);
  letter-spacing: 0.5em;
  margin-bottom: var(--s-3);
}
.log-footer-credits {
  font-size: var(--text-xs);
  color: var(--ink-light);
  font-weight: 600;
  letter-spacing: 0.04em;
}
.log-back {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  margin-top: var(--s-4);
  padding: var(--s-2) var(--s-4);
  font-size: var(--text-xs);
  color: var(--rose-deep);
  background: var(--surface);
  border: 1.5px solid var(--line);
  border-radius: var(--r-pill);
  font-weight: 700;
  transition: all var(--dur-fast) var(--ease);
}
.log-back:hover {
  background: var(--rose);
  color: #fff;
  border-color: var(--rose);
  transform: translateY(-1px);
}

/* ---- Mobile (log detail) ---- */
@media (max-width: 640px) {
  .log-hero { margin: 0 calc(-1 * var(--s-3)) var(--s-6); height: 360px; }
  .log-hero-text { left: var(--s-4); right: var(--s-4); bottom: var(--s-4); }
  .log-profiles { grid-template-columns: 1fr; gap: var(--s-3); }
  .log-metabar { padding: var(--s-3); border-radius: var(--r-md); }
  .msg-bubble { max-width: 95%; padding: var(--s-4); }
  .msg--user .msg-bubble { max-width: 92%; }
}

/* ============================================================
   COUPLE PAGE COMPONENTS
   ============================================================ */

/* ---- Top nav (sticky) ---- */
.couple-nav {
  position: sticky;
  top: 0;
  z-index: 40;
  margin: 0;
  padding: var(--s-3) var(--s-5);
  background: var(--nav-bg, rgba(253, 247, 245, 0.85));
  backdrop-filter: blur(10px) saturate(140%);
  -webkit-backdrop-filter: blur(10px) saturate(140%);
  border-bottom: 1px solid var(--line);
}
.couple-nav-back {
  display: inline-block;
  font-size: var(--text-xs);
  color: var(--ink-sub);
  letter-spacing: 0.04em;
  font-weight: 600;
  text-decoration: none;
  /* horizontal centering + max-width now handled by .nav-inner */
}
.couple-nav-back:hover {
  color: var(--couple-primary, var(--rose-deep));
}

/* ---- Page container ---- */
.couple-page {
  max-width: 960px;
  margin: 0 auto;
  padding: 0 var(--s-5) var(--s-8);
}

/* ---- Hero ---- */
.couple-hero {
  position: relative;
  margin: var(--s-4) calc(-1 * var(--s-5)) var(--s-7);
  border-radius: var(--r-xl);
  overflow: hidden;
  isolation: isolate;
  height: clamp(380px, 58vh, 540px);
  display: flex;
  align-items: flex-end;
  justify-content: center;
}
.couple-hero-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: var(--couple-hero-x, 50%) var(--couple-hero-y, 50%);
  z-index: 0;
}
.couple-hero-overlay {
  position: absolute;
  inset: 0;
  z-index: 1;
  background:
    linear-gradient(180deg,
      rgba(0,0,0,0)   30%,
      rgba(0,0,0,0.20) 60%,
      rgba(0,0,0,0.65) 100%);
}
/* Fallback: if no image, show themed gradient */
.couple-hero:not(.has-image) .couple-hero-overlay {
  background:
    radial-gradient(ellipse at top left,
      var(--couple-accent, var(--peach)) 0%, transparent 60%),
    radial-gradient(ellipse at bottom right,
      var(--couple-primary, var(--rose)) 0%,
      var(--couple-primary-deep, var(--rose-deep)) 80%);
}
.couple-hero:not(.has-image) .couple-hero-overlay::after {
  content: "";
  position: absolute;
  inset: 0;
  background-image: radial-gradient(rgba(255,255,255,0.28) 1px, transparent 1px);
  background-size: 16px 16px;
  mix-blend-mode: overlay;
  opacity: 0.5;
}
.couple-hero-content {
  position: relative;
  z-index: 2;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s-3);
  padding: var(--s-7) var(--s-5);
  text-align: center;
}

.couple-hero-portraits {
  display: flex;
  align-items: center;
  gap: var(--s-3);
}
.portrait {
  width: 124px;
  height: 124px;
  border-radius: 50%;
  overflow: hidden;
  border: 4px solid #fff;
  box-shadow: 0 8px 24px rgba(0,0,0,0.22);
  background: #fff;
  transition: transform var(--dur-base) var(--ease);
}
.portrait img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center top;
}
.portrait:hover {
  transform: translateY(-3px);
}
.couple-hero-x {
  font-family: var(--font-display);
  font-size: var(--text-2xl);
  color: rgba(255,255,255,0.85);
  font-weight: 200;
}

.couple-hero-text {
  text-align: center;
  color: #fff;
}
.couple-hero-bracket {
  font-family: var(--font-serif);
  font-size: var(--text-sm);
  letter-spacing: 0.1em;
  opacity: 0.85;
  margin-bottom: var(--s-2);
}
.couple-hero-alias {
  font-family: var(--font-display);
  font-size: clamp(2rem, 5vw, 3rem);
  font-weight: 700;
  margin-bottom: var(--s-2);
  color: #fff;                /* override base.css h1 ink color */
  text-shadow: 0 2px 16px rgba(0,0,0,0.50);
  letter-spacing: -0.01em;
}
.couple-hero-tagline {
  font-family: var(--font-serif);
  font-style: italic;
  font-size: var(--text-md);
  opacity: 0.95;
  margin-bottom: var(--s-3);
}
.couple-hero-tags {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2);
  justify-content: center;
}

/* ---- Tag chips ---- */
.tag {
  display: inline-block;
  padding: 4px 12px;
  border-radius: var(--r-pill);
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.02em;
}
.couple-hero-tags .tag {
  background: rgba(255,255,255,0.22);
  border: 1px solid rgba(255,255,255,0.4);
  color: #fff;
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
}
.tag--sm { padding: 2px 10px; font-size: 11px; }
.tag--xs { padding: 1px 8px; font-size: 10px; }

/* ---- Profile cards ---- */
.couple-profiles {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--s-4);
  margin-bottom: var(--s-7);
}
.profile-card {
  display: grid;
  grid-template-columns: 100px 1fr;
  gap: var(--s-4);
  background: var(--surface);
  border: 1.5px solid var(--line);
  border-radius: var(--r-lg);
  padding: var(--s-4);
  box-shadow: var(--shadow);
  transition: transform var(--dur-base) var(--ease),
              box-shadow var(--dur-base) var(--ease);
}
.profile-card:hover {
  transform: translateY(-2px);
  box-shadow: var(--shadow-up);
}
.profile-card.role-char {
  border-left: 4px solid var(--couple-primary, var(--rose));
}
.profile-card.role-persona {
  border-left: 4px solid var(--couple-accent, var(--lilac));
}
.profile-image {
  width: 100px;
  height: 100px;
  border-radius: var(--r-md);
  overflow: hidden;
  background: var(--surface-2);
  display: grid;
  place-items: center;
}
.profile-image img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center top;
}
.profile-image-fallback {
  font-family: var(--font-display);
  font-size: var(--text-2xl);
  color: var(--couple-primary, var(--rose));
}
.profile-body {
  min-width: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
}
.profile-role {
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 0.2em;
  color: var(--ink-sub);
  text-transform: uppercase;
  margin-bottom: 4px;
}
.profile-name {
  font-family: var(--font-serif);
  font-size: var(--text-lg);
  font-weight: 700;
  margin-bottom: var(--s-2);
}
.profile-bio {
  font-size: var(--text-sm);
  color: var(--ink-sub);
  font-style: italic;
  line-height: 1.6;
  margin-bottom: var(--s-2);
}
.profile-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
}
.profile-tags .tag--sm {
  background: var(--surface-2);
  color: var(--ink-sub);
}

/* ---- Section header (shared) ---- */
.section-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  margin-bottom: var(--s-4);
  padding-bottom: var(--s-2);
  border-bottom: 1px dashed var(--line);
}
.section-title {
  font-family: var(--font-display);
  font-size: var(--text-lg);
  color: var(--couple-primary-deep, var(--rose-deep));
  font-weight: 700;
  letter-spacing: 0.02em;
}
.section-glyphs {
  font-size: var(--text-xs);
  color: var(--ink-light);
  letter-spacing: 0.4em;
}

/* ---- Anniversaries ---- */
.couple-anniversaries {
  margin-bottom: var(--s-7);
}
.anniv-list {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-3);
  list-style: none;
}
.anniv-item {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  padding: var(--s-2) var(--s-4);
  background: var(--surface);
  border: 1.5px solid var(--line);
  border-radius: var(--r-pill);
  box-shadow: var(--shadow);
  transition: transform var(--dur-fast) var(--ease);
}
.anniv-item:hover {
  transform: translateY(-1px);
  border-color: var(--couple-primary, var(--rose));
}
.anniv-glyph {
  color: var(--couple-primary, var(--rose));
  font-size: var(--text-md);
}
.anniv-label {
  font-weight: 700;
  font-size: var(--text-sm);
  color: var(--ink);
}
.anniv-date {
  font-family: var(--font-display);
  font-size: var(--text-xs);
  color: var(--ink-sub);
  letter-spacing: 0.06em;
  padding-left: var(--s-2);
  border-left: 1px solid var(--line);
}

/* ---- Logs gallery ---- */
.couple-logs {
  margin-bottom: var(--s-6);
}
.couple-logs-count {
  font-family: var(--font-display);
  font-size: var(--text-xs);
  color: var(--ink-sub);
  letter-spacing: 0.08em;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  padding: 2px 10px;
}
/* Pull the count + sort onto a small right-side cluster so the section
   header still has the section title on the far left. */
.couple-logs .section-header {
  align-items: center;
}
.couple-logs .section-header > .couple-logs-count {
  margin-left: auto;
}
.couple-logs-sort {
  appearance: none;
  background: transparent;
  border: 1px solid var(--line);
  color: var(--ink-sub);
  font-family: var(--font-display);
  font-size: var(--text-xs);
  letter-spacing: 0.08em;
  padding: 2px 10px;
  border-radius: var(--r-pill);
  cursor: pointer;
  margin-left: var(--s-2);
  display: inline-flex;
  align-items: center;
  gap: 4px;
  transition: color var(--dur-fast) var(--ease),
              border-color var(--dur-fast) var(--ease),
              background var(--dur-fast) var(--ease);
}
.couple-logs-sort:hover,
.couple-logs-sort:focus-visible {
  color: var(--couple-primary-deep, var(--rose-deep));
  border-color: var(--couple-primary, var(--rose));
  outline: none;
}
.couple-logs-sort-glyph {
  font-weight: 700;
  display: inline-block;
  transition: transform var(--dur-fast) var(--ease);
}
/* When ascending is on, flip the arrow + relabel via JS */
.couple-logs[data-order="asc"] .couple-logs-sort-glyph {
  transform: rotate(180deg);
}
/* CSS-only re-order: the cards are emitted newest-first; in asc mode
   we use flexbox-style negative ordering on each card (set inline by
   the JS so it scales to N cards). The grid container becomes flex-
   compatible when [data-order=asc] toggles since CSS grid honors
   `order` too. */
.couple-logs-grid > .log-card {
  /* default order set inline so the asc toggle just flips the sign;
     unset means natural document order = newest first */
  order: 0;
}
.couple-logs-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: var(--s-4);
}
.couple-logs-empty {
  text-align: center;
  color: var(--ink-sub);
  font-style: italic;
  padding: var(--s-6);
  background: var(--surface-2);
  border-radius: var(--r-md);
}

/* ---- Log card ---- */
.log-card {
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border: 1.5px solid var(--line);
  border-radius: var(--r-lg);
  overflow: hidden;
  box-shadow: var(--shadow);
  transition: transform var(--dur-base) var(--ease),
              box-shadow var(--dur-base) var(--ease),
              border-color var(--dur-base) var(--ease);
  text-decoration: none;
  color: inherit;
  /* Same off-screen render skip as .gallery-tile. A single couple
     can accumulate 100-500 logs over time; without content-visibility
     all hero background-images would fetch eagerly and every card's
     paint cost adds up on the couple home. */
  content-visibility: auto;
  contain-intrinsic-size: auto 320px;
}
.log-card:hover {
  transform: translateY(-4px);
  box-shadow: var(--shadow-up);
  border-color: var(--couple-primary, var(--rose));
}
.log-card-img {
  position: relative;
  aspect-ratio: 16 / 10;
  background-color: var(--surface-2);
  background-size: cover;
  background-position: center 30%;
}
.log-card-img::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, transparent 60%, rgba(0,0,0,0.15) 100%);
}
.log-card-lock {
  position: absolute;
  top: var(--s-2);
  right: var(--s-2);
  z-index: 1;
  background: rgba(255,255,255,0.94);
  width: 28px;
  height: 28px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  font-size: 14px;
  box-shadow: 0 2px 8px rgba(0,0,0,0.18);
}
.log-card-lock--soft {
  background: var(--surface-2);
  color: var(--ink-sub);
}

/* Type badge for novel cards on the couple mini-homepage —
   small 📖 chip in the top-LEFT (opposite the lock marker) so it
   reads as "this entry is a novel, not a chat log". */
.log-card-type {
  position: absolute;
  top: var(--s-2);
  left: var(--s-2);
  z-index: 1;
  background: rgba(255, 255, 255, 0.94);
  width: 28px;
  height: 28px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  font-size: 14px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.18);
}
/* Novel cards with no hero image fall back to a soft gradient so the
   card doesn't look broken. The badge stays visible. */
.log-card--novel .log-card-img {
  background-color: var(--surface-2);
  background-image: linear-gradient(135deg,
    var(--surface) 0%,
    rgba(196, 168, 224, 0.10) 100%);
}

/* ─── Locked-garden style log card (passwordCardStyle == garden) ─
   The log scope's analog of the couple-level "비밀의 화원". Title /
   thumbnail / date are all hidden — the card just signals "there's
   a locked page here" without leaking metadata. Click still opens
   the StatiCrypt password gate as usual. */
.log-card--locked-garden {
  /* Same shape and rounded corners as a normal log-card so the grid
     stays even, but the body is replaced by a centered lock motif. */
  align-items: center;
  justify-content: center;
  text-align: center;
  aspect-ratio: 16 / 10;
  background: linear-gradient(135deg,
    var(--surface) 0%,
    var(--grain, rgba(238, 122, 166, 0.08)) 100%);
  border-style: dashed;
}
.log-card--locked-garden:hover {
  border-style: solid;
}
.log-card-locked-garden {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s-1);
  padding: var(--s-5);
  color: var(--ink-sub);
}
.log-card-locked-garden .lock-glyph {
  font-size: 32px;
  margin-bottom: var(--s-1);
  opacity: 0.7;
}
.log-card-locked-garden .lock-title-ko {
  font-family: var(--font-serif);
  font-size: var(--text-md);
  margin: 0;
  color: var(--ink);
  letter-spacing: 0.04em;
}
.log-card-locked-garden .lock-title-en {
  font-family: var(--font-display);
  font-size: 10px;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  margin: 0;
  color: var(--ink-light);
}
.log-card-body {
  padding: var(--s-4);
  display: flex;
  flex-direction: column;
  flex: 1;
}
.log-card-no {
  font-family: var(--font-display);
  font-size: 10px;
  letter-spacing: 0.2em;
  color: var(--couple-primary-deep, var(--rose-deep));
  text-transform: uppercase;
  margin-bottom: 4px;
}
.log-card-title {
  font-family: var(--font-serif);
  font-size: var(--text-md);
  font-weight: 700;
  color: var(--ink);
  margin-bottom: 4px;
  line-height: 1.3;
}
.log-card-subtitle {
  font-size: var(--text-sm);
  color: var(--ink-sub);
  font-style: italic;
  margin-bottom: var(--s-3);
  line-height: 1.5;
}
.log-card-foot {
  margin-top: auto;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--s-2);
}
.log-card-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  flex: 1;
  min-width: 0;
}
.log-card-tags .tag--xs {
  background: var(--surface-2);
  color: var(--ink-sub);
}
.log-card-date {
  font-family: var(--font-display);
  font-size: 10px;
  color: var(--ink-light);
  letter-spacing: 0.06em;
  white-space: nowrap;
}
/* archive-no row holds the archive label on the left and the length
   chip (29턴 / N단락) right-aligned. Title flows under it. */
.log-card-no-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-2);
  margin-bottom: var(--s-2);
}
.log-card-no-row .log-card-no {
  margin-bottom: 0;
}
.log-card-length {
  font-family: var(--font-mono);
  font-size: 10px;
  color: var(--ink-sub);
  background: var(--surface-2);
  border-radius: 999px;
  padding: 1px 8px;
  letter-spacing: 0.04em;
  white-space: nowrap;
  flex-shrink: 0;
}

/* ---- Floating BGM widget ---- */
.couple-bgm {
  position: fixed;
  bottom: var(--s-4);
  right: var(--s-4);
  z-index: 50;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: var(--s-2);
}
.couple-bgm-toggle {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: var(--surface);
  border: 1.5px solid var(--couple-primary, var(--rose));
  border-radius: var(--r-pill);
  padding: var(--s-2) var(--s-4);
  font-family: var(--font-display);
  font-size: var(--text-xs);
  font-weight: 700;
  color: var(--couple-primary-deep, var(--rose-deep));
  letter-spacing: 0.08em;
  box-shadow: var(--shadow);
  cursor: pointer;
  transition: all var(--dur-fast) var(--ease);
}
.couple-bgm-toggle:hover {
  background: var(--couple-primary, var(--rose));
  color: #fff;
  transform: translateY(-1px);
  box-shadow: var(--shadow-up);
}
.couple-bgm-toggle[aria-expanded="true"] {
  background: var(--couple-primary, var(--rose));
  color: #fff;
}
.bgm-icon {
  font-size: var(--text-md);
}
.couple-bgm-player {
  background: #000;
  border-radius: var(--r-md);
  overflow: hidden;
  box-shadow: var(--shadow-up);
  border: 1.5px solid var(--couple-primary, var(--rose));
}
.couple-bgm-player iframe {
  display: block;
}
.couple-bgm-player[hidden] {
  display: none;
}

/* ---- Mobile (couple page) ---- */
@media (max-width: 640px) {
  .couple-page { padding: 0 var(--s-3) var(--s-7); }
  .couple-nav { padding: var(--s-3) var(--s-3); }
  .couple-hero {
    margin: var(--s-3) calc(-1 * var(--s-3)) var(--s-5);
    min-height: 320px;
    padding: var(--s-5) var(--s-4);
  }
  .portrait { width: 88px; height: 88px; border-width: 3px; }
  .couple-hero-x { font-size: var(--text-xl); }
  .couple-hero-portraits { gap: var(--s-2); }
  .couple-profiles { grid-template-columns: 1fr; gap: var(--s-3); }
  .profile-card {
    grid-template-columns: 80px 1fr;
    gap: var(--s-3);
    padding: var(--s-3);
  }
  .profile-image { width: 80px; height: 80px; }
  .anniv-list { flex-direction: column; }
  .anniv-item { width: 100%; }
  .couple-logs-grid { grid-template-columns: 1fr; }
  .couple-bgm { bottom: var(--s-3); right: var(--s-3); }
  .couple-bgm-player iframe { width: 280px; height: 158px; }
}

/* ============================================================
   HOME PAGE — sticky nav + hero + section heading + footer
   ============================================================ */

/* Page-level garden gradient (only on home) */
.home-body {
  background-color: var(--bg);
  /* "Blush" gradient: subtle dot grain + two corner ellipses (top-left
     blush, bottom-right blush). All three layers use the inline theme
     vars so the home page background follows the chosen site theme.
     Falls back gracefully on pages without a theme block injected
     (the vars are defined defensively in tokens.css too). */
  background-image:
    radial-gradient(var(--grain, rgba(238,122,166,.08)) 1px, transparent 1px),
    radial-gradient(ellipse 70% 50% at 20% 12%,
      var(--blush-tl, rgba(232,213,245,.45)) 0%, transparent 60%),
    radial-gradient(ellipse 70% 50% at 80% 95%,
      var(--blush-br, rgba(255,213,194,.40)) 0%, transparent 60%);
  background-size: 14px 14px, 100% 100%, 100% 100%;
  background-attachment: fixed;
}

/* ---- Top sticky nav ---- */
.top-nav {
  position: sticky;
  top: 0;
  z-index: 50;
  padding: var(--s-3) var(--s-5);
  background: var(--nav-bg, rgba(253, 247, 245, 0.78));
  backdrop-filter: blur(12px) saturate(140%);
  -webkit-backdrop-filter: blur(12px) saturate(140%);
  border-bottom: 1px solid var(--line);
}
.top-nav-inner {
  /* Layout properties live on .nav-inner; this rule only adds the
     home-page-specific gap override (a hair wider than the default). */
  gap: var(--s-5);
}
.top-nav-brand {
  display: inline-flex;
  align-items: baseline;
  gap: 8px;
  color: var(--rose-deep);
  font-family: var(--font-display);
  font-size: var(--text-md);
  font-weight: 700;
  letter-spacing: 0.02em;
  text-decoration: none;
}
.top-nav-brand .glyph {
  color: var(--rose);
  font-size: var(--text-sm);
}
.top-nav-links {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: var(--text-sm);
  color: var(--ink-sub);
}
.top-nav-link {
  padding: 6px 14px;
  border-radius: var(--r-pill);
  font-weight: 600;
  text-decoration: none;
  color: inherit;
  transition: background var(--dur-fast), color var(--dur-fast);
}
.top-nav-link:hover {
  background: var(--surface);
  color: var(--rose-deep);
}
.top-nav-link.is-cta {
  background: var(--surface);
  color: var(--rose-deep);
  border: 1px solid var(--line-strong);
  box-shadow: var(--shadow);
}
.top-nav-link.is-cta:hover {
  background: var(--rose);
  color: #fff;
  border-color: transparent;
}
.top-nav-sep {
  width: 1px;
  height: 14px;
  background: var(--line);
  margin: 0 4px;
}

/* ---- Hero ---- */
.hero {
  position: relative;
  max-width: 1080px;
  margin: 0 auto;
  padding: 88px var(--s-5) 56px;
  text-align: center;
  isolation: isolate;
}
.hero-deco {
  position: absolute;
  pointer-events: none;
  color: var(--rose);
  opacity: 0.35;
  z-index: -1;
  line-height: 1;
}
.hero-deco--tl { top: 60px;  left: 8%;  transform: rotate(-12deg); font-size: 22px; }
.hero-deco--tr { top: 96px;  right: 10%; transform: rotate(18deg); font-size: 18px; color: var(--lilac); }
.hero-deco--ml { top: 240px; left: 4%;  font-size: 14px; color: var(--peach); }
.hero-deco--mr { top: 200px; right: 6%; transform: rotate(20deg); font-size: 16px; color: var(--lilac); }

.hero-ornament {
  display: inline-flex;
  align-items: center;
  gap: 16px;
  color: var(--rose);
  font-size: var(--text-sm);
  letter-spacing: 0.4em;
  margin-bottom: var(--s-4);
  opacity: 0.85;
}
.hero-ornament::before, .hero-ornament::after {
  content: "";
  width: 48px;
  height: 1px;
  background: linear-gradient(90deg, transparent, var(--rose) 50%, transparent);
}

.hero-title {
  font-family: var(--font-display);
  font-size: clamp(2.8rem, 7vw, 4.4rem);
  font-weight: 700;
  color: var(--rose-deep);
  letter-spacing: -0.02em;
  line-height: 1;
  margin-bottom: var(--s-3);
  text-shadow: 0 2px 0 var(--surface-2);
}

.hero-bracket {
  font-family: 'Cormorant Garamond', var(--font-serif), serif;
  font-style: italic;
  font-weight: 500;
  font-size: clamp(14px, 2.4vw, 18px);
  color: var(--lilac-deep);
  letter-spacing: 0.32em;
  text-transform: uppercase;
  margin-bottom: var(--s-5);
  opacity: 0.9;
}
.hero-bracket::before, .hero-bracket::after {
  content: "·";
  margin: 0 12px;
  color: var(--lilac);
  opacity: 0.6;
}

.hero-tagline {
  font-family: var(--font-serif);
  font-style: italic;
  font-size: clamp(14px, 2vw, 16px);
  color: var(--ink-sub);
  max-width: 480px;
  margin: 0 auto var(--s-6);
  line-height: 1.7;
}

/* Hero stats now share a row with the chronicle CTA pill so the
   chronicle entry-point lives next to the headline counter rather
   than competing in the top-nav. Wraps below 480px. */
.hero-stats-row {
  display: inline-flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--s-3);
  justify-content: center;
}
.hero-stats {
  display: inline-flex;
  flex-wrap: wrap;
  gap: 6px;
  background: var(--surface);
  border: 1.5px solid var(--line);
  border-radius: var(--r-pill);
  padding: 8px;
  box-shadow: var(--shadow);
}
.hero-chronicle-cta {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 8px 18px;
  background: linear-gradient(135deg, var(--rose) 0%, var(--rose-deep) 100%);
  color: #fff;
  font-family: var(--font-display);
  font-size: var(--text-xs);
  font-weight: 700;
  letter-spacing: 0.06em;
  border-radius: var(--r-pill);
  text-decoration: none;
  box-shadow: var(--shadow);
  transition: transform var(--dur-fast) var(--ease),
              box-shadow var(--dur-fast) var(--ease);
}
.hero-chronicle-cta:hover {
  transform: translateY(-2px);
  box-shadow: var(--shadow-up);
  color: #fff;
}
.hero-chronicle-glyph { font-size: var(--text-sm); }
.hero-chronicle-arrow {
  font-size: 12px;
  opacity: 0.85;
  transition: transform var(--dur-fast) var(--ease);
}
.hero-chronicle-cta:hover .hero-chronicle-arrow {
  transform: translateX(2px);
}
.hero-stat {
  padding: 4px 14px;
  font-size: var(--text-xs);
  font-weight: 700;
  color: var(--ink-sub);
  letter-spacing: 0.04em;
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.hero-stat strong {
  color: var(--rose-deep);
  font-family: var(--font-display);
  font-size: var(--text-sm);
}
.hero-stat-sep {
  width: 1px;
  background: var(--line);
  align-self: stretch;
  margin: 4px 0;
}

/* ---- Section heading ---- */
.section {
  max-width: 1080px;
  margin: 0 auto;
  padding: 0 var(--s-5);
}
.section-heading {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 18px;
  margin: var(--s-7) 0 var(--s-6);
  font-family: var(--font-display);
  font-size: var(--text-md);
  font-weight: 700;
  color: var(--rose-deep);
  letter-spacing: 0.16em;
  text-transform: uppercase;
}
.section-heading::before, .section-heading::after {
  content: "";
  flex: 0 1 120px;
  height: 1px;
  background: linear-gradient(90deg, transparent, var(--line-strong), transparent);
}
.section-heading-glyph {
  font-size: var(--text-sm);
  color: var(--rose);
  letter-spacing: 0;
  text-transform: none;
  font-family: var(--font-body);
}
.section-heading .ko { margin-left: -8px; }
.section-heading .en {
  font-family: 'Cormorant Garamond', var(--font-serif), serif;
  font-style: italic;
  font-weight: 500;
  font-size: var(--text-xs);
  text-transform: lowercase;
  letter-spacing: 0.3em;
  color: var(--lilac-deep);
  margin-left: 6px;
  opacity: 0.7;
}

/* ---- Home footer (slim, no credits) ---- */
.home-footer {
  max-width: 1080px;
  margin: var(--s-8) auto 0;
  padding: var(--s-7) var(--s-5);
  text-align: center;
  border-top: 1px dashed var(--line);
}
.home-footer-glyphs {
  font-size: var(--text-md);
  color: var(--rose);
  letter-spacing: 0.5em;
  opacity: 0.7;
}

/* ---- Mobile ---- */
@media (max-width: 720px) {
  .top-nav { padding: 12px var(--s-3); }
  .top-nav-inner { gap: 12px; }
  .top-nav-link { padding: 6px 10px; font-size: var(--text-xs); }
  .hero { padding: 56px var(--s-3) 32px; }
  .hero-deco { display: none; }
  .section { padding: 0 var(--s-3); }
  .section-heading { font-size: var(--text-sm); }
  .section-heading::before, .section-heading::after { flex-basis: 32px; }
  .home-footer { padding: var(--s-5) var(--s-3); }
}

/* ---- Couple grid ---- */
.couples-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(360px, 1fr));
  gap: var(--s-5);
}
.couples-empty {
  text-align: center;
  color: var(--ink-sub);
  font-style: italic;
  padding: var(--s-7);
  background: var(--surface-2);
  border-radius: var(--r-md);
}

.couple-grid-card {
  /* position: relative so absolutely-positioned descendants
     (couple-grid-card-pin, couple-grid-card-key, the keyed::after
     ring) anchor to this card instead of escaping up to the viewport.
     Without this, .couple-grid-card--keyed::after { position: absolute;
     inset: 0 } drew a 1.5px ring around the entire viewport, which
     happened to disappear on hover because :hover applies a transform
     that creates a new containing block. */
  position: relative;
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border: 1.5px solid var(--line);
  border-radius: var(--r-xl);
  overflow: hidden;
  text-decoration: none;
  color: inherit;
  box-shadow: var(--shadow);
  transition: transform var(--dur-base) var(--ease),
              box-shadow var(--dur-base) var(--ease),
              border-color var(--dur-base) var(--ease);
  isolation: isolate;
  /* Off-screen card → skip render/paint (and Chrome defers the
     background-image fetch). Home page rarely has >20 couples so
     this is mostly defensive, but cheap and harmless. */
  content-visibility: auto;
  contain-intrinsic-size: auto 380px;
}
.couple-grid-card:hover {
  transform: translateY(-6px);
  box-shadow: var(--shadow-up);
  border-color: var(--couple-primary, var(--rose));
}
.couple-grid-card--pinned {
  border-color: var(--couple-primary, var(--rose));
  border-width: 2px;
}

.couple-grid-card-image {
  position: relative;
  aspect-ratio: 16 / 10;
  background-color: var(--surface-2);
  background-size: cover;
  background-position: center 35%;
}
.couple-grid-card-image::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg,
    transparent 50%,
    rgba(0,0,0,0.18) 100%);
}
.couple-grid-card-pin {
  position: absolute;
  top: var(--s-3);
  left: var(--s-3);
  z-index: 2;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 4px 10px;
  background: linear-gradient(135deg, #ffe9b0, #ffd089);
  color: #8a5a00;
  font-family: var(--font-display);
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 0.12em;
  border-radius: var(--r-pill);
  box-shadow: 0 4px 12px rgba(218, 165, 32, 0.35);
}
.couple-grid-card-lock {
  position: absolute;
  top: var(--s-3);
  right: var(--s-3);
  z-index: 2;
  background: rgba(255,255,255,0.94);
  width: 32px;
  height: 32px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  font-size: 15px;
  box-shadow: 0 2px 8px rgba(0,0,0,0.18);
}
.couple-grid-card-lock.soft {
  background: rgba(255,255,255,0.78);
  color: var(--ink-sub);
}

/* ---- Locked-with-key card (passwordCardStyle: "key") ----
   Looks like a normal couple card but click goes to the encrypted
   page; the ⚿ glyph in the corner is the only visual cue. */
.couple-grid-card-key {
  position: absolute;
  top: var(--s-3);
  right: var(--s-3);
  z-index: 2;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.94);
  color: var(--couple-primary-deep, #b88dc8);
  display: grid;
  place-items: center;
  font-size: 18px;
  line-height: 1;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.18);
  transition: transform var(--dur-fast) var(--ease),
              box-shadow var(--dur-fast) var(--ease);
}
.couple-grid-card--keyed:hover .couple-grid-card-key {
  transform: scale(1.08) rotate(-6deg);
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.22);
}
/* Subtle inset on the keyed card itself so it reads as "this one's
   different" without looking broken */
.couple-grid-card--keyed::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  box-shadow: inset 0 0 0 1.5px rgba(184, 141, 200, 0.35);
}

/* ---- Locked card — Secret Garden (heart with real keyhole) ---- */
.couple-grid-card--locked {
  position: relative;
  isolation: isolate;
  min-height: 360px;
  /* "비밀의 화원" lock card. Originally hand-painted in lavender; now
     follows the site theme via the lilac family + lock-blush helpers
     so non-rose themes (notebook = grayscale lock card, mint = green
     lock card, etc.) come along for the ride. */
  border: 1.5px solid var(--lilac);
  background:
    radial-gradient(circle at 20% 18%,
      var(--lock-blush-tl, rgba(232,213,245,0.7)) 0%, transparent 40%),
    radial-gradient(circle at 80% 82%,
      var(--lock-blush-br, rgba(255,213,194,0.6)) 0%, transparent 40%),
    linear-gradient(160deg,
      var(--lock-base-from, #fff5fa) 0%,
      var(--lock-base-to,   #f5eafa) 100%);
}

/* Lace top */
.couple-grid-card--locked::before {
  content: "";
  position: absolute;
  top: 0; left: 0; right: 0; height: 18px;
  background-image:
    radial-gradient(circle at center, var(--lilac) 4px, transparent 5px);
  background-size: 18px 18px;
  background-position: 0 -9px;
  opacity: 0.5;
  z-index: 0;
}
/* Lace bottom */
.couple-grid-card--locked::after {
  content: "";
  position: absolute;
  bottom: 0; left: 0; right: 0; height: 18px;
  background-image:
    radial-gradient(circle at center, var(--lilac) 4px, transparent 5px);
  background-size: 18px 18px;
  background-position: 0 9px;
  opacity: 0.5;
  z-index: 0;
}

/* Scattered flower glyphs */
.lock-flower {
  position: absolute;
  color: var(--lilac);
  pointer-events: none;
  z-index: 0;
}
.lock-flower:nth-child(1) { top: 36px;    left: 28px;  font-size: 20px; opacity: 0.6;  }
.lock-flower:nth-child(2) { top: 60px;    right: 36px; font-size: 14px; opacity: 0.5;  transform: rotate(20deg);  }
.lock-flower:nth-child(3) { bottom: 70px; left: 40px;  font-size: 16px; opacity: 0.55; }
.lock-flower:nth-child(4) { bottom: 42px; right: 30px; font-size: 22px; opacity: 0.6;  transform: rotate(-15deg); }
.lock-flower:nth-child(5) { top: 48%;     left: 18px;  font-size: 12px; opacity: 0.45; }
.lock-flower:nth-child(6) { top: 38%;     right: 22px; font-size: 14px; opacity: 0.5;  transform: rotate(30deg);  }

/* Centered content stack */
.lock-content {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 36px 28px;
  gap: 12px;
  z-index: 1;
  pointer-events: none;
}

/* Lavender heart with a real keyhole punched into its lower bowl */
.lock-heart {
  width: 100px;
  height: 100px;
  position: relative;
  display: grid;
  place-items: center;
  margin-bottom: 4px;
  transition: transform var(--dur-base) var(--ease);
}
.lock-heart::before {
  content: "❤";
  font-size: 84px;
  /* Halfway between lilac and lilac-deep — falls back to the legacy
     hand-picked lavender if the modern color-mix() isn't supported */
  color: color-mix(in srgb, var(--lilac) 55%, var(--lilac-deep) 45%);
  line-height: 1;
  filter: drop-shadow(0 4px 12px var(--line-strong));
}
@supports not (color: color-mix(in srgb, red, blue)) {
  .lock-heart::before { color: var(--lilac); }
}
.couple-grid-card--locked:hover .lock-heart {
  transform: scale(1.04) rotate(-2deg);
}

/* Keyhole — circle on top of a tapered slot below, in deep purple */
.keyhole {
  position: absolute;
  top: 56%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 14px;
  height: 24px;
  z-index: 1;
}
.keyhole::before {
  /* round head */
  content: "";
  position: absolute;
  top: 0;
  left: 50%;
  transform: translateX(-50%);
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--lilac-deep);
  box-shadow: inset 0 2px 3px rgba(0, 0, 0, 0.45);
}
.keyhole::after {
  /* tapered slot */
  content: "";
  position: absolute;
  top: 11px;
  left: 50%;
  transform: translateX(-50%);
  width: 10px;
  height: 13px;
  background: var(--lilac-deep);
  clip-path: polygon(35% 0, 65% 0, 100% 100%, 0 100%);
}

.lock-title-ko {
  font-family: var(--font-serif);
  font-style: italic;
  font-size: var(--text-lg);
  color: var(--lilac-deep);
  letter-spacing: 0.08em;
  margin: 0;
}
.lock-title-en {
  font-family: 'Cormorant Garamond', var(--font-serif), serif;
  font-style: italic;
  font-weight: 500;
  font-size: var(--text-xs);
  color: var(--lilac);
  letter-spacing: 0.3em;
  text-transform: uppercase;
  margin: -4px 0 0;
}
.lock-hint {
  font-family: var(--font-serif);
  font-size: var(--text-sm);
  color: var(--ink-sub);
  font-style: italic;
  margin: 8px 0 0;
}
.lock-hint::before { content: "❀ "; color: var(--lilac); }
.lock-hint::after  { content: " ❀"; color: var(--lilac); }

.couple-grid-card--locked:hover {
  transform: translateY(-4px);
  box-shadow: var(--shadow-up);
  border-color: var(--lilac);
}

.couple-grid-card-body {
  padding: var(--s-5);
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
  flex: 1;
}
.couple-grid-card-bracket {
  font-family: var(--font-serif);
  font-size: var(--text-sm);
  letter-spacing: 0.1em;
  color: var(--couple-primary, var(--rose));
  font-style: italic;
}
.couple-grid-card-alias {
  font-family: var(--font-display);
  font-size: var(--text-xl);
  font-weight: 700;
  color: var(--ink);
  margin: 0;
  line-height: 1.2;
}
.couple-grid-card-tagline {
  font-family: var(--font-serif);
  font-size: var(--text-sm);
  font-style: italic;
  color: var(--ink-sub);
  line-height: 1.5;
}
.couple-grid-card-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
}
.couple-grid-card-tags .tag--xs {
  background: var(--surface-2);
  color: var(--couple-primary-deep, var(--rose-deep));
  border: 1px solid var(--line);
}

.couple-grid-card-annivs {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2);
  padding: var(--s-2) 0;
  border-top: 1px dashed var(--line);
}
.anniv-mini {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: var(--text-xs);
  color: var(--ink-sub);
}
.anniv-mini-glyph {
  color: var(--couple-primary, var(--rose));
  font-size: 13px;
}
.anniv-mini-label {
  font-weight: 700;
  color: var(--ink);
}
.anniv-mini-date {
  font-family: var(--font-display);
  letter-spacing: 0.04em;
  color: var(--ink-light);
}

.couple-grid-card-meta {
  margin-top: auto;
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2);
  align-items: center;
}
.meta-pill {
  display: inline-block;
  padding: 3px 10px;
  border-radius: var(--r-pill);
  font-family: var(--font-display);
  font-size: 11px;
  letter-spacing: 0.06em;
  background: var(--surface-2);
  color: var(--ink-sub);
  border: 1px solid var(--line);
}
.meta-pill--accent {
  background: var(--couple-primary, var(--rose));
  color: #fff;
  border-color: transparent;
}

/* ============================================================
   CHRONICLE PAGE
   ============================================================ */

.chronicle-nav {
  position: sticky;
  top: 0;
  z-index: 40;
  padding: var(--s-3) var(--s-5);
  background: var(--nav-bg, rgba(253, 247, 245, 0.85));
  backdrop-filter: blur(10px) saturate(140%);
  -webkit-backdrop-filter: blur(10px) saturate(140%);
  border-bottom: 1px solid var(--line);
}
.chronicle-nav-back {
  display: inline-block;
  font-size: var(--text-xs);
  color: var(--ink-sub);
  letter-spacing: 0.04em;
  font-weight: 600;
  text-decoration: none;
  /* horizontal centering + max-width now handled by .nav-inner */
}
.chronicle-nav-back:hover {
  color: var(--rose-deep);
}

.chronicle-page {
  max-width: 880px;
  margin: 0 auto;
  padding: var(--s-6) var(--s-5) var(--s-8);
}


/* ---- About page (Phase 0-C) ---- */
/* Body rendered from _data/about.md via scripts/parse_about.py.
   Markdown lite outputs `.about-heading` (H1-H3) and `.about-paragraph`;
   raw HTML fences (```...```) pass through verbatim so the owner can
   drop in inline-styled cards. */
.about-page {
  max-width: 640px;
  margin: 0 auto;
  padding: var(--s-8) var(--s-5);
  text-align: center;
}
.about-heading {
  font-family: var(--font-display, "Cormorant Garamond", serif);
  font-weight: 500;
  font-style: italic;
  color: var(--rose-deep);
  letter-spacing: 0.02em;
  margin: 0 0 var(--s-6);
}
.about-heading[id="ab-1"] {
  /* First heading reads as the page title — slightly bigger */
  font-size: 2rem;
}
.about-page > .about-heading + .about-heading {
  /* Headings adjacent in source (no blank line between #/##/###) read
     as a tight section grouping — tug them visually close instead of
     pushing them apart. The previous +s-7 was inverted: it spaced the
     group out further than a heading→paragraph pair. */
  margin-top: calc(-1 * var(--s-5));
}
.about-paragraph {
  font-size: 1.05rem;
  line-height: 1.85;
  color: var(--ink);
  word-break: keep-all;
  margin: 0 0 var(--s-5);
}
.about-paragraph a {
  color: var(--rose-deep);
  text-decoration: underline;
  text-decoration-color: rgba(0, 0, 0, 0.15);
  text-underline-offset: 4px;
}
.about-paragraph a:hover {
  color: var(--rose);
  text-decoration-color: currentColor;
}
.about-page hr {
  border: 0;
  border-top: 1px dashed var(--line);
  margin: var(--s-7) auto;
  width: 60%;
}

.chronicle-header {
  text-align: center;
  margin-bottom: var(--s-7);
}
.chronicle-title {
  font-family: var(--font-display);
  font-size: clamp(2rem, 5vw, 3rem);
  font-weight: 700;
  color: var(--rose-deep);
  margin: var(--s-3) 0 6px;
  letter-spacing: -0.01em;
}
.chronicle-subtitle {
  font-family: var(--font-serif);
  font-style: italic;
  color: var(--ink-sub);
  font-size: var(--text-sm);
}

.chronicle-timeline {
  list-style: none;
  position: relative;
  margin: 0;
  padding-left: 0;
}
.chronicle-timeline::before {
  content: "";
  position: absolute;
  left: 102px;
  top: 0;
  bottom: 0;
  width: 1px;
  background: linear-gradient(180deg,
    transparent 0,
    var(--line-strong) 8%,
    var(--line-strong) 92%,
    transparent 100%);
}

/* Year header — inserted by build_chronicle_page whenever the date
   year changes. Sits as a flow-only <li> (no thumbnail, no link),
   gives the eye an anchor every time the year ticks over. Sticks to
   the top of the viewport while scrolling through that year's entries
   so the reader always knows what era they're in. */
.chronicle-year {
  list-style: none;
  margin: var(--s-7) 0 var(--s-4);
  padding: 0;
  position: sticky;
  top: 56px;
  z-index: 5;
  background: linear-gradient(180deg,
    var(--bg) 0%,
    var(--bg) 70%,
    transparent 100%);
}
.chronicle-year:first-child { margin-top: 0; }
.chronicle-year-label {
  display: inline-block;
  font-family: var(--font-display, "Cormorant Garamond", serif);
  font-style: italic;
  font-size: clamp(1.4rem, 3vw, 2rem);
  font-weight: 500;
  color: var(--rose-deep);
  letter-spacing: 0.02em;
  padding: 0.2em 0.6em 0.3em;
  border-bottom: 2px solid var(--line-strong);
}
.chronicle-year-count {
  display: inline-block;
  margin-left: 0.6em;
  font-family: var(--font-mono, monospace);
  font-size: 0.78rem;
  color: var(--ink-sub);
  letter-spacing: 0.08em;
  vertical-align: middle;
  opacity: 0.7;
}

.chronicle-entry {
  display: grid;
  grid-template-columns: 92px 1fr;
  gap: var(--s-5);
  padding: var(--s-4) 0;
  position: relative;
  /* Off-screen entries skip layout + paint entirely. Each entry is
     small (~80-140px) but they add up at 100+ scale; without this,
     the browser pays a render cost for every entry on the chronicle
     page even though only ~10 fit in viewport at once. Sticky year
     headers + this rule together give the chronicle the "lazy
     timeline" feel — past years cost almost nothing until scrolled
     into view. */
  content-visibility: auto;
  contain-intrinsic-size: auto 120px;
}
.chronicle-entry::before {
  content: "✿";
  position: absolute;
  left: 92px;
  top: var(--s-5);
  transform: translateX(-50%);
  width: 22px;
  height: 22px;
  background: var(--bg);
  color: var(--couple-primary, var(--rose));
  display: grid;
  place-items: center;
  border-radius: 50%;
  border: 1.5px solid var(--couple-primary, var(--rose));
  font-size: 11px;
  z-index: 1;
}

.chronicle-entry-date {
  text-align: right;
  padding-top: var(--s-4);
}
.chronicle-entry-date-text {
  font-family: var(--font-display);
  font-size: var(--text-sm);
  letter-spacing: 0.06em;
  color: var(--ink-sub);
  font-weight: 700;
}

.chronicle-entry-content {
  background: var(--surface);
  border: 1.5px solid var(--line);
  border-left: 3px solid var(--couple-primary, var(--rose));
  border-radius: var(--r-md);
  padding: var(--s-4) var(--s-5);
  box-shadow: var(--shadow);
  transition: transform var(--dur-base) var(--ease),
              box-shadow var(--dur-base) var(--ease);
}
.chronicle-entry-content:hover {
  transform: translateY(-2px);
  box-shadow: var(--shadow-up);
}

.chronicle-entry-couple {
  display: inline-block;
  font-family: var(--font-display);
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--couple-primary-deep, var(--rose-deep));
  text-decoration: none;
  margin-bottom: 4px;
  padding: 2px 8px;
  background: var(--surface-2);
  border-radius: var(--r-pill);
  border: 1px solid var(--line);
}
.chronicle-entry-couple:hover {
  background: var(--couple-primary, var(--rose));
  color: #fff;
  border-color: transparent;
}

.chronicle-entry-link {
  display: block;
  text-decoration: none;
  color: inherit;
}
.chronicle-entry-title {
  font-family: var(--font-serif);
  font-size: var(--text-md);
  font-weight: 700;
  color: var(--ink);
  margin: 6px 0 var(--s-3);
  line-height: 1.4;
}
.chronicle-entry-link:hover .chronicle-entry-title {
  color: var(--couple-primary-deep, var(--rose-deep));
}
.chronicle-entry-subtitle {
  font-style: italic;
  color: var(--ink-sub);
  font-weight: 400;
  font-size: var(--text-sm);
}
.chronicle-entry-lock {
  font-size: 0.9em;
  opacity: 0.8;
}
.chronicle-entry-foot {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2);
  align-items: center;
  justify-content: space-between;
}
.chronicle-entry-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
}
.chronicle-entry-tags .tag--xs {
  background: var(--surface-2);
  color: var(--ink-sub);
}
.chronicle-entry-archive {
  font-family: var(--font-display);
  font-size: 10px;
  letter-spacing: 0.16em;
  color: var(--ink-light);
  text-transform: uppercase;
}

/* ---- Locked chronicle entries ---- */
/* garden style: title/subtitle/tags hidden, just the placeholder block.
   Same idea as the couple page's locked-garden card but in timeline form. */
.chronicle-entry--locked-garden .chronicle-entry-link {
  background: var(--surface-2);
  border: 1px dashed var(--line-strong);
  border-radius: var(--r-sm);
  padding: var(--s-3) var(--s-4);
  margin-top: 6px;
  transition: border-color var(--dur-fast) var(--ease),
              background var(--dur-fast) var(--ease);
}
.chronicle-entry--locked-garden .chronicle-entry-link:hover {
  border-color: var(--couple-primary, var(--rose));
  background: var(--surface);
}
.chronicle-entry--locked-garden .chronicle-entry-title {
  margin: 0;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  color: var(--ink-sub);
  font-style: italic;
  font-weight: 600;
  font-size: var(--text-sm);
  letter-spacing: 0.04em;
}
.chronicle-entry-locked-glyph {
  font-style: normal;
  font-size: 1.2em;
  opacity: 0.85;
}
.chronicle-entry-locked-label {
  font-style: italic;
}

/* key style: title/tags visible, ⚿ marker, subtle lock-tinted ring */
.chronicle-entry--locked-key .chronicle-entry-link {
  position: relative;
}
.chronicle-entry--locked-key .chronicle-entry-lock {
  font-size: 0.75em;
  letter-spacing: 0;
  margin-left: 4px;
  color: var(--couple-primary-deep, var(--rose-deep));
  opacity: 0.85;
}

.chronicle-empty {
  list-style: none;
  text-align: center;
  font-style: italic;
  color: var(--ink-sub);
  padding: var(--s-7);
}

/* ---- Mobile (home + chronicle) ---- */
@media (max-width: 640px) {
  .home-page { padding: var(--s-5) var(--s-3) var(--s-7); }
  .couples-grid {
    grid-template-columns: 1fr;
    gap: var(--s-4);
  }
  .couple-grid-card-body { padding: var(--s-4); }

  .chronicle-page { padding: var(--s-5) var(--s-3) var(--s-7); }
  .chronicle-timeline::before {
    left: 16px;
  }
  .chronicle-entry {
    grid-template-columns: 1fr;
    gap: var(--s-2);
    padding-left: 36px;
  }
  .chronicle-entry::before {
    left: 16px;
    top: var(--s-3);
  }
  .chronicle-entry-date {
    text-align: left;
    padding-top: 0;
  }
  .chronicle-entry-content { padding: var(--s-3) var(--s-4); }
}

/* ============================================================
   INLINE IMAGES IN LOG MESSAGE BUBBLES
   ============================================================ */

.msg-image-wrap {
  margin: var(--s-4) 0;
  text-align: center;
}
.msg-image-wrap--kv {
  /* KV (key visual) sits as a header image at the top of the message
     bubble — no extra top margin so it hugs the bubble edge. */
  margin-top: 0;
  margin-bottom: var(--s-5);
}
.msg-image-wrap--kv .msg-image {
  /* Slightly bigger / more prominent — it's the "header image" */
  max-height: 70vh;
  border-radius: var(--r-lg);
}
.msg-image {
  display: inline-block;
  max-width: 100%;
  max-height: 60vh;
  height: auto;
  border-radius: var(--r-md);
  border: 1px solid var(--line);
  box-shadow: var(--shadow);
  cursor: zoom-in;
  vertical-align: middle;
  transition: transform var(--dur-fast) var(--ease),
              box-shadow var(--dur-fast) var(--ease);
}
.msg-image:hover {
  transform: translateY(-2px);
  box-shadow: var(--shadow-up);
}

/* ============================================================
   LIGHTBOX (overlay for clicked images, in log + gallery)
   ============================================================ */

.lightbox {
  position: fixed;
  inset: 0;
  z-index: 1000;
  background: rgba(20, 14, 18, 0.88);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  display: grid;
  place-items: center;
  cursor: zoom-out;
  padding: var(--s-5);
  animation: lb-fade var(--dur-base) var(--ease);
}
.lightbox[hidden] { display: none; }
@keyframes lb-fade {
  from { opacity: 0; }
  to   { opacity: 1; }
}
.lightbox-img {
  max-width: 100%;
  max-height: 100%;
  width: auto;
  height: auto;
  object-fit: contain;
  border-radius: var(--r-sm);
  box-shadow: 0 0 80px rgba(0, 0, 0, 0.6);
  display: block;
}
.lightbox-close {
  position: fixed;
  top: var(--s-4);
  right: var(--s-4);
  z-index: 1001;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.12);
  color: #fff;
  font-size: 28px;
  font-weight: 300;
  line-height: 1;
  display: grid;
  place-items: center;
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease);
}
.lightbox-close:hover {
  background: rgba(255, 255, 255, 0.24);
}

/* ============================================================
   COUPLE GALLERY PAGE
   ============================================================ */

.gallery-page {
  max-width: 1080px;
  margin: 0 auto;
  padding: 0 var(--s-5) var(--s-8);
}
.gallery-header {
  text-align: center;
  margin: var(--s-7) 0 var(--s-6);
}
.gallery-title {
  font-family: var(--font-display);
  font-size: clamp(2rem, 5vw, 2.6rem);
  font-weight: 700;
  color: var(--couple-primary-deep, var(--rose-deep));
  letter-spacing: -0.01em;
  margin-bottom: var(--s-2);
}
.gallery-subtitle {
  font-family: var(--font-serif);
  font-style: italic;
  color: var(--ink-sub);
  font-size: var(--text-sm);
  letter-spacing: 0.04em;
}
.gallery-stats {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  margin-top: var(--s-3);
  padding: 4px 14px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  font-size: var(--text-xs);
  color: var(--ink-sub);
  font-weight: 600;
}
.gallery-stats strong {
  color: var(--couple-primary-deep, var(--rose-deep));
  font-family: var(--font-display);
}

.gallery-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
  gap: var(--s-3);
}
.gallery-tile {
  position: relative;
  aspect-ratio: 4 / 5;
  border-radius: var(--r-md);
  overflow: hidden;
  border: 1px solid var(--line);
  box-shadow: var(--shadow);
  background: var(--surface-2);
  cursor: zoom-in;
  isolation: isolate;
  transition: transform var(--dur-base) var(--ease),
              box-shadow var(--dur-base) var(--ease);
  /* Skip rendering off-screen tiles entirely (paint + layout). Pairs
     with loading="lazy" on the inner <img>: lazy stops the download,
     content-visibility stops the render. At 1000+ tiles this cuts
     the gallery's resident memory + scroll-frame cost dramatically.
     contain-intrinsic-size reserves layout space so the scroll bar
     doesn't jump when tiles materialize into view. */
  content-visibility: auto;
  contain-intrinsic-size: auto 240px;
}
.gallery-tile:hover {
  transform: translateY(-4px);
  box-shadow: var(--shadow-up);
}
.gallery-tile-img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.gallery-tile-meta {
  position: absolute;
  inset: auto 0 0 0;
  z-index: 1;
  padding: var(--s-3) var(--s-3) var(--s-2);
  background: linear-gradient(180deg,
    transparent 0%,
    rgba(0, 0, 0, 0.65) 100%);
  color: #fff;
  opacity: 0;
  transform: translateY(8px);
  transition: opacity var(--dur-fast) var(--ease),
              transform var(--dur-fast) var(--ease);
}
.gallery-tile:hover .gallery-tile-meta {
  opacity: 1;
  transform: translateY(0);
}
.gallery-tile-title {
  display: block;
  font-family: var(--font-serif);
  font-size: var(--text-xs);
  font-weight: 700;
  margin-bottom: 2px;
  text-shadow: 0 1px 4px rgba(0, 0, 0, 0.5);
}
.gallery-tile-date {
  display: block;
  font-family: var(--font-display);
  font-size: 10px;
  letter-spacing: 0.08em;
  opacity: 0.85;
}
.gallery-tile-jump {
  position: absolute;
  top: var(--s-2);
  right: var(--s-2);
  z-index: 2;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.92);
  color: var(--couple-primary-deep, var(--rose-deep));
  display: grid;
  place-items: center;
  font-size: 14px;
  font-weight: 700;
  text-decoration: none;
  opacity: 0;
  transform: scale(0.8);
  transition: all var(--dur-fast) var(--ease);
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
}
.gallery-tile:hover .gallery-tile-jump {
  opacity: 1;
  transform: scale(1);
}
.gallery-tile-jump:hover {
  background: var(--couple-primary, var(--rose));
  color: #fff;
}

/* ─── Per-log filter (chip row above the grid) ──────────────────── */
.gallery-filter {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  justify-content: center;
  margin: 0 auto var(--s-5);
  padding: 6px;
  max-width: 720px;
}
.gallery-filter-chip {
  appearance: none;
  border: 1px solid var(--line);
  background: var(--surface);
  color: var(--ink-sub);
  padding: 6px 14px;
  border-radius: var(--r-pill);
  font-family: var(--font-sans);
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.02em;
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease),
              color var(--dur-fast) var(--ease),
              border-color var(--dur-fast) var(--ease),
              transform var(--dur-fast) var(--ease);
  white-space: nowrap;
}
.gallery-filter-chip:hover {
  border-color: var(--couple-primary, var(--rose));
  color: var(--couple-primary-deep, var(--rose-deep));
  transform: translateY(-1px);
}
.gallery-filter-chip.is-active {
  background: var(--couple-primary, var(--rose));
  border-color: var(--couple-primary, var(--rose));
  color: #fff;
}
.gallery-filter-chip .count {
  margin-left: 4px;
  opacity: 0.7;
  font-weight: 500;
  font-size: 10px;
}

/* ─── Dropdown picker (used when log count > threshold) ─────────── */
.gallery-filter--compact { gap: 8px; }

.gallery-filter-picker-wrap {
  position: relative;
  display: inline-block;
}

.gallery-filter-picker {
  appearance: none;
  border: 1px solid var(--line);
  background: var(--surface);
  color: var(--ink-sub);
  padding: 6px 12px 6px 14px;
  border-radius: var(--r-pill);
  font-family: var(--font-sans);
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.02em;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  white-space: nowrap;
  transition: background var(--dur-fast) var(--ease),
              color var(--dur-fast) var(--ease),
              border-color var(--dur-fast) var(--ease);
}
.gallery-filter-picker:hover {
  border-color: var(--couple-primary, var(--rose));
  color: var(--couple-primary-deep, var(--rose-deep));
}
.gallery-filter-picker[aria-expanded="true"] {
  border-color: var(--couple-primary, var(--rose));
  color: var(--couple-primary-deep, var(--rose-deep));
  background: var(--surface-2);
}
.gallery-filter-picker--active {
  background: var(--couple-primary, var(--rose));
  border-color: var(--couple-primary, var(--rose));
  color: #fff;
}
.gallery-filter-picker--active:hover {
  color: #fff;
}
.gallery-filter-picker--active[aria-expanded="true"] {
  background: var(--couple-primary, var(--rose));
  color: #fff;
}
.gallery-filter-picker .count {
  opacity: 0.7;
  font-size: 10px;
  font-weight: 500;
}
.gallery-filter-picker--active .count {
  opacity: 0.9;
}
.gallery-filter-picker .clear {
  margin-left: 4px;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.25);
  display: inline-grid;
  place-items: center;
  font-size: 10px;
  line-height: 1;
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease);
}
.gallery-filter-picker .clear:hover {
  background: rgba(255, 255, 255, 0.5);
}
.gallery-filter-picker-caret {
  font-size: 10px;
  opacity: 0.65;
  transition: transform var(--dur-fast) var(--ease);
}
.gallery-filter-picker[aria-expanded="true"] .gallery-filter-picker-caret {
  transform: rotate(180deg);
}

/* The dropdown panel */
.gallery-filter-panel {
  position: absolute;
  z-index: 30;
  top: calc(100% + 6px);
  left: 50%;
  transform: translateX(-50%);
  min-width: 240px;
  max-width: min(360px, calc(100vw - var(--s-4)));
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  box-shadow: 0 12px 40px rgba(0, 0, 0, 0.12);
  padding: 8px;
  display: flex;
  flex-direction: column;
  gap: 6px;
  max-height: 50vh;
  overflow: hidden;
}
.gallery-filter-panel[hidden] { display: none; }

.gallery-filter-panel-search {
  appearance: none;
  border: 1px solid var(--line);
  background: var(--surface-2);
  border-radius: var(--r-pill);
  padding: 8px 14px;
  font-family: var(--font-sans);
  font-size: var(--text-xs);
  color: var(--ink);
  outline: none;
  transition: border-color var(--dur-fast) var(--ease);
}
.gallery-filter-panel-search::placeholder {
  color: var(--ink-sub);
  opacity: 0.7;
}
.gallery-filter-panel-search:focus {
  border-color: var(--couple-primary, var(--rose));
}

.gallery-filter-panel-list {
  display: flex;
  flex-direction: column;
  gap: 2px;
  overflow-y: auto;
  padding: 2px;
  scrollbar-width: thin;
}

.gallery-filter-picker-item {
  appearance: none;
  border: none;
  background: transparent;
  text-align: left;
  padding: 7px 10px;
  border-radius: var(--r-sm, 8px);
  font-family: var(--font-sans);
  font-size: var(--text-xs);
  color: var(--ink);
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 6px;
  transition: background var(--dur-fast) var(--ease);
}
.gallery-filter-picker-item:hover,
.gallery-filter-picker-item:focus-visible {
  background: var(--couple-accent, var(--rose-soft, #fdf0f5));
  outline: none;
}
.gallery-filter-picker-item .title {
  flex: 1 1 auto;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  font-weight: 600;
}
.gallery-filter-picker-item .lock {
  font-size: 10px;
  opacity: 0.85;
}
.gallery-filter-picker-item .count {
  flex: 0 0 auto;
  font-size: 10px;
  opacity: 0.6;
  font-weight: 500;
}

.gallery-filter-panel-empty {
  padding: 12px 10px;
  text-align: center;
  color: var(--ink-sub);
  font-size: var(--text-xs);
  font-style: italic;
}

/* ─── Locked tile (blurred preview + lock badge) ────────────────── */
.gallery-tile--locked {
  cursor: default;
}
.gallery-tile--locked .gallery-tile-img {
  filter: blur(18px) saturate(0.6) brightness(0.92);
  transform: scale(1.12);   /* hide blur edge bleed */
  pointer-events: none;
}
.gallery-tile--locked::after {
  content: "🔒";
  position: absolute;
  inset: 0;
  z-index: 1;
  display: grid;
  place-items: center;
  font-size: 28px;
  background: rgba(255, 255, 255, 0.18);
  text-shadow: 0 2px 8px rgba(0, 0, 0, 0.35);
  pointer-events: none;
}
.gallery-tile--locked .gallery-tile-meta {
  opacity: 0;       /* never reveal title/date for locked tiles */
}
.gallery-tile--locked .gallery-tile-jump {
  /* lock-icon variant: always visible, semi-transparent */
  opacity: 1;
  transform: scale(1);
  background: rgba(255, 255, 255, 0.85);
}

.gallery-empty {
  text-align: center;
  padding: var(--s-8) var(--s-5);
  color: var(--ink-sub);
  font-style: italic;
  background: var(--surface-2);
  border-radius: var(--r-md);
  border: 1px dashed var(--line-strong);
}

/* Tab/section toggle on couple page (mini-homepage ↔ gallery) */
.couple-section-tabs {
  display: flex;
  justify-content: center;
  gap: var(--s-2);
  margin: 0 0 var(--s-6);
  padding: 4px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  width: max-content;
  margin-left: auto;
  margin-right: auto;
  box-shadow: var(--shadow);
}
.couple-section-tab {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 18px;
  border-radius: var(--r-pill);
  font-size: var(--text-xs);
  font-weight: 700;
  letter-spacing: 0.06em;
  color: var(--ink-sub);
  text-decoration: none;
  transition: background var(--dur-fast) var(--ease),
              color var(--dur-fast) var(--ease);
}
.couple-section-tab:hover {
  color: var(--couple-primary-deep, var(--rose-deep));
}
.couple-section-tab.is-active {
  background: var(--couple-primary, var(--rose));
  color: #fff;
}
/* Excerpts tab count badge (Phase E-2b) — small pill showing how
   many excerpts this couple has. Same visual weight as the dirty
   badge / log-editor-tab-count in admin. */
.couple-section-tab-count {
  display: inline-block;
  margin-left: 2px;
  padding: 0 6px;
  border-radius: var(--r-pill);
  background: color-mix(in srgb, var(--ink-sub) 18%, transparent);
  font-size: 0.75em;
  font-weight: 700;
  line-height: 1.5;
}
.couple-section-tab.is-active .couple-section-tab-count {
  background: rgba(255, 255, 255, 0.28);
}

/* Mobile */
@media (max-width: 640px) {
  .gallery-page { padding: 0 var(--s-3) var(--s-7); }
  .gallery-grid { grid-template-columns: repeat(2, 1fr); gap: var(--s-2); }
  .lightbox-close { top: var(--s-3); right: var(--s-3); width: 38px; height: 38px; }
  /* Filter row scrolls horizontally on small screens to keep tiles tall */
  .gallery-filter {
    flex-wrap: nowrap;
    overflow-x: auto;
    overflow-y: hidden;
    /* safe center — chips center when they fit, fall back to start
       (scrollable, first chip never clipped) when they overflow. */
    justify-content: safe center;
    padding: 6px 4px;
    margin-left: calc(-1 * var(--s-3));
    margin-right: calc(-1 * var(--s-3));
    scrollbar-width: none;
  }
  .gallery-filter::-webkit-scrollbar { display: none; }
  .gallery-filter-chip { flex-shrink: 0; padding: 5px 12px; }
  .gallery-filter-picker { flex-shrink: 0; }
  .gallery-filter-panel {
    min-width: min(280px, calc(100vw - var(--s-4)));
    max-height: 60vh;
  }
  .gallery-tile--locked::after { font-size: 22px; }
}

/* ============================================================
   IMAGE HIDE TOGGLE (Phase 5-A.7)
   ------------------------------------------------------------
   Reader can flip "이미지 숨김" in the viewer-settings panel
   (only shown on reading pages). Body class `.is-images-hidden`
   is set + persisted in localStorage. Hides:
     - .msg-image-wrap   — log message inline + KV images
     - .log-hero-img     — log page cover image (title text stays)
     - .log-hero-overlay — gradient overlay (no image to overlay)
     - .novel-prose img / figure — future novel images
   ============================================================ */

body.is-images-hidden .msg-image-wrap,
body.is-images-hidden .log-hero-img,
body.is-images-hidden .log-hero-overlay,
body.is-images-hidden .novel-prose img,
body.is-images-hidden .novel-prose figure {
  display: none;
}
/* When the hero image is hidden, the hero box still exists so the
   title stays visible — but it no longer needs the tall image height
   or the dark overlay. Shrink and de-fancy it. */
body.is-images-hidden .log-hero {
  height: auto;
  min-height: auto;
  padding: var(--s-6) var(--s-5);
  background: linear-gradient(135deg,
    var(--surface) 0%,
    var(--surface-2) 100%);
  border-radius: var(--r-lg);
  border: 1px solid var(--line);
  box-shadow: none;
  margin-bottom: var(--s-6);
}
body.is-images-hidden .log-hero-text {
  position: static;
  color: var(--ink);
  text-shadow: none;
}
body.is-images-hidden .log-archive-no { color: var(--ink-light); }
body.is-images-hidden .log-title { color: var(--rose-deep); }
body.is-images-hidden .log-subtitle { color: var(--ink-sub); }

/* ============================================================
   LOG PAGE — eBook mode overrides (Phase 5-A.4)
   ------------------------------------------------------------
   The viewer-settings panel on log pages has a "본문 모드"
   toggle (채팅 / 이북). When the reader picks 이북, body gains
   `.is-ebook-mode` and the log article re-skins into the same
   column stage that novel pages use:
     - Hide chat chrome (hero, profiles, metabar, storyline, footer)
     - Strip msg-bubble background/padding/max-width so messages
       flow as continuous prose
     - Hide msg-speaker labels (chronological prose, not script)
     - .log-body becomes the horizontal column scroller; ebook-
       reader.js handles columns + pager + nav
   Toggling back removes the class and the chat layout returns.
   ============================================================ */

body.is-ebook-mode .log-hero,
body.is-ebook-mode .log-profiles,
body.is-ebook-mode .log-metabar,
body.is-ebook-mode .log-storyline,
body.is-ebook-mode .log-footer {
  display: none;
}

body.is-ebook-mode .log-body {
  height: calc(100vh - 240px);
  min-height: 320px;
  overflow-x: auto;
  overflow-y: hidden;
  scrollbar-width: none;
  scroll-behavior: smooth;
  /* Native horizontal pan is blocked here too — keeps log ebook view
     aligned to column boundaries on touchscreens. */
  touch-action: pan-y;
  /* Reading typography matches novel-prose so the same
     viewer-settings font picker reads identically here. */
  font-family: var(--font-serif);
  font-size: 16px;
  line-height: 1.85;
  letter-spacing: 0.01em;
  color: var(--ink);
  orphans: 2;
  widows: 2;
  background: none;
  padding: 0;
  margin: 0;
  display: block;
  gap: 0;
}
body.is-ebook-mode .log-body::-webkit-scrollbar { display: none; }
/* Allow messages (and their paragraphs) to flow across columns
   naturally — that's the point of column layout. We only protect
   images / decorative blocks from splitting (rules below for
   .msg-image-wrap). */

/* Strip chat-bubble structure so messages flow as prose */
body.is-ebook-mode .msg,
body.is-ebook-mode .msg--char,
body.is-ebook-mode .msg--user {
  display: block;
  max-width: none;
  margin: 0 0 0.6em;
  padding: 0;
  background: none;
  border: none;
  box-shadow: none;
}
body.is-ebook-mode .msg-speaker,
body.is-ebook-mode .msg-num {
  display: none;
}
body.is-ebook-mode .msg-bubble {
  /* !important so the higher-specificity dark/sepia overrides
     (body.is-theme-dark .msg--char .msg-bubble) don't drag a tinted
     bubble back into ebook flow — ebook mode flattens chat structure
     entirely and the page background should show through. */
  background: none !important;
  border: none !important;
  border-radius: 0;
  padding: 0;
  margin: 0;
  max-width: none;
  box-shadow: none;
  text-indent: 1.2em;
}
body.is-ebook-mode .msg-bubble p {
  margin: 0 0 0.5em;
  text-indent: 1.2em;
}

/* Inline + KV images need to respect column-width in ebook mode —
   otherwise the natural max-height (70vh on KV / 60vh on inline)
   wins and the image overflows the column, looking "cut off" on
   the right edge. Force display:block + tighter max-height tied to
   the eBook stage height. */
body.is-ebook-mode .msg-image-wrap,
body.is-ebook-mode .msg-image-wrap--kv {
  width: 100%;
  margin: 0.6em 0;
  text-align: center;
  break-inside: avoid;
}
body.is-ebook-mode .msg-image,
body.is-ebook-mode .msg-image-wrap--kv .msg-image {
  display: block;
  margin: 0 auto;
  max-width: 100%;
  /* Stage is calc(100vh - 240px) ≈ 60vh on a 1080px viewport,
     so cap at 50vh to leave room for surrounding paragraphs. */
  max-height: 50vh;
  width: auto;
  height: auto;
}

/* Give the page some breathing room above the stage in ebook mode. */
body.is-ebook-mode .log-page {
  padding-top: var(--s-5);
  padding-bottom: 0;
  margin-bottom: var(--s-5);
}

/* Wide mode (Shift+F) — log page mirrors novel-page when wide. */
@media (min-width: 769px) {
  body.is-ebook-mode.is-novel-wide .log-page {
    max-width: min(1400px, calc(100vw - 48px));
    padding-left: var(--s-3);
    padding-right: var(--s-3);
  }
}

@media (max-width: 768px) {
  body.is-ebook-mode .log-body {
    font-size: 15px;
    line-height: 1.75;
    height: calc(100vh - 220px);
  }
}

/* ============================================================
   NOVEL PAGE (Phase 5-A.1)
   Single-scroll prose page for meta.type === "novel". The
   multi-column eBook layout + page navigation come in 5-A.2 —
   this stylesheet just gives a clean readable column for now.
   ============================================================ */

.novel-body {
  background-color: var(--bg);
  background-image:
    radial-gradient(rgba(238, 122, 166, 0.08) 1px, transparent 1px);
  background-size: 14px 14px;
  background-attachment: fixed;
}

.novel-nav {
  position: sticky;
  top: 0;
  z-index: 40;
  margin: 0;
  padding: var(--s-3) var(--s-5);
  background: var(--nav-bg, rgba(253, 247, 245, 0.85));
  backdrop-filter: blur(10px) saturate(140%);
  -webkit-backdrop-filter: blur(10px) saturate(140%);
  border-bottom: 1px solid var(--line);
}
.novel-nav-back {
  display: inline-block;
  font-size: var(--text-xs);
  color: var(--ink-sub);
  letter-spacing: 0.04em;
  font-weight: 600;
  text-decoration: none;
}
.novel-nav-back:hover {
  color: var(--rose-deep);
}

.novel-page {
  max-width: 720px;
  margin: 0 auto;
  padding: var(--s-7) var(--s-5) var(--s-8);
  /* Smoothly grow when wide-mode toggles via the pager button. */
  transition: max-width 0.28s var(--ease, ease);
}
/* Wide-mode (operator clicked the ⛶ button or pressed Shift+F).
   Persisted in localStorage by ebook-reader.js so the next visit
   already opens at the chosen width.
   Scoped to desktop (>=769px) — on narrow viewports the default
   720px max-width already saturates the screen, and a naive
   wide-mode rule would actually shrink the page (since 92vw < 720
   below ~783px). Below the breakpoint the body class is a no-op. */
@media (min-width: 769px) {
  body.is-novel-wide .novel-page {
    max-width: min(1400px, calc(100vw - 48px));
    padding-left: var(--s-3);
    padding-right: var(--s-3);
  }
}

/* Optional book cover (Phase 5-B.3) — shown above .novel-header when
   meta.hero.src is set. Capped at 200px tall so the eBook column
   stage below still gets a comfortable reading area; the column
   stage's calc-height adjusts via the :has(.novel-cover) rule below. */
.novel-cover {
  position: relative;
  width: 100%;
  height: 200px;
  margin: 0 0 var(--s-6);
  border-radius: var(--r-lg);
  overflow: hidden;
  box-shadow: var(--shadow);
  background: var(--surface-2);
}
@media (max-width: 768px) {
  .novel-cover { height: 140px; }
}
/* When a cover is present the eBook stage shortens by the cover's
   height + its bottom margin so the entire pair (cover + stage)
   still fits in viewport. */
.novel-page:has(.novel-cover) .novel-prose {
  height: calc(100vh - 460px);
}
@media (max-width: 768px) {
  .novel-page:has(.novel-cover) .novel-prose {
    height: calc(100vh - 360px);
  }
}
.novel-cover-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: var(--cover-x, 50%) var(--cover-y, 35%);
}
.novel-cover-overlay {
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg,
    transparent 40%,
    rgba(0, 0, 0, 0.18) 100%);
  pointer-events: none;
}

.novel-header {
  text-align: center;
  margin-bottom: var(--s-7);
  padding-bottom: var(--s-6);
  border-bottom: 1px dashed var(--line);
}
.novel-archive-no {
  font-family: var(--font-display);
  font-size: 11px;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--ink-light);
  margin-bottom: var(--s-3);
}
.novel-title {
  font-family: var(--font-display);
  font-size: clamp(28px, 5vw, 40px);
  font-weight: 700;
  color: var(--rose-deep);
  letter-spacing: -0.01em;
  line-height: 1.2;
  margin: 0 0 var(--s-3);
}
.novel-subtitle {
  font-family: 'Cormorant Garamond', var(--font-serif), serif;
  font-style: italic;
  font-size: clamp(14px, 2vw, 16px);
  color: var(--ink-sub);
  letter-spacing: 0.04em;
  margin: 0 0 var(--s-4);
}
.novel-meta {
  display: inline-flex;
  flex-wrap: wrap;
  gap: var(--s-2);
  justify-content: center;
}

/* Reading body — uses var(--font-serif) so the viewer-settings panel
   font picker swap applies here too. ebook-reader.js applies
   column-count / column-gap inline at runtime; here we only set up
   the fixed-height horizontal scroll container that holds them. */
.novel-prose {
  font-family: var(--font-serif);
  font-size: 16px;
  line-height: 1.85;
  color: var(--ink);
  letter-spacing: 0.01em;
  /* eBook stage: fixed height, horizontal column scroll, hidden bars */
  height: calc(100vh - 260px);
  min-height: 320px;
  overflow-x: auto;
  overflow-y: hidden;
  scrollbar-width: none;
  scroll-behavior: smooth;
  /* Smooth out paragraph orphans / column splits */
  orphans: 2;
  widows: 2;
  /* Block the browser's native horizontal pan so user swipes don't
     drift the columns out of alignment with the page boundaries.
     `pan-y` still permits vertical page scrolling so the reader can
     reach the pager below the stage. ebook-reader.js's swipe handler
     receives the same touch events and drives page nav via scrollTo. */
  touch-action: pan-y;
}
.novel-prose::-webkit-scrollbar { display: none; }
/* Only protect indivisible atoms (headings, page-breaks, figures) —
   paragraphs should flow across columns naturally so a long
   paragraph isn't forced to start a new page. */
.novel-prose .novel-heading,
.novel-prose .novel-page-break,
.novel-prose figure,
.novel-prose img {
  break-inside: avoid;
}
.novel-prose p {
  margin: 0 0 var(--s-5);
  text-indent: 1.2em;          /* 한국어 단락 — 첫 단락도 동일하게 들여쓰기 */
}
.novel-prose strong {
  font-weight: 700;
  color: var(--ink);
}
.novel-prose em {
  font-style: italic;
  color: var(--ink-sub);
}

.novel-heading {
  font-family: var(--font-display);
  color: var(--rose-deep);
  margin: var(--s-7) 0 var(--s-4);
  letter-spacing: 0.04em;
}
/* Heading sizes are em-relative so the body font-size toggle in the
   viewer-settings panel scales them proportionally — keeps the typo
   ratio between body text and chapter titles. */
.novel-heading--h1 {
  font-size: 1.75em;
  text-align: center;
  margin-top: var(--s-8);
}
.novel-heading--h1::before,
.novel-heading--h1::after {
  content: " ✿ ";
  color: var(--rose);
  opacity: 0.5;
  font-size: 0.7em;
  vertical-align: middle;
}
.novel-heading--h2 {
  font-size: 1.4em;
}
.novel-heading--h3 {
  font-size: 1.1em;
  font-weight: 600;
}

/* Page break — visible as a subtle ornament + forces a column break
   in eBook mode so the next chapter starts on a fresh page. */
.novel-page-break {
  border: none;
  display: flex;
  align-items: center;
  justify-content: center;
  margin: var(--s-7) 0;
  height: 24px;
  break-after: column;
}
.novel-page-break::before {
  content: "❀ ✿ ❀";
  color: var(--rose);
  opacity: 0.4;
  letter-spacing: 0.6em;
  font-size: 14px;
}

/* ---- eBook pager (Phase 5-A.2) ----
   Sits below the .novel-prose stage with prev/next/first/last
   buttons + "current / total" indicator. ebook-reader.js builds it
   on script init and inserts it as a sibling immediately after the
   prose article. */
.novel-pager {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-3);
  margin: var(--s-5) auto 0;
  padding: 0 var(--s-4);
  font-family: var(--font-display);
}
.novel-pager-btn {
  width: 38px;
  height: 38px;
  border-radius: 50%;
  border: 1.5px solid var(--line);
  background: var(--surface);
  cursor: pointer;
  font-size: 18px;
  font-weight: 700;
  line-height: 1;
  color: var(--ink-sub);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  transition: transform var(--dur-fast) var(--ease),
              border-color var(--dur-fast) var(--ease),
              color var(--dur-fast) var(--ease),
              background var(--dur-fast) var(--ease);
}
.novel-pager-btn:hover:not(:disabled) {
  border-color: var(--rose);
  color: var(--rose-deep);
  background: var(--surface-2);
  transform: translateY(-1px);
}
.novel-pager-btn:active:not(:disabled) {
  transform: translateY(0);
}
.novel-pager-btn:disabled {
  opacity: 0.3;
  cursor: not-allowed;
}
.novel-pager-btn--first,
.novel-pager-btn--last {
  font-size: 14px;
  width: 32px;
  height: 32px;
}
.novel-pager-indicator {
  font-size: 13px;
  letter-spacing: 0.18em;
  color: var(--ink-sub);
  min-width: 80px;
  text-align: center;
  padding: 0 var(--s-3);
  display: inline-flex;
  align-items: baseline;
  justify-content: center;
  gap: 4px;
}
.novel-pager-current {
  color: var(--rose-deep);
  font-weight: 700;
  font-size: 16px;
}
.novel-pager-sep {
  opacity: 0.4;
}
.novel-pager-total {
  color: var(--ink-sub);
}

/* Subtle vertical hairline separating navigation from utility buttons. */
.novel-pager-sep-line {
  width: 1px;
  height: 18px;
  background: var(--line-strong, var(--line));
  margin: 0 var(--s-2);
  opacity: 0.5;
}

/* Expand / collapse toggle — sits to the right of the page nav.
   When pressed (`.is-active`) we tint it to mark the on state. */
.novel-pager-btn--expand {
  font-size: 16px;
}
.novel-pager-btn--expand.is-active {
  border-color: var(--rose);
  color: var(--rose-deep);
  background: var(--surface-2);
}
.novel-pager-expand-glyph {
  display: inline-block;
  line-height: 1;
  /* The ⛶ character sits a touch low — nudge it up. */
  transform: translateY(-1px);
}

/* Bookmark toggle (Phase 5-C.1) — ✦ glyph; pink-filled when the
   current page is also pinned to the URL hash (#page=N) so the
   reader can share the link. localStorage "resume reading" runs
   silently in the background and isn't reflected in this button. */
.novel-pager-btn--bookmark {
  font-size: 14px;
}
.novel-pager-btn--bookmark.is-active {
  border-color: var(--rose);
  color: #fff;
  background: var(--rose);
}
.novel-pager-btn--bookmark.is-active:hover:not(:disabled) {
  background: var(--rose-deep);
  color: #fff;
}
.novel-pager-bookmark-glyph {
  display: inline-block;
  line-height: 1;
}

/* Reading progress bar (Phase 5-C.2) — 2px-tall sliver fixed to the
   top of the viewport that fills as the reader flips pages. Hidden
   when there's only one page (nothing to track) and removed when
   ebook mode is deactivated on a log page. */
.rena-ebook-progress {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: 2px;
  z-index: 60;
  background: rgba(238, 122, 166, 0.10);
  pointer-events: none;
}
.rena-ebook-progress-fill {
  height: 100%;
  width: 0%;
  background: linear-gradient(90deg,
    var(--rose, #ee7aa6) 0%,
    var(--rose-deep, #c14d7d) 100%);
  transition: width 0.3s var(--ease, ease);
  box-shadow: 0 0 6px rgba(238, 122, 166, 0.4);
}
@media (prefers-reduced-motion: reduce) {
  .rena-ebook-progress-fill { transition: none; }
}

/* ============================================================
   /bookmarks/ — visitor's saved bookmarks (Phase 5-C.5)
   localStorage-only collection, rendered by
   /js/bookmarks-collection.js. The page itself is just a list
   shell + a notice that this data is browser-cache-bound.
   ============================================================ */
.bookmarks-page {
  max-width: 720px;
  margin: 0 auto;
  padding: var(--s-6) var(--s-5) var(--s-8);
}
.bookmarks-header {
  text-align: center;
  margin-bottom: var(--s-6);
}
.bookmarks-title {
  font-family: var(--font-display);
  font-size: clamp(28px, 4vw, 40px);
  color: var(--rose-deep);
  margin: 0 0 var(--s-2);
  letter-spacing: 0.02em;
}
.bookmarks-subtitle {
  font-family: 'Cormorant Garamond', var(--font-serif), serif;
  font-style: italic;
  font-size: var(--text-sm);
  color: var(--lilac-deep);
  letter-spacing: 0.32em;
  text-transform: uppercase;
  margin: 0;
  opacity: 0.8;
}

.bookmarks-warn {
  display: flex;
  align-items: flex-start;
  gap: var(--s-3);
  background: #fdf2e3;
  border: 1px solid #e6c89c;
  border-radius: var(--r-md);
  padding: var(--s-4) var(--s-5);
  font-size: var(--text-xs);
  line-height: 1.6;
  color: #7d4d1a;
  margin-bottom: var(--s-5);
}
.bookmarks-warn .warn-glyph {
  font-size: 16px;
  flex-shrink: 0;
  margin-top: 1px;
}
/* The prose lives in one flex item so <strong> stays inline and the
   text flows — without this wrapper each <strong> becomes its own
   flex column. */
.bookmarks-warn .warn-text {
  flex: 1;
  min-width: 0;
}
body.is-theme-dark .bookmarks-warn {
  background: rgba(232, 165, 91, 0.15);
  border-color: rgba(232, 165, 91, 0.4);
  color: #e8c596;
}

.bookmarks-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
}

/* Saved-excerpts section (Phase E-3) — sits below the bookmarks
   list on /bookmarks/, reuses .bookmark-item card styling. */
.saved-excerpts-section {
  margin-top: var(--s-7);
  padding-top: var(--s-5);
  border-top: 1px dashed var(--line);
}
/* Sort toggle on the /bookmarks/ lists — reuses the .couple-logs-sort
   button; these rules just place it and flip the glyph on asc. */
.bookmarks-listhead {
  display: flex;
  justify-content: flex-end;
  margin-bottom: var(--s-3);
}
.bookmarks-head-actions {
  display: flex;
  align-items: center;
  gap: var(--s-2);
}
.couple-logs-sort[data-order="asc"] .couple-logs-sort-glyph {
  transform: rotate(180deg);
}
.saved-excerpts-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-3);
  margin-bottom: var(--s-4);
}
.saved-excerpts-title {
  font-family: var(--font-display);
  font-size: var(--text-lg);
  color: var(--rose-deep);
  margin: 0;
  font-weight: 500;
}
.saved-excerpts-export {
  font-family: var(--font-body);
  font-size: var(--text-sm);
  color: var(--rose-deep);
  background: var(--surface-2);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-pill);
  padding: 6px 14px;
  cursor: pointer;
  white-space: nowrap;
  transition: background var(--dur-fast) var(--ease);
}
.saved-excerpts-export:hover:not(:disabled) {
  background: var(--peach);
}
.saved-excerpts-export:disabled {
  opacity: 0.45;
  cursor: default;
}

.bookmark-item {
  display: flex;
  align-items: stretch;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  overflow: hidden;
  transition: transform var(--dur-fast) var(--ease),
              box-shadow var(--dur-fast) var(--ease),
              border-color var(--dur-fast) var(--ease);
}
.bookmark-item:hover {
  border-color: var(--rose);
  box-shadow: var(--shadow);
  transform: translateY(-1px);
}

.bookmark-item-link {
  flex: 1 1 auto;
  display: flex;
  align-items: center;
  gap: var(--s-3);
  padding: var(--s-4) var(--s-4);
  text-decoration: none;
  color: inherit;
  min-width: 0;
}
.bookmark-item-glyph {
  flex-shrink: 0;
  font-size: 18px;
  color: var(--rose);
  width: 32px;
  height: 32px;
  display: grid;
  place-items: center;
  background: rgba(238, 122, 166, 0.10);
  border-radius: 50%;
}
.bookmark-item-text {
  min-width: 0;
  flex: 1 1 auto;
}
.bookmark-item-title {
  font-family: var(--font-serif);
  font-size: var(--text-md);
  color: var(--ink);
  margin: 0 0 4px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  font-weight: 600;
}
.bookmark-item-preview {
  font-family: var(--font-serif);
  font-style: italic;
  font-size: var(--text-xs);
  color: var(--ink-sub);
  margin: 0 0 6px;
  line-height: 1.5;
  /* Clamp to 2 lines */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.bookmark-item-meta {
  font-family: var(--font-display);
  font-size: 11px;
  letter-spacing: 0.06em;
  color: var(--ink-sub);
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}
.bookmark-item-page {
  color: var(--rose-deep);
  font-weight: 600;
}
.bookmark-item-sep {
  color: var(--ink-light);
}

.bookmark-item-remove {
  flex-shrink: 0;
  width: 44px;
  border: none;
  border-left: 1px solid var(--line);
  background: transparent;
  cursor: pointer;
  font-size: 14px;
  color: var(--ink-light);
  transition: background var(--dur-fast) var(--ease),
              color var(--dur-fast) var(--ease);
}
.bookmark-item-remove:hover {
  background: rgba(238, 122, 166, 0.08);
  color: var(--rose-deep);
}

.bookmarks-empty {
  text-align: center;
  color: var(--ink-sub);
  padding: var(--s-7) var(--s-4);
  background: var(--surface-2);
  border-radius: var(--r-md);
  font-style: italic;
  line-height: 1.6;
}
.bookmarks-empty-glyph {
  display: inline-block;
  margin: 0 4px;
  color: var(--rose);
  font-style: normal;
}

/* ============================================================
   /excerpts/ — literary excerpts mockup (Phase 6 design)
   ============================================================ */
.excerpts-page {
  max-width: 880px;
  margin: 0 auto;
  padding: var(--s-6) var(--s-5) var(--s-8);
}
.excerpts-header {
  text-align: center;
  margin-bottom: var(--s-5);
}
.excerpts-title {
  font-family: var(--font-display);
  font-size: clamp(28px, 4vw, 40px);
  color: var(--rose-deep);
  margin: 0 0 var(--s-2);
  letter-spacing: 0.02em;
}
.excerpts-subtitle {
  font-family: 'Cormorant Garamond', var(--font-serif), serif;
  font-style: italic;
  font-size: var(--text-sm);
  color: var(--lilac-deep);
  letter-spacing: 0.32em;
  text-transform: uppercase;
  margin: 0 0 var(--s-3);
  opacity: 0.8;
}
.excerpts-tagline {
  font-family: var(--font-serif);
  font-style: italic;
  font-size: var(--text-sm);
  color: var(--ink-sub);
  margin: 0;
  line-height: 1.6;
}

/* Filter chips */
.excerpts-filters {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 8px;
  margin-bottom: var(--s-6);
}
.excerpt-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 14px;
  border: 1.5px solid var(--line);
  background: var(--surface);
  border-radius: var(--r-pill);
  font-family: var(--font-display);
  font-size: var(--text-xs);
  color: var(--ink-sub);
  cursor: pointer;
  letter-spacing: 0.04em;
  transition: all var(--dur-fast) var(--ease);
}
.excerpt-chip:hover {
  border-color: var(--rose);
  color: var(--rose-deep);
}
.excerpt-chip.is-active {
  background: var(--rose);
  border-color: var(--rose);
  color: #fff;
}
.excerpt-chip-count {
  font-size: 10px;
  opacity: 0.75;
  font-variant-numeric: tabular-nums;
}

/* Card grid */
.excerpts-grid {
  list-style: none;
  margin: 0 0 var(--s-6);
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(340px, 1fr));
  gap: var(--s-4);
}
.excerpts-grid > .line-card {
  /* default order; the newest/oldest toggle flips the sign */
  order: 0;
}

/* Per-couple excerpts sort toggle row — right-aligned above the grid,
   mirrors the couple-page log grid's sort button placement. */
.excerpts-sort-row {
  display: flex;
  justify-content: flex-end;
  margin: 0 0 var(--s-3);
}
/* The shared .gallery-filter carries a --s-5 gap sized for the gallery
   page; on the excerpts page the sort row sits right under the chips,
   so that gap leaves the lone button floating. Tighten it here. */
.excerpts-couple-page .gallery-filter {
  margin-bottom: var(--s-2);
}

.excerpt-card {
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border: 1.5px solid var(--line);
  border-radius: var(--r-md);
  padding: var(--s-5) var(--s-5) var(--s-4);
  box-shadow: var(--shadow);
  position: relative;
  isolation: isolate;
  transition: transform var(--dur-fast) var(--ease),
              box-shadow var(--dur-fast) var(--ease),
              border-color var(--dur-fast) var(--ease);
}
.excerpt-card:hover {
  transform: translateY(-2px);
  box-shadow: var(--shadow-up);
  border-color: var(--rose);
}

/* Subtle left-edge color stripe per type */
.excerpt-card::before {
  content: "";
  position: absolute;
  top: 0; left: 0; bottom: 0;
  width: 3px;
  border-radius: var(--r-md) 0 0 var(--r-md);
  background: var(--line-strong);
}
.excerpt-card--log::before   { background: var(--rose); }
.excerpt-card--novel::before { background: var(--lilac); }
.excerpt-card--book::before  { background: var(--mint-deep); }

.excerpt-card-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: var(--s-3);
}
.excerpt-card-type {
  font-size: 16px;
  width: 28px;
  height: 28px;
  display: grid;
  place-items: center;
  background: rgba(238, 122, 166, 0.10);
  border-radius: 50%;
}
.excerpt-card--novel .excerpt-card-type {
  background: rgba(196, 168, 224, 0.16);
}
.excerpt-card--book .excerpt-card-type {
  background: rgba(168, 216, 200, 0.20);
}
.excerpt-card-date {
  font-family: var(--font-display);
  font-size: 10px;
  letter-spacing: 0.16em;
  color: var(--ink-light);
}

.excerpt-card-quote {
  font-family: var(--font-serif);
  font-size: var(--text-md);
  line-height: 1.75;
  color: var(--ink);
  margin: 0 0 var(--s-4);
  padding: 0;
  border: none;
  letter-spacing: 0.01em;
  /* Slight indent + opening curly quote sets the bookish tone */
  text-indent: -0.4em;
  padding-left: 0.4em;
}
.excerpt-card-quote::before {
  content: "“";
  color: var(--rose);
  font-family: 'Cormorant Garamond', var(--font-serif), serif;
  font-size: 1.3em;
  font-weight: 600;
  line-height: 0;
  vertical-align: -0.3em;
  margin-right: 4px;
}
.excerpt-card-quote::after {
  content: "”";
  color: var(--rose);
  font-family: 'Cormorant Garamond', var(--font-serif), serif;
  font-size: 1.3em;
  font-weight: 600;
  line-height: 0;
  vertical-align: -0.3em;
  margin-left: 4px;
}
.excerpt-card-quote strong {
  color: var(--rose-deep);
  font-weight: 700;
}
.excerpt-card-quote em {
  color: var(--lilac-deep);
  font-style: italic;
}

.excerpt-card-foot {
  margin-top: auto;
  padding-top: var(--s-3);
  border-top: 1px dashed var(--line);
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
}
.excerpt-card-source {
  font-family: var(--font-display);
  font-size: var(--text-xs);
  color: var(--ink-sub);
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px;
  letter-spacing: 0.04em;
}
a.excerpt-card-source:hover {
  color: var(--rose-deep);
}
.excerpt-card-source-couple {
  font-style: italic;
  color: var(--lilac-deep);
}
.excerpt-card-source-sep {
  color: var(--ink-light);
}
.excerpt-card-source-title {
  font-weight: 700;
}
.excerpt-card-source-author {
  color: var(--ink-sub);
}
.excerpt-card-source-page {
  font-variant-numeric: tabular-nums;
  color: var(--mint-deep);
  font-weight: 700;
  margin-left: auto;
  background: rgba(168, 216, 200, 0.15);
  padding: 1px 8px;
  border-radius: var(--r-pill);
}
.excerpt-card-source-arrow {
  color: var(--rose);
  margin-left: auto;
  font-size: 14px;
}
a.excerpt-card-source:hover .excerpt-card-source-arrow {
  transform: translateX(2px);
  transition: transform var(--dur-fast) var(--ease);
}

.excerpt-card-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
}

.excerpts-mockup-note {
  margin: var(--s-7) 0 var(--s-5);
  padding: var(--s-4) var(--s-5);
  background: #fdf2e3;
  border: 1px solid #e6c89c;
  border-radius: var(--r-md);
  font-size: var(--text-xs);
  color: #7d4d1a;
  line-height: 1.6;
  text-align: center;
}
.excerpts-mockup-note code {
  font-family: var(--font-mono, ui-monospace, Menlo, monospace);
  background: rgba(255, 255, 255, 0.5);
  padding: 1px 6px;
  border-radius: 3px;
  font-size: 11px;
}

@media (max-width: 640px) {
  .excerpts-page { padding: var(--s-5) var(--s-3) var(--s-7); }
  .excerpts-grid { grid-template-columns: 1fr; gap: var(--s-3); }
  .excerpt-card { padding: var(--s-4) var(--s-4) var(--s-3); }
}

/* Reading font-size scale (Phase 5-C.4) — applies on top of the
   chosen serif font. Affects narrative reading: .novel-prose,
   .log-body in ebook mode, and .msg-bubble in chat mode. UI / nav
   / pager stay fixed so layout doesn't drift. Column count auto-
   recomputes on the synthetic 'resize' event the toggle dispatches. */
body.is-text-sm .novel-prose,
body.is-text-sm.is-ebook-mode .log-body,
body.is-text-sm .msg-bubble {
  font-size: 14px;
  line-height: 1.75;
}
body.is-text-lg .novel-prose,
body.is-text-lg.is-ebook-mode .log-body,
body.is-text-lg .msg-bubble {
  font-size: 18px;
  line-height: 1.95;
}
@media (max-width: 768px) {
  body.is-text-sm .novel-prose,
  body.is-text-sm.is-ebook-mode .log-body,
  body.is-text-sm .msg-bubble {
    font-size: 13px;
  }
  body.is-text-lg .novel-prose,
  body.is-text-lg.is-ebook-mode .log-body,
  body.is-text-lg .msg-bubble {
    font-size: 17px;
  }
}

/* Hide the expand toggle below the eBook-reader's mobile breakpoint
   (768px). Default 720px max-width already saturates a phone screen
   so "expand" wouldn't actually grow anything — the button would be
   a confusing no-op. The separator hairline goes with it. */
@media (max-width: 768px) {
  .novel-pager-btn--expand,
  .novel-pager-sep-line { display: none; }
}

@media (max-width: 640px) {
  .novel-nav { padding: var(--s-3); }
  .novel-page { padding: var(--s-5) var(--s-4) var(--s-7); }
  .novel-prose {
    font-size: 15px;
    line-height: 1.75;
    height: calc(100vh - 220px);
  }
  .novel-pager { gap: var(--s-2); }
  .novel-pager-btn { width: 34px; height: 34px; font-size: 16px; }
  .novel-pager-btn--first,
  .novel-pager-btn--last { width: 28px; height: 28px; font-size: 12px; }
  .novel-pager-indicator { min-width: 60px; font-size: 12px; }
  .novel-pager-current { font-size: 14px; }
}

/* ============================================================
   Excerpts (Phase E-1) — hybrid text+optional-bg cards
   Used in two surfaces:
     1. Per-log section: <section class="log-excerpts-section">
     2. /excerpts/ aggregate page: <main class="excerpts-page">
   Both share the same .excerpt-card primitive.
   ============================================================ */

/* ---- Per-log section (bottom of each log page) ---- */
.log-excerpts-section {
  margin: 48px auto 24px;
  max-width: 880px;
  padding: 0 16px;
}
.log-excerpts-section-title {
  /* extends .section-heading from base layout — local nudges only */
  margin: 0 0 22px;
}
/* Horizontal scroll carousel — matches 교보문고's "문장수집" UX.
   Each card snaps to start on scroll. Cards have fixed width so
   they stay readable inside their landscape aspect ratio. */
.log-excerpts-section-grid {
  display: flex;
  flex-direction: row;
  gap: 14px;
  overflow-x: auto;
  overflow-y: hidden;
  scroll-snap-type: x mandatory;
  /* Negative margin lets cards extend to viewport edges within the
     centered max-width section, so the user can swipe naturally
     without dead zones. */
  padding: 6px 16px 14px;
  margin: 0 -16px;
  /* Thin native scrollbar on hover/scroll; cleaner than a full chunk. */
  scrollbar-width: thin;
}
.log-excerpts-section-grid > .line-card {
  flex: 0 0 320px;
  scroll-snap-align: start;
}
@media (max-width: 640px) {
  .log-excerpts-section-grid > .line-card {
    flex: 0 0 280px;
  }
}

/* Hide the section in ebook mode — ebook reader is for linear
   continuous reading, the excerpt grid breaks the flow. */
body.is-ebook-mode .log-excerpts-section {
  display: none !important;
}

/* ---- /excerpts/ aggregate page ---- */
.excerpts-page {
  max-width: 1200px;
  margin: 0 auto;
  padding: 24px 20px 80px;
}
.excerpts-page-head {
  text-align: center;
  margin-bottom: var(--s-7);
}
.excerpts-page-title {
  font-family: var(--font-display);
  font-size: clamp(2rem, 5vw, 3rem);
  font-weight: 700;
  color: var(--rose-deep);
  margin: var(--s-3) 0 6px;
  letter-spacing: -0.01em;
}
.excerpts-page-subtitle {
  font-family: var(--font-serif);
  font-style: italic;
  color: var(--ink-sub);
  font-size: var(--text-sm);
  margin: 0;
}
.excerpts-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: 18px;
}
.excerpts-empty {
  text-align: center;
  color: var(--ink-sub);
  padding: 60px 20px;
  font-style: italic;
}
.excerpts-pagination {
  display: flex;
  justify-content: center;
  gap: 6px;
  margin: 32px 0 20px;
}
.excerpts-pagination .excerpts-page-link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 36px;
  height: 36px;
  padding: 0 10px;
  border-radius: 999px;
  background: var(--surface, #fff);
  border: 1px solid var(--line, #e4dcc7);
  color: var(--ink-sub, #6b6755);
  text-decoration: none;
  font-size: 0.9rem;
  transition: all 140ms;
}
.excerpts-pagination .excerpts-page-link:hover {
  border-color: var(--rose, #ee7aa6);
  color: var(--rose-deep, #c14d7d);
}
.excerpts-pagination .excerpts-page-link.is-current {
  background: var(--rose-deep, #c14d7d);
  color: white;
  border-color: var(--rose-deep, #c14d7d);
}

/* ============================================================
   Posts feed (Phase P-A)
   ------------------------------------------------------------
   Per-couple /<couple>/posts/ feed page. Lightweight blog-style
   cards with an image carousel + caption. Sits as a sibling of
   gallery and excerpts under the same .gallery-page chrome.
   ============================================================ */

/* The posts feed itself is single-column, narrower than the
   gallery-page container (1080) for readable line lengths in
   the caption. */
.posts-page .posts-feed {
  max-width: 720px;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: var(--s-5);
}

.post-card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  overflow: hidden;
  box-shadow: var(--shadow);
}
/* Text-only variant — no images. Subtle left accent so it reads
   as "thought / 잡담" without screaming for attention. */
.post-card--text {
  border-left: 3px solid color-mix(in srgb,
    var(--couple-primary, var(--rose)) 25%, transparent);
}
.post-card--text .post-body { padding: var(--s-5); }
/* Pinned variant — TODO future surface (small icon in date row).
   Class is emitted but no visual yet to keep v1 quiet. */

/* ----- Carousel ----- */
.post-carousel {
  position: relative;
  background: #0e0d12;
}
.post-carousel-track {
  display: flex;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}
.post-carousel-track::-webkit-scrollbar { display: none; }
.post-carousel-slide {
  flex: 0 0 100%;
  scroll-snap-align: start;
  aspect-ratio: 4 / 3;
  display: flex;
  align-items: center;
  justify-content: center;
  position: relative;
}
.post-carousel-slide img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  cursor: zoom-in;            /* hint: click to view full */
}
/* Per-image fit override (Phase P-B). cover (default) crops to fill,
   contain letterboxes so the full image is visible against the dark
   carousel background. */
.post-carousel-slide img[data-fit="contain"] {
  object-fit: contain;
}
/* Single-image post: hide controls + dots. They're emitted into
   the DOM anyway so the JS doesn't have to branch on count, but
   stay invisible. */
.post-carousel--single .post-carousel-controls,
.post-carousel--single .post-carousel-dots { display: none; }

.post-carousel-controls {
  position: absolute;
  inset: 0;
  pointer-events: none;
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 0 var(--s-3);
}
.post-carousel-btn {
  pointer-events: auto;
  background: rgba(255, 255, 255, 0.88);
  border: 0;
  border-radius: 999px;
  width: 36px;
  height: 36px;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  box-shadow: var(--shadow);
  font-size: 18px;
  color: var(--ink);
  transition: transform var(--dur-fast) var(--ease);
}
.post-carousel-btn:hover { transform: scale(1.06); }
.post-carousel-btn:disabled {
  opacity: 0.35;
  cursor: default;
  transform: none;
}

.post-carousel-dots {
  position: absolute;
  bottom: var(--s-3);
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  gap: 6px;
  background: rgba(0, 0, 0, 0.35);
  padding: 5px 9px;
  border-radius: 999px;
}
.post-carousel-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.5);
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease),
              transform var(--dur-fast) var(--ease);
}
.post-carousel-dot.is-active {
  background: #fff;
  transform: scale(1.3);
}

/* ----- Body ----- */
.post-body { padding: var(--s-4) var(--s-5); }
.post-date {
  display: inline-block;
  font-family: var(--font-serif);
  font-style: italic;
  color: var(--ink-sub);
  font-size: var(--text-xs);
  margin-bottom: 6px;
}
.post-caption {
  font-family: var(--font-body);
  font-size: var(--text-md);
  color: var(--ink);
  line-height: 1.7;
  white-space: pre-wrap;        /* preserve manual line breaks */
  word-break: break-word;
  margin: 0;
}
.post-caption strong {
  color: var(--couple-primary-deep, var(--rose-deep));
}
.post-caption em {
  font-style: italic;
  color: var(--ink-sub);
}
.post-tags {
  margin-top: var(--s-3);
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}
.post-tag {
  font-size: var(--text-xs);
  color: var(--couple-primary-deep, var(--rose-deep));
  background: color-mix(in srgb,
    var(--couple-primary, var(--rose)) 12%, transparent);
  padding: 3px 10px;
  border-radius: 999px;
}

@media (max-width: 640px) {
  .post-body { padding: var(--s-3) var(--s-4); }
  .post-carousel-btn { width: 32px; height: 32px; font-size: 16px; }
}


/* ============================================================
   .line-card — the per-excerpt card primitive.
   Renamed from .excerpt-card to avoid colliding with the
   pre-existing (unbuilt) bookmark/excerpt feature in this file.
   Used on both per-log section and /excerpts/ aggregate page.
   ============================================================ */

.line-card {
  position: relative;
  /* Themed gradient bg — picks up the page's --rose / --lilac
     tokens so it doesn't look like a generic flat card. Mirrors
     the chat bubble vibe at a different intensity. */
  background:
    linear-gradient(135deg,
      color-mix(in srgb, var(--rose, #ee7aa6) 6%, var(--surface, #fff)) 0%,
      color-mix(in srgb, var(--lilac, #c4a8e0) 5%, var(--surface, #fff)) 100%);
  border: 1px solid color-mix(in srgb,
    var(--rose, #ee7aa6) 22%, transparent);
  border-radius: 14px;
  padding: 18px 20px 14px;
  /* Landscape card aspect (~postcard ratio) → uniform grid, looks
     more like a "card" than a column. Text clamped at fewer lines
     to fit; click handler in /js/line-card.js opens a modal for
     the full quote. */
  aspect-ratio: 3 / 2;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  cursor: pointer;
  box-shadow:
    0 2px 8px rgba(80, 70, 30, 0.06),
    0 8px 20px color-mix(in srgb,
      var(--rose, #ee7aa6) 8%, transparent);
  transition: transform 180ms ease, box-shadow 180ms ease;
}
.line-card:hover {
  transform: translateY(-2px);
  box-shadow:
    0 4px 12px rgba(80, 70, 30, 0.08),
    0 14px 28px color-mix(in srgb,
      var(--rose, #ee7aa6) 14%, transparent);
}

/* With background image: layer image under text with darker
   overlay than v1 (60→85%, was 45→65%) — earlier overlay was
   too light and color-saturated images washed out the text. */
.line-card--with-bg {
  color: white;
  border-color: transparent;
  /* Stronger themed glow when there's an image — it visually
     anchors the card more (otherwise the image floats). */
  box-shadow:
    0 4px 14px rgba(20, 18, 28, 0.18),
    0 12px 32px color-mix(in srgb,
      var(--rose, #ee7aa6) 18%, transparent);
}
.line-card-bg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  z-index: 0;
}
.line-card--with-bg::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(
    180deg,
    rgba(15, 13, 22, 0.60) 0%,
    rgba(15, 13, 22, 0.85) 100%);
  z-index: 1;
  pointer-events: none;
}
.line-card--with-bg .line-card-text,
.line-card--with-bg .line-card-meta {
  position: relative;
  z-index: 2;
}
.line-card--with-bg .line-card-text {
  color: white;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.55);
}
.line-card--with-bg .line-card-link {
  color: rgba(255, 255, 255, 0.94);
}
.line-card--with-bg .line-card-link:hover {
  color: white;
}
.line-card--with-bg .line-card-context {
  color: rgba(255, 255, 255, 0.82);
}

/* Featured (pinned) — accent border on top of whatever else */
.line-card--featured {
  border-color: var(--rose, #ee7aa6);
}

/* (PNG download button styles removed — Phase E-2a feature was
   rolled back after several rounds couldn't match the user's
   standalone 로그메이커 quality. Users who want a share-ready
   image create it in 로그메이커 and upload as an inlay image.) */

.line-card-text {
  margin: 0;
  font-family: var(--font-display, 'Cormorant Garamond', 'Gowun Batang', serif);
  font-size: 1.05rem;
  line-height: 1.6;
  color: var(--ink, #2a2a26);
  font-style: italic;
  font-weight: 400;
  border: none;
  padding: 0;
  word-break: keep-all;
  overflow-wrap: break-word;
  /* No flex grow — that fought with -webkit-line-clamp (the flex
     sizer overrode the clamp, so long text cut without ellipsis).
     Text takes its natural height up to the clamp limit; meta
     pins to the bottom of the flex column via margin-top:auto. */
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 5;
  overflow: hidden;
}
/* When card is shown inside the modal: no clamp, full text. */
.line-card--expanded .line-card-text {
  display: block;
  -webkit-line-clamp: unset;
  overflow: visible;
  white-space: pre-wrap;
}

.line-card-meta {
  /* Pinned to the bottom of the card via margin-top:auto inside
     the flex column. Thin separator divides it from the (possibly
     clamped) quote text. */
  display: block;
  margin-top: auto;
  padding-top: 12px;
  border-top: 1px solid color-mix(in srgb,
    var(--ink-sub, #6b6755) 18%, transparent);
  font-size: 0.78rem;
  color: var(--ink-sub, #6b6755);
  line-height: 1.5;
  flex-shrink: 0;
}
.line-card--with-bg .line-card-meta {
  border-top-color: rgba(255, 255, 255, 0.22);
}
.line-card-pin {
  display: block;
  font-size: 0.9rem;
  color: var(--rose-deep, #c14d7d);
  line-height: 1.5;
}
.line-card-link {
  display: block;
  text-decoration: none;
  color: var(--rose-deep, #c14d7d);
  font-weight: 500;
  line-height: 1.5;
}
.line-card-link:hover {
  text-decoration: underline;
}
.line-card-context {
  display: block;
  font-style: italic;
  opacity: 0.85;
  line-height: 1.5;
}

@media (max-width: 640px) {
  /* Per-couple page grid → single column. Per-log section
     stays horizontal scroll (rule already in its own block above). */
  .excerpts-grid {
    grid-template-columns: 1fr;
  }
  .line-card { padding: 18px 16px 12px; }
  .line-card-text { font-size: 1rem; }
}

/* ============================================================
   Click-to-expand modal — shows the full text of a clamped card.
   Card is cloned into the modal slot and given .line-card--expanded
   which strips the clamp + ellipsis. Handler in /js/line-card.js.
   ============================================================ */

.line-card-modal {
  position: fixed;
  inset: 0;
  z-index: 100;
  background: rgba(15, 13, 22, 0.7);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px 16px;
  overflow-y: auto;
}
.line-card-modal[hidden] {
  display: none;
}
.line-card-modal-inner {
  position: relative;
  width: 100%;
  max-width: 560px;
  margin: auto;
}
.line-card-modal-close {
  position: absolute;
  top: -8px;
  right: -8px;
  z-index: 2;
  width: 36px;
  height: 36px;
  border-radius: 999px;
  border: none;
  background: rgba(255, 255, 255, 0.92);
  color: var(--ink, #2a2a26);
  cursor: pointer;
  font-size: 1rem;
  line-height: 1;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25);
}
.line-card-modal-close:hover {
  background: white;
}
.line-card-modal-slot {
  /* The cloned card goes here. Reset aspect-ratio so the modal
     card sizes to its full content. */
}
.line-card--expanded {
  aspect-ratio: auto !important;
  cursor: default !important;
  max-height: none !important;
}

/* ============================================================
   Excerpt capture & highlight (Phase E-3)
   ------------------------------------------------------------
   - .excerpt-toolbar : floating toolbar shown near a text
     selection inside a log message bubble (excerpt-capture.js)
   - .excerpt-highlight : <mark> wrap painted onto the body for
     published / locally-saved excerpts (excerpt-highlight.js).
     Two distinct highlighter colors; per-source toggles add a
     body class that flattens the band back to transparent.
   ============================================================ */
.excerpt-toolbar {
  position: absolute;
  z-index: 60;
  display: flex;
  gap: 2px;
  padding: 4px;
  background: var(--surface);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-pill);
  box-shadow: var(--shadow-up);
}
.excerpt-toolbar[hidden] {
  display: none;
}
.excerpt-toolbar-btn {
  border: none;
  background: transparent;
  font-family: var(--font-body);
  font-size: var(--text-sm);
  color: var(--ink);
  padding: 4px 12px;
  border-radius: var(--r-pill);
  cursor: pointer;
  white-space: nowrap;
  transition: background var(--dur-fast) var(--ease),
              color var(--dur-fast) var(--ease);
}
.excerpt-toolbar-btn:hover {
  background: var(--surface-2);
  color: var(--rose-deep);
}
.excerpt-toolbar-btn--done {
  color: var(--mint-deep);
  pointer-events: none;
}

/* Body highlight marks — a plump translucent "pill" behind the
   phrase (overrides the browser-default yellow <mark>). Warm tones —
   butter for published, rose for saved — pop on the faintly cool-
   washed chat bubbles and never collide with the lilac 속마음 pill.
   The bubbles are only an 8% theme wash (20% in dark) so a fixed
   warm pair stays legible across every site theme. */
.excerpt-highlight {
  color: inherit;
  background: transparent;
  padding: 2px 5px;
  border-radius: 6px;
  box-decoration-break: clone;
  -webkit-box-decoration-break: clone;
}
.excerpt-highlight--published {
  background: rgba(255, 206, 74, 0.42);
}
.excerpt-highlight--saved {
  background: rgba(238, 120, 158, 0.34);
}
/* Dark reading mode — lift the wash so the pill still glows on the
   darkened bubble. */
body.is-theme-dark .excerpt-highlight--published {
  background: rgba(255, 214, 96, 0.50);
}
body.is-theme-dark .excerpt-highlight--saved {
  background: rgba(246, 132, 172, 0.46);
}
/* Per-source toggles (viewer-settings) — fully neutralize the mark,
   padding included, so toggled-off text reads exactly like plain. */
body.hl-published-off .excerpt-highlight--published,
body.hl-saved-off .excerpt-highlight--saved {
  background: transparent;
  padding: 0;
}
