/* =================================================================
   Grail Story Moment — load-bearing journey CSS.
   See docs/superpowers/specs/2026-05-14-grail-story-moment-design.md
   and public/mocks/grail-story-preview.html (visual source of truth).

   Hard rules:
   - Don't add MIN_DWELL_MS phases here (client controller owns timing)
   - Don't reintroduce a fixed-pixel rail height in writing state —
     use calc(100% - 36px) to match settled state's bottom margin
   - Don't drop the [data-state="revealing"] / [data-state="settled"]
     collapse rules on .canvas-stage and .canvas-stage--reduced — they
     prevent the empty-box-above-story bug under reduced motion
   ================================================================ */

/* ============= Grailify button micro-moment =============
   Adds confetti container + state-driven animations on top of the
   base .badge / .badge--grail rules defined in tags.css. We only
   add `position: relative` and `overflow: visible` here so confetti
   children can anchor + escape; we never re-declare base colors. */
.badge--grail {
  position: relative;          /* anchor for confetti children */
  /* The button is its own micro-stage. overflow: visible so confetti can fly out. */
  overflow: visible;
}
.badge--grail[data-grailify-state="processing"] {
  /* Subtle soft pulse during the story's writing phase */
  animation: grailify-pulse 1400ms ease-in-out infinite;
  border-color: var(--color-amber);
  color: var(--color-amber-2);
}
.badge--grail[data-grailify-state="settled"] {
  border-color: var(--color-amber);
  color: var(--color-amber-2);
  background: var(--color-amber-light);
}
@keyframes grailify-pulse {
  0%, 100% { transform: scale(1.00); }
  50%      { transform: scale(1.025); }
}

/* Two labels, controlled by data-grailify-state */
.grailify-label { transition: opacity 120ms var(--ease-out); }
.grailify-label--settled { display: none; }
.badge--grail[data-grailify-state="settled"] .grailify-label--idle     { display: none; }
.badge--grail[data-grailify-state="settled"] .grailify-label--settled  { display: inline; }

/* Rename pulse — fired once when transitioning processing → settled */
.badge--grail.is-rename-pulsing {
  animation: grailify-rename 300ms var(--ease-spring) forwards;
}
@keyframes grailify-rename {
  0%   { transform: scale(1.00); }
  47%  { transform: scale(1.15); }
  100% { transform: scale(1.00); }
}

/* Confetti particles spawned by JS — blue + gold burst at tap */
.grailify-confetti {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 6px;
  height: 3px;
  border-radius: 1.5px;
  pointer-events: none;
  opacity: 1;
  --tx: 0px;
  --ty: 0px;
  --rotate: 0deg;
  animation: confetti-fly var(--dur, 700ms) var(--ease-out) forwards;
}
.grailify-confetti--blue { background: var(--color-electric); }
.grailify-confetti--gold { background: var(--color-amber); }
@keyframes confetti-fly {
  0%   { transform: translate(-50%, -50%) translate(0, 0) rotate(0deg); opacity: 1; }
  60%  { opacity: 1; }
  100% { transform: translate(-50%, -50%) translate(var(--tx), var(--ty)) rotate(var(--rotate)); opacity: 0; }
}

/* When the controller is mid-orchestration (hold-collapsed during the
   pre-animation scroll), every transition/animation in the slot is
   short-circuited so the state="empty" snap-back is invisible and the
   subsequent state="armed" replays the entrance transitions fresh. */
.story-slot--instant,
.story-slot--instant *,
.story-slot--instant *::before,
.story-slot--instant *::after {
  transition: none !important;
  animation: none !important;
}

/* ============= Story slot — the stage ============= */
.story-slot {
  position: relative;
  border-top: 1px solid rgba(26,13,8,0.08);
  padding: 0 18px;
  max-height: 0;
  overflow: hidden;
  transition: max-height 460ms var(--ease-out), padding 320ms var(--ease-out);
}
.story-slot[data-state="armed"],
.story-slot[data-state="writing"],
.story-slot[data-state="revealing"],
.story-slot[data-state="settled"],
.story-slot[data-state="failing"] {
  max-height: 1400px;
  padding: 18px 18px 22px;
}

