/* ══════════════════════════════════════════════════════════════
   INTERACTIVE MODULE — Residual scoped styles
   Only rules that cannot be expressed as Tailwind utilities live
   here. All structural layout, spacing, and typography has moved
   to Tailwind classes in the partials.

   What remains:
   · @keyframes animations
   · 3D transform / backface-visibility (flip cards)
   · clip-path (before/after slider — set dynamically by JS)
   · .module-shell shell structural rules (aspect ratio, complex shadow)
   · JS-targeted state classes (quiz-feedback, is-matched, etc.)
   · Stagger animation system
   · CSS-only interaction patterns (details/summary, tab active state)
   · hotspot pulse animation
══════════════════════════════════════════════════════════════ */

/* ── Slide area ─────────────────────────────────────────────── */

.module-shell .slide.active {
  display: flex;
  flex-direction: column;
  animation: m-slideEnter 0.45s cubic-bezier(0.16,1,0.3,1) both;
}

.module-shell .slide-inner {
  flex: 1;
  padding: 28px 36px;
  overflow-y: auto;
  scrollbar-width: thin;
  scrollbar-color: var(--color-light-dark) transparent;
  position: relative;
}

/* ── Reveal button + overlay panel ───────────────────────────────
   Shared by every slide type that offers a reveal — text, text_image,
   two_column/matrix, highlights (see _reveal.html.erb). Deliberately an
   overlay rather than expanding inline: inline growth inside the
   fixed-aspect interactive shell risks pushing content past the shell's
   height and triggering .slide-inner's own overflow-y: auto — the exact
   class of bug the section/takeaway block-context overflow fix already
   had to work around once. An overlay never adds height, so it can't
   trigger that regardless of how much reveal text an author writes.

   Built on the native Popover API (popover/popovertarget), not
   <details> — a popover renders in the browser's top layer, entirely
   outside the normal containing-block hierarchy, so a transformed
   ancestor (e.g. .stagger's entrance animation, which leaves transform:
   translateY(0) permanently applied via animation-fill-mode: forwards)
   can't hijack its position. That's exactly what was happening with the
   <details>-based version: every wiring site nests the reveal inside a
   .stagger container, and position: absolute/fixed both resolve their
   containing block against the nearest transformed ancestor regardless
   of <details> itself. The popover mechanism sidesteps that at the
   platform level, which is also why one rule now works for both the
   interactive shell and block-lesson context — no more separate
   block-content-context override needed, see block_content.css's
   history if picking this apart later.

   Most of the panel's visual styling lives as Tailwind utility classes
   directly on the element in _reveal.html.erb — this file only holds
   what Tailwind can't express: the @starting-style entrance transition
   (a CSS at-rule, not a static utility) needs a stable class to hook
   onto, hence .reveal-panel still exists as a bare selector here even
   though it no longer carries any properties of its own. */

.reveal-trigger {
  border-color: var(--reveal-color);
  color: var(--reveal-color);
}

.reveal-trigger:hover {
  background: var(--reveal-color);
  color: #fff;
}


/* Entrance fade — the state it transitions FROM the moment it becomes
   visible. No matching exit transition: animating OUT of display: none
   needs transition-behavior: allow-discrete, support for which is recent
   enough that it's not worth the added complexity here — closing snaps
   away instantly, which reads fine for a dismiss action. */
@starting-style {
  .reveal-panel:popover-open {
    opacity: 0;
    transform: translate(-50%, calc(-50% + 16px)) scale(0.97);
  }
}

/* ── Typography ─────────────────────────────────────────────── */

.module-shell .slide-title {
  font-family: var(--font-heading);
  font-size: 1.45rem;
  font-weight: 700;
  color: var(--color-dark);
  line-height: 1.2;
  margin-bottom: 20px;
}

.module-shell .slide-subtitle {
  font-size: 1rem;
  color: var(--color-dark);
  margin-bottom: 16px;
  font-weight: 500;
  line-height: 1.35;
}

/* ── Long text slide — scale prose for 16:9 shell ──────────── */

.module-shell .prose {
  font-size: 0.875rem; /* = text-sm, matches other slide body text */
}

.module-shell .prose h2 {
  font-size: 1.125rem;
}

.module-shell .prose h3 {
  font-size: 1rem;
}

/* ── Animations ─────────────────────────────────────────────── */

@keyframes m-slideEnter {
  from { opacity: 0; transform: translateY(14px) scale(0.99); }
  to   { opacity: 1; transform: translateY(0) scale(1); }
}

@keyframes m-fadeIn { from { opacity: 0; } to { opacity: 1; } }

@keyframes m-slideUp {
  from { opacity: 0; transform: translateY(30px); }
  to   { opacity: 1; transform: translateY(0); }
}

