@bovinphang/frontend-craft 2.6.0 → 2.7.0
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/.claude-plugin/marketplace.json +3 -3
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +38 -1
- package/CHANGELOG.zh-CN.md +37 -1
- package/README.md +10 -6
- package/README.openclaw.md +3 -1
- package/README.openclaw.zh-CN.md +3 -1
- package/README.zh-CN.md +10 -6
- package/agents/fec-typescript-reviewer.md +1 -1
- package/docs/example-prompts.md +81 -69
- package/docs/ja-JP/README.md +56 -52
- package/docs/ko-KR/README.md +45 -41
- package/docs/project-structure.md +8 -4
- package/docs/superpowers/plans/2026-05-29-fec-debug.md +491 -0
- package/docs/superpowers/specs/2026-05-29-fec-debug-design.md +340 -0
- package/docs/zh-CN/example-prompts.md +81 -69
- package/docs/zh-CN/project-structure.md +8 -4
- package/docs/zh-TW/README.md +33 -29
- package/openclaw.plugin.json +1 -1
- package/package.json +3 -3
- package/skills/eval_queries.json +154 -56
- package/skills/fec-backend-requirements-handoff/SKILL.md +45 -0
- package/skills/fec-code-review/SKILL.md +1 -1
- package/skills/fec-drawio-studio/SKILL.md +69 -0
- package/skills/fec-drawio-studio/data/THIRD_PARTY_NOTICES.md +10 -0
- package/skills/fec-drawio-studio/data/brand-icons.json +878 -0
- package/skills/fec-drawio-studio/data/shape-index.json.gz +0 -0
- package/skills/fec-drawio-studio/references/data-residency.md +27 -0
- package/skills/fec-drawio-studio/references/diagram-patterns.md +41 -0
- package/skills/fec-drawio-studio/references/layout-and-export.md +57 -0
- package/skills/fec-drawio-studio/references/troubleshooting.md +29 -0
- package/skills/fec-drawio-studio/references/xml-and-mermaid.md +54 -0
- package/skills/fec-drawio-studio/scripts/brand-symbols.mjs +99 -0
- package/skills/fec-drawio-studio/scripts/diagram-lint.mjs +143 -0
- package/skills/fec-drawio-studio/scripts/diagram-url.mjs +102 -0
- package/skills/fec-drawio-studio/scripts/layout-graph.mjs +107 -0
- package/skills/fec-drawio-studio/scripts/png-embed-fix.mjs +26 -0
- package/skills/fec-drawio-studio/scripts/scan-go-packages.mjs +36 -0
- package/skills/fec-drawio-studio/scripts/scan-js-modules.mjs +52 -0
- package/skills/fec-drawio-studio/scripts/scan-python-classes.mjs +30 -0
- package/skills/fec-drawio-studio/scripts/scan-python-modules.mjs +55 -0
- package/skills/fec-drawio-studio/scripts/scan-rust-modules.mjs +35 -0
- package/skills/fec-drawio-studio/scripts/scan-ts-modules.mjs +7 -0
- package/skills/fec-drawio-studio/scripts/shape-query.mjs +81 -0
- package/skills/fec-drawio-studio/scripts/studio-core.mjs +139 -0
- package/skills/fec-drawio-studio/styles/built-in/corporate.json +49 -0
- package/skills/fec-drawio-studio/styles/built-in/default.json +49 -0
- package/skills/fec-drawio-studio/styles/built-in/handdrawn.json +49 -0
- package/skills/fec-drawio-studio/styles/schema.json +112 -0
- package/skills/fec-e2e-testing/SKILL.md +3 -1
- package/skills/fec-e2e-testing/references/e2e-ci-reporting.md +62 -0
- package/skills/fec-e2e-testing/references/e2e-special-scenarios.md +52 -0
- package/skills/fec-e2e-testing/references/e2e-visual-regression.md +60 -0
- package/skills/fec-image-generation/SKILL.md +56 -0
- package/skills/fec-image-generation/references/artifact-routing.md +80 -0
- package/skills/fec-image-generation/references/diagram-workflows.md +43 -0
- package/skills/fec-image-generation/references/png-qa-autofix.md +30 -0
- package/skills/fec-image-generation/scripts/png-qa.mjs +431 -0
- package/skills/fec-implement-from-design/SKILL.md +7 -0
- package/skills/fec-legacy-to-modern-migration/SKILL.md +24 -168
- package/skills/fec-legacy-to-modern-migration/references/migration-execution-checklist.md +110 -0
- package/skills/fec-legacy-to-modern-migration/references/migration-strategy-and-mapping.md +48 -0
- package/skills/fec-motion-interaction/SKILL.md +7 -0
- package/skills/fec-nextjs-project-standard/SKILL.md +7 -1
- package/skills/fec-performance-optimization/SKILL.md +5 -1
- package/skills/fec-performance-optimization/references/framework-performance-patterns.md +38 -0
- package/skills/fec-performance-optimization/references/report-template.md +6 -0
- package/skills/fec-react-project-standard/SKILL.md +11 -3
- package/skills/fec-react-project-standard/references/react-performance-patterns.md +35 -0
- package/skills/fec-react-project-standard/references/react-project-structure.md +197 -0
- package/skills/fec-react-project-standard/references/react-quality-patterns.md +131 -0
- package/skills/fec-react-project-standard/references/react-runtime-patterns.md +293 -0
- package/skills/fec-responsive-layout/references/responsive-layout-patterns.md +11 -0
- package/skills/fec-source-driven-development/SKILL.md +26 -0
- package/skills/fec-tailwind-design-system/references/tailwind-system-patterns.md +5 -1
- package/skills/fec-typescript-project-standard/SKILL.md +54 -0
- package/skills/{fec-typescript-type-safety → fec-typescript-project-standard}/references/type-safety-patterns.md +1 -1
- package/skills/{fec-typescript-type-safety/SKILL.md → fec-typescript-project-standard/references/type-safety.md} +1 -10
- package/skills/fec-ui-design/SKILL.md +26 -8
- package/skills/fec-ui-design/data/style-archetypes.json +40 -0
- package/skills/fec-ui-design/data/ux-quality-rules.json +35 -0
- package/skills/fec-ui-design/references/design-intelligence.md +66 -6
- package/skills/fec-ui-design/references/pre-delivery-checklist.md +6 -0
- package/skills/fec-ui-design/scripts/design-system.mjs +119 -1
- package/skills/fec-vue3-project-standard/SKILL.md +11 -2
- package/skills/fec-vue3-project-standard/references/vue3-composables-routing.md +163 -0
- package/skills/fec-vue3-project-standard/references/vue3-performance-patterns.md +13 -0
- package/skills/fec-vue3-project-standard/references/vue3-project-structure.md +177 -0
- package/skills/fec-vue3-project-standard/references/vue3-state-api-quality.md +216 -0
- package/skills/fec-web-video-presentation/SKILL.md +61 -0
- package/skills/fec-web-video-presentation/data/starter-themes.json +59 -0
- package/skills/fec-web-video-presentation/references/chapter-craft.md +37 -0
- package/skills/fec-web-video-presentation/references/content-plan.md +38 -0
- package/skills/fec-web-video-presentation/references/recording-and-audio.md +33 -0
- package/skills/fec-web-video-presentation/references/theme-system.md +36 -0
- package/skills/fec-web-video-presentation/scripts/create-video-presentation.mjs +430 -0
- package/skills/metadata.json +340 -164
- package/skills/relations.json +116 -25
- package/templates/shared/rules/fec-ci-cd.md +20 -108
- package/templates/shared/rules/fec-design-system.md +47 -223
- package/templates/shared/rules/fec-error-handling.md +35 -179
- package/templates/shared/rules/fec-i18n.md +43 -91
- package/templates/shared/rules/fec-refactoring.md +4 -0
- package/templates/shared/rules/fec-rendering-patterns.md +28 -171
- package/templates/shared/rules/fec-responsive-design.md +34 -211
- package/templates/shared/rules/fec-state-management.md +41 -144
- package/templates/shared/rules/fec-typescript.md +47 -290
- package/skills/fec-e2e-testing/references/e2e-advanced.md +0 -176
- package/skills/fec-react-project-standard/references/react-project-details.md +0 -642
- package/skills/fec-vue3-project-standard/references/vue3-project-details.md +0 -547
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Chapter Craft
|
|
2
|
+
|
|
3
|
+
## Stage Rules
|
|
4
|
+
|
|
5
|
+
- Use a fixed 16:9 stage, normally 1920 by 1080, scaled to the viewport.
|
|
6
|
+
- A chapter is a pure function of the current step.
|
|
7
|
+
- One step should focus on one visual idea.
|
|
8
|
+
- Lists reveal one meaningful item at a time.
|
|
9
|
+
- Use hidden or low-emphasis controls so the recording surface stays clean.
|
|
10
|
+
- Keep large text readable when screen-recorded and compressed.
|
|
11
|
+
|
|
12
|
+
## Visual Decisions
|
|
13
|
+
|
|
14
|
+
Before implementing a chapter, answer:
|
|
15
|
+
|
|
16
|
+
- What is the relationship between this step and the previous step: contrast, reveal, zoom, sequence, proof, consequence, or recap?
|
|
17
|
+
- What should the viewer remember after this step?
|
|
18
|
+
- Which source detail makes the screen more specific than a generic slide?
|
|
19
|
+
- Is this a diagram, object, quote, metric, timeline, interface state, or spatial metaphor?
|
|
20
|
+
- What can move because the content itself changes, rather than because everything needs an entrance animation?
|
|
21
|
+
|
|
22
|
+
## Anti-Patterns
|
|
23
|
+
|
|
24
|
+
- Synchronized stagger of many bullets when the narration discusses them one by one.
|
|
25
|
+
- Purple gradient backgrounds, floating decoration, or generic cards that do not explain the content.
|
|
26
|
+
- Fake dashboards, fake screenshots, fake citations, or invented metrics.
|
|
27
|
+
- Tiny body text that looks fine in a browser but fails in recorded video.
|
|
28
|
+
- Persistent chrome, large page numbers, or visible instructions in the recording frame.
|
|
29
|
+
|
|
30
|
+
## Completion Check
|
|
31
|
+
|
|
32
|
+
- Every step renders a complete screen.
|
|
33
|
+
- Text fits at the target stage size.
|
|
34
|
+
- The chapter still makes sense if the recording pauses on any step.
|
|
35
|
+
- Visual state follows narration order.
|
|
36
|
+
- Any media used is local, real, generated for the task, or clearly marked as a placeholder.
|
|
37
|
+
- Keyboard and click navigation still work after the chapter is registered.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Content Plan
|
|
2
|
+
|
|
3
|
+
## Script
|
|
4
|
+
|
|
5
|
+
Use the same language as the source material unless the user asks otherwise. Convert written text into spoken wording, but keep the author's claims, order, names, and important numbers intact.
|
|
6
|
+
|
|
7
|
+
For every script:
|
|
8
|
+
|
|
9
|
+
- Keep one idea per beat.
|
|
10
|
+
- Remove footnote-like detours unless they are central to the story.
|
|
11
|
+
- Mark uncertain facts instead of smoothing them into confident claims.
|
|
12
|
+
- Keep technical names exact.
|
|
13
|
+
- Prefer short sentences that can be spoken without rereading.
|
|
14
|
+
|
|
15
|
+
## Outline
|
|
16
|
+
|
|
17
|
+
The outline is the implementation contract for the web presentation.
|
|
18
|
+
|
|
19
|
+
Include:
|
|
20
|
+
|
|
21
|
+
- Title and intended audience.
|
|
22
|
+
- Total target duration.
|
|
23
|
+
- Chapters with stable ids.
|
|
24
|
+
- Step count per chapter.
|
|
25
|
+
- One screen task per step.
|
|
26
|
+
- Content pool per chapter: facts, numbers, quotes, examples, media, diagrams, or UI states available for visuals.
|
|
27
|
+
- Asset list with owner, path, missing status, and replacement strategy.
|
|
28
|
+
|
|
29
|
+
Avoid:
|
|
30
|
+
|
|
31
|
+
- Naming concrete animations too early.
|
|
32
|
+
- Specifying CSS implementation details.
|
|
33
|
+
- Stuffing several list items into one step when they need separate attention.
|
|
34
|
+
- Adding unsupported claims just to make a scene feel full.
|
|
35
|
+
|
|
36
|
+
## Acceptance
|
|
37
|
+
|
|
38
|
+
A content plan is ready when another implementer can build the presentation without deciding what each chapter says, what each step reveals, or which source facts are available for visuals.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Recording And Audio
|
|
2
|
+
|
|
3
|
+
## Recording Modes
|
|
4
|
+
|
|
5
|
+
Manual mode:
|
|
6
|
+
|
|
7
|
+
- Click, Space, ArrowRight, or PageDown advances one step.
|
|
8
|
+
- ArrowLeft or PageUp goes back.
|
|
9
|
+
- Best when a human narrator is recording live.
|
|
10
|
+
|
|
11
|
+
Auto mode:
|
|
12
|
+
|
|
13
|
+
- Uses per-step durations from the project data.
|
|
14
|
+
- Best when narration audio is already aligned or when making a clean screen capture.
|
|
15
|
+
|
|
16
|
+
## Recording Checklist
|
|
17
|
+
|
|
18
|
+
- Browser console has no errors.
|
|
19
|
+
- Stage is centered and scaled without distortion.
|
|
20
|
+
- No horizontal or vertical scrollbars appear inside the capture frame.
|
|
21
|
+
- Controls are hidden unless hovered or focused.
|
|
22
|
+
- Current step persists only when helpful; reset it after changing chapter order or step count.
|
|
23
|
+
- Record a 10 second sample before doing the full take.
|
|
24
|
+
|
|
25
|
+
## Audio Policy
|
|
26
|
+
|
|
27
|
+
Audio is optional. A project may use:
|
|
28
|
+
|
|
29
|
+
- Existing narration files from the user.
|
|
30
|
+
- Host-native TTS or voiceover tools.
|
|
31
|
+
- A project-owned TTS pipeline.
|
|
32
|
+
|
|
33
|
+
Keep audio alignment provider-neutral. Store segment text and file paths in project data, but do not encode a vendor-specific API contract into the skill.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Theme System
|
|
2
|
+
|
|
3
|
+
Themes are starting points, not templates. A theme defines the visual climate of the recording surface while leaving chapters free to create content-specific layouts.
|
|
4
|
+
|
|
5
|
+
## Token Roles
|
|
6
|
+
|
|
7
|
+
Use semantic CSS variables:
|
|
8
|
+
|
|
9
|
+
- `--stage-bg`
|
|
10
|
+
- `--stage-fg`
|
|
11
|
+
- `--muted-fg`
|
|
12
|
+
- `--accent`
|
|
13
|
+
- `--accent-2`
|
|
14
|
+
- `--panel`
|
|
15
|
+
- `--line`
|
|
16
|
+
- `--shadow`
|
|
17
|
+
- `--font-display`
|
|
18
|
+
- `--font-body`
|
|
19
|
+
- `--radius`
|
|
20
|
+
- `--step-duration`
|
|
21
|
+
|
|
22
|
+
## Starter Theme Policy
|
|
23
|
+
|
|
24
|
+
The bundled starter themes are intentionally few:
|
|
25
|
+
|
|
26
|
+
- Use them to create a runnable skeleton.
|
|
27
|
+
- Replace colors and type choices when a real brand or product context exists.
|
|
28
|
+
- Do not let all chapters become identical; keep typography and color coherent while varying layout and visual devices per chapter.
|
|
29
|
+
|
|
30
|
+
## Custom Theme Checklist
|
|
31
|
+
|
|
32
|
+
- Contrast passes for recorded text.
|
|
33
|
+
- Accent colors have different semantic jobs.
|
|
34
|
+
- Background supports compression without muddy gradients.
|
|
35
|
+
- Typography remains readable at 1080p and in lower-resolution reuploads.
|
|
36
|
+
- Motion can be reduced without destroying meaning.
|
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
|
|
6
|
+
const skillDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
7
|
+
const themesPath = path.join(skillDir, "data", "starter-themes.json");
|
|
8
|
+
const themes = JSON.parse(fs.readFileSync(themesPath, "utf8"));
|
|
9
|
+
|
|
10
|
+
const args = process.argv.slice(2);
|
|
11
|
+
const options = parseArgs(args);
|
|
12
|
+
|
|
13
|
+
if (options.help) {
|
|
14
|
+
printHelp();
|
|
15
|
+
process.exit(0);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (options.listThemes) {
|
|
19
|
+
for (const theme of themes) {
|
|
20
|
+
console.log(`${theme.id}\t${theme.name}\t${theme.bestFor}`);
|
|
21
|
+
}
|
|
22
|
+
process.exit(0);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const target = path.resolve(process.cwd(), options.target ?? "presentation");
|
|
26
|
+
const theme = themes.find((candidate) => candidate.id === options.theme);
|
|
27
|
+
|
|
28
|
+
if (!theme) {
|
|
29
|
+
fail(`Unknown theme "${options.theme}". Run with --list-themes to see available themes.`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const files = buildFiles(theme);
|
|
33
|
+
|
|
34
|
+
if (options.dryRun) {
|
|
35
|
+
console.log(`Would create ${files.length} files in ${target}`);
|
|
36
|
+
console.log(`Theme: ${theme.id} (${theme.name})`);
|
|
37
|
+
for (const file of files) console.log(`- ${file.name}`);
|
|
38
|
+
process.exit(0);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (fs.existsSync(target) && fs.readdirSync(target).length > 0) {
|
|
42
|
+
fail(`Target directory is not empty: ${target}`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
for (const file of files) {
|
|
46
|
+
const fullPath = path.join(target, file.name);
|
|
47
|
+
fs.mkdirSync(path.dirname(fullPath), { recursive: true });
|
|
48
|
+
fs.writeFileSync(fullPath, file.body, "utf8");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
console.log(`Created web video presentation at ${target}`);
|
|
52
|
+
console.log(`Theme: ${theme.id} (${theme.name})`);
|
|
53
|
+
console.log("Next:");
|
|
54
|
+
console.log(" cd " + path.relative(process.cwd(), target));
|
|
55
|
+
console.log(" npm install");
|
|
56
|
+
console.log(" npm run dev");
|
|
57
|
+
|
|
58
|
+
function parseArgs(values) {
|
|
59
|
+
const out = {
|
|
60
|
+
dryRun: false,
|
|
61
|
+
help: false,
|
|
62
|
+
listThemes: false,
|
|
63
|
+
target: undefined,
|
|
64
|
+
theme: themes[0]?.id ?? "editorial-slate",
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
for (let index = 0; index < values.length; index += 1) {
|
|
68
|
+
const value = values[index];
|
|
69
|
+
if (value === "--help" || value === "-h") out.help = true;
|
|
70
|
+
else if (value === "--dry-run") out.dryRun = true;
|
|
71
|
+
else if (value === "--list-themes") out.listThemes = true;
|
|
72
|
+
else if (value.startsWith("--theme=")) out.theme = value.slice("--theme=".length);
|
|
73
|
+
else if (value === "--theme") {
|
|
74
|
+
const next = values[index + 1];
|
|
75
|
+
if (!next || next.startsWith("--")) fail("Missing value for --theme.");
|
|
76
|
+
out.theme = next;
|
|
77
|
+
index += 1;
|
|
78
|
+
}
|
|
79
|
+
else if (value.startsWith("--")) fail(`Unknown option: ${value}`);
|
|
80
|
+
else if (!out.target) out.target = value;
|
|
81
|
+
else fail(`Unexpected argument: ${value}`);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return out;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function buildFiles(theme) {
|
|
88
|
+
const tokenCss = renderTokens(theme.tokens);
|
|
89
|
+
return [
|
|
90
|
+
{
|
|
91
|
+
name: "package.json",
|
|
92
|
+
body: JSON.stringify(
|
|
93
|
+
{
|
|
94
|
+
scripts: {
|
|
95
|
+
dev: "vite",
|
|
96
|
+
build: "tsc --noEmit && vite build",
|
|
97
|
+
preview: "vite preview",
|
|
98
|
+
},
|
|
99
|
+
dependencies: {
|
|
100
|
+
react: "latest",
|
|
101
|
+
"react-dom": "latest",
|
|
102
|
+
},
|
|
103
|
+
devDependencies: {
|
|
104
|
+
"@types/react": "latest",
|
|
105
|
+
"@types/react-dom": "latest",
|
|
106
|
+
"@vitejs/plugin-react": "latest",
|
|
107
|
+
typescript: "latest",
|
|
108
|
+
vite: "latest",
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
null,
|
|
112
|
+
2,
|
|
113
|
+
) + "\n",
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
name: "index.html",
|
|
117
|
+
body: `<!doctype html>
|
|
118
|
+
<html lang="en">
|
|
119
|
+
<head>
|
|
120
|
+
<meta charset="UTF-8" />
|
|
121
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
122
|
+
<title>Web Video Presentation</title>
|
|
123
|
+
</head>
|
|
124
|
+
<body>
|
|
125
|
+
<div id="root"></div>
|
|
126
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
127
|
+
</body>
|
|
128
|
+
</html>
|
|
129
|
+
`,
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
name: "tsconfig.json",
|
|
133
|
+
body: JSON.stringify(
|
|
134
|
+
{
|
|
135
|
+
compilerOptions: {
|
|
136
|
+
target: "ES2022",
|
|
137
|
+
useDefineForClassFields: true,
|
|
138
|
+
lib: ["DOM", "DOM.Iterable", "ES2022"],
|
|
139
|
+
allowJs: false,
|
|
140
|
+
skipLibCheck: true,
|
|
141
|
+
esModuleInterop: true,
|
|
142
|
+
allowSyntheticDefaultImports: true,
|
|
143
|
+
strict: true,
|
|
144
|
+
forceConsistentCasingInFileNames: true,
|
|
145
|
+
module: "ESNext",
|
|
146
|
+
moduleResolution: "Node",
|
|
147
|
+
resolveJsonModule: true,
|
|
148
|
+
isolatedModules: true,
|
|
149
|
+
noEmit: true,
|
|
150
|
+
jsx: "react-jsx",
|
|
151
|
+
},
|
|
152
|
+
include: ["src"],
|
|
153
|
+
},
|
|
154
|
+
null,
|
|
155
|
+
2,
|
|
156
|
+
) + "\n",
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
name: "vite.config.ts",
|
|
160
|
+
body: `import { defineConfig } from 'vite';
|
|
161
|
+
import react from '@vitejs/plugin-react';
|
|
162
|
+
|
|
163
|
+
export default defineConfig({
|
|
164
|
+
plugins: [react()],
|
|
165
|
+
});
|
|
166
|
+
`,
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
name: "src/main.tsx",
|
|
170
|
+
body: `import React, { useEffect, useMemo, useState } from 'react';
|
|
171
|
+
import { createRoot } from 'react-dom/client';
|
|
172
|
+
import './styles.css';
|
|
173
|
+
|
|
174
|
+
type Scene = {
|
|
175
|
+
eyebrow: string;
|
|
176
|
+
title: string;
|
|
177
|
+
body: string;
|
|
178
|
+
detail: string;
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
const scenes: Scene[] = [
|
|
182
|
+
{
|
|
183
|
+
eyebrow: '01 / Hook',
|
|
184
|
+
title: 'Replace this with the first spoken beat',
|
|
185
|
+
body: 'One step equals one idea. Keep it readable in a 1080p recording.',
|
|
186
|
+
detail: 'Use source facts, product states, diagrams, or real assets to make the screen specific.',
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
eyebrow: '02 / Proof',
|
|
190
|
+
title: 'Show the evidence, not a generic slide',
|
|
191
|
+
body: 'Turn a claim into a visual: timeline, interface state, metric, map, comparison, or system diagram.',
|
|
192
|
+
detail: 'Advance with Space, ArrowRight, PageDown, or click. Go back with ArrowLeft or PageUp.',
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
eyebrow: '03 / Close',
|
|
196
|
+
title: 'End on the idea the viewer should remember',
|
|
197
|
+
body: 'Swap this starter content for your script and outline.',
|
|
198
|
+
detail: 'Controls stay quiet so the stage is clean for recording.',
|
|
199
|
+
},
|
|
200
|
+
];
|
|
201
|
+
|
|
202
|
+
const storageKey = 'fec-web-video-presentation-step-v1';
|
|
203
|
+
|
|
204
|
+
function App() {
|
|
205
|
+
const [step, setStep] = useState(() => Number(localStorage.getItem(storageKey) ?? 0));
|
|
206
|
+
const current = scenes[Math.min(Math.max(step, 0), scenes.length - 1)];
|
|
207
|
+
const progress = useMemo(() => ((step + 1) / scenes.length) * 100, [step]);
|
|
208
|
+
|
|
209
|
+
useEffect(() => {
|
|
210
|
+
localStorage.setItem(storageKey, String(step));
|
|
211
|
+
}, [step]);
|
|
212
|
+
|
|
213
|
+
useEffect(() => {
|
|
214
|
+
const onKeyDown = (event: KeyboardEvent) => {
|
|
215
|
+
if (event.key === 'ArrowRight' || event.key === 'PageDown' || event.key === ' ') {
|
|
216
|
+
event.preventDefault();
|
|
217
|
+
setStep((value) => Math.min(value + 1, scenes.length - 1));
|
|
218
|
+
}
|
|
219
|
+
if (event.key === 'ArrowLeft' || event.key === 'PageUp') {
|
|
220
|
+
event.preventDefault();
|
|
221
|
+
setStep((value) => Math.max(value - 1, 0));
|
|
222
|
+
}
|
|
223
|
+
if (event.key.toLowerCase() === 'r') {
|
|
224
|
+
setStep(0);
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
window.addEventListener('keydown', onKeyDown);
|
|
228
|
+
return () => window.removeEventListener('keydown', onKeyDown);
|
|
229
|
+
}, []);
|
|
230
|
+
|
|
231
|
+
return (
|
|
232
|
+
<main className="viewport" onClick={() => setStep((value) => Math.min(value + 1, scenes.length - 1))}>
|
|
233
|
+
<section className="stage" aria-live="polite">
|
|
234
|
+
<div className="scene-grid">
|
|
235
|
+
<div className="copy">
|
|
236
|
+
<p className="eyebrow">{current.eyebrow}</p>
|
|
237
|
+
<h1>{current.title}</h1>
|
|
238
|
+
<p className="body">{current.body}</p>
|
|
239
|
+
</div>
|
|
240
|
+
<div className="visual" aria-label="Visual evidence placeholder">
|
|
241
|
+
<span>{String(step + 1).padStart(2, '0')}</span>
|
|
242
|
+
<p>{current.detail}</p>
|
|
243
|
+
</div>
|
|
244
|
+
</div>
|
|
245
|
+
<div className="progress" aria-hidden="true">
|
|
246
|
+
<span style={{ width: \`\${progress}%\` }} />
|
|
247
|
+
</div>
|
|
248
|
+
</section>
|
|
249
|
+
</main>
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
createRoot(document.getElementById('root')!).render(<App />);
|
|
254
|
+
`,
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
name: "src/styles.css",
|
|
258
|
+
body: `${tokenCss}
|
|
259
|
+
|
|
260
|
+
* {
|
|
261
|
+
box-sizing: border-box;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
html,
|
|
265
|
+
body,
|
|
266
|
+
#root {
|
|
267
|
+
margin: 0;
|
|
268
|
+
min-height: 100%;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
body {
|
|
272
|
+
background: #111;
|
|
273
|
+
color: var(--stage-fg);
|
|
274
|
+
font-family: var(--font-body);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
.viewport {
|
|
278
|
+
min-height: 100vh;
|
|
279
|
+
display: grid;
|
|
280
|
+
place-items: center;
|
|
281
|
+
padding: 24px;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
.stage {
|
|
285
|
+
width: min(100vw - 48px, calc((100vh - 48px) * 16 / 9));
|
|
286
|
+
aspect-ratio: 16 / 9;
|
|
287
|
+
position: relative;
|
|
288
|
+
overflow: hidden;
|
|
289
|
+
background: var(--stage-bg);
|
|
290
|
+
color: var(--stage-fg);
|
|
291
|
+
box-shadow: var(--shadow);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
.scene-grid {
|
|
295
|
+
height: 100%;
|
|
296
|
+
display: grid;
|
|
297
|
+
grid-template-columns: 1.12fr 0.88fr;
|
|
298
|
+
gap: 72px;
|
|
299
|
+
align-items: center;
|
|
300
|
+
padding: 96px;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
.eyebrow {
|
|
304
|
+
color: var(--accent);
|
|
305
|
+
font-size: 24px;
|
|
306
|
+
font-weight: 700;
|
|
307
|
+
margin: 0 0 26px;
|
|
308
|
+
text-transform: uppercase;
|
|
309
|
+
letter-spacing: 0.08em;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
h1 {
|
|
313
|
+
font-family: var(--font-display);
|
|
314
|
+
font-size: clamp(48px, 6vw, 104px);
|
|
315
|
+
line-height: 0.96;
|
|
316
|
+
letter-spacing: 0;
|
|
317
|
+
margin: 0;
|
|
318
|
+
text-wrap: balance;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
.body {
|
|
322
|
+
color: var(--muted-fg);
|
|
323
|
+
font-size: 32px;
|
|
324
|
+
line-height: 1.35;
|
|
325
|
+
margin: 36px 0 0;
|
|
326
|
+
max-width: 820px;
|
|
327
|
+
text-wrap: pretty;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
.visual {
|
|
331
|
+
min-height: 560px;
|
|
332
|
+
border: 1px solid var(--line);
|
|
333
|
+
border-radius: var(--radius);
|
|
334
|
+
background: var(--panel);
|
|
335
|
+
display: grid;
|
|
336
|
+
align-content: space-between;
|
|
337
|
+
padding: 42px;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
.visual span {
|
|
341
|
+
color: var(--accent-2);
|
|
342
|
+
font-family: var(--font-display);
|
|
343
|
+
font-size: 132px;
|
|
344
|
+
line-height: 1;
|
|
345
|
+
font-variant-numeric: tabular-nums;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
.visual p {
|
|
349
|
+
margin: 0;
|
|
350
|
+
color: var(--muted-fg);
|
|
351
|
+
font-size: 26px;
|
|
352
|
+
line-height: 1.4;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
.progress {
|
|
356
|
+
position: absolute;
|
|
357
|
+
left: 32px;
|
|
358
|
+
right: 32px;
|
|
359
|
+
bottom: 28px;
|
|
360
|
+
height: 3px;
|
|
361
|
+
background: color-mix(in srgb, var(--line) 70%, transparent);
|
|
362
|
+
opacity: 0;
|
|
363
|
+
transition: opacity 160ms ease;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
.stage:hover .progress,
|
|
367
|
+
.stage:focus-within .progress {
|
|
368
|
+
opacity: 1;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
.progress span {
|
|
372
|
+
display: block;
|
|
373
|
+
height: 100%;
|
|
374
|
+
background: var(--accent);
|
|
375
|
+
transition: width var(--step-duration) ease;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
@media (prefers-reduced-motion: reduce) {
|
|
379
|
+
.progress,
|
|
380
|
+
.progress span {
|
|
381
|
+
transition: none;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
`,
|
|
385
|
+
},
|
|
386
|
+
{
|
|
387
|
+
name: "README.md",
|
|
388
|
+
body: `# Web Video Presentation
|
|
389
|
+
|
|
390
|
+
Generated with Frontend Craft.
|
|
391
|
+
|
|
392
|
+
- 16:9 browser recording stage
|
|
393
|
+
- Step-driven scenes
|
|
394
|
+
- Keyboard and click navigation
|
|
395
|
+
- Theme tokens in \`src/styles.css\`
|
|
396
|
+
|
|
397
|
+
Replace the starter scenes in \`src/main.tsx\` with your script and outline.
|
|
398
|
+
`,
|
|
399
|
+
},
|
|
400
|
+
];
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
function renderTokens(tokens) {
|
|
404
|
+
return `:root {
|
|
405
|
+
--stage-bg: ${tokens.stageBg};
|
|
406
|
+
--stage-fg: ${tokens.stageFg};
|
|
407
|
+
--muted-fg: ${tokens.mutedFg};
|
|
408
|
+
--accent: ${tokens.accent};
|
|
409
|
+
--accent-2: ${tokens.accent2};
|
|
410
|
+
--panel: ${tokens.panel};
|
|
411
|
+
--line: ${tokens.line};
|
|
412
|
+
--shadow: ${tokens.shadow};
|
|
413
|
+
--font-display: ${tokens.fontDisplay};
|
|
414
|
+
--font-body: ${tokens.fontBody};
|
|
415
|
+
--radius: ${tokens.radius};
|
|
416
|
+
--step-duration: ${tokens.stepDuration};
|
|
417
|
+
}`;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
function printHelp() {
|
|
421
|
+
console.log(`Usage:
|
|
422
|
+
node skills/fec-web-video-presentation/scripts/create-video-presentation.mjs [target] [--theme=<id>] [--dry-run]
|
|
423
|
+
node skills/fec-web-video-presentation/scripts/create-video-presentation.mjs --list-themes
|
|
424
|
+
`);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
function fail(message) {
|
|
428
|
+
console.error(`[create-video-presentation] ${message}`);
|
|
429
|
+
process.exit(1);
|
|
430
|
+
}
|