/* The amber rail */
.story-rail {
  position: absolute;
  top: 18px;
  left: 18px;
  width: 3px;
  background: var(--color-amber);
  border-radius: 2px;
  height: 0;
  transition: height 380ms var(--ease-out);
}
.story-slot[data-state="armed"] .story-rail,
.story-slot[data-state="writing"] .story-rail,
.story-slot[data-state="failing"] .story-rail {
  /* Rail extends below the writing canvas so it pre-balances the final
     composition. In settled state the rail spans past the regen icon at
     the bottom-right of the body; this length anticipates that asymmetry
     and prevents the writing stage from feeling top-heavy.
     Use the SAME formula as settled (`calc(100% - 36px)`) so the bottom
     margin from the slot edge is identical in both states — without it,
     a fixed pixel height clips against the slot's bottom padding and the
     rail visually touches the section divider below. */
  height: calc(100% - 36px);
}
.story-slot[data-state="revealing"] .story-rail,
.story-slot[data-state="settled"] .story-rail {
  height: calc(100% - 36px);
  transition: height 320ms var(--ease-out);
}

/* The eyebrow */
.story-eyebrow {
  position: relative;
  display: inline-block;
  font-family: 'Sora', sans-serif;
  font-weight: 600;
  font-size: 11px;
  color: var(--color-amber);
  text-transform: uppercase;
  letter-spacing: 0.40em;
  opacity: 0;
  margin-bottom: 12px;
  padding-left: 14px;
  transition: opacity 200ms var(--ease-out), letter-spacing 220ms var(--ease-out);
}
.story-slot:not([data-state="empty"]) .story-eyebrow {
  opacity: 1;
  letter-spacing: 0.30em;
}

/* ============================================================ */
/* THE SHARED CANVAS — same box during writing AND settled       */
/* ============================================================ */
.story-canvas {
  position: relative;
  margin-left: 14px;
  margin-top: 8px;
  padding: 22px 18px;
  background: linear-gradient(180deg, var(--color-cream-card) 0%, var(--color-parch) 100%);
  border: 1.5px solid rgba(26,13,8,0.10);
  border-radius: 4px;
  opacity: 0;
  transform: translateY(8px);
  transition:
    opacity 280ms var(--ease-out),
    transform 280ms var(--ease-out),
    background 360ms var(--ease-out),
    border-color 360ms var(--ease-out),
    padding 320ms var(--ease-out);
  overflow: hidden;
  min-height: 0;
}
.story-slot[data-state="armed"] .story-canvas,
.story-slot[data-state="writing"] .story-canvas,
.story-slot[data-state="failing"] .story-canvas {
  opacity: 1;
  transform: none;
  min-height: 144px;
}
/* The writing canvas (parchment box + border) is the stage for the pen
   moment. The instant we leave writing, the reveal container should
   contain ONLY the body + regen — the outer slot already has the gold
   rail and eyebrow, so the inner box has nothing to add. Background +
   border fade out via the existing 360ms transitions on .story-canvas;
   padding/min-height collapse so the body sits where the pen used to. */
.story-slot[data-state="revealing"] .story-canvas {
  opacity: 1;
  transform: none;
  min-height: 0;
  background: transparent;
  border-color: transparent;
  padding: 4px 0;
}
.story-slot[data-state="settled"] .story-canvas {
  opacity: 1;
  transform: none;
  min-height: 0;
  background: transparent;
  border-color: transparent;
  padding: 4px 0;
}

/* Writing-state elements — pen, ink, caption — sit on the canvas */
.canvas-stage {
  position: relative;
  height: 100px;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: opacity 220ms var(--ease-out), height 320ms var(--ease-out);
}
.story-slot[data-state="revealing"] .canvas-stage,
.story-slot[data-state="settled"] .canvas-stage {
  opacity: 0;
  height: 0;
  pointer-events: none;
  overflow: hidden;
}