@keyframes m-shake {
  0%, 100% { transform: translateX(0); }
  25%       { transform: translateX(-6px); }
  75%       { transform: translateX(6px); }
}

@keyframes m-hotPulse {
  0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--color-accent) 50%, transparent); }
  50%       { box-shadow: 0 0 0 10px transparent; }
}

.module-shell .stagger {
  opacity: 0;
  animation: m-slideUp 0.55s cubic-bezier(0.16,1,0.3,1) forwards;
}
.module-shell .d1 { animation-delay: 0.05s; }
.module-shell .d2 { animation-delay: 0.10s; }
.module-shell .d3 { animation-delay: 0.18s; }
.module-shell .d4 { animation-delay: 0.26s; }
.module-shell .d5 { animation-delay: 0.34s; }

/* Same entrance timing as .stagger, opacity only — no transform, so it
   never becomes a containing block for a position: absolute descendant
   (which was the actual bug: .stagger's animation-fill-mode: forwards
   leaves transform: translateY(0) permanently applied after it finishes,
   not the same as no transform at all — and ANY non-"none" transform on
   an ancestor hijacks position: absolute descendants regardless of
   position: relative. That's a hard CSS rule, not something an
   !important override can reliably out-cascade if anything about the
   fix's application is in doubt, so this removes the cause directly
   instead: used on the specific containers that hold a reveal button —
   see _text.html.erb, _text_image.html.erb, _two_column.html.erb,
   _matrix.html.erb, _highlights.html.erb — in place of .stagger. The d1-d5
   delay classes are shared with .stagger, nothing else to change. */

.module-shell .stagger-fade {
  opacity: 0;
  animation: m-fadeIn 0.55s cubic-bezier(0.16,1,0.3,1) forwards;
}

/* ── Static preview — the slide editor's Preview tab and grid thumbnails
   are both non-interactive read-only renders of a module-shell slide.

   1. Suppresses the stagger fade-in, which otherwise just replays
      distractingly on every re-render (every preview refresh; a whole
      grid of cards mounting as the author scrolls) rather than reading
      as a one-time reveal the way it does in the actual player.

   2. Recreates the real shell's flex-column height context. In the live
      player, .module-shell > ... > .slide.active is display:flex with a
      real bounded height (the shell's own aspect-video), which is what
      lets a slide's flex-1 children (.slide-inner, .intro-slide,
      .takeaway-slide, and carousel/before-after/video-embed/hotspot-area's
      own internal flex-1 chains) actually fill real space instead of
      collapsing to their content's natural height. static-preview never
      had that ancestor, so anything depending on flex-1 fell back to
      minimum height — carousel cards not showing, peek mode losing its
      partial-card scroll, intro/takeaway rendering as a compact block
      instead of centred and full-bleed. display:flex here is that same
      mechanism; the actual height comes from the caller — grid thumbnails
      already sit inside a fixed 1024x576 canvas (h-full picks that up for
      free), the Preview tab adds aspect-video itself for the same effect.
   ── */
.module-shell.static-preview {
  display: flex;
  flex-direction: column;
}

.module-shell.static-preview .stagger {
  opacity: 1;
  animation: none;
}

/* ── Hotspot pulse ──────────────────────────────────────────── */

.module-shell .hotspot-btn {
  animation: m-hotPulse 2.5s infinite;
}

.module-shell .hs-tooltip.show {
  display: block;
  animation: m-fadeIn 0.2s ease;
}

/* ── Flip card 3D ───────────────────────────────────────────── */

.module-shell .flip-card {
  perspective: 1000px;
}

.module-shell .flip-inner {
  position: relative;
  width: 100%;
  height: 100%;
  min-height: 120px;
  transition: transform 0.6s;
  transform-style: preserve-3d;
}

.module-shell .flip-card.flipped .flip-inner {
  transform: rotateY(180deg);
}

/* ── Before/after slider ────────────────────────────────────── */
/* clip-path is set dynamically by JS — stub here for clarity */

.module-shell .comp-after {
  clip-path: polygon(50% 0, 100% 0, 100% 100%, 50% 100%);
}

