reel-studio 0.0.0-stage → 0.2.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/.claude/skills/capture-site/SKILL.md +39 -0
- package/.claude/skills/character-2d/SKILL.md +38 -0
- package/.claude/skills/make-video/SKILL.md +56 -0
- package/.claude/skills/new-brand/SKILL.md +19 -0
- package/.claude/skills/pick-engine/SKILL.md +38 -0
- package/.claude/skills/scene-3d/SKILL.md +41 -0
- package/.claude/skills/setup/SKILL.md +29 -0
- package/LICENSE +21 -0
- package/README.md +119 -2
- package/bin/reel-studio.mjs +32 -0
- package/brands/_template/README.md +46 -0
- package/brands/_template/assets/.gitkeep +0 -0
- package/brands/_template/compliance.md +19 -0
- package/brands/_template/tokens.css +24 -0
- package/core/README.md +58 -0
- package/core/lib/base.css +31 -0
- package/core/lib/runtime.js +126 -0
- package/core/package-lock.json +148 -0
- package/core/package.json +16 -0
- package/core/render.mjs +163 -0
- package/engines/README.md +46 -0
- package/engines/blender/example_scene.py +47 -0
- package/engines/blender/render.sh +21 -0
- package/engines/blender/run.py +96 -0
- package/engines/capture.mjs +74 -0
- package/engines/pixi.js +30 -0
- package/engines/svg-rig.js +81 -0
- package/engines/three.js +51 -0
- package/examples/engines-demo/README.md +22 -0
- package/examples/engines-demo/index.html +108 -0
- package/examples/engines-demo/plates/blob.mp4 +0 -0
- package/package.json +29 -4
- package/productions/_TEMPLATE/README.md +88 -0
- package/productions/_TEMPLATE/audio/.gitkeep +0 -0
- package/productions/_TEMPLATE/composition/.gitkeep +0 -0
- package/productions/_TEMPLATE/export/.gitkeep +0 -0
- package/productions/_TEMPLATE/qa/.gitkeep +0 -0
- package/productions/_TEMPLATE/refs/.gitkeep +0 -0
- package/scripts/extract-frames.sh +54 -0
- package/scripts/init-project.sh +67 -0
- package/scripts/mix_audio.py +78 -0
- package/scripts/qa-video.sh +95 -0
- package/scripts/qa_motion.py +66 -0
- package/scripts/svg-to-png.sh +37 -0
- package/scripts/transcribe.sh +25 -0
- package/templates/project/CLAUDE.md +34 -0
- package/templates/project/STATUS.md +21 -0
- package/templates/project/gitignore +15 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: capture-site
|
|
3
|
+
description: Capture a live web page the brand owns (its 3D product scene, web app or dashboard) frame by frame into a video plate with engines/capture.mjs, driving scroll and clicks along a shot list. Use when the product's real visuals already exist on a website.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Capturing a live page
|
|
7
|
+
|
|
8
|
+
Only capture pages the brand owns or has rights to. Never capture a competitor's site.
|
|
9
|
+
|
|
10
|
+
1. Open the page in a normal browser first. Note the sections, what scroll position shows each shot, and which UI
|
|
11
|
+
(nav, cookie banner, captions) must be hidden.
|
|
12
|
+
2. Write a drive module in the production's `capture/drive.mjs`:
|
|
13
|
+
```js
|
|
14
|
+
export async function setup(page) {
|
|
15
|
+
await page.addStyleTag({ content: "nav, .cookie { visibility: hidden !important; }" });
|
|
16
|
+
for (let i = 0; i < 90; i++) await page.evaluate(() => window.__step(1 / 30)); // let smoothed cameras settle
|
|
17
|
+
}
|
|
18
|
+
export async function frame(page, t) {
|
|
19
|
+
await page.evaluate((y) => window.scrollTo(0, y), scrollAt(t)); // from a SHOTS table, linear between keys
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
Keep the shot timing in one `SHOTS` table (`[video time, scroll or scene position]`) and copy beat times from the storyboard.
|
|
23
|
+
3. Capture: `node engines/capture.mjs <url> /tmp/frames --duration 28 --size 540x960 --scale 2 --drive capture/drive.mjs`.
|
|
24
|
+
540×960 at scale 2 gives 1080×1920 and makes most sites use their phone layout.
|
|
25
|
+
4. Encode with every frame a keyframe:
|
|
26
|
+
`ffmpeg -framerate 30 -i /tmp/frames/f_%05d.png -c:v libx264 -preset slow -crf 12 -pix_fmt yuv420p -g 1 composition/scene.mp4`.
|
|
27
|
+
5. Overlay copy in the composition with `Studio.video` + GSAP. Shot times and copy beats live in two files, so change both together.
|
|
28
|
+
|
|
29
|
+
**Watch for adaptive quality.** Many 3D sites lower their resolution when frames look slow, and under the
|
|
30
|
+
virtual clock every frame "takes" one step (1/30 s). Find the check in the page source (search `devicePixelRatio`,
|
|
31
|
+
`setPixelRatio`, `perf`) and make `setup` pass it, e.g. settle at 1/60 s steps. Then assert the canvas renders at
|
|
32
|
+
full size (`canvas.width === clientWidth * devicePixelRatio`), as REEL-001's `checkResolution` does. A soft plate
|
|
33
|
+
looks like a screen recording.
|
|
34
|
+
|
|
35
|
+
The capture replaces `performance.now`, `Date.now` and `requestAnimationFrame` with a virtual clock. Pages that
|
|
36
|
+
animate with CSS transitions or `setTimeout` will not be frame-exact: hide those parts or drive them yourself.
|
|
37
|
+
|
|
38
|
+
Worked example: `productions/bike-logger/REEL-001-kit/capture/drive.mjs` (shot table, scroll mapping, hidden
|
|
39
|
+
captions, settling the camera).
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: character-2d
|
|
3
|
+
description: Animate a 2D cartoon character or mascot in a video - an SVG rig drawn in code (engines/svg-rig.js), a designer's Lottie file, or a Rive file. Covers part naming, idle/blink/talk/wave/hop, lip flap synced to voiceover, and the brand's mascot rules.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# 2D characters
|
|
7
|
+
|
|
8
|
+
Read the brand's mascot rules first (`brands/<slug>/README.md`, *Mascot*). A brand mascot is only drawn from the
|
|
9
|
+
brand's own files; never redraw it from memory.
|
|
10
|
+
|
|
11
|
+
## SVG rig (no designer file)
|
|
12
|
+
|
|
13
|
+
1. Draw the character as an inline `<svg>` from simple shapes in the brand colours. Name the parts:
|
|
14
|
+
`#body`, `#eyes`, `#mouth-closed`, `#mouth-open`, `#arm-l`, `#arm-r` (any can be left out).
|
|
15
|
+
2. `const c = SvgRig(svgEl)` then chain: `.idle(from, to)`, `.blink([t…])`, `.talk(from, to)`, `.wave(t)`, `.hop(t)`.
|
|
16
|
+
3. Arms pivot at the inner, lower corner of the arm's box. If an arm swings off the body, set `data-pivot="x% y%"` on it.
|
|
17
|
+
4. Lip flap synced to voiceover: `scripts/transcribe.sh audio/vo.wav audio/vo` gives word timings (JSON). Pass the
|
|
18
|
+
word start times as `.talk(0, 0, { beats: [...] })`.
|
|
19
|
+
5. Reuse: once a character is approved, save its SVG in `brands/<slug>/assets/characters/` and add it to the brand README.
|
|
20
|
+
|
|
21
|
+
## Lottie (designer file)
|
|
22
|
+
|
|
23
|
+
`Studio.lottie(el, "/brands/<slug>/assets/lottie/<file>.json", { start, loop, speed })`. Load
|
|
24
|
+
`/core/node_modules/lottie-web/build/player/lottie.min.js` before the runtime. Check that the file uses no
|
|
25
|
+
expressions or effects lottie-web cannot draw (render a still, compare with the designer's preview).
|
|
26
|
+
|
|
27
|
+
## Rive (designer file)
|
|
28
|
+
|
|
29
|
+
Not built yet. When the first `.riv` arrives: write `engines/rive.js` with the low-level runtime
|
|
30
|
+
(`@rive-app/canvas-advanced`): load the file, then on each seek reset the artboard and advance the animation or
|
|
31
|
+
state machine to `t` (state machines cannot jump, so step from 0 at a fixed rate). Test with two md5-matching
|
|
32
|
+
renders, then update `engines/README.md`.
|
|
33
|
+
|
|
34
|
+
## Character craft
|
|
35
|
+
|
|
36
|
+
- Anticipation before a move (a small squash before a hop), overshoot after (`back.out`).
|
|
37
|
+
- Blink every 2–4 s at irregular intervals. A still character looks dead.
|
|
38
|
+
- Hold poses long enough to read (≥ 0.4 s) on a phone.
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: make-video
|
|
3
|
+
description: Make a marketing video, reel, ad, post or carousel for any brand in brands/. Covers the whole path - brief, storyboard, approval, plates and audio, composition, render, frame-by-frame QA and a documented delivery folder. Use for any "make a reel / ad / post / video for <brand>" request.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Making a marketing video
|
|
7
|
+
|
|
8
|
+
## Start
|
|
9
|
+
|
|
10
|
+
1. No `core/render.mjs` in the project → run the `setup` skill first. Read `STATUS.md` (open work, decisions, learnings).
|
|
11
|
+
2. Find the brand in `brands/<slug>/`. Read its `README.md` and `compliance.md`. No brand folder yet → run the
|
|
12
|
+
`new-brand` skill first. Never borrow one brand's look, facts or voice for another.
|
|
13
|
+
3. Create `productions/<brand>/<ID>-<slug>/` from `productions/_TEMPLATE/`. IDs: `REEL-###`, `POST-###`, never reused.
|
|
14
|
+
|
|
15
|
+
## 1. Storyboard, then stop
|
|
16
|
+
|
|
17
|
+
Fill sections 1–4 of the production README: the problem, the benefit, 3 hook variants, one row per beat (time,
|
|
18
|
+
visual, **engine**, on-screen text, sound), caption. Pick engines with the `pick-engine` skill and say why.
|
|
19
|
+
State size, length, placement and sound (VO / music / silent).
|
|
20
|
+
|
|
21
|
+
**Stop and ask for approval.** No plates, audio or composition before the box is ticked.
|
|
22
|
+
|
|
23
|
+
## 2. Produce
|
|
24
|
+
|
|
25
|
+
| Step | How |
|
|
26
|
+
|---|---|
|
|
27
|
+
| Plates | 3D: `scene-3d` skill (Three.js live, or Blender → MP4). Live site: `capture-site` skill. Footage: `Studio.video` |
|
|
28
|
+
| Character | `character-2d` skill (SVG rig, Lottie, Rive) |
|
|
29
|
+
| Audio | ElevenLabs MCP (`.mcp.json`) for VO, SFX and music → `audio/`. Cue sheet `audio/cues.json` → `python3 scripts/mix_audio.py audio/cues.json audio/mix.wav`. Word timings for captions or lip flap: `scripts/transcribe.sh` |
|
|
30
|
+
| Composition | `composition/index.html`. Load `/core/lib/base.css`, then `/brands/<slug>/tokens.css`. Copy the pattern from `examples/engines-demo/index.html`. Text and CTA inside `.safe` (paid) or `.safe-organic` |
|
|
31
|
+
| Render | `node core/render.mjs video <comp> export/<ID>-<lang>.mp4 --audio audio/mix.wav` (posts: `still`) |
|
|
32
|
+
|
|
33
|
+
## 3. QA (never skip)
|
|
34
|
+
|
|
35
|
+
1. `scripts/qa-video.sh export/<ID>.mp4 qa` → `qa/report.md`. Every ❌ fixed. Every jump either an intended cut
|
|
36
|
+
(say so in the report) or fixed.
|
|
37
|
+
2. `node core/render.mjs stills <comp> qa/beats --at <every beat start and end> --safe`, then **look at each one**:
|
|
38
|
+
text inside the safe zone, nothing cut off, no half-loaded asset, no overlap, spelling, brand colours.
|
|
39
|
+
3. Determinism spot check when a WebGL or plate engine is used: render one frame twice, the md5s match.
|
|
40
|
+
4. Listen once with sound: VO in sync with text, music ducked under VO, −14 LUFS.
|
|
41
|
+
5. Fact check: every claim in section 6 of the README has a source. Compliance list passes.
|
|
42
|
+
|
|
43
|
+
## 4. Deliver
|
|
44
|
+
|
|
45
|
+
Fill README sections 5–7 (production log, fact check, rebuild commands). Status `delivered`. Update `STATUS.md`:
|
|
46
|
+
the production row, anything learned (what worked, an engine quirk) in *Learnings*.
|
|
47
|
+
|
|
48
|
+
## Defaults (state them in every storyboard)
|
|
49
|
+
|
|
50
|
+
| | |
|
|
51
|
+
|---|---|
|
|
52
|
+
| Reel / video ad / Story | 1080×1920, 9:16, 30 fps CFR, H.264, −14 LUFS. Paid 9–20 s, organic 15–45 s |
|
|
53
|
+
| Post / carousel | 1080×1350, 4:5 |
|
|
54
|
+
| Safe zone, paid Reels | top 14%, bottom 35%, sides 6% (`.safe`) |
|
|
55
|
+
| Safe zone, organic Reels | top 11.5%, bottom 22%, left 6%, right 11% (`.safe-organic`) |
|
|
56
|
+
| Silent pieces | All meaning on screen; captions ≥ 42 px; each line held ≥ 1.5 s |
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: new-brand
|
|
3
|
+
description: Set up a new brand (product, app, side project) in brands/<slug>/ so videos can be made for it - facts and their sources, look (colours, fonts, logo, mascot), voice, compliance and default engines. Use before the first video for a brand, or when asked to add a brand.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Adding a brand
|
|
7
|
+
|
|
8
|
+
1. `cp -R brands/_template brands/<slug>` (lowercase-kebab).
|
|
9
|
+
2. **Collect from the source, never from memory.** Website, app repo (UI copy, theme colours, fonts, assets), store
|
|
10
|
+
listing, existing ads. Ask the user for what is not findable. Record each source in *Fact sources*.
|
|
11
|
+
3. Fill `README.md`: who it is, audience, positioning, look, voice, default engines. Copy logo, fonts and reference
|
|
12
|
+
images into `assets/` (convert SVG logos to PNG with `scripts/svg-to-png.sh` when needed).
|
|
13
|
+
4. Fill `tokens.css` with the real colours and fonts. Check by rendering a quick still: a stage with the logo, the
|
|
14
|
+
colours as swatches and a headline in each font. Look at it.
|
|
15
|
+
5. Fill `compliance.md`: keep the *Always* list, add the brand's own rules (sector rules, claims that need sources,
|
|
16
|
+
what the mascot may not do).
|
|
17
|
+
6. Add the brand to `STATUS.md` (*Brands*) with any open questions, and to the brand table in `README.md`.
|
|
18
|
+
|
|
19
|
+
A brand is ready when no ❓ or placeholder is left and the check still looks right.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pick-engine
|
|
3
|
+
description: Choose the rendering engine(s) for a video beat or a whole piece - GSAP, SVG rig, Lottie, Rive, Pixi, Three.js, Blender, page capture or footage - from the look wanted, the assets available and the render cost. Use while writing a storyboard or when asked "what should we use for this?"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Picking an engine
|
|
7
|
+
|
|
8
|
+
The full table and the determinism rules are in `engines/README.md`. Read it first.
|
|
9
|
+
|
|
10
|
+
## Decide by asking, in order
|
|
11
|
+
|
|
12
|
+
1. **Is it mostly text, UI or layout?** → GSAP only. Most ads are this. Stop here unless a hero visual is needed.
|
|
13
|
+
2. **Is there a character?**
|
|
14
|
+
- A designer gave a Lottie (`.json`) → Lottie.
|
|
15
|
+
- A designer gave a Rive (`.riv`) → Rive (not built yet: say so and plan the adapter, see `engines/README.md`).
|
|
16
|
+
- No designer file → SVG rig (`engines/svg-rig.js`). Draw it from simple shapes in the brand colours.
|
|
17
|
+
3. **Is the hero a physical product or a 3D object?**
|
|
18
|
+
- Real CAD or a GLB model, or camera moves around an object → Three.js.
|
|
19
|
+
- Wants soft light, a premium look, or physics (cloth, liquid, many falling objects) → Blender plate.
|
|
20
|
+
- Cartoon 3D: Three.js `toon()` + `outline()` for speed; Blender `toon()` for quality.
|
|
21
|
+
4. **Does the brand already have a live web page with the visual (a 3D scene, a web app)?** → page capture. It reuses
|
|
22
|
+
real work and shows the real product.
|
|
23
|
+
5. **Lots of small moving things (confetti, particles, crowds)?** → Pixi on top.
|
|
24
|
+
6. **Real people or places?** → footage plate (stock or AI video). Check the brand's compliance on AI disclosure.
|
|
25
|
+
Never use generated footage to show product features.
|
|
26
|
+
|
|
27
|
+
## Costs to weigh
|
|
28
|
+
|
|
29
|
+
| Engine | Render cost (6 s at 1080×1920, M-series Mac) | Change cost |
|
|
30
|
+
|---|---|---|
|
|
31
|
+
| GSAP / SVG / Lottie / Pixi / Three.js | seconds (demo: 16 s for 180 frames, all engines) | edit HTML, re-render |
|
|
32
|
+
| Blender (Eevee) | ~9 s per second of video at 540×960 for the simple example scene, including start-up; much more for full size, complex scenes or Cycles | edit the Python scene, re-render the plate, then the composition |
|
|
33
|
+
| Page capture | ~1 s per frame (the Bike Logger capture took ≈5 min for 28 s) | re-drive the page, re-capture |
|
|
34
|
+
|
|
35
|
+
## Write the choice down
|
|
36
|
+
|
|
37
|
+
In the storyboard's *Engines* field and per beat: the engine and one reason ("Three.js: real CAD of the box,
|
|
38
|
+
camera orbit"). More than two engines in one piece → justify it or simplify.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: scene-3d
|
|
3
|
+
description: Build a 3D shot for a video - live in the composition with Three.js (engines/three.js) or as a pre-rendered Blender plate (engines/blender). Covers toon/cartoon looks, product and CAD models, camera moves, physics, and keeping every frame deterministic.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# 3D shots
|
|
7
|
+
|
|
8
|
+
## Three.js (live layer)
|
|
9
|
+
|
|
10
|
+
- Use `threeLayer(el, { update(t), background, fov })` from `/engines/three.js`. Add an import map for `three`
|
|
11
|
+
(see `examples/engines-demo/index.html`).
|
|
12
|
+
- `update(t)` sets every transform from `t`. Camera moves: keyframe with a GSAP tween on a plain object
|
|
13
|
+
(`tl.to(cam, { x: 4, z: 8, duration: 2 }, 3)`) and copy it into `camera.position` in `update`.
|
|
14
|
+
- Models: `GLTFLoader` from `three/addons/loaders/GLTFLoader.js` (add `"three/addons/": "/core/node_modules/three/examples/jsm/"`
|
|
15
|
+
to the import map). Wrap the load in `Studio.wait(...)`. Put model files in the brand's `assets/models/` or the production's `composition/`.
|
|
16
|
+
- Cartoon look: `toon(color, { steps })` + `outline(mesh, { thickness })`. Hard light: one `DirectionalLight` + low `AmbientLight`.
|
|
17
|
+
- Animated GLB clips: create an `AnimationMixer`, then in `update` call `mixer.setTime(t - start)`. Never `mixer.update(delta)`.
|
|
18
|
+
- HTML tags that follow 3D points: in `update(t)`, call `camera.updateMatrixWorld()` and the parent's
|
|
19
|
+
`updateMatrixWorld()` before `vector.applyMatrix4(obj.matrixWorld).project(camera)`. Three.js refreshes these only
|
|
20
|
+
inside `render()`, so without it a tag follows the previous frame. Example: `productions/bike-logger/REEL-002-engines`.
|
|
21
|
+
- CAD meshes as JSON (vertices + indices): `toNonIndexed()` + `computeVertexNormals()` gives crisp flat facets.
|
|
22
|
+
Render with `threeLayer(..., { pixelRatio: 2 })` for supersampled edges.
|
|
23
|
+
- Physics: bake it (simulate at a fixed 1/120 s step into an array before the first frame) and look up by `t`.
|
|
24
|
+
|
|
25
|
+
## Blender (plate)
|
|
26
|
+
|
|
27
|
+
- A scene script defines `build(ctx)`; copy `engines/blender/example_scene.py`. `ctx` gives `toon(rgb)`,
|
|
28
|
+
`key(obj, "location", t, value)`, `size`, `fps`, `duration`, `frames`, `scene`.
|
|
29
|
+
- Render: `engines/blender/render.sh scene.py composition/plates/<name>.mp4 --size 1080x1920 --duration 4 [--engine EEVEE|CYCLES|WORKBENCH]`.
|
|
30
|
+
Draft at `--size 540x960`, final at full size.
|
|
31
|
+
- Backdrop colour: show it to the camera only (Light Path "Is Camera Ray" mix, as in the example), or it lights the
|
|
32
|
+
scene and flattens toon bands.
|
|
33
|
+
- Colour management is set to *Standard* so brand hex colours come out as typed.
|
|
34
|
+
- Physics and simulations: bake them in the scene script before rendering.
|
|
35
|
+
- In the composition: `<video src="plates/<name>.mp4">` + `Studio.video(el, { start })`. Plates have no alpha; put
|
|
36
|
+
text and characters on top with HTML layers.
|
|
37
|
+
|
|
38
|
+
## Check
|
|
39
|
+
|
|
40
|
+
Render the key beats as stills and look at them. For Three.js, render one frame twice and compare md5s. Note render
|
|
41
|
+
times in the production log.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: setup
|
|
3
|
+
description: Set up Reel Studio in the current project - copy the renderer, engines, scripts and templates into it, install dependencies and check Chrome, ffmpeg and the optional tools. Use right after installing the plugin, when asked to "set up reel studio", or when another skill needs core/render.mjs and it is missing. Also updates the kit with --update.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Setting up Reel Studio in a project
|
|
7
|
+
|
|
8
|
+
Compositions load `/core/...`, `/engines/...` and `/brands/...` from the project root, so the kit lives in the
|
|
9
|
+
project, not only in the plugin. This copies it in once.
|
|
10
|
+
|
|
11
|
+
1. Kit root: `${CLAUDE_PLUGIN_ROOT}`. If that text is shown literally (not a path), the skill is not running from
|
|
12
|
+
the plugin: the kit is already in this project (a clone of the repo, or `npx reel-studio init`). Skip to step 3;
|
|
13
|
+
to update the kit there, run `npx reel-studio@latest init --update`.
|
|
14
|
+
2. Run, from the project root:
|
|
15
|
+
```bash
|
|
16
|
+
bash "${CLAUDE_PLUGIN_ROOT}/scripts/init-project.sh" "${CLAUDE_PLUGIN_ROOT}" .
|
|
17
|
+
```
|
|
18
|
+
It copies `core/`, `engines/`, `scripts/`, `examples/engines-demo/`, `brands/_template/` and
|
|
19
|
+
`productions/_TEMPLATE/`, adds `CLAUDE.md`, `STATUS.md` and `.gitignore` if missing, never overwrites existing
|
|
20
|
+
files, runs `npm install` in `core/` and prints which tools are found.
|
|
21
|
+
To refresh `core/`, `engines/` and `scripts/` after a plugin update, add `--update` as the third argument.
|
|
22
|
+
3. Fix any ❌ the script prints: tell the user the one command for each (e.g. `brew install ffmpeg`). Chrome
|
|
23
|
+
elsewhere → `CHROME_PATH=/path/to/chrome`.
|
|
24
|
+
4. Check the machine with one frame (no Blender needed):
|
|
25
|
+
```bash
|
|
26
|
+
node core/render.mjs still examples/engines-demo/index.html /tmp/reel-studio-check.png --t 3
|
|
27
|
+
```
|
|
28
|
+
Look at the PNG: a toon donut with "Three.js" above it means rendering works.
|
|
29
|
+
5. Next: no brand in `brands/` yet → offer the `new-brand` skill. Otherwise → `make-video`.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Murathan Yıldırım
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,120 @@
|
|
|
1
|
-
|
|
1
|
+
<h1 align="center">🎬 Reel Studio</h1>
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
<p align="center">
|
|
4
|
+
<a href="https://www.npmjs.com/package/reel-studio"><img src="https://img.shields.io/npm/v/reel-studio?color=e8a33d" alt="npm"></a>
|
|
5
|
+
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-3ecf7a" alt="MIT"></a>
|
|
6
|
+
<img src="https://img.shields.io/badge/Claude%20Code-plugin-8b93a1" alt="Claude Code plugin">
|
|
7
|
+
</p>
|
|
8
|
+
|
|
9
|
+
<p align="center"><b>Make marketing reels, ads and posts with code, for any brand, with Claude Code.</b><br>
|
|
10
|
+
Plain HTML pages on one clock → headless Chrome → MP4. Every frame is reproducible.</p>
|
|
11
|
+
|
|
12
|
+
<p align="center">
|
|
13
|
+
<img src="docs/media/bike-logger-reel.gif" width="240" alt="Bike Logger reel">
|
|
14
|
+
|
|
15
|
+
<img src="docs/media/engines-demo.gif" width="240" alt="Engines demo reel">
|
|
16
|
+
</p>
|
|
17
|
+
<p align="center"><sub>Left: <b>Bike Logger</b> kit reel (28 s, made by an agent from the product's live site) · Right: one 6 s reel using every engine<br>
|
|
18
|
+
Full-quality MP4s: <a href="https://github.com/murathany7/reel-studio/releases/latest">Releases</a></sub></p>
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## How it works
|
|
23
|
+
|
|
24
|
+
<p align="center"><img src="docs/media/pipeline.svg" width="860" alt="Brief → Storyboard → You approve → HTML on one clock → Frame QA → MP4"></p>
|
|
25
|
+
|
|
26
|
+
You ask Claude Code: *"make a reel for <brand>"*. It writes a brief and a storyboard, waits for your yes, builds the
|
|
27
|
+
piece as an HTML page, renders every frame, checks them, and delivers a folder with the video and how it was made.
|
|
28
|
+
|
|
29
|
+
## Example: Bike Logger
|
|
30
|
+
|
|
31
|
+
A robotics kit reel, built by capturing the product's own 3D web page and laying brand copy on top.
|
|
32
|
+
|
|
33
|
+
<p align="center"><img src="docs/media/bike-logger-strip.jpg" alt="Frames from the Bike Logger reel"></p>
|
|
34
|
+
|
|
35
|
+
| What | Where |
|
|
36
|
+
|---|---|
|
|
37
|
+
| 🏷️ The brand: facts + sources, colours, fonts, voice, rules | [`brands/bike-logger/`](brands/bike-logger) |
|
|
38
|
+
| 📝 Brief → storyboard → fact check → delivery notes | [`productions/bike-logger/REEL-001-kit/README.md`](productions/bike-logger/REEL-001-kit/README.md) |
|
|
39
|
+
| 🧩 The composition (one HTML page) | [`composition/index.html`](productions/bike-logger/REEL-001-kit/composition/index.html) |
|
|
40
|
+
| 🔍 The QA report | [`qa/report.md`](productions/bike-logger/REEL-001-kit/qa/report.md) |
|
|
41
|
+
|
|
42
|
+
<table><tr>
|
|
43
|
+
<td width="50%"><img src="brands/bike-logger/assets/reference/brand-check.png" alt="Brand check card"><br><sub><b>Brand check</b>: one still that proves the brand's look renders right before any video is made.</sub></td>
|
|
44
|
+
<td width="50%"><img src="docs/media/qa-contact-sheet.jpg" alt="QA contact sheet with safe zones"><br><sub><b>Frame QA</b>: a contact sheet with the platform's safe zones drawn over it, plus a motion check that flags jumps.</sub></td>
|
|
45
|
+
</tr></table>
|
|
46
|
+
|
|
47
|
+
## Engines, all on one clock
|
|
48
|
+
|
|
49
|
+
<p align="center"><img src="docs/media/engines-strip.jpg" alt="Blender, Three.js, SVG rig and Pixi frames"></p>
|
|
50
|
+
|
|
51
|
+
| Look | Engine |
|
|
52
|
+
|---|---|
|
|
53
|
+
| Text, UI, layouts | HTML/CSS + GSAP |
|
|
54
|
+
| Cartoon character | SVG rig · Lottie |
|
|
55
|
+
| Particles | Pixi.js |
|
|
56
|
+
| 3D, toon 3D | Three.js |
|
|
57
|
+
| Premium 3D, physics | Blender (pre-rendered plate) |
|
|
58
|
+
| Your live website | Page capture |
|
|
59
|
+
|
|
60
|
+
Mix them freely in one piece. Rules for keeping each frame a pure function of `t`: [`engines/README.md`](engines/README.md).
|
|
61
|
+
|
|
62
|
+
## Install
|
|
63
|
+
|
|
64
|
+
**In Claude Code** (recommended), two commands:
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
/plugin marketplace add murathany7/reel-studio
|
|
68
|
+
/plugin install reel-studio@reel-studio
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Then, in the folder where your videos should live, ask:
|
|
72
|
+
|
|
73
|
+
> *Set up Reel Studio here*
|
|
74
|
+
|
|
75
|
+
It copies the renderer into the folder, installs it and checks Chrome and ffmpeg. You need
|
|
76
|
+
**Node 18+, Google Chrome and ffmpeg** (`brew install ffmpeg`). Blender is optional, for 3D plates.
|
|
77
|
+
|
|
78
|
+
**Or with npm**, in any folder (no plugin needed; the skills are copied into `.claude/skills/`):
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
npx reel-studio init
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
**Or with Python or Homebrew**, the same kit and the same `init` (the renderer still runs on Node):
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
pipx install reel-studio && reel-studio init # or: uvx reel-studio init
|
|
88
|
+
brew install murathany7/reel-studio/reel-studio && reel-studio init # brings Node and ffmpeg
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
**Or clone it** and work inside the repo:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
git clone https://github.com/murathany7/reel-studio && cd reel-studio
|
|
95
|
+
cd core && npm install && cd ..
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Use
|
|
99
|
+
|
|
100
|
+
> *Add my product as a new brand: https://my-product.com* → facts, colours, fonts, voice, rules
|
|
101
|
+
> *Make a 15 s reel for <brand> about <feature>* → brief and storyboard, then the video once you approve
|
|
102
|
+
|
|
103
|
+
## What's inside
|
|
104
|
+
|
|
105
|
+
```
|
|
106
|
+
core/ renderer (HTML → MP4/PNG) and the Studio clock
|
|
107
|
+
engines/ one adapter per engine
|
|
108
|
+
brands/ one folder per brand (_template to start)
|
|
109
|
+
productions/ one folder per piece (brief, composition, audio, QA, export)
|
|
110
|
+
scripts/ audio mix, transcription, QA, frame tools
|
|
111
|
+
.claude/skills setup · make-video · new-brand · pick-engine · scene-3d · character-2d · capture-site
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Contributing
|
|
115
|
+
|
|
116
|
+
Open source and early. New engine adapters, brand examples and QA checks are all welcome; open an issue or a PR.
|
|
117
|
+
|
|
118
|
+
## License
|
|
119
|
+
|
|
120
|
+
[MIT](LICENSE). Brand folders (logos, copy, product facts) belong to their owners and are included only as examples.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// npx reel-studio init [dir] [--update]: copy the Reel Studio kit and its Claude Code skills into a project.
|
|
3
|
+
import { spawnSync } from "node:child_process";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
|
|
7
|
+
const KIT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
8
|
+
const [cmd, ...rest] = process.argv.slice(2);
|
|
9
|
+
|
|
10
|
+
if (cmd === "init") {
|
|
11
|
+
if (process.platform === "win32") {
|
|
12
|
+
console.error("reel-studio init needs bash and rsync (macOS, Linux or WSL).");
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
const r = spawnSync("bash", [path.join(KIT, "scripts/init-project.sh"), KIT, ...rest, "--with-skills"], { stdio: "inherit" });
|
|
16
|
+
if (r.status === 0) {
|
|
17
|
+
console.log(`
|
|
18
|
+
Done. Open this folder in Claude Code and ask:
|
|
19
|
+
"Add my product as a new brand: https://…"
|
|
20
|
+
"Make a 15 s reel for <brand> about <feature>"`);
|
|
21
|
+
}
|
|
22
|
+
process.exit(r.status ?? 1);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
console.log(`Reel Studio: marketing videos made with code, run by Claude Code.
|
|
26
|
+
|
|
27
|
+
npx reel-studio init [dir] copy the kit and skills into dir (default: here) and install it
|
|
28
|
+
npx reel-studio init [dir] --update refresh core/, engines/, scripts/ and skills from this version
|
|
29
|
+
|
|
30
|
+
Claude Code plugin instead: /plugin marketplace add murathany7/reel-studio
|
|
31
|
+
More: https://github.com/murathany7/reel-studio`);
|
|
32
|
+
process.exit(cmd && cmd !== "help" && cmd !== "--help" ? 1 : 0);
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# <Brand name>
|
|
2
|
+
|
|
3
|
+
> Copy this folder to `brands/<slug>/` (lowercase-kebab, e.g. `yuppo`, `bike-logger`) and fill every section.
|
|
4
|
+
> The `new-brand` skill walks through it. A brand is ready when every ❓ is gone.
|
|
5
|
+
|
|
6
|
+
## Who it is
|
|
7
|
+
|
|
8
|
+
| | |
|
|
9
|
+
|---|---|
|
|
10
|
+
| Product | (one line: what it is, for whom) |
|
|
11
|
+
| Website / store links | |
|
|
12
|
+
| Audience | (who watches; what they care about; where they scroll) |
|
|
13
|
+
| Positioning | (the one thing it does better) |
|
|
14
|
+
| Languages | (first language; later ones) |
|
|
15
|
+
| Platforms | Instagram Reels / TikTok / YouTube Shorts / Meta ads / … |
|
|
16
|
+
|
|
17
|
+
## Fact sources (never invent facts)
|
|
18
|
+
|
|
19
|
+
Where true product facts come from, in order of trust. Anything not found here is **not claimed**.
|
|
20
|
+
|
|
21
|
+
| Source | Path or URL | What it settles |
|
|
22
|
+
|---|---|---|
|
|
23
|
+
| e.g. app UI copy | `../<app-repo>/…` | feature names, exact wording |
|
|
24
|
+
| e.g. live website | | specs, prices |
|
|
25
|
+
|
|
26
|
+
## Look
|
|
27
|
+
|
|
28
|
+
| | |
|
|
29
|
+
|---|---|
|
|
30
|
+
| Colours | in `tokens.css` (name, hex, when to use) |
|
|
31
|
+
| Fonts | in `assets/fonts/` + `tokens.css` |
|
|
32
|
+
| Logo | `assets/logo.svg` (+ rules: clear space, min size, on dark/light) |
|
|
33
|
+
| Mascot / characters | (if any: files, engine — SVG rig, Lottie or Rive, what it may and may not do) |
|
|
34
|
+
| Default engines | (e.g. GSAP + SVG rig for a cartoon mascot, Three.js for a product) |
|
|
35
|
+
| References | `assets/reference/` (what to take from each) |
|
|
36
|
+
|
|
37
|
+
## Voice
|
|
38
|
+
|
|
39
|
+
- Sounds like: …
|
|
40
|
+
- Never sounds like: …
|
|
41
|
+
- Words we use / avoid: …
|
|
42
|
+
- Voiceover voice (ElevenLabs voice id, settings) and music mood: …
|
|
43
|
+
|
|
44
|
+
## Compliance
|
|
45
|
+
|
|
46
|
+
See `compliance.md`: what must never appear, claims that need a source, platform rules.
|
|
File without changes
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Compliance: <Brand name>
|
|
2
|
+
|
|
3
|
+
Every piece is checked against this list before delivery. A ❌ blocks delivery.
|
|
4
|
+
|
|
5
|
+
## Always (every brand)
|
|
6
|
+
|
|
7
|
+
- No invented product facts, numbers, prices, reviews or testimonials. Each fact traces to a source in `README.md`.
|
|
8
|
+
- No competitor names, logos or copy. Reference ads are for structure only.
|
|
9
|
+
- No third-party logos, certifications or "as seen in" without written permission.
|
|
10
|
+
- AI-generated people, voices or footage are disclosed where the platform requires it.
|
|
11
|
+
- Text, logo and CTA sit inside the safe zone for the placement (paid vs organic).
|
|
12
|
+
- Music and SFX are licensed for commercial use (ElevenLabs output or a cleared library).
|
|
13
|
+
|
|
14
|
+
## This brand
|
|
15
|
+
|
|
16
|
+
| Rule | Why |
|
|
17
|
+
|---|---|
|
|
18
|
+
| (e.g. no medical promises) | (law, platform policy, brand) |
|
|
19
|
+
| | |
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/* <Brand name> tokens. Load after /core/lib/base.css.
|
|
2
|
+
* Fonts live in brands/<slug>/assets/fonts/ and are loaded with absolute paths. */
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
@font-face { font-family: "BrandDisplay"; src: url("/brands/<slug>/assets/fonts/Display.woff2") format("woff2"); font-weight: 400 800; }
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
:root {
|
|
9
|
+
--bg: #ffffff;
|
|
10
|
+
--ink: #111111;
|
|
11
|
+
--accent: #ff5a1f;
|
|
12
|
+
|
|
13
|
+
--font-display: system-ui, sans-serif;
|
|
14
|
+
--font-body: system-ui, sans-serif;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/* Type scale for 1080-wide stages */
|
|
18
|
+
.display { font-family: var(--font-display); font-weight: 700; letter-spacing: -0.02em; line-height: 1.04; }
|
|
19
|
+
.hook { font-size: 96px; }
|
|
20
|
+
.headline { font-size: 72px; }
|
|
21
|
+
.caption { font-size: 52px; font-weight: 600; line-height: 1.2; }
|
|
22
|
+
.support { font-size: 42px; font-weight: 500; line-height: 1.25; }
|
|
23
|
+
.fine { font-size: 28px; line-height: 1.3; opacity: .75; }
|
|
24
|
+
.hl { color: var(--accent); }
|
package/core/README.md
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Core renderer
|
|
2
|
+
|
|
3
|
+
Turns an HTML composition into a **frame-exact MP4** or **PNG**. For every frame it calls `window.__seek(t)`,
|
|
4
|
+
waits for every engine to draw, screenshots the stage in Chrome, and pipes the PNGs into ffmpeg.
|
|
5
|
+
|
|
6
|
+
Requirements: Node 18+, Google Chrome, ffmpeg. Run `npm install` in `core/` once. Blender (optional) for 3D plates.
|
|
7
|
+
|
|
8
|
+
## Commands (from the repo root)
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
# Video (size, fps, duration from Studio.setup), optional mixed audio track
|
|
12
|
+
node core/render.mjs video productions/<brand>/<ID>/composition/index.html productions/<brand>/<ID>/export/<ID>.mp4 --audio <mix.wav>
|
|
13
|
+
|
|
14
|
+
# One frame (post, cover, or a beat to check). --safe draws the Reels safe zones
|
|
15
|
+
node core/render.mjs still <composition.html> <out.png> --t 2.4 [--safe]
|
|
16
|
+
|
|
17
|
+
# Many frames for review
|
|
18
|
+
node core/render.mjs stills <composition.html> <out_dir> --every 0.5
|
|
19
|
+
node core/render.mjs stills <composition.html> <out_dir> --at 0,0.4,1.2,3.0 --safe
|
|
20
|
+
|
|
21
|
+
# Audio: VO + music (auto-ducked) + SFX → one track at -14 LUFS
|
|
22
|
+
python3 scripts/mix_audio.py <audio/cues.json> <audio/mix.wav>
|
|
23
|
+
|
|
24
|
+
# QA: specs, 30 fps CFR, loudness, hitches/jumps, frozen/black, safe-zone sheet
|
|
25
|
+
scripts/qa-video.sh <video.mp4> <qa_dir> [1080x1920] [30]
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Preview live: serve the repo root (`python3 -m http.server 8080`) and open the composition.
|
|
29
|
+
Add `?t=2.5` to freeze a frame, `?safe=1` for the safe-zone overlay.
|
|
30
|
+
|
|
31
|
+
## Writing a composition
|
|
32
|
+
|
|
33
|
+
```html
|
|
34
|
+
<link rel="stylesheet" href="/core/lib/base.css">
|
|
35
|
+
<link rel="stylesheet" href="/brands/<brand>/tokens.css">
|
|
36
|
+
<div id="stage">
|
|
37
|
+
<div class="layer" id="bg"></div> <!-- one layer per engine -->
|
|
38
|
+
<div class="safe"> … text … </div>
|
|
39
|
+
</div>
|
|
40
|
+
<script src="/core/node_modules/gsap/dist/gsap.min.js"></script>
|
|
41
|
+
<script src="/core/lib/runtime.js"></script>
|
|
42
|
+
<script>
|
|
43
|
+
const tl = Studio.setup({ width: 1080, height: 1920, fps: 30, duration: 12 });
|
|
44
|
+
tl.from(".hook", { y: 60, opacity: 0, duration: 0.5, ease: "back.out(2)" }, 0.1);
|
|
45
|
+
</script>
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Engines (Three.js, Pixi, SVG rig, Lottie, plates): see `engines/README.md` and the working example in
|
|
49
|
+
`examples/engines-demo/index.html`.
|
|
50
|
+
|
|
51
|
+
| Runtime API | |
|
|
52
|
+
|---|---|
|
|
53
|
+
| `Studio.setup({ width, height, fps, duration })` | Size the stage; returns the paused GSAP timeline `Studio.tl` |
|
|
54
|
+
| `Studio.onSeek(fn)` | `fn(t)` draws a layer at time `t` (may return a promise) |
|
|
55
|
+
| `Studio.wait(promise)` | Hold frame 0 until it settles |
|
|
56
|
+
| `Studio.random(seed)` | Seeded random number generator |
|
|
57
|
+
| `Studio.lottie(el, path, { start, loop, speed })` | Lottie layer |
|
|
58
|
+
| `Studio.video(el, { start })` | Pre-rendered plate (Blender, capture, footage) |
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/* Brand-neutral base for every composition. A brand adds its own tokens.css
|
|
2
|
+
* (colours, fonts, type scale) from brands/<name>/tokens.css after this file. */
|
|
3
|
+
|
|
4
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
5
|
+
html, body { background: #000; }
|
|
6
|
+
body {
|
|
7
|
+
-webkit-font-smoothing: antialiased;
|
|
8
|
+
text-rendering: geometricPrecision;
|
|
9
|
+
font-family: var(--font-body, system-ui, sans-serif);
|
|
10
|
+
color: var(--ink, #111);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/* The stage is always exactly the export size (set by runtime.js). */
|
|
14
|
+
#stage { position: relative; overflow: hidden; background: var(--bg, #fff); }
|
|
15
|
+
.layer { position: absolute; inset: 0; }
|
|
16
|
+
.layer > canvas { display: block; width: 100%; height: 100%; }
|
|
17
|
+
|
|
18
|
+
/* ---- Safe zones (9:16 Reels / Stories) ----
|
|
19
|
+
.safe paid Reels (Meta): top 14%, bottom 35%, sides 6%
|
|
20
|
+
.safe-organic organic Reels: top 11.5%, bottom 22%, left 6%, right 11%
|
|
21
|
+
runtime.js adds .show-safe when ?safe=1 (render with --safe). */
|
|
22
|
+
.safe { position: absolute; left: 6%; right: 6%; top: 14%; bottom: 35%; }
|
|
23
|
+
.safe-organic { position: absolute; left: 6%; right: 11%; top: 11.5%; bottom: 22%; }
|
|
24
|
+
.show-safe #stage::after {
|
|
25
|
+
content: ""; position: absolute; inset: 0; pointer-events: none; z-index: 9999;
|
|
26
|
+
background:
|
|
27
|
+
linear-gradient(rgba(255,0,0,.28), rgba(255,0,0,.28)) top / 100% 14% no-repeat,
|
|
28
|
+
linear-gradient(rgba(255,0,0,.28), rgba(255,0,0,.28)) bottom / 100% 35% no-repeat,
|
|
29
|
+
linear-gradient(rgba(255,0,0,.28), rgba(255,0,0,.28)) left / 6% 100% no-repeat,
|
|
30
|
+
linear-gradient(rgba(255,0,0,.28), rgba(255,0,0,.28)) right / 6% 100% no-repeat;
|
|
31
|
+
}
|