/* Ink scribbles */
.ink-lines {
  position: absolute;
  inset: 8px 18px 12px 18px;
  pointer-events: none;
  opacity: 0;
  transition: opacity 240ms var(--ease-out);
}
.story-slot[data-state="writing"] .ink-lines { opacity: 0.55; }
.ink-lines path {
  stroke: var(--color-amber);
  stroke-width: 1.5;
  stroke-linecap: round;
  fill: none;
  stroke-dasharray: 200;
  stroke-dashoffset: 200;
  opacity: 0;
}
.story-slot[data-state="writing"] .ink-lines path {
  animation: ink-scribble 2800ms var(--ease-out) infinite;
}
.ink-lines path:nth-child(2) { animation-delay: 900ms !important; }
.ink-lines path:nth-child(3) { animation-delay: 1800ms !important; }
@keyframes ink-scribble {
  0%   { stroke-dashoffset: 200; opacity: 0; }
  20%  { opacity: 0.6; }
  60%  { stroke-dashoffset: 0;   opacity: 0.7; }
  90%  { opacity: 0.3; }
  100% { stroke-dashoffset: -200; opacity: 0; }
}

/* Glyph stage (magnifying glass → pen flip) */
.glyph-stage {
  position: relative;
  width: 72px; height: 72px;
  perspective: 600px;
  z-index: 2;
  transform: scale(0.6);
  opacity: 0;
  transition: transform 360ms var(--ease-spring), opacity 240ms var(--ease-out);
}
.story-slot[data-state="armed"] .glyph-stage,
.story-slot[data-state="writing"] .glyph-stage {
  transform: scale(1);
  opacity: 1;
}

.glyph {
  position: absolute; inset: 0;
  transform-style: preserve-3d;
  transform: rotateY(0deg);
  transition: transform 480ms var(--ease-brand);
}
.story-slot[data-state="writing"] .glyph,
.story-slot[data-state="failing"] .glyph { transform: rotateY(180deg); }

.glyph__face {
  position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: center;
  backface-visibility: hidden;
}
.glyph__face--back { transform: rotateY(180deg); }
.glyph svg { width: 100%; height: 100%; }

/* Pen scratch motion (only during writing) — traces three lines of writing
   across the canvas: left→right, drop, repeat. Total cycle 3200ms gives a
   natural writing cadence. The keyframe lives on the glyph __face--back so
   the rotateY(180deg) baseline is preserved while the pen translates. */
.story-slot[data-state="writing"] .glyph__face--back {
  animation: pen-scratch 3200ms linear infinite;
  transform-origin: 60% 70%;
}
@keyframes pen-scratch {
  /* Line 1: left → right, with small jitter per stroke */
  0%   { transform: rotateY(180deg) translate(-68px, -16px) rotate(-1deg); }
  6%   { transform: rotateY(180deg) translate(-50px, -14px) rotate(2deg); }
  12%  { transform: rotateY(180deg) translate(-28px, -17px) rotate(-1deg); }
  18%  { transform: rotateY(180deg) translate(0px,   -15px) rotate(1deg); }
  24%  { transform: rotateY(180deg) translate(28px,  -17px) rotate(-2deg); }
  30%  { transform: rotateY(180deg) translate(58px,  -15px) rotate(1deg); }
  /* Carriage return — pen lifts up-right, swings down-left */
  34%  { transform: rotateY(180deg) translate(60px,  -8px)  rotate(4deg); }
  37%  { transform: rotateY(180deg) translate(-66px, 4px)   rotate(-3deg); }
  /* Line 2 */
  42%  { transform: rotateY(180deg) translate(-46px, 6px)   rotate(1deg); }
  48%  { transform: rotateY(180deg) translate(-18px, 4px)   rotate(-1deg); }
  54%  { transform: rotateY(180deg) translate(14px,  7px)   rotate(2deg); }
  60%  { transform: rotateY(180deg) translate(46px,  5px)   rotate(-1deg); }
  64%  { transform: rotateY(180deg) translate(60px,  9px)   rotate(2deg); }
  /* Carriage return */
  67%  { transform: rotateY(180deg) translate(60px,  14px)  rotate(4deg); }
  70%  { transform: rotateY(180deg) translate(-66px, 24px)  rotate(-3deg); }
  /* Line 3 */
  75%  { transform: rotateY(180deg) translate(-40px, 26px)  rotate(1deg); }
  81%  { transform: rotateY(180deg) translate(-8px,  24px)  rotate(-1deg); }
  87%  { transform: rotateY(180deg) translate(28px,  26px)  rotate(2deg); }
  92%  { transform: rotateY(180deg) translate(58px,  25px)  rotate(-1deg); }
  /* Lift pen and swing back to start position */
  96%  { transform: rotateY(180deg) translate(20px,  -2px)  rotate(2deg); }
  100% { transform: rotateY(180deg) translate(-68px, -16px) rotate(-1deg); }
}