/* ── Accordion details/summary smooth open/close ───────────────
   <details> has no built-in animation — its native behaviour is to just
   set display:none on everything but the summary when closed, an
   instant show/hide with nothing to transition. Overriding that default
   (forcing the wrapper to always render) and animating
   grid-template-rows from 0fr to 1fr instead gives a smooth transition
   without needing a guessed max-height pixel value, which is the usual
   workaround and doesn't interpolate as cleanly. Not scoped to
   .module-shell — this needs to work in block-lesson context too. %>

details.acc-item .acc-body-wrapper,
details.acc-pill-item .acc-body-wrapper {
  display: grid;
  grid-template-rows: 0fr;
  opacity: 0;
  overflow: hidden;
  transition: grid-template-rows 300ms ease-in-out, opacity 200ms ease-in-out;
}

details.acc-item[open] .acc-body-wrapper,
details.acc-pill-item[open] .acc-body-wrapper {
  grid-template-rows: 1fr;
  opacity: 1;
}

.acc-body-wrapper > div {
  min-height: 0;
}

.module-shell details[open] .acc-trigger {
  background: var(--color-highlight);
  color: var(--color-accent-dark);
}

.module-shell details[open] .acc-icon {
  transform: rotate(180deg);
}

/* ── Accordion pills layout icon swap ──────────────────────────
   Explicit svg.classname selectors, not Tailwind's group-open:hidden
   directly on the svg — the same lesson learned from the reveal button's
   icon swap: a global svg[viewBox="0 0 24 24"] rule elsewhere sets
   display:inline-flex with higher specificity than a single utility
   class, so the "hide by default" rule needs enough specificity of its
   own to reliably win regardless of source order. */

.acc-pill-trigger svg.acc-pill-icon-close {
  display: none;
}

.acc-pill-item[open] .acc-pill-trigger svg.acc-pill-icon-plus {
  display: none;
}

.acc-pill-item[open] .acc-pill-trigger svg.acc-pill-icon-close {
  display: block;
}

/* ── Tabs active state (set by tabs Stimulus controller) ────── */

.module-shell .tab-btn.active {
  color: var(--color-accent);
  border-bottom-color: var(--color-accent);
}

.module-shell .tab-pane.active {
  display: block;
  animation: m-fadeIn 0.3s ease;
}

/* ── Quiz / matching / drag-drop JS state classes ───────────── */

.module-shell .quiz-opt.correct   { background: var(--color-success-light); border-color: var(--color-success); color: var(--color-success); }
.module-shell .quiz-opt.incorrect { background: var(--color-error-light); border-color: var(--color-error); color: var(--color-error); }

.module-shell .quiz-feedback.show    { display: block; }
.module-shell .quiz-feedback.success { background: var(--color-success-light); color: var(--color-success); }
.module-shell .quiz-feedback.error   { background: var(--color-error-light); color: var(--color-error); }

.module-shell .matching-item.is-selected {
  border-color: var(--color-accent);
  background: var(--color-highlight);
  color: var(--color-accent-dark);
  box-shadow: 0 0 0 3px var(--color-highlight);
}

.module-shell .matching-item.is-selected .matching-connector {
  background: var(--color-accent);
}

.module-shell .matching-item.is-matched {
  border-color: var(--color-success);
  background: var(--color-success-light);
  color: var(--color-success);
  cursor: default;
}

.module-shell .matching-item.is-matched .matching-connector {
  background: var(--color-success);
}

.module-shell .matching-item.is-wrong {
  border-color: var(--color-error);
  background: var(--color-error-light);
  animation: m-shake 0.4s ease;
}

.module-shell .correct-placed {
  background: var(--color-success-light);
  border-color: var(--color-success);
  color: var(--color-success-dark);
  cursor: default;
}

.module-shell .incorrect-placed {
  border-color: var(--color-error);
  animation: m-shake 0.4s ease;
}

/* ── True/false JS state classes ────────────────────────────── */

.module-shell .tf-btn--correct {
  border-color: var(--color-success) !important;
  background: var(--color-success-light) !important;
  color: var(--color-success) !important;
}

.module-shell .tf-btn--wrong {
  border-color: var(--color-error) !important;
  background: var(--color-error-light) !important;
  color: var(--color-error) !important;
  animation: m-shake 0.4s ease;
}

/* ── Slide menu overlay ─────────────────────────────────────── */

.module-shell .slide-menu.open {
  display: flex;
  animation: m-fadeIn 0.25s ease;
}

/* ── Slide menu item states ─────────────────────────────────── */


.module-shell .slide-menu-item.current {
  background: var(--color-accent);
  border-color: var(--color-accent);
  color: white;
}

/* ── Modal open state ───────────────────────────────────────── */

.module-shell .ff-modal.open {
  display: flex;
  animation: m-fadeIn 0.2s ease;
}

/* ── Objectives stagger (index-based delay set inline) ──────── */

.module-shell .objectives-item {
  opacity: 0;
  animation: m-slideUp 0.55s cubic-bezier(0.16,1,0.3,1) forwards;
}

/* ── Footer nav JS state ────────────────────────────────────── */

.module-shell .slide-dot.active {
  width: 18px;
  border-radius: 100px;
  background: var(--color-accent);
}