/* ============================================================================
   view_transitions.css — View Transitions API styling for Swiss Vault app.
   ----------------------------------------------------------------------------
   Day 3C Land 2. When HTMX swaps a fragment with ``hx-swap="... transition:true"``,
   modern browsers (Chromium 111+, Safari 18+) wrap the swap in the View
   Transitions API and animate between the old and new states via the
   ``::view-transition-*`` pseudo-elements. Firefox <126 ignores the opt-in
   entirely and swaps instantly — no CSS polyfill, no JS shim.

   Scoping contract
   ----------------
   Every rule is nested inside ``body.app-mode`` so it only applies on the
   live server (where ``{% block body_class %}app-mode{% endblock %}`` is
   set on every page that extends _base.html.j2 with the app chrome). The
   static catalog renderer in ``ui_foundation/build.py`` does NOT emit
   ``app-mode`` on the body, so the pixel-diff gate sees zero animation
   CSS effect — HTML output is identical, CSS computed values are identical.

   Rhythm + easing
   ---------------
   Concept D Premium Warm is a vellum palette with subtle movement.
   Animations mirror that restraint: 180 ms, cubic-bezier tuned for
   "authoritative ease-out" rather than Material's bouncy spring.
============================================================================ */

body.app-mode {
  /* Honour user motion preferences — zero duration for reduced-motion. */
  --view-transition-duration: 180ms;
  --view-transition-easing: cubic-bezier(0.22, 0.61, 0.36, 1);
}

@media (prefers-reduced-motion: reduce) {
  body.app-mode {
    --view-transition-duration: 0ms;
  }
}

/* ---------------------------------------------------------------------- *
 *  Root-level transition — covers full-page navigation and any element   *
 *  not explicitly named with ``view-transition-name``.                    *
 * ---------------------------------------------------------------------- */

body.app-mode::view-transition-old(root),
body.app-mode::view-transition-new(root) {
  animation-duration: var(--view-transition-duration);
  animation-timing-function: var(--view-transition-easing);
  animation-fill-mode: both;
}

body.app-mode::view-transition-old(root) {
  animation-name: swiss-fade-out;
}

body.app-mode::view-transition-new(root) {
  animation-name: swiss-fade-in;
}

/* ---------------------------------------------------------------------- *
 *  Review card swaps (approve / regenerate / dismiss) — slot-named so    *
 *  the specific card transitions independently of the page chrome.       *
 * ---------------------------------------------------------------------- */

body.app-mode .review[style*="view-transition-name"]::view-transition-old(*),
body.app-mode .review[style*="view-transition-name"]::view-transition-new(*) {
  animation-duration: var(--view-transition-duration);
  animation-timing-function: var(--view-transition-easing);
}

/* Timeline event rows prepended via sse-swap="timeline.new" — slide in
   from the top while the rest of the list drops by one row. */
body.app-mode #timeline-days::view-transition-new(timeline-row) {
  animation: swiss-slide-down-in var(--view-transition-duration)
             var(--view-transition-easing) both;
}

/* ---------------------------------------------------------------------- *
 *  Keyframes                                                             *
 * ---------------------------------------------------------------------- */

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

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

@keyframes swiss-slide-down-in {
  from {
    opacity: 0;
    transform: translateY(-6px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

/* ---------------------------------------------------------------------- *
 *  Day 5 — Aria Live Pulse                                                *
 *  Typewriter cursor: a soft blinking caret appended to the draft-stream  *
 *  while tokens are arriving. Once the .done event swaps the span for a   *
 *  <textarea>, the cursor disappears automatically (the span is gone).    *
 *  Scoped under .app-mode to keep the pixel-diff catalog unaffected.       *
 * ---------------------------------------------------------------------- */

body.app-mode .draft-stream::after {
  content: "";
  display: inline-block;
  width: 2px;
  height: 1.1em;
  margin-left: 1px;
  vertical-align: -0.15em;
  background: currentColor;
  opacity: 0.55;
  animation: swiss-caret-blink 1.0s steps(2, end) infinite;
}

@keyframes swiss-caret-blink {
  0%, 55% { opacity: 0.55; }
  55.01%, 100% { opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
  body.app-mode .draft-stream::after {
    animation: none;
    opacity: 0.4;
  }
}

/* Textarea that swaps in after the ``.done`` event — subtle premium
   surface instead of the default browser look. */
body.app-mode .draft-textarea {
  width: 100%;
  min-height: 5.5rem;
  padding: 0.5rem 0.6rem;
  font: inherit;
  color: inherit;
  background: color-mix(in srgb, var(--ivory, #fdfaf4) 70%, transparent);
  border: 1px solid var(--hair, rgba(0, 0, 0, 0.08));
  border-radius: 4px;
  resize: vertical;
  line-height: 1.5;
}

body.app-mode .draft-textarea:focus {
  outline: none;
  border-color: color-mix(in srgb, var(--sage, #6b8a6b) 65%, transparent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--sage, #6b8a6b) 18%, transparent);
}
