@cyber-dash-tech/revela 0.4.0 → 0.4.1
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.
- package/README.md +28 -1
- package/README.zh-CN.md +28 -1
- package/designs/monet/preview.html +2293 -0
- package/designs/starter/DESIGN.md +778 -0
- package/designs/starter/preview.html +271 -0
- package/designs/summit/preview.html +2284 -0
- package/lib/commands/designs-new.ts +167 -0
- package/lib/commands/designs-preview.ts +36 -0
- package/lib/commands/help.ts +3 -0
- package/lib/design/designs.ts +176 -3
- package/package.json +5 -1
- package/plugin.ts +39 -0
- package/tools/designs-author.ts +62 -0
|
@@ -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 -->
|