/* Pen toss on reveal */
.story-slot[data-state="revealing"] .glyph-stage {
  animation: pen-toss 320ms var(--ease-spring) forwards;
}
@keyframes pen-toss {
  0%   { transform: scale(1) translate(0,0) rotate(0deg); opacity: 1; }
  30%  { transform: scale(1.1) translate(8px,-14px) rotate(-12deg); opacity: 1; }
  100% { transform: scale(0.7) translate(80px,-90px) rotate(35deg); opacity: 0; }
}

/* Pen drop on failure */
.story-slot[data-state="failing"] .glyph-stage {
  animation: pen-drop 220ms var(--ease-out) forwards;
}
@keyframes pen-drop {
  0%   { transform: scale(1) translate(0,0) rotate(0deg); }
  100% { transform: scale(0.95) translate(0, 14px) rotate(8deg); }
}

/* Caption */
.caption {
  position: absolute;
  bottom: -6px;
  left: 0; right: 0;
  text-align: center;
  font-family: 'Sora', sans-serif;
  font-weight: 300;
  font-size: 12px;
  color: var(--color-ink-muted);
  opacity: 0;
  transition: opacity 240ms var(--ease-out);
}
.caption.is-shown { opacity: 1; }

/* Reduced-motion "writing" caption — used in place of pen */
.canvas-stage--reduced {
  display: none;
  font-family: 'Sora', sans-serif;
  font-weight: 300;
  font-size: 14px;
  color: var(--color-ink-muted);
  letter-spacing: 0.02em;
}
.canvas-stage--reduced .pulse-dot {
  display: inline-block;
  width: 6px; height: 6px;
  background: var(--color-amber);
  border-radius: 50%;
  margin-left: 8px;
  animation: pulse-dot 1400ms ease-in-out infinite;
}
@keyframes pulse-dot {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.35; }
}

/* ============= Reveal content (lives inside the canvas) =============
   Wrapped in .canvas-content so we can collapse its layout space to 0
   during writing — otherwise pull-quote + body paragraphs make the
   canvas balloon to ~500px even though they're opacity:0. */
.canvas-content {
  max-height: 0;
  overflow: hidden;
  opacity: 0;
  transition: max-height 520ms var(--ease-out), opacity 240ms var(--ease-out);
}
.story-slot[data-state="revealing"] .canvas-content,
.story-slot[data-state="settled"] .canvas-content {
  max-height: 1400px;
  opacity: 1;
}

.pull-quote {
  font-family: 'Fraunces', serif;
  font-style: italic;
  font-weight: 700;
  font-size: 22px;
  line-height: 1.25;
  letter-spacing: -0.01em;
  color: var(--color-ink);
  margin: 0 0 18px 0;
  opacity: 0;
  transform: scale(0.92);
  transition: opacity 280ms var(--ease-out), transform 280ms var(--ease-spring);
}
.pull-quote.is-shown { opacity: 1; transform: scale(1); }

/* Cross-file overrides — narrative.css and item_detail.css both define
   a `.pull-quote` rule with an amber `border-left` (renders as a 2nd
   gold bar inside the story slot, parallel to the slot's own rail)
   and narrative.css gives `.pull-quote__text` `color: var(--color-ivory)`
   (invisible on the cream slot background). grail_story.css declares
   only typography + opacity for `.pull-quote`, so the border + the
   ivory text color flow through. Specificity here is (0,0,2,0) — one
   higher than the conflicting plain `.pull-quote` rules — so these
   neutralize the cross-file leakage within the story slot only. */
