@cyber-dash-tech/revela 0.4.0 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,778 @@
1
+ ---
2
+ name: starter
3
+ description: Neutral structural base design for AI-authored Revela themes
4
+ author: cyber-dash
5
+ version: 1.0.0
6
+ internal: true
7
+ preview:
8
+ ---
9
+
10
+ ## Visual Style — Starter Theme
11
+
12
+ Starter is the neutral structural base for generating new Revela designs. It should be treated as a stable layout/component system, not as a strong visual identity. When deriving a new design, preserve the structure and replace the visual language.
13
+
14
+ <!-- @design:foundation:start -->
15
+
16
+ ### Color Palette
17
+
18
+ ```css
19
+ :root {
20
+ --bg-frame: #111315;
21
+ --bg-page: #f6f7f8;
22
+ --bg-page-alt: #eceff2;
23
+ --surface: #ffffff;
24
+ --surface-strong: #dfe5eb;
25
+ --text-primary: #17191c;
26
+ --text-secondary: #4f5965;
27
+ --text-muted: #7a8490;
28
+ --line: rgba(23, 25, 28, 0.12);
29
+ --line-strong: rgba(23, 25, 28, 0.26);
30
+ --accent-primary: #3b82f6;
31
+ --accent-secondary: #64748b;
32
+ --accent-soft: #dbeafe;
33
+ --accent-danger: #dc2626;
34
+ --shadow-soft: rgba(15, 23, 42, 0.16);
35
+ --font-display: 'Inter', ui-sans-serif, sans-serif;
36
+ --font-body: 'Inter', ui-sans-serif, sans-serif;
37
+ --font-size-body: 17px;
38
+ --font-size-meta: 12px;
39
+ --font-size-body-strong: 20px;
40
+ }
41
+ ```
42
+
43
+ Accent usage guidance:
44
+ - `--accent-primary` — primary emphasis, active states, key data callouts
45
+ - `--accent-secondary` — secondary emphasis, muted graphics, structural marks
46
+ - `--accent-soft` — pale accent fills, subtle backgrounds, low-contrast highlights
47
+ - `--accent-danger` — negative indicators and warnings only
48
+
49
+ ### Typography
50
+
51
+ - **Display / heading font**: `Inter` — neutral sans-serif base for all headings and display text
52
+ - **Body font**: `Inter` — neutral sans-serif base for copy, captions, labels, and UI text
53
+ - Font link tag:
54
+ ```html
55
+ <link rel="preconnect" href="https://fonts.googleapis.com">
56
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
57
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
58
+ ```
59
+ - cover h1: `88px` to `116px`, weight `700` to `800`, line-height `0.92` to `1.0`
60
+ - inner-layout h2: `34px` to `56px`, weight `650` to `800`, line-height `1.02` to `1.12`
61
+ - inner-layout h3: `22px` to `28px`, weight `650` to `750`, line-height `1.12` to `1.2`
62
+ - Body: `17px`, line-height `1.55` to `1.65`
63
+ - Eyebrow / caption / metadata: `12px`, uppercase optional, letter-spacing `0.08em` to `0.16em`
64
+ - Stat number: `72px` to `92px`, weight `700` to `800`, line-height `0.92`
65
+
66
+ All sizes are fixed `px` for the 1920x1080 canvas. JS `transform: scale()` handles viewport adaptation. Never use `clamp()` or viewport-relative units for internal slide layout.
67
+
68
+ ### Visual Schema Rules
69
+
70
+ Before generating a derived design, extract a visual schema from references:
71
+ - `reference type`: flat vector, photography, UI screenshot, webpage, editorial deck, mascot, geometric motif, etc.
72
+ - `composition`: full-bleed, bottom strip, side rail, centered emblem, dense grid, sparse field, etc.
73
+ - `scale`: how much canvas height/width the motif occupies
74
+ - `anchoring`: bottom, corner, side, centered, background, inline component
75
+ - `typography relationship`: dominant type, supporting type, or motif-led composition
76
+ - `decorative language`: rules, dots, doodles, grids, cards, image treatments, etc.
77
+ - `must preserve`: composition and scale traits that define the reference
78
+ - `must avoid`: visual drift such as enlarging a small motif into a full-slide background
79
+
80
+ Preserve composition, not only color and shape.
81
+
82
+ ### SVG Motif Rules
83
+
84
+ For flat vector, doodle, mascot, blob, line-art, or geometric references, prefer the `svg-motif` component. Use a fixed `viewBox`; place the SVG with CSS; keep facial features, doodles, and geometric details inside the SVG coordinate system. Do not build complex illustration details from scattered CSS absolute-positioned divs.
85
+
86
+ For photography, UI screenshots, webpages, and product surfaces, do not convert the reference to SVG. Extract palette, type scale, spacing, layout rhythm, borders, image treatment, and surface behavior instead.
87
+
88
+ ### Page Framing
89
+
90
+ - The browser viewport is a neutral dark frame.
91
+ - The slide page is a light neutral surface.
92
+ - Default canvas padding is compact: `10px`, so preview and generated decks have a large usable page.
93
+ - Use page edge, rules, cards, panels, and quiet geometry as neutral structure. Avoid built-in industry-specific imagery.
94
+
95
+ ### Grid System
96
+
97
+ - **1920x1080 fixed canvas** with one `.slide-canvas` per slide.
98
+ - All slides contain one `.page` unless a layout explicitly defines otherwise.
99
+ - Dense content should use clear slots, grid tracks, and reusable components.
100
+ - Layouts define structure only. They should not encode a topic, industry, or aesthetic identity.
101
+
102
+ ### HTML Structure
103
+
104
+ Every generated presentation must use this exact HTML skeleton:
105
+
106
+ ```html
107
+ <!DOCTYPE html>
108
+ <html lang="{language}">
109
+ <head>
110
+ <meta charset="UTF-8">
111
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
112
+ <title>{Presentation Title}</title>
113
+ <link rel="preconnect" href="https://fonts.googleapis.com">
114
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
115
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
116
+ <!-- <script src="https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js"></script> only if charts are needed -->
117
+ <style>/* all CSS here */</style>
118
+ </head>
119
+ <body>
120
+ <section class="slide" slide-qa="false" data-index="0">
121
+ <div class="slide-canvas"><div class="page">...</div></div>
122
+ </section>
123
+ <script>/* all JS here */</script>
124
+ </body>
125
+ </html>
126
+ ```
127
+
128
+ ### Core CSS
129
+
130
+ ```css
131
+ * { box-sizing: border-box; margin: 0; padding: 0; }
132
+ html { scroll-snap-type: y mandatory; overflow-y: scroll; height: 100%; }
133
+ body { background: var(--bg-frame); color: var(--text-primary); font-family: var(--font-body); -webkit-font-smoothing: antialiased; height: 100%; }
134
+ .slide { min-height: 100dvh; scroll-snap-align: start; display: flex; align-items: center; justify-content: center; overflow: hidden; background: var(--bg-frame); }
135
+ .slide-canvas { width: 1920px; height: 1080px; flex-shrink: 0; transform-origin: center center; position: relative; overflow: hidden; padding: 10px; }
136
+ .page { position: relative; width: 100%; height: 100%; background: var(--bg-page); color: var(--text-primary); padding: 56px 64px 64px; box-shadow: 0 24px 80px var(--shadow-soft); display: flex; flex-direction: column; overflow: hidden; }
137
+ .page.alt { background: var(--bg-page-alt); }
138
+ .eyebrow, .caption, .meta-label { font-size: var(--font-size-meta); line-height: 1.4; letter-spacing: 0.12em; text-transform: uppercase; color: var(--text-muted); }
139
+ h1, h2, h3, h4 { font-family: var(--font-display); font-weight: 750; letter-spacing: -0.035em; color: var(--text-primary); }
140
+ h1 { font-size: 96px; line-height: 0.94; }
141
+ h2 { font-size: 46px; line-height: 1.04; }
142
+ h3 { font-size: 26px; line-height: 1.14; }
143
+ p, li { font-size: var(--font-size-body); line-height: 1.6; color: var(--text-secondary); }
144
+ .rule { width: 100%; height: 1px; background: var(--line); }
145
+ .rule.strong { background: var(--line-strong); }
146
+ .media-frame { position: relative; overflow: hidden; background: var(--surface-strong); }
147
+ .media-frame img { width: 100%; height: 100%; display: block; object-fit: cover; }
148
+ .media-caption { margin-top: 12px; font-size: var(--font-size-meta); line-height: 1.5; letter-spacing: 0.12em; text-transform: uppercase; color: var(--text-muted); }
149
+ .editorial-list { list-style: none; display: flex; flex-direction: column; gap: 14px; }
150
+ .editorial-list li { position: relative; padding-left: 20px; font-size: var(--font-size-body); line-height: 1.58; color: var(--text-secondary); }
151
+ .editorial-list li::before { content: ''; position: absolute; left: 0; top: 8px; width: 6px; height: 6px; background: var(--accent-primary); }
152
+ .reveal { opacity: 0; transform: translateY(18px); transition: opacity 0.55s cubic-bezier(0.22, 1, 0.36, 1), transform 0.55s cubic-bezier(0.22, 1, 0.36, 1); }
153
+ .reveal.visible { opacity: 1; transform: translateY(0); }
154
+ ```
155
+
156
+ ### SlidePresentation Class (Complete JavaScript)
157
+
158
+ ```javascript
159
+ class SlidePresentation {
160
+ constructor() {
161
+ this.slides = document.querySelectorAll('.slide');
162
+ this.currentSlide = 0;
163
+ this.setupScaling();
164
+ this.setupIntersectionObserver();
165
+ this.setupKeyboardNav();
166
+ this.setupTouchNav();
167
+ this.setupMouseWheel();
168
+ }
169
+ setupScaling() {
170
+ const canvases = document.querySelectorAll('.slide-canvas');
171
+ const BASE_W = 1920;
172
+ const BASE_H = 1080;
173
+ const update = () => {
174
+ const scale = Math.min(window.innerWidth / BASE_W, window.innerHeight / BASE_H);
175
+ canvases.forEach((canvas) => { canvas.style.transform = `scale(${scale})`; });
176
+ };
177
+ window.addEventListener('resize', update);
178
+ update();
179
+ }
180
+ setupIntersectionObserver() {
181
+ const observer = new IntersectionObserver((entries) => {
182
+ entries.forEach((entry) => {
183
+ if (entry.isIntersecting) entry.target.querySelectorAll('.reveal').forEach((el) => el.classList.add('visible'));
184
+ });
185
+ }, { threshold: 0.2 });
186
+ this.slides.forEach((slide) => observer.observe(slide));
187
+ }
188
+ setupKeyboardNav() {
189
+ document.addEventListener('keydown', (event) => {
190
+ if (['ArrowDown', 'ArrowRight', ' ', 'PageDown'].includes(event.key)) { event.preventDefault(); this.goTo(this.currentSlide + 1); }
191
+ else if (['ArrowUp', 'ArrowLeft', 'PageUp'].includes(event.key)) { event.preventDefault(); this.goTo(this.currentSlide - 1); }
192
+ });
193
+ }
194
+ setupTouchNav() {
195
+ let startY = 0;
196
+ document.addEventListener('touchstart', (event) => { startY = event.touches[0].clientY; }, { passive: true });
197
+ document.addEventListener('touchend', (event) => {
198
+ const deltaY = startY - event.changedTouches[0].clientY;
199
+ if (Math.abs(deltaY) > 40) this.goTo(this.currentSlide + (deltaY > 0 ? 1 : -1));
200
+ }, { passive: true });
201
+ }
202
+ setupMouseWheel() {
203
+ let last = 0;
204
+ document.addEventListener('wheel', (event) => {
205
+ const now = Date.now();
206
+ if (now - last < 800) return;
207
+ last = now;
208
+ this.goTo(this.currentSlide + (event.deltaY > 0 ? 1 : -1));
209
+ }, { passive: true });
210
+ }
211
+ goTo(index) {
212
+ const clamped = Math.max(0, Math.min(this.slides.length - 1, index));
213
+ this.slides[clamped].scrollIntoView({ behavior: 'smooth' });
214
+ this.currentSlide = clamped;
215
+ }
216
+ }
217
+ new SlidePresentation();
218
+ ```
219
+
220
+ <!-- @design:foundation:end -->
221
+
222
+ <!-- @design:rules:start -->
223
+
224
+ ### Composition Rules
225
+
226
+ - **Structure first.** Use layouts and slots before adding decoration.
227
+ - **Visual schema first.** Derived designs must identify reference type, composition, scale, anchoring, typography relationship, decorative language, must-preserve, and must-avoid before writing CSS.
228
+ - **Preserve composition.** A bottom strip stays bottom anchored; a small corner motif stays small; a sparse reference stays sparse.
229
+ - **Stable layout CSS.** Do not rewrite base layout/container CSS unless the structure itself must change. Prefer tokens, typography, component skins, and small motif components.
230
+ - **Reusable class vocabulary.** New classes must be documented in this DESIGN.md. Avoid many one-off selectors in generated decks.
231
+ - **SVG for vector motifs.** Use `svg-motif` for flat vector, doodle, mascot, blob, line-art, and geometric references.
232
+ - **Images for photographic references.** Use image treatment rules rather than fake SVG when the reference is photographic, UI, webpage, or product imagery.
233
+ - **Preview must be real.** A design preview should show actual layout/component behavior, not empty placeholder boxes only.
234
+
235
+ ### Common Mistakes
236
+
237
+ - Copying a reference's colors while losing its composition and scale.
238
+ - Enlarging small decorative marks into full-slide backgrounds.
239
+ - Rebuilding characters with many absolutely positioned CSS divs instead of a single SVG component.
240
+ - Inventing slide-specific classes that are not documented in the design vocabulary.
241
+ - Mixing too many layout ideas on one slide instead of using a clear slot structure.
242
+
243
+ <!-- @design:rules:end -->
244
+
245
+ <!-- @design:layouts:start -->
246
+
247
+ ### Layout Types
248
+
249
+ Each `<section class="slide">` must set `slide-qa="true"` or `slide-qa="false"`. Use the QA flag on each layout marker.
250
+
251
+ <!-- @layout:fullbleed:start qa=false -->
252
+ #### Fullbleed
253
+
254
+ Full-page layout for a single dominant component such as `image-title`, a large `svg-motif`, or a sparse title field.
255
+
256
+ ```html
257
+ <section class="slide" slide-qa="false" data-index="N">
258
+ <div class="slide-canvas">
259
+ <div class="page" style="padding:0;">
260
+ <!-- [slot: content] — usually image-title, svg-motif, or a custom hero component -->
261
+ </div>
262
+ </div>
263
+ </section>
264
+ ```
265
+ <!-- @layout:fullbleed:end -->
266
+
267
+ <!-- @layout:narrative:start qa=true -->
268
+ #### Narrative
269
+
270
+ Asymmetric two-column layout. Use when one side needs more visual or reading weight.
271
+
272
+ ```html
273
+ <section class="slide" slide-qa="true" data-index="N">
274
+ <div class="slide-canvas">
275
+ <div class="page" style="padding:0;">
276
+ <div class="narrative-grid">
277
+ <div><!-- [slot: left] — 1+ components --></div>
278
+ <div><!-- [slot: right] — 1+ components --></div>
279
+ </div>
280
+ </div>
281
+ </div>
282
+ </section>
283
+ ```
284
+
285
+ ```css
286
+ .narrative-grid { display: grid; grid-template-columns: minmax(0, 1.618fr) minmax(0, 1fr); grid-template-rows: minmax(0, 1fr); height: 100%; overflow: hidden; align-items: stretch; }
287
+ .narrative-grid--reverse { grid-template-columns: minmax(0, 1fr) minmax(0, 1.618fr); }
288
+ .narrative-grid > * { overflow: hidden; min-height: 0; min-width: 0; }
289
+ ```
290
+ <!-- @layout:narrative:end -->
291
+
292
+ <!-- @layout:narrative-reverse:start qa=true -->
293
+ #### Narrative Reverse
294
+
295
+ Mirrored asymmetric two-column layout. Same structure as `narrative`, with the wider column on the right.
296
+
297
+ ```html
298
+ <section class="slide" slide-qa="true" data-index="N">
299
+ <div class="slide-canvas">
300
+ <div class="page" style="padding:0;">
301
+ <div class="narrative-grid narrative-grid--reverse">
302
+ <div><!-- [slot: left] — 1+ components --></div>
303
+ <div><!-- [slot: right] — 1+ components --></div>
304
+ </div>
305
+ </div>
306
+ </div>
307
+ </section>
308
+ ```
309
+ <!-- @layout:narrative-reverse:end -->
310
+
311
+ <!-- @layout:highlight-cols:start qa=true -->
312
+ #### Highlight Cols
313
+
314
+ Equal-column layout for parallel ideas, feature groups, proof points, or compact component showcases.
315
+
316
+ ```html
317
+ <section class="slide" slide-qa="true" data-index="N">
318
+ <div class="slide-canvas">
319
+ <div class="page">
320
+ <div style="display:flex;flex-direction:column;gap:10px;margin-bottom:28px;max-width:620px;">
321
+ <p class="eyebrow">Section Label</p>
322
+ <h2>Parallel columns title</h2>
323
+ </div>
324
+ <div class="highlight-cols-grid" style="flex:1;min-height:0;">
325
+ <div><!-- [slot: 1] --></div>
326
+ <div><!-- [slot: 2] --></div>
327
+ <div><!-- [slot: 3] --></div>
328
+ </div>
329
+ </div>
330
+ </div>
331
+ </section>
332
+ ```
333
+
334
+ ```css
335
+ .highlight-cols-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(0, 1fr)); gap: 32px; overflow: hidden; align-items: stretch; }
336
+ .highlight-cols-grid > * { overflow: hidden; min-height: 0; }
337
+ ```
338
+ <!-- @layout:highlight-cols:end -->
339
+
340
+ <!-- @layout:halves:start qa=true -->
341
+ #### Halves
342
+
343
+ Symmetric two-column layout for direct comparison, paired evidence, or split workflows.
344
+
345
+ ```html
346
+ <section class="slide" slide-qa="true" data-index="N">
347
+ <div class="slide-canvas">
348
+ <div class="page" style="padding:0;">
349
+ <div class="halves-grid">
350
+ <div><!-- [slot: left] --></div>
351
+ <div><!-- [slot: right] --></div>
352
+ </div>
353
+ </div>
354
+ </div>
355
+ </section>
356
+ ```
357
+
358
+ ```css
359
+ .halves-grid { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); height: 100%; overflow: hidden; align-items: stretch; }
360
+ .halves-grid > * { overflow: hidden; min-height: 0; min-width: 0; }
361
+ ```
362
+ <!-- @layout:halves:end -->
363
+
364
+ <!-- @layout:stacked:start qa=true -->
365
+ #### Stacked
366
+
367
+ Two-row layout for a compact header/summary above a larger evidence, chart, or flow area.
368
+
369
+ ```html
370
+ <section class="slide" slide-qa="true" data-index="N">
371
+ <div class="slide-canvas">
372
+ <div class="page" style="padding:0;">
373
+ <div class="stacked-grid">
374
+ <div class="stacked-top"><!-- [slot: top] --></div>
375
+ <div class="stacked-bottom"><!-- [slot: bottom] --></div>
376
+ </div>
377
+ </div>
378
+ </div>
379
+ </section>
380
+ ```
381
+
382
+ ```css
383
+ .stacked-grid { display: grid; grid-template-rows: minmax(0, 1fr) minmax(0, 1.618fr); height: 100%; width: 100%; overflow: hidden; }
384
+ .stacked-top, .stacked-bottom { overflow: hidden; min-height: 0; }
385
+ ```
386
+ <!-- @layout:stacked:end -->
387
+
388
+ <!-- @design:layouts:end -->
389
+
390
+ <!-- @design:components:start -->
391
+
392
+ ### Components
393
+
394
+ Components are reusable primitives. Derived designs should preserve coverage and skin them through variables, typography, surfaces, and small motif components.
395
+
396
+ <!-- @component:text-panel:start -->
397
+ #### Text Panel (.text-panel)
398
+
399
+ Reusable text container for headings, body copy, lists, and footer metadata.
400
+
401
+ ```html
402
+ <div class="text-panel text-panel--light">
403
+ <div class="text-panel-body">
404
+ <p class="eyebrow">Context</p>
405
+ <h2>Panel heading</h2>
406
+ <ul class="editorial-list"><li><strong>Signal.</strong> Supporting copy.</li></ul>
407
+ </div>
408
+ <div class="text-panel-footer"><span class="caption">Source</span><span class="caption">01</span></div>
409
+ </div>
410
+ ```
411
+
412
+ ```css
413
+ .text-panel { height: 100%; padding: 56px 48px 34px; display: flex; flex-direction: column; justify-content: space-between; gap: 32px; }
414
+ .text-panel--light { background: var(--bg-page-alt); color: var(--text-primary); }
415
+ .text-panel--dark { background: #1f242b; color: #f8fafc; --text-primary: #f8fafc; --text-secondary: #cbd5e1; --text-muted: #94a3b8; --line: rgba(248,250,252,0.16); }
416
+ .text-panel-body { display: flex; flex-direction: column; gap: 14px; }
417
+ .text-panel-footer { display: flex; justify-content: space-between; align-items: flex-end; gap: 18px; }
418
+ ```
419
+ <!-- @component:text-panel:end -->
420
+
421
+ <!-- @component:stat-card:start -->
422
+ #### Stat Card (.stat-card)
423
+
424
+ Compact data statement with large numeric value, label, and explanatory copy.
425
+
426
+ ```html
427
+ <div class="stat-card">
428
+ <p class="eyebrow">Metric</p>
429
+ <div class="stat-card-value">72%</div>
430
+ <h3>Short implication</h3>
431
+ <p>One or two lines explaining the signal.</p>
432
+ </div>
433
+ ```
434
+
435
+ ```css
436
+ .stat-card { height: 100%; display: flex; min-height: 0; flex-direction: column; justify-content: flex-start; gap: 16px; padding-top: 8px; }
437
+ .stat-card--horizontal { flex-direction: row; align-items: flex-start; gap: 30px; }
438
+ .stat-card-value { font-family: var(--font-display); font-size: 88px; line-height: 0.9; letter-spacing: -0.05em; font-weight: 800; font-variant-numeric: tabular-nums; color: var(--accent-primary); }
439
+ ```
440
+ <!-- @component:stat-card:end -->
441
+
442
+ <!-- @component:editorial-image-top:start -->
443
+ #### Editorial Image Top (.editorial-image-top)
444
+
445
+ Media-over-copy module. Use for examples, visual proof, screenshots, or neutral placeholders.
446
+
447
+ ```html
448
+ <div class="editorial-image-top">
449
+ <div class="media-frame editorial-media"><img src="..." alt=""></div>
450
+ <div class="editorial-module-body"><p class="eyebrow">Label</p><h3>Module heading</h3><p>Short supporting text.</p></div>
451
+ </div>
452
+ ```
453
+
454
+ ```css
455
+ .editorial-image-top { display: flex; flex-direction: column; gap: 16px; height: 100%; }
456
+ .editorial-image-top .editorial-media { height: 240px; border: 1px solid var(--line); }
457
+ .editorial-module-body { display: flex; flex-direction: column; gap: 12px; }
458
+ ```
459
+ <!-- @component:editorial-image-top:end -->
460
+
461
+ <!-- @component:editorial-text-top:start -->
462
+ #### Editorial Text Top (.editorial-text-top)
463
+
464
+ Text-over-media module for explanation first, visual second.
465
+
466
+ ```html
467
+ <div class="editorial-text-top">
468
+ <div class="editorial-module-body"><p class="eyebrow">Label</p><h3>Module heading</h3><p>Short supporting text.</p></div>
469
+ <div class="media-frame editorial-media"></div>
470
+ </div>
471
+ ```
472
+
473
+ ```css
474
+ .editorial-text-top { display: flex; flex-direction: column; gap: 16px; height: 100%; }
475
+ .editorial-text-top .editorial-media { flex: 1; min-height: 180px; border: 1px solid var(--line); }
476
+ ```
477
+ <!-- @component:editorial-text-top:end -->
478
+
479
+ <!-- @component:editorial-text-left:start -->
480
+ #### Editorial Text Left (.editorial-text-left)
481
+
482
+ Horizontal text-and-visual module for compact evidence or feature explanation.
483
+
484
+ ```html
485
+ <div class="editorial-text-left">
486
+ <div class="editorial-text-left-header"><p class="eyebrow">Label</p><h3>Module heading</h3></div>
487
+ <div class="editorial-text-left-content">
488
+ <div class="editorial-text-left-copy"><p>Short copy.</p></div>
489
+ <div class="editorial-text-left-visual"><div class="media-frame"></div></div>
490
+ </div>
491
+ </div>
492
+ ```
493
+
494
+ ```css
495
+ .editorial-text-left { display: flex; flex-direction: column; gap: 0; height: 100%; overflow: hidden; border: 1px solid var(--line); }
496
+ .editorial-text-left-header { flex-shrink: 0; padding: 24px 26px 16px; border-bottom: 1px solid var(--line); }
497
+ .editorial-text-left-content { display: flex; flex: 1; min-height: 0; }
498
+ .editorial-text-left-copy { flex: 1.1; min-width: 0; padding: 20px 24px; display: flex; flex-direction: column; justify-content: flex-start; }
499
+ .editorial-text-left-visual { flex: 1; min-width: 0; min-height: 0; align-self: stretch; overflow: hidden; position: relative; background: var(--surface-strong); }
500
+ ```
501
+ <!-- @component:editorial-text-left:end -->
502
+
503
+ <!-- @component:echart-panel:start -->
504
+ #### EChart Panel (.echart-panel)
505
+
506
+ Chart container with header, chart area, and caption.
507
+
508
+ ```html
509
+ <div class="echart-panel">
510
+ <div class="echart-panel-header"><p class="eyebrow">Chart</p><h3>Chart heading</h3><p class="chart-subtitle">Subtitle</p></div>
511
+ <div class="echart-container" id="chart-id"></div>
512
+ <p class="chart-caption">Source: dataset</p>
513
+ </div>
514
+ ```
515
+
516
+ ```css
517
+ .echart-panel { display: flex; flex-direction: column; height: 100%; gap: 0; }
518
+ .echart-panel-header { flex-shrink: 0; padding-bottom: 16px; border-bottom: 1px solid var(--line); margin-bottom: 20px; }
519
+ .chart-subtitle { margin-top: 4px; font-size: 13px; color: var(--text-muted); line-height: 1.4; }
520
+ .echart-container { flex: 1; min-height: 0; }
521
+ .chart-caption { flex-shrink: 0; margin-top: 12px; font-size: 11px; letter-spacing: 0.12em; text-transform: uppercase; color: var(--text-muted); }
522
+ ```
523
+ <!-- @component:echart-panel:end -->
524
+
525
+ <!-- @component:flow-horizontal:start -->
526
+ #### Flow Horizontal (.flow-horizontal)
527
+
528
+ Horizontal process with numbered markers.
529
+
530
+ ```html
531
+ <div class="flow-horizontal">
532
+ <div class="flow-item"><div class="flow-number" data-n="01"></div><div class="flow-body"><h4>Step</h4><p>Short text.</p></div></div>
533
+ </div>
534
+ ```
535
+
536
+ ```css
537
+ .flow-number { position: relative; width: 36px; height: 36px; flex-shrink: 0; border: 1px solid var(--line-strong); background: var(--surface); display: flex; align-items: center; justify-content: center; }
538
+ .flow-number::after { content: attr(data-n); font-size: 12px; font-weight: 800; color: var(--accent-primary); }
539
+ .flow-body h4 { font-size: 20px; font-weight: 700; line-height: 1.14; }
540
+ .flow-body p { margin-top: 8px; font-size: 17px; line-height: 1.6; color: var(--text-secondary); }
541
+ .flow-horizontal { position: relative; display: flex; align-items: flex-start; width: 100%; }
542
+ .flow-horizontal::before { content: ''; position: absolute; top: 17px; left: 0; right: 0; height: 1px; background: var(--line-strong); z-index: 0; }
543
+ .flow-horizontal .flow-item { flex: 1; display: flex; flex-direction: column; gap: 18px; padding-right: 40px; }
544
+ .flow-horizontal .flow-number { position: relative; z-index: 1; }
545
+ ```
546
+ <!-- @component:flow-horizontal:end -->
547
+
548
+ <!-- @component:flow-vertical:start -->
549
+ #### Flow Vertical (.flow-vertical)
550
+
551
+ Vertical process for side panels and narrow slots.
552
+
553
+ ```html
554
+ <div class="flow-vertical">
555
+ <div class="flow-item"><div class="flow-marker"><div class="flow-number" data-n="01"></div><div class="flow-line"></div></div><div class="flow-body"><h4>Step</h4><p>Short text.</p></div></div>
556
+ </div>
557
+ ```
558
+
559
+ ```css
560
+ .flow-vertical { display: flex; flex-direction: column; width: 100%; }
561
+ .flow-vertical .flow-item { display: flex; gap: 28px; align-items: flex-start; }
562
+ .flow-vertical .flow-marker { display: flex; flex-direction: column; align-items: center; flex-shrink: 0; }
563
+ .flow-vertical .flow-line { width: 1px; flex: 1; min-height: 28px; background: var(--line-strong); margin: 6px 0; }
564
+ .flow-vertical .flow-body { padding-bottom: 32px; }
565
+ .flow-vertical .flow-item.last .flow-body { padding-bottom: 0; }
566
+ ```
567
+ <!-- @component:flow-vertical:end -->
568
+
569
+ <!-- @component:data-table:start -->
570
+ #### Data Table (.data-table)
571
+
572
+ Dense tabular data with optional highlights and deltas.
573
+
574
+ ```html
575
+ <div class="data-table-wrap">
576
+ <div class="data-table-label">Dataset</div>
577
+ <table class="data-table"><thead><tr><th>Item</th><th>Value</th></tr></thead><tbody><tr><td>Example</td><td>42</td></tr></tbody></table>
578
+ <p class="table-caption">Source note</p>
579
+ </div>
580
+ ```
581
+
582
+ ```css
583
+ .data-table-wrap { width: 100%; }
584
+ .data-table-label { font-size: 10px; font-weight: 800; letter-spacing: 0.14em; text-transform: uppercase; color: var(--text-muted); margin-bottom: 8px; }
585
+ .data-table { width: 100%; border-collapse: collapse; font-family: var(--font-body); font-size: 17px; font-variant-numeric: tabular-nums; color: var(--text-primary); }
586
+ .data-table thead tr { border-bottom: 1.5px solid var(--line-strong); }
587
+ .data-table th { padding: 0 12px 10px 0; text-align: left; font-size: 13px; font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase; color: var(--text-muted); white-space: nowrap; }
588
+ .data-table th:not(:first-child), .data-table td:not(:first-child) { text-align: right; }
589
+ .data-table tbody tr { border-bottom: 1px solid var(--line); }
590
+ .data-table td { padding: 9px 12px 9px 0; line-height: 1.4; color: var(--text-secondary); }
591
+ .data-table .delta.positive { color: var(--accent-primary); }
592
+ .data-table .delta.negative { color: var(--accent-danger); }
593
+ .table-caption { margin-top: 12px; font-size: 11px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--text-muted); }
594
+ ```
595
+ <!-- @component:data-table:end -->
596
+
597
+ <!-- @component:image-title:start -->
598
+ #### Image Title (.image-title)
599
+
600
+ Hero title component for image, surface, or abstract visual backgrounds.
601
+
602
+ ```html
603
+ <div class="image-title image-title--left">
604
+ <div class="image-title-media"></div>
605
+ <div class="image-title-overlay"></div>
606
+ <div class="image-title-fg"><div class="image-title-body"><p class="image-title-eyebrow">Label</p><h1>Title</h1><p class="image-title-subtitle">Subtitle</p></div></div>
607
+ </div>
608
+ ```
609
+
610
+ ```css
611
+ .image-title { position: relative; width: 100%; height: 100%; overflow: hidden; color: #f8fafc; background: #1f242b; }
612
+ .image-title-media { position: absolute; inset: 0; z-index: 0; background: linear-gradient(135deg, #1f2937, #475569); }
613
+ .image-title-overlay { position: absolute; inset: 0; z-index: 1; background: linear-gradient(90deg, rgba(15,23,42,0.78), rgba(15,23,42,0.18)); }
614
+ .image-title-fg { position: relative; z-index: 2; height: 100%; display: flex; flex-direction: column; justify-content: space-between; padding: 72px 84px; }
615
+ .image-title--left .image-title-body { max-width: 760px; }
616
+ .image-title--right .image-title-fg { text-align: right; }
617
+ .image-title--right .image-title-body { max-width: 860px; margin-left: auto; }
618
+ .image-title-eyebrow { font-size: 12px; font-weight: 800; letter-spacing: 0.18em; text-transform: uppercase; color: rgba(248,250,252,0.62); margin-bottom: 20px; }
619
+ .image-title h1 { color: #f8fafc; font-size: 104px; line-height: 0.92; letter-spacing: -0.055em; }
620
+ .image-title-subtitle { margin-top: 24px; font-size: 18px; line-height: 1.56; color: rgba(248,250,252,0.78); max-width: 520px; }
621
+ ```
622
+ <!-- @component:image-title:end -->
623
+
624
+ <!-- @component:toc:start -->
625
+ #### TOC (.toc-panel)
626
+
627
+ Table of contents or section index.
628
+
629
+ ```html
630
+ <div class="toc-panel"><div class="toc-panel-inner"><div class="toc-header"><p class="eyebrow">Contents</p><h2>Agenda</h2></div><div class="toc-list"><div class="toc-item"><span>01</span><strong>Section title</strong><em>03</em></div></div></div></div>
631
+ ```
632
+
633
+ ```css
634
+ .toc-panel { height: 100%; padding: 54px 52px 42px; display: flex; overflow: hidden; background: var(--bg-page); }
635
+ .toc-panel-inner { display: flex; flex-direction: column; justify-content: space-between; width: 100%; gap: 32px; }
636
+ .toc-header { max-width: 620px; display: flex; flex-direction: column; gap: 18px; }
637
+ .toc-list { display: flex; flex-direction: column; border-top: 1px solid var(--line-strong); }
638
+ .toc-item { display: grid; grid-template-columns: 70px 1fr 60px; gap: 24px; align-items: baseline; padding: 22px 0; border-bottom: 1px solid var(--line); }
639
+ .toc-item span, .toc-item em { font-style: normal; color: var(--text-muted); font-size: 12px; letter-spacing: 0.14em; text-transform: uppercase; }
640
+ .toc-item strong { font-size: 28px; line-height: 1.08; color: var(--text-primary); }
641
+ ```
642
+ <!-- @component:toc:end -->
643
+
644
+ <!-- @component:quote:start -->
645
+ #### Quote (.quote-block)
646
+
647
+ Large quotation or summary statement.
648
+
649
+ ```html
650
+ <div class="quote-block"><p class="quote-mark">“</p><blockquote>Statement text goes here.</blockquote><p class="quote-source">Source</p></div>
651
+ ```
652
+
653
+ ```css
654
+ .quote-block { height: 100%; display: flex; flex-direction: column; justify-content: center; gap: 24px; max-width: 980px; }
655
+ .quote-mark { font-size: 96px; line-height: 0.7; color: var(--accent-primary); }
656
+ .quote-block blockquote { font-family: var(--font-display); font-size: 54px; line-height: 1.06; letter-spacing: -0.04em; color: var(--text-primary); }
657
+ .quote-source { font-size: 13px; text-transform: uppercase; letter-spacing: 0.14em; color: var(--text-muted); }
658
+ ```
659
+ <!-- @component:quote:end -->
660
+
661
+ <!-- @component:brand-watermark:start -->
662
+ #### Brand Watermark (.brand-watermark)
663
+
664
+ Small neutral identity mark for preview and closing slides.
665
+
666
+ ```html
667
+ <div class="brand-watermark"><span></span><strong>Brand</strong></div>
668
+ ```
669
+
670
+ ```css
671
+ .brand-watermark { display: inline-flex; align-items: center; gap: 10px; color: var(--text-muted); font-size: 12px; letter-spacing: 0.12em; text-transform: uppercase; }
672
+ .brand-watermark span { width: 18px; height: 18px; border: 2px solid var(--accent-primary); display: block; }
673
+ .brand-watermark strong { font-weight: 800; color: var(--text-secondary); }
674
+ ```
675
+ <!-- @component:brand-watermark:end -->
676
+
677
+ <!-- @component:page-number:start -->
678
+ #### Page Number (.page-number)
679
+
680
+ Small page number utility.
681
+
682
+ ```html
683
+ <div class="page-number">04</div>
684
+ ```
685
+
686
+ ```css
687
+ .page-number { position: absolute; right: 34px; bottom: 26px; z-index: 10; font-size: 12px; letter-spacing: 0.14em; color: var(--text-muted); }
688
+ .page-number--light { color: rgba(248,250,252,0.72); }
689
+ ```
690
+ <!-- @component:page-number:end -->
691
+
692
+ <!-- @component:timeline-journey-horizontal:start -->
693
+ #### Timeline Journey Horizontal (.timeline-journey-horizontal)
694
+
695
+ Horizontal timeline for milestones.
696
+
697
+ ```html
698
+ <div class="timeline-journey-horizontal"><div class="timeline-node"><span>01</span><h4>Milestone</h4><p>Short note.</p></div></div>
699
+ ```
700
+
701
+ ```css
702
+ .timeline-journey-horizontal { position: relative; display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 28px; }
703
+ .timeline-journey-horizontal::before { content: ''; position: absolute; left: 0; right: 0; top: 18px; height: 1px; background: var(--line-strong); }
704
+ .timeline-node { position: relative; z-index: 1; display: flex; flex-direction: column; gap: 12px; padding-right: 18px; }
705
+ .timeline-node span { width: 36px; height: 36px; display: flex; align-items: center; justify-content: center; background: var(--surface); border: 1px solid var(--line-strong); color: var(--accent-primary); font-size: 12px; font-weight: 800; }
706
+ ```
707
+ <!-- @component:timeline-journey-horizontal:end -->
708
+
709
+ <!-- @component:timeline-journey-vertical:start -->
710
+ #### Timeline Journey Vertical (.timeline-journey-vertical)
711
+
712
+ Vertical timeline for narrow slots.
713
+
714
+ ```html
715
+ <div class="timeline-journey-vertical"><div class="timeline-v-node"><span>01</span><div><h4>Milestone</h4><p>Short note.</p></div></div></div>
716
+ ```
717
+
718
+ ```css
719
+ .timeline-journey-vertical { display: flex; flex-direction: column; gap: 0; }
720
+ .timeline-v-node { display: grid; grid-template-columns: 42px 1fr; gap: 18px; padding-bottom: 26px; position: relative; }
721
+ .timeline-v-node::before { content: ''; position: absolute; left: 17px; top: 42px; bottom: 4px; width: 1px; background: var(--line-strong); }
722
+ .timeline-v-node span { width: 36px; height: 36px; display: flex; align-items: center; justify-content: center; background: var(--surface); border: 1px solid var(--line-strong); color: var(--accent-primary); font-size: 12px; font-weight: 800; }
723
+ ```
724
+ <!-- @component:timeline-journey-vertical:end -->
725
+
726
+ <!-- @component:svg-motif:start -->
727
+ #### SVG Motif (.svg-motif)
728
+
729
+ Pattern for flat vector motifs, doodles, mascots, blob characters, line-art, and abstract geometric visuals. Keep drawing details inside the SVG; CSS only places and sizes the motif.
730
+
731
+ ```html
732
+ <div class="svg-motif svg-motif--bottom" aria-hidden="true">
733
+ <svg viewBox="0 0 1600 420" role="img" aria-label="Decorative vector motif">
734
+ <rect x="0" y="350" width="1600" height="24" fill="var(--accent-soft)" />
735
+ <path d="M120 330 C160 240 260 220 330 290 C380 340 290 370 180 370 Z" fill="var(--accent-primary)" />
736
+ <circle cx="230" cy="310" r="8" fill="var(--text-primary)" />
737
+ </svg>
738
+ </div>
739
+ ```
740
+
741
+ ```css
742
+ .svg-motif { position: relative; pointer-events: none; color: var(--text-primary); }
743
+ .svg-motif svg { display: block; width: 100%; height: 100%; overflow: visible; }
744
+ .svg-motif--bottom { position: absolute; left: 0; right: 0; bottom: 0; height: 30%; }
745
+ .svg-motif--side { position: absolute; right: 0; top: 0; bottom: 0; width: 34%; }
746
+ .svg-motif--corner { position: absolute; right: 40px; bottom: 36px; width: 360px; height: 220px; }
747
+ .svg-motif--hero { width: 100%; height: 100%; }
748
+ ```
749
+
750
+ Usage rules:
751
+ - Use fixed `viewBox` values such as `0 0 1600 420` for strips or `0 0 600 600` for emblems.
752
+ - For bottom strips, the wrapper should usually occupy `20%` to `35%` of slide height.
753
+ - Do not let a small reference motif become a full-slide mascot unless the user requests it.
754
+ - Do not create eyes, mouths, doodles, or character details as separate CSS-positioned HTML elements outside the SVG.
755
+ <!-- @component:svg-motif:end -->
756
+
757
+ <!-- @design:components:end -->
758
+
759
+ <!-- @design:chart-rules:start -->
760
+
761
+ ### Data Visualization (ECharts)
762
+
763
+ - Use neutral chart styling by default: clean axes, limited series count, and restrained labels.
764
+ - Use `--accent-primary` for the main series and `--accent-secondary` for supporting series.
765
+ - Avoid dashboard chrome, glowing charts, and excessive gridlines.
766
+ - Always include a short chart caption or source note when data is shown.
767
+ - Keep chart containers inside `echart-panel` so QA can measure stable geometry.
768
+
769
+ Recommended ECharts defaults:
770
+
771
+ ```javascript
772
+ const baseChartText = getComputedStyle(document.documentElement).getPropertyValue('--text-secondary').trim();
773
+ const baseChartLine = getComputedStyle(document.documentElement).getPropertyValue('--line').trim();
774
+ const primary = getComputedStyle(document.documentElement).getPropertyValue('--accent-primary').trim();
775
+ const secondary = getComputedStyle(document.documentElement).getPropertyValue('--accent-secondary').trim();
776
+ ```
777
+
778
+ <!-- @design:chart-rules:end -->