/* Auto-generated by scripts/extract-broadside-runtime.py midnight-purple — DO NOT hand-edit.
 * Source: html_gen_prompt.py _MIDNIGHT_PURPLE_EXTRA_TOKENS + _MIDNIGHT_PURPLE_STAT_CARD_CSS
 * Loaded by editor.html so editor + viewer render midnight-purple decks
 * even when the saved markup lost the prompt-injected <style> block.
 * (Rainy 2026-05-29 — AI gen for these 4 templates dropped prompt CSS.)
 */

[data-template="midnight-purple"] {

  /* ══════════════════════════════════════════════════════════════
     midnight-purple v2 design system — 1:1 from design-specs/midnight-purple.html
     ══════════════════════════════════════════════════════════════ */

  /* ── FONT FAMILIES ── */
  --font-heading: 'Segoe UI', 'Calibri', 'PingFang SC', 'Microsoft YaHei', system-ui, sans-serif !important;
  --font-body: 'Segoe UI', 'Calibri', 'PingFang SC', 'Microsoft YaHei', system-ui, sans-serif !important;
  --font-mono: 'JetBrains Mono', 'SF Mono', 'Cascadia Code', Consolas, monospace !important;

  /* ── COLOR · SLIDE TOKENS ── */
  --bg-deep: #100D1E !important;
  --bg-deeper: #0A0814 !important;
  --accent-purple: #7B4EFF !important;
  --accent-teal: #00D4BE !important;
  --card-fill: #000000 !important;
  --line: #FFFFFF !important;
  --muted-1: #BFBFBF !important;
  --muted-2: #D9D9D9 !important;
  --soft: #BBB8DD !important;

  /* ── TYPOGRAPHY · 10-tier scale (Section B, design-specs/midnight-purple.html) ── */
  /* Cover Hero — 0 Cover, F Closing, D Chapter title (1–2 words at 120px) */
  --fs-cover-h1: 120px !important; --fw-cover-h1: 700 !important; --lh-cover-h1: 1.1 !important; --ls-cover-h1: -1px !important;
  --fs-cover-sub: 40px !important; --fw-cover-sub: 400 !important; --lh-cover-sub: 1.2 !important; --ls-cover-sub: 0.3px !important;
  /* H1 Page Hero — E3 / E4 / E5 / E6 / E7 hero titles */
  --fs-hero: 80px !important; --fw-hero: 600 !important; --lh-hero: 1.1 !important; --ls-hero: -1px !important;
  /* H2 Section Heading — E1 / E2 / E8 centered, E5 numerals */
  --fs-h2: 52px !important; --fw-h2: 600 !important; --lh-h2: 1.1 !important; --ls-h2: -0.5px !important;
  /* H3 Tertiary / Stat — E2 73%, E7 90/55, E5 ghost word */
  --fs-h3: 38px !important; --fw-h3: 600 !important; --lh-h3: 1.1 !important; --ls-h3: -0.5px !important;
  /* H4 Card Title — E1 card title */
  --fs-h4: 30px !important; --fw-h4: 600 !important; --lh-h4: 1.2 !important; --ls-h4: -0.3px !important;
  /* Card Stat / Title (E1 dual rule) */
  --fs-card-stat-num: 80px !important; --fw-card-stat-num: 700 !important; --ls-card-stat-num: -2px !important;
  --fs-card-stat-text: 52px !important; --fw-card-stat-text: 700 !important; --ls-card-stat-text: -1px !important;
  /* Subhead — Section Label, E4 bullet items */
  --fs-subhead: 22px !important; --fw-subhead: 700 !important; --ls-subhead: 2.5px !important;
  /* Body — every layout's body paragraph */
  --fs-body: 26px !important; --fw-body: 400 !important; --lh-body: 1.5 !important;
  /* Caption — small inline labels (E2 "Heading" inside cards) */
  --fs-caption: 22px !important; --fw-caption: 600 !important; --lh-caption: 1.4 !important; --ls-caption: 1.5px !important;
  /* Micro · Mono — tags, eyebrows, JetBrains Mono metadata */
  --fs-mono: 22px !important; --fw-mono: 500 !important;
  /* Legacy aliases for older selectors (do not remove — generated decks rely on these) */
  --fs-section: 80px !important; --fw-section: 600 !important; --lh-section: 1.1 !important;
  --fs-stat: 120px !important; --fw-stat: 700 !important; --lh-stat: 1 !important;
  --fs-stat-md: 80px !important; --fw-stat-md: 700 !important;
  --fs-stat-sm: 52px !important; --fw-stat-sm: 600 !important;
  --fs-card-body: 26px !important; --fw-card-body: 400 !important; --lh-card-body: 1.5 !important;
  --fs-card-label: 22px !important; --fw-card-label: 700 !important; --ls-card-label: 2.5px !important;
  --fs-tag: 18px !important; --fw-tag: 400 !important; --ls-tag: 0 !important;
  --fs-numeral: 52px !important; --fw-numeral: 600 !important; --lh-numeral: 1 !important;
  --ls-numeral: -0.5px !important;

  /* ── SPACING ── */
  --sp-4: 4px !important; --sp-8: 8px !important; --sp-12: 12px !important; --sp-16: 16px !important;
  --sp-20: 20px !important; --sp-24: 24px !important; --sp-32: 32px !important; --sp-40: 40px !important;
  --sp-48: 48px !important; --sp-60: 60px !important; --sp-80: 80px !important;

  /* ── BORDER RADIUS ── */
  --radius-sm: 8px !important; --radius-md: 16px !important; --radius-lg: 24px !important; --radius-xl: 32px !important;
}