.story-slot .pull-quote {
  border: 0;
  padding: 0;
}
.story-slot .pull-quote__text {
  color: var(--color-ink);
  margin: 0;
}

.story-body { position: relative; }
.story-body p {
  font-family: 'Sora', sans-serif;
  font-weight: 300;
  font-size: 15px;
  line-height: 1.55;
  color: var(--color-ink);
  margin: 0 0 12px 0;
  opacity: 0;
  transform: translateY(8px);
  transition: opacity 280ms var(--ease-out), transform 280ms var(--ease-out);
}
.story-body p.is-shown { opacity: 1; transform: none; }

/* Regenerate as a tiny icon button, right-aligned in a footer row */
.canvas-content__footer {
  display: flex;
  justify-content: flex-end;
  padding-top: 4px;
}
.regen-icon {
  width: 30px;
  height: 30px;
  border-radius: 50%;
  background: transparent;
  border: 1px solid rgba(26,13,8,0.14);
  color: var(--color-ink-muted);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  opacity: 0;
  padding: 0;
  transition: opacity 260ms var(--ease-out), border-color 200ms var(--ease-out), color 200ms var(--ease-out);
}
.regen-icon.is-shown { opacity: 1; }
.regen-icon:hover { border-color: var(--color-ink); color: var(--color-ink); }
.regen-icon:focus-visible {
  outline: 2px solid var(--color-red);
  outline-offset: 2px;
}
.regen-icon svg { width: 13px; height: 13px; }

/* ============= Reduced motion ============= */
/* The proper way: kill the writing animations entirely AND swap the canvas
   stage to a static "Writing your story…" caption with a slow pulsing dot. */
.story-slot[data-reduced="true"] .canvas-stage { display: none; }
.story-slot[data-reduced="true"] .canvas-stage--reduced { display: flex; align-items: center; justify-content: center; height: 80px; }
/* Collapse the reduced-motion stage AFTER reveal — without these, the empty 80px
   slot persists above the story content in settled state. */
.story-slot[data-reduced="true"][data-state="revealing"] .canvas-stage--reduced,
.story-slot[data-reduced="true"][data-state="settled"] .canvas-stage--reduced { display: none; }
.story-slot[data-reduced="true"] .glyph-stage,
.story-slot[data-reduced="true"] .ink-lines { display: none; }
.story-slot[data-reduced="true"] .pull-quote { transition: opacity 200ms linear; transform: none; }
.story-slot[data-reduced="true"] .story-body p { transition: opacity 200ms linear; transform: none; }
.story-slot[data-reduced="true"] .story-canvas { transition: opacity 160ms linear, background 200ms linear, border-color 200ms linear; transform: none; }

/* Grailify button: also kill the processing pulse + rename-pulse keyframe + confetti under reduced motion */
.reduced-motion-on .badge--grail,
.reduced-motion-on .badge--grail.is-rename-pulsing { animation: none !important; transform: none !important; }
.reduced-motion-on .grailify-confetti { display: none !important; }

@media (prefers-reduced-motion: reduce) {
  /* Mirror the data-reduced=true selectors so the system preference wins on its own */
  .story-slot .canvas-stage { display: none; }
  .story-slot .canvas-stage--reduced { display: flex; align-items: center; justify-content: center; height: 80px; }
  /* Same collapse rule for OS-level prefers-reduced-motion */
  .story-slot[data-state="revealing"] .canvas-stage--reduced,
  .story-slot[data-state="settled"] .canvas-stage--reduced { display: none; }
  .story-slot .glyph-stage,
  .story-slot .ink-lines { display: none; }
  .story-slot .pull-quote { transition: opacity 200ms linear; transform: none; }
  .story-slot .story-body p { transition: opacity 200ms linear; transform: none; }
  .pulse-dot { animation: none; opacity: 0.6; }
  .badge--grail, .badge--grail.is-rename-pulsing { animation: none !important; transform: none !important; }
  .grailify-confetti { display: none !important; }
}