/* ─── 1) SECTION FRAME (1920×1080 dark stage) ─── */
section.slide[data-template="midnight-purple"] {
  position: relative !important;
  width: 1920px !important;
  height: 1080px !important;
  background: var(--bg-deep) !important;
  color: #fff !important;
  overflow: hidden !important;
  font-family: var(--font-heading) !important;
  padding: 0 !important;
}

/* ─── 2) COVER / CLOSING — black stage + breathing orbs (Section 0 / F)
       Values 1:1 from design-specs/midnight-purple.html .cover-stage rules ─── */
[data-template="midnight-purple"] .cover-stage {
  position: absolute !important; inset: 0 !important;
  background: #000 !important;
  overflow: hidden !important;
  font-family: 'Segoe UI', Calibri, sans-serif !important;
}
[data-template="midnight-purple"] .cover-stage .bg {
  position: absolute !important; inset: 0 !important;
  background: url('midnight-cover-bg.png') center/cover no-repeat !important;
}
[data-template="midnight-purple"] .cover-stage .orb,
[data-template="midnight-purple"] .orb {
  position: absolute !important; border-radius: 50% !important;
  mix-blend-mode: screen !important;
  animation: mpBreathe var(--dur, 4s) ease-in-out infinite !important;
  animation-delay: var(--delay, 0s) !important;
  transform-origin: center !important;
  pointer-events: none !important;
}
[data-template="midnight-purple"] .cover-stage .orb1 {
  width: 390px !important; height: 720px !important;
  background: radial-gradient(ellipse at 60% 30%, #a0eeff 0%, #4488ff 30%, #6633cc 65%, transparent 100%) !important;
  top: 90px !important; left: -90px !important;
  filter: blur(42px) !important;
  opacity: 0.75 !important;
  --dur: 5.2s !important; --delay: 0s !important;
}
[data-template="midnight-purple"] .cover-stage .orb2 {
  width: 930px !important; height: 930px !important;
  background: radial-gradient(ellipse at 40% 35%, #88aaff 0%, #cc44ff 35%, #6622cc 60%, #330088 80%, transparent 100%) !important;
  top: 45px !important; right: -120px !important;
  filter: blur(48px) !important;
  opacity: 0.8 !important;
  --dur: 6.8s !important; --delay: -2.1s !important;
}
[data-template="midnight-purple"] .cover-stage .orb3 {
  width: 570px !important; height: 540px !important;
  background: radial-gradient(ellipse at 50% 50%, #ff66bb55 0%, #8844ff44 50%, transparent 80%) !important;
  top: 300px !important; left: 450px !important;
  filter: blur(75px) !important;
  opacity: 0.6 !important;
  --dur: 8s !important; --delay: -3.5s !important;
}
[data-template="midnight-purple"] .cover-stage .dark-overlay {
  position: absolute !important; inset: 0 !important;
  background: linear-gradient(135deg, rgba(5,3,20,0.55) 0%, rgba(5,3,20,0.2) 60%, rgba(5,3,20,0.45) 100%) !important;
  pointer-events: none !important;
}

/* AI-gen cover orb tuning — keeps thumbnail/sample deck untouched.
   Same orb design but ~70% scale + larger blur + lower opacity, so the
   editor full-size render reads as subtle/refined instead of the 930px
   orb2 dominating half the slide. (Rainy 2026-06-19: editor 大图 orb 太
   "粗糙", should match dashboard thumbnail's refined feel.) */
[data-template="midnight-purple"] .cover-stage .orb.orb--ai-gen.orb1 {
  width: 280px !important; height: 500px !important;
  top: 140px !important; left: -50px !important;
  filter: blur(55px) !important;
  opacity: 0.6 !important;
}
[data-template="midnight-purple"] .cover-stage .orb.orb--ai-gen.orb2 {
  width: 520px !important; height: 520px !important;
  top: 130px !important; right: -60px !important;
  filter: blur(70px) !important;
  opacity: 0.7 !important;
}
[data-template="midnight-purple"] .cover-stage .orb.orb--ai-gen.orb3 {
  width: 340px !important; height: 320px !important;
  top: 380px !important; left: 520px !important;
  filter: blur(85px) !important;
  opacity: 0.48 !important;
}
[data-template="midnight-purple"] .cover-stage .content {
  position: absolute !important; left: 132px !important; top: 50% !important;
  transform: translateY(-50%) !important;
  z-index: 10 !important;
  animation: mpFadeUp 1.4s cubic-bezier(0.22,1,0.36,1) both !important;
}
[data-template="midnight-purple"] .cover-stage .content h1,
[data-template="midnight-purple"] .cover-stage h1 {
  font-size: 120px !important; font-weight: 700 !important;
  color: #fff !important;
  letter-spacing: -1.5px !important;
  line-height: 1.1 !important;
  margin: 0 0 33px !important;
  text-shadow: 0 0 90px rgba(150,100,255,0.5), 0 3px 30px rgba(0,0,0,0.5) !important;
  animation: mpFadeUp 1.4s 0.2s cubic-bezier(0.22,1,0.36,1) both !important;
}
[data-template="midnight-purple"] .cover-stage .subtitle {
  font-size: 40px !important; color: #bbb8dd !important;
  margin-bottom: 54px !important; font-weight: 400 !important;
  letter-spacing: 0.3px !important;
  animation: mpFadeUp 1.4s 0.45s cubic-bezier(0.22,1,0.36,1) both !important;
}
[data-template="midnight-purple"] .cover-stage .line-wrap {
  width: 510px !important; height: 6px !important; border-radius: 3px !important;
  background: linear-gradient(to right, #7B4EFF, #00D4BE) !important;
  overflow: hidden !important; position: relative !important;
  animation: mpFadeUp 1.4s 0.65s cubic-bezier(0.22,1,0.36,1) both !important;
}
[data-template="midnight-purple"] .cover-stage .line-sheen {
  position: absolute !important; top: 0 !important; left: -60% !important;
  width: 60% !important; height: 100% !important;
  background: linear-gradient(to right, transparent, rgba(255,255,255,0.75), transparent) !important;
  animation: mpSheen 3s 1.8s ease-in-out infinite !important;
}
/* Section F · closing — subtitle wrap-width is the only delta vs cover.
   Spec line 1364: "subtitle is constrained to a narrower max-width: 800px
   wrap so the closing reads as a more compact, restrained final beat." */
[data-template="midnight-purple"][data-layout="closing"] .cover-stage .subtitle {
  max-width: 800px !important;
}

/* ─── 3) HALO DECORATIONS — picked per layout ─── */
[data-template="midnight-purple"] .slide-halo-corner {
  position: absolute !important; inset: 0 !important;
  background:
    radial-gradient(ellipse 1100px 280px at 18% 105%, rgba(180,120,255,0.65) 0%, rgba(180,120,255,0.20) 35%, transparent 70%),
    radial-gradient(ellipse 800px 320px at 38% 110%, rgba(140,170,255,0.55) 0%, rgba(140,170,255,0.18) 35%, transparent 70%),
    radial-gradient(ellipse 1400px 260px at 60% 108%, rgba(180,120,255,0.50) 0%, rgba(180,120,255,0.16) 35%, transparent 70%),
    radial-gradient(ellipse 900px 300px at 88% 112%, rgba(255,102,187,0.55) 0%, rgba(255,102,187,0.18) 35%, transparent 70%);
  filter: blur(36px) !important; pointer-events: none !important; mix-blend-mode: screen !important;
}
[data-template="midnight-purple"] .slide-halo-diagonal {
  position: absolute !important; inset: 0 !important;
  background:
    radial-gradient(ellipse 900px 600px at 12% 100%, rgba(255,102,187,0.32) 0%, rgba(255,102,187,0.10) 35%, transparent 65%),
    radial-gradient(ellipse 1100px 500px at 32% 105%, rgba(180,120,255,0.50) 0%, rgba(180,120,255,0.16) 35%, transparent 65%),
    radial-gradient(ellipse 800px 600px at 105% -5%, rgba(140,170,255,0.55) 0%, rgba(140,170,255,0.18) 35%, transparent 65%),
    radial-gradient(ellipse 1100px 480px at 80% 0%, rgba(180,120,255,0.55) 0%, rgba(180,120,255,0.18) 35%, transparent 65%);
  filter: blur(40px) !important; pointer-events: none !important; mix-blend-mode: screen !important;
}
[data-template="midnight-purple"] .slide-halo-top {
  position: absolute !important; inset: 0 !important;
  background:
    radial-gradient(ellipse 700px 420px at 38% -8%, rgba(140,170,255,0.45) 0%, rgba(140,170,255,0.14) 35%, transparent 65%),
    radial-gradient(ellipse 900px 480px at 60% -12%, rgba(180,120,255,0.55) 0%, rgba(180,120,255,0.18) 35%, transparent 65%),
    radial-gradient(ellipse 1000px 540px at 82% -10%, rgba(180,120,255,0.65) 0%, rgba(180,120,255,0.22) 35%, transparent 65%),
    radial-gradient(ellipse 700px 460px at 100% 5%, rgba(255,102,187,0.50) 0%, rgba(255,102,187,0.16) 38%, transparent 68%);
  filter: blur(44px) !important; pointer-events: none !important; mix-blend-mode: screen !important;
}
[data-template="midnight-purple"] .slide-halo-center {
  position: absolute !important; inset: 0 !important;
  background: radial-gradient(circle 1300px at -20% 125%, rgba(255,102,187,0.70) 0%, rgba(180,120,255,0.35) 28%, rgba(123,78,255,0.10) 55%, transparent 75%) !important;
  pointer-events: none !important; mix-blend-mode: screen !important;
}
[data-template="midnight-purple"] .slide-halo-wide {
  position: absolute !important; inset: 0 !important;
  background: radial-gradient(ellipse 1800px 600px at 50% -50%, rgba(140,170,255,0.65) 0%, rgba(180,120,255,0.38) 25%, rgba(255,102,187,0.14) 55%, transparent 78%) !important;
  pointer-events: none !important; mix-blend-mode: screen !important;
}

/* ─── 4) S-CARD — black-fill data card
       Position 默认 relative,让 grid container 里的 cards 正确按 grid flow 排列。
       E6/E7 layout 用 inline `style="position: absolute !important; left: Xpx !important; top: Ypx"` 会 !important;
       覆盖这条 — inline 优先级总比 CSS 高。
       (Rainy 2026-05-28 修: 原来是 absolute → grid items 全部脱离 flow 叠在 (0,0)) */
[data-template="midnight-purple"] .s-card {
  position: relative !important;
  background: var(--card-fill) !important;
  border: 2px solid var(--line) !important;
  border-radius: var(--radius-xl) !important;
  color: #fff !important;
  padding: 28px 32px !important;
  display: flex !important; flex-direction: column !important;
  z-index: 2 !important;
}
/* AI-gen lays cards out by inline absolute coords (e.g. style="left:1023px;
   top:611px;"). With the relative position above, those coords become
   offsets from each card's normal-flow position → cards stack vertically
   and the last one tumbles off the bottom of the slide. When a card has
   an inline left/top, it MUST be absolute; only the layout-grid cards
   (no inline coords) keep the relative default. (Rainy 2026-06-19: card
   位移严重, last card sat below slide canvas.) */
[data-template="midnight-purple"] .s-card[style*="left:"],
[data-template="midnight-purple"] .s-card[style*="top:"] {
  position: absolute !important;
}
[data-template="midnight-purple"] .s-card.accent {
  border: 4px solid var(--accent-purple) !important;
  border-radius: 31px !important;
}
[data-template="midnight-purple"] .s-card.teal {
  border-color: var(--accent-teal) !important;
}

/* ─── 5) S-CARD INNER ELEMENTS ─── */
[data-template="midnight-purple"] .s-num {
  font-size: var(--fs-stat-md) !important;
  font-weight: var(--fw-stat-md) !important;
  line-height: 1 !important;
  color: var(--accent-purple) !important;
}
[data-template="midnight-purple"] .s-card.teal .s-num { color: var(--accent-teal); }
[data-template="midnight-purple"] .s-stat-lg {
  font-size: var(--fs-stat) !important;
  font-weight: var(--fw-stat) !important;
  line-height: 1 !important;
  color: #fff !important;
}
[data-template="midnight-purple"] .s-stat-sm {
  font-size: var(--fs-stat-sm) !important;
  font-weight: var(--fw-stat-sm) !important;
  line-height: 1 !important;
  color: #fff !important;
}
[data-template="midnight-purple"] .s-label {
  font-size: var(--fs-card-label) !important;
  font-weight: var(--fw-card-label) !important;
  letter-spacing: var(--ls-card-label) !important;
  text-transform: uppercase !important;
  color: #fff !important;
  margin-top: 8px !important;
}
[data-template="midnight-purple"] .s-body {
  font-size: var(--fs-card-body) !important;
  font-weight: var(--fw-card-body) !important;
  line-height: var(--lh-card-body) !important;
  color: var(--muted-1) !important;
}
[data-template="midnight-purple"] .s-tag {
  display: inline-block !important;
  font-size: var(--fs-tag) !important;
  font-weight: var(--fw-tag) !important;
  background: var(--bg-deep) !important;
  border: 1px solid rgba(255,255,255,0.20) !important;
  border-radius: 9999px !important;
  padding: 6px 18px !important;
  color: var(--muted-1) !important;
}
[data-template="midnight-purple"] .s-progress {
  height: 4px !important;
  background: rgba(255,255,255,0.15) !important;
  border-radius: 2px !important;
  overflow: hidden !important;
  margin-top: auto !important;
}
[data-template="midnight-purple"] .s-progress > .fill {
  display: block !important;
  height: 100% !important;
  background: linear-gradient(to right, var(--accent-purple), var(--accent-teal)) !important;
}

/* ─── 6) SLIDE-LEVEL TITLE & BODY ELEMENTS ─── */
[data-template="midnight-purple"] .tmpl-hero-title {
  position: absolute !important;
  font-size: var(--fs-section) !important;
  font-weight: var(--fw-section) !important;
  line-height: var(--lh-section) !important;
  color: #fff !important;
  z-index: 3 !important;
}
[data-template="midnight-purple"] .tmpl-hero-title.xl {
  font-size: var(--fs-hero) !important;
}
[data-template="midnight-purple"] .tmpl-rule {
  position: absolute !important;
  height: 1px !important;
  background: rgba(255,255,255,0.5) !important;
  z-index: 3 !important;
}
[data-template="midnight-purple"] .tmpl-label {
  position: absolute !important;
  font-size: var(--fs-card-label) !important;
  font-weight: 600 !important;
  letter-spacing: 0.5px !important;
  text-transform: uppercase !important;
  color: var(--accent-teal) !important;
  z-index: 3 !important;
}
/* Inside a .s-card (flex column with items stacked), .tmpl-label /
   .tmpl-body MUST flow naturally — otherwise the default absolute
   positioning above pins them at (0,0) and they overlap with the
   stat number. (Rainy 2026-06-19: ADOBE SURVEY 2025 eyebrow叠在 86%
   数字上面.) Model already emits inline `position:relative` but the
   above !important wins; this scoped override fixes it. */
[data-template="midnight-purple"] .s-card .tmpl-label,
[data-template="midnight-purple"] .s-card .tmpl-body {
  position: relative !important;
  left: auto !important;
  top: auto !important;
  right: auto !important;
  bottom: auto !important;
  width: auto !important;
}
/* .s-chart-placeholder — chart slide canvas container per spec
   (design-specs/midnight-purple.html E8 section). Runtime CSS was
   missing this implementation, so chart slides rendered as bare
   <canvas> on the dark slide background with no bordered area.
   Now matches the other templates' chart containers (.s42-chart for
   warm-earth, .tmpl-img for gradient-bloom, .chart-host for biostate-
   dark). Subtle rounded rectangle that frames the chart without
   washing it out. Padding gives chart breathing room inside.
   (Rainy 2026-06-19.) */
[data-template="midnight-purple"] .s-chart-placeholder {
  position: absolute !important;
  background: rgba(255, 255, 255, 0.025) !important;
  border: 1px solid rgba(255, 255, 255, 0.08) !important;
  border-radius: 24px !important;
  padding: 36px !important;
  box-sizing: border-box !important;
  z-index: 2 !important;
}
[data-template="midnight-purple"] .s-chart-placeholder canvas {
  width: 100% !important;
  height: 100% !important;
  display: block !important;
}
[data-template="midnight-purple"] .s-page-title {
  position: absolute !important;
  color: #fff !important;
  font-family: var(--font-heading) !important;
  z-index: 3 !important;
}

/* Stat grids inside .s-card (model emits inline `display:grid`) —
   force equal row heights + top alignment so labels of varying
   length don't push the next row's stat-sm to a different visual
   position. (Rainy 2026-06-19: 4 数据卡片上下左右不对齐.) */
[data-template="midnight-purple"] .s-card div[style*="grid"] {
  align-items: start !important;
  grid-auto-rows: 1fr !important;
}
[data-template="midnight-purple"] .s-card div[style*="grid"] > div {
  display: flex !important;
  flex-direction: column !important;
  align-items: flex-start !important;
  justify-content: flex-start !important;
}
[data-template="midnight-purple"] .tmpl-body {
  position: absolute !important;
  font-size: var(--fs-body) !important;
  font-weight: var(--fw-body) !important;
  line-height: var(--lh-body) !important;
  color: var(--muted-1) !important;
  z-index: 3 !important;
}
[data-template="midnight-purple"] .tmpl-numeral {
  position: absolute !important;
  font-size: var(--fs-numeral) !important;
  font-weight: var(--fw-numeral) !important;
  line-height: 1 !important;
  color: #fff !important;
  z-index: 3 !important;
}
[data-template="midnight-purple"] .tmpl-ghost-word {
  position: absolute !important;
  font-size: 80px !important;
  font-weight: 400 !important;
  color: rgba(255,255,255,0.18) !important;
  z-index: 1 !important;
}
[data-template="midnight-purple"] .tmpl-img {
  position: absolute !important;
  border-radius: var(--radius-xl) !important;
  overflow: hidden !important;
  z-index: 2 !important;
}
[data-template="midnight-purple"] .tmpl-img > img,
[data-template="midnight-purple"] .tmpl-img > canvas {
  width: 100% !important; height: 100% !important;
  object-fit: cover !important;
  display: block !important;
}
[data-template="midnight-purple"] .tmpl-chart {
  position: absolute !important;
  background: rgba(0,0,0,0.4) !important;
  border: 2px dashed rgba(123,78,255,0.5) !important;
  border-radius: var(--radius-lg) !important;
  z-index: 2 !important;
}
/* When _fixOrphanCanvases (generate-modal.js Pass 1) adds .tmpl-img +
   .chart-host to a bare canvas parent (because the parent had chart-
   slot geometry but no class), the resulting class combo had no
   midnight-purple-specific border. Chart slides therefore rendered
   as naked canvases on dark slide bg with no visual frame. Mirror
   the .tmpl-chart border treatment so any midnight-purple chart slot
   — regardless of which class the model/Pass1 picked — gets the same
   purple dashed border + dark backdrop. (Rainy 2026-06-19.) */
[data-template="midnight-purple"] .chart-host,
[data-template="midnight-purple"] .tmpl-img.chart-host {
  background: rgba(0,0,0,0.4) !important;
  border: 2px dashed rgba(123,78,255,0.5) !important;
  border-radius: var(--radius-lg) !important;
}
[data-template="midnight-purple"] .tmpl-arrow {
  width: 64px !important; height: 64px !important;
  flex-shrink: 0 !important;
}
[data-template="midnight-purple"] .tmpl-arrow path {
  stroke: var(--accent-purple) !important;
  stroke-width: 4 !important;
  fill: none !important;
}

/* ─── 7) D · CHAPTER SEPARATOR
       Layout-specific (Section D, design-specs/midnight-purple.html). ─── */
[data-template="midnight-purple"][data-layout="D"] .d-left {
  position: absolute !important; left: 0 !important; top: 0 !important;
  width: 960px !important; height: 1080px !important;
  overflow: hidden !important;
  z-index: 1 !important;
}
[data-template="midnight-purple"][data-layout="D"] .d-left .cover-stage {
  position: absolute !important; left: 0 !important; top: 0 !important;
  width: 960px !important; height: 1080px !important;
  transform: scaleX(-1) !important; transform-origin: center !important;
}
[data-template="midnight-purple"] .d-divider {
  position: absolute !important; left: 957px !important; top: 60px !important;
  width: 5px !important; height: 960px !important;
  background: linear-gradient(to bottom, #00D4BE 0%, #7B4EFF 100%) !important;
  border-radius: 3px !important;
  z-index: 3 !important;
}
[data-template="midnight-purple"] .d-num {
  position: absolute !important; left: 1120px !important; top: 200px !important;
  font-size: 120px !important; font-weight: 800 !important;
  color: #fff !important;
  line-height: 1 !important; letter-spacing: -1px !important;
  z-index: 3 !important;
}
[data-template="midnight-purple"] .d-label {
  position: absolute !important; left: 1120px !important; top: 360px !important;
  font-family: 'JetBrains Mono', 'SF Mono', Consolas, monospace !important;
  font-size: 18px !important; color: #BFBFBF !important;
  letter-spacing: 6px !important; text-transform: uppercase !important;
  z-index: 3 !important;
}
[data-template="midnight-purple"] .d-title {
  position: absolute !important; left: 1120px !important; bottom: 120px !important; right: 80px !important;
  top: auto !important;
  font-size: 120px !important; font-weight: 700 !important;
  line-height: 1.1 !important; letter-spacing: -1px !important;
  color: #fff !important;
  margin: 0 !important;
  max-width: 720px !important;
  z-index: 3 !important;
}

/* ─── 8) E1 · 6-CARD GRID (Slide 21, corner halo)
       Cards 506×250 · 2px white stroke · rx 32 · accents rotate.
       Positions: x: 169/707/1245, y: 400/680. */
[data-template="midnight-purple"][data-layout="E1"] .s-card {
  border: 2px solid #fff !important;
  border-radius: 32px !important;
  padding: 36px 40px !important;
  justify-content: space-between !important;
}
[data-template="midnight-purple"][data-layout="E1"] .e1-progress {
  height: 8px !important;
  background: rgba(255,255,255,0.08) !important;
  border-radius: 4px !important;
  overflow: hidden !important;
}
[data-template="midnight-purple"][data-layout="E1"] .e1-progress > .fill {
  display: block !important; height: 100% !important;
  border-radius: 4px !important;
}

/* ─── 9) E2 · 3 ACCENT SQUARES (Slide 22, corner halo)
       Cards 321×321 · 4px purple stroke · rx 31. Positions x: 245/799/1353 y:322. */
[data-template="midnight-purple"][data-layout="E2"] .s-card {
  border: 4px solid #7B4EFF !important;
  border-radius: 31px !important;
  padding: 36px 36px !important;
  justify-content: flex-start !important;
}
[data-template="midnight-purple"][data-layout="E2"] .s-pill-tag {
  display: inline-block !important;
  padding: 8px 18px !important;
  font-size: 18px !important;
  color: #CFBAFF !important;
  background: #100D1E !important;
  border: 1px solid #1E1934 !important;
  border-radius: 9.5px !important;
}

/* ─── 10) E5 · NUMBERED SECTION LIST (Slide 25, wide halo)
       Hero 80px @ x:120 y:120. Two entries (01/02). */
[data-template="midnight-purple"][data-layout="E5"] .e5-rule {
  position: absolute !important;
  height: 1px !important;
  background: rgba(255,255,255,0.5) !important;
  width: 1680px !important;
  z-index: 3 !important;
}
[data-template="midnight-purple"][data-layout="E5"] .e5-numeral {
  position: absolute !important;
  font-size: 52px !important; font-weight: 600 !important;
  letter-spacing: -0.5px !important;
  color: #fff !important;
  line-height: 1 !important;
  z-index: 3 !important;
}
[data-template="midnight-purple"][data-layout="E5"] .e5-subhead {
  position: absolute !important;
  font-size: 44px !important; font-weight: 700 !important;
  letter-spacing: 2.5px !important;
  text-transform: uppercase !important;
  color: #fff !important;
  z-index: 3 !important;
}
[data-template="midnight-purple"][data-layout="E5"] .e5-body {
  position: absolute !important;
  font-size: 32px !important; line-height: 1.5 !important;
  color: #D9D9D9 !important;
  max-width: 1100px !important;
  z-index: 3 !important;
}
[data-template="midnight-purple"][data-layout="E5"] .e5-ghost {
  position: absolute !important;
  font-size: 80px !important; font-weight: 400 !important;
  color: rgba(255,255,255,0.18) !important;
  z-index: 1 !important;
}

/* ─── 11) E8 · CHART.JS CANVAS (Slide 30, corner halo)
       Hero centered y:90. Canvas 1708×780 @ x:106 y:230. */
/* Any chart container in E8 — .e8-chart (spec class) / .tmpl-chart
   (alt spec) / .tmpl-img.chart-host (what _fixOrphanCanvases Pass 1
   actually produces from model's bare canvas) — gets forced to fill
   the canonical canvas area below the heading. Without this, model's
   inline width/height (often 600×400 or similar) shrinks the chart
   into the corner of an otherwise empty slide. (Rainy 2026-06-19:
   "chart 还是没有布局".) */
[data-template="midnight-purple"][data-layout="E8"] .e8-chart,
[data-template="midnight-purple"][data-layout="E8"] .tmpl-chart,
[data-template="midnight-purple"][data-layout="E8"] .chart-host,
[data-template="midnight-purple"][data-layout="E8"] .tmpl-img.chart-host {
  position: absolute !important;
  left: 106px !important; top: 230px !important;
  /* max-width / max-height MUST be none, otherwise model's inline
     max-width:820px caps the forced width even though width is
     !important. min-width / min-height + margin:0 likewise neutralise
     the inline margin:1.2em auto 0 that would otherwise re-center
     a smaller box. (Rainy 2026-06-19: chart 偏左只占 50% width.) */
  width: 1708px !important; height: 780px !important;
  max-width: none !important; max-height: none !important;
  min-width: 1708px !important; min-height: 780px !important;
  margin: 0 !important;
  border-radius: 24px !important;
  /* Rainy 2026-06-19: "chart 不要有虚线边框". The dashed border was
     copied from the spec mock (which uses it to indicate "Chart.js
     placeholder" in the design-spec document), but production chart
     slides should just let the chart sit cleanly on slide bg. */
  background: transparent !important;
  border: none !important;
  z-index: 2 !important;
  overflow: hidden !important;
}
[data-template="midnight-purple"][data-layout="E8"] .e8-chart canvas,
[data-template="midnight-purple"][data-layout="E8"] .tmpl-chart canvas,
[data-template="midnight-purple"][data-layout="E8"] .chart-host canvas,
[data-template="midnight-purple"][data-layout="E8"] .tmpl-img.chart-host canvas {
  width: 100% !important; height: 100% !important;
  display: block !important;
}
/* E8 has only heading + chart in spec — no caption / subtitle slot.
   Model sometimes emits a <p> caption ("Global spending on digital
   transformation..., in trillions USD") that floats top-left of slide
   and looks like a stray label. Hide any non-heading non-chart text
   element under E8 obj-html-inner so the slide reads clean per spec.
   (Rainy 2026-06-19: "左上角的小字是什么".) */
[data-template="midnight-purple"][data-layout="E8"] .obj-html-inner > p,
[data-template="midnight-purple"][data-layout="E8"] .obj-html-inner > span,
[data-template="midnight-purple"][data-layout="E8"] .obj-html-inner > div:not(.tmpl-chart):not(.tmpl-img):not(.chart-host):not(.e8-chart):not(.s-chart-placeholder):not(.slide-bg):not([class*="slide-halo"]):not([class*="cover-"]),
[data-template="midnight-purple"][data-layout="E8"] > p,
[data-template="midnight-purple"][data-layout="E8"] > span {
  display: none !important;
}

/* E8 heading per spec: centered at top:90, 80px serif-sans. Model
   inline often writes "left:120;top:200;width:580" (left-aligned in
   left half) which fights the centered-canvas chart design. Force
   spec position so heading + chart compose like the gallery preview.
   Match ANY heading tag (h1/h2/h3) — model uses h2 too — plus the
   spec class names. Search broadly inside the slide-scale-inner
   wrapper so headings nested deep in obj-html-inner / deck-object
   also pick this up. (Rainy 2026-06-19: "heading 呢".) */
[data-template="midnight-purple"][data-layout="E8"] h1,
[data-template="midnight-purple"][data-layout="E8"] h2,
[data-template="midnight-purple"][data-layout="E8"] h3,
[data-template="midnight-purple"][data-layout="E8"] .tmpl-hero-title,
[data-template="midnight-purple"][data-layout="E8"] .s-page-title {
  position: absolute !important;
  left: 0 !important; right: 0 !important; top: 90px !important;
  width: auto !important; max-width: none !important;
  text-align: center !important;
  font-size: 80px !important;
  font-weight: 600 !important;
  margin: 0 !important;
  padding: 0 80px !important;
  z-index: 3 !important;
  color: #fff !important;
}

/* ─── 12) ANIMATIONS ─── */
@keyframes mpBreathe {
  0%   { transform: scale(1);    opacity: var(--op-lo, 0.65); }
  50%  { transform: scale(1.08); opacity: var(--op-hi, 0.90); }
  100% { transform: scale(1);    opacity: var(--op-lo, 0.65); }
}
@keyframes mpFadeUp {
  from { opacity: 0; transform: translateY(calc(-50% + 42px)); }
  to   { opacity: 1; transform: translateY(-50%); }
}
@keyframes mpSheen {
  0%   { left: -60%; }
  60%  { left: 120%; }
  100% { left: 120%; }
}
emitted 23466 chars to stdout

/* Slot bounds — see code/scripts/check_slot_bounds.py, which flagged these.
 *
 * Nothing sits beneath them, but the canvas does not scroll: unbounded copy
 * runs off the bottom edge, losing the text as surely as printing over a
 * neighbour would.
 *
 * This file is generated from html_gen_prompt.py, so the same declarations were
 * added to that source — a regeneration keeps them instead of quietly dropping
 * the fix. (Rainy 2026-07-28.)
 */
[data-template="midnight-purple"] .d-label { max-height: 660px !important; overflow: hidden !important; }

[data-template="midnight-purple"] .d-label { --ocu-min-font: 22px; }
