Install Omd 3d Hero
Pick your agent. Every command is ready to paste.
npx skills add kwakseongjae/oh-my-design --skill omd-3d-heroThe open skills CLI detects your agents (Claude Code, Cursor, Codex and 70+ more) and installs to the right folder. Add -g to install globally.
01
Install it
Run the command above, or upload the folder in Claude.
02
Ask naturally
Describe the task. Your agent loads omd-3d-hero when the request matches its description.
03
Name it if needed
If it does not trigger, say “use the omd-3d-hero skill”. In Codex, type $omd-3d-hero.
The skill itself
SKILL.md
omd-3d-hero — a rendered first screen, from DESIGN.md
Most "3D hero" output is a floating torus on a purple gradient. This skill does one thing instead: a studio set lit like a Blender clay render — a seamless cyclorama, soft key light, contact shadows, ambient occlusion — with a floating pile of objects in the brand's own colors, shaped by the brand's own radius tokens (pill buttons → capsules and spheres, 16px corners → rounded cubes) and seeded by the brand color, so every DESIGN.md gets its own arrangement. On desktop the pile is alive: it drifts, the cursor pushes through it, a click scatters it and it gathers again, and setSpec() morphs it into another brand. The headline stays real text on clean backdrop to the left; no object can cross into that column, and contrast is measured on the worst frame, including during interaction.
One spec, two engines. scripts/spec.mjs turns DESIGN.md into a scene spec. The same spec drives:
| Branch | When | What it ships | Cost |
|---|---|---|---|
three (default) |
always available | poster (baked from the live scene) + the live, interactive scene on desktop | three@0.186.1 + addons + scene: ~229 KiB gzip from the CDN, ~150 KiB bundled; 8.3 ms median frame with AO on Apple M5 Pro (upper bound) |
blender |
Blender ≥ 4.2 on the machine (scripts/blender.mjs finds it) |
Cycles poster + an 8 s EEVEE loop (loop.webm / loop.mp4) of the same pile, gently bobbing |
no runtime JS for the scene; video 71–208 KiB at 1280px |
Both branches use the same HTML template, the same poster rule and the same verify script. Interaction (pointer, scatter, brand and scheme switch) is three.js only; the Blender branch renders the same settled pile, so its poster matches the three.js poster object for object (verified for the light scheme; the dark Blender branch is not verified yet). The Blender branch runs Blender headless from the CLI (blender -b -P), never through an MCP server: no live session, no model-written Python executed inside Blender.
Working files: examples/hero.html (template), engines/three/scene.js + engines/three/pile.js (the simulation, also run by spec.mjs in Node), engines/blender/scene.py, scripts/spec.mjs, scripts/blender.mjs, scripts/verify.mjs.
1. When to use / when not
Use when:
- a launch, product, campaign or showcase page wants a first screen that looks rendered (light, depth, material), not drawn;
- the project has a DESIGN.md with a brand color, a surface/canvas color and ideally radius tokens.
Don't use when:
- there is no DESIGN.md or no brand color.
spec.mjsexits 3; runomd-init. Never pick "nice" colors (unknown means absent); - the page needs the product itself in 3D (a phone, a bottle). That is a product render from a model or a parts list, not this skill;
- the brand is flat and graphic. Two flat inks fit better:
omd-shader-hero; - the page already has a hero video or photo. One hero, one medium.
2. Procedure
Spec from tokens.
node <skill>/scripts/spec.mjs DESIGN.md --out spec.json # light scheme (default) node <skill>/scripts/spec.mjs DESIGN.md --scheme dark --out spec.jsonIt reads three color formats (Core v2
- **color.<role>**, Foundations prose pairs, frontmattercolors:) and everyrounded:block, then picks:- backdrop: a light neutral whose role names a surface (canvas/surface/background), preferring a tinted one over pure white (a lit white set blows out). Dark scheme: the lightest neutral or ink token under luminance 0.08, so a near-black rather than pure black (a pure-black set shows no floor and no shadow; the mirror of the white rule);
- primary: the role named primary/brand, else the most saturated brand color; accent only if DESIGN.md names a second brand color with real chroma (palette steps like
red100don't count, and neither does a near-white tint like karrot'sbrand-tint #fff5f0, whose HSL saturation is 1 but chroma 0.06); - ink and clay, one satin object and the matte ones. Light: the darkest ink and the lightest neutral (white clay on a tinted set reads by its shading). Dark: the ends swap, and clay can't simply be mirrored: a near-black clay on a near-black set vanishes. So ink = the lightest token (a light satin object) and clay = the darkest grey neutral or ink token (chroma < 0.15) that clears 2:1 against the chosen backdrop, else the lightest; never the backdrop itself;
- shape:
pillif afull/pill/circleradius ≥ 999 exists (capsules, spheres),squareif every radius ≤ 2px (boxes), elserounded(rounded cubes, tall blocks, spheres) with bevel = max radius / 64 (16px → 0.25); - glass: clear glass tinted by the primary color through its depth (absorption, not paint), one per size tier;
- pile: 28 bodies in three size tiers, materials from fixed per-tier lists (the brand color dominates), seeded by the primary hex, then settled for 10 s in Node by
engines/three/pile.js. The settled poses go into the spec: the poster, the first live frame and the Blender render all start from them; - text: the ink with the best contrast on the backdrop token (dark: among light tokens, luminance > 0.5); CTA label: DESIGN.md's own
on-primaryif it reaches 3:1 at 19px bold (WCAG large text), else the best of white / text / black. Accessibility wins over brand match.
What
--scheme darkpicks for the three fixtures (every hex is a DESIGN.md token):Fixture backdrop text primary ink (satin) clay (matte) toss grey800#333d4bcanvas#ffffff (11.0:1)primary#3182f6canvas#ffffffgrey600#6b7684karrot foreground#212124canvas#ffffff (16.1:1)primary#ff6f0fcanvas#ffffffmuted#868b94baemin corporate-foreground#232324 (notbaemin-dark#000000)canvas#ffffff (15.7:1)primary#0cefd3canvas#ffffffcorporate-muted#6c6d6fThe light and dark specs of one DESIGN.md have the same 28 bodies (same seed, shapes and settled poses); only materials and light levels differ, so
setSpeccan switch between them (§3).Write the page. Copy
examples/hero.htmlnext toscene.js, then fill it:node <skill>/scripts/spec.mjs DESIGN.md --write path/to/hero.html --poster poster.webpThat rewrites
#hero-vars(--hero-bg,--hero-text,--hero-brand,--hero-on-brand,--hero-radius,--hero-poster) and the#hero-specJSON. Setdata-engine="three"or"video"on.hero, anddata-scene-srcto wherescene.jslives;pile.jssits in the same folder.Copy is real. Headline + one sentence + one CTA, in the user's words. Never invent metrics, customers or investors. Korean display type follows
/hangul:keep-all+overflow-wrap: anywhere, line-height 1.3, tracking -0.02em,text-wrap: balance,font-synthesis: none.Make the poster.
- three:
node <skill>/scripts/verify.mjs path/to/hero.html --bake poster.webprenders the live scene at t = 0 on the GPU, 1920×1080, and encodes WebP in the browser. - blender:
node <skill>/scripts/blender.mjs spec.json out/ --loop, thennode <skill>/scripts/verify.mjs --encode out/poster.png out/poster.webp. Putloop.webm,loop.mp4andposter.webpnext to the page;data-video-srcis the path without extension.
- three:
Verify on a real GPU (§5) and look at the frames at full size.
3. The scene
One archetype for now, studio with the pile-right composition:
- Cyclorama: floor → quarter-circle cove (r 2.5) → wall, one profile shared by both engines, in the backdrop token (roughness 0.82).
- The pile: 28 objects held by a spring toward one point near 70% of the frame width, colliding as capsules (a sphere is a capsule with no length, a rounded cube is approximated by a sphere). Fixed 120 Hz step, seeded, no physics library:
pile.jsis ~5 KiB gzip and runs the same in Node and the browser. - Hard planes, applied after every collision pass: the floor, the back wall, a ceiling, a front limit (nothing comes at the lens), the right frame edge, and the copy guard: the plane through the camera that projects to the screen's vertical center line. A body wholly on its right side projects wholly right of that line, so no push, scatter or brand switch can cover the headline. The horizontal view is held at 16:9 framing on narrower boxes, so the guard holds there too.
- Light: a large soft key from the upper left (Blender area light 6 m; three
DirectionalLight+VSMShadowMapradius 14), a weaker rim from behind right (a sun in Blender: an area light that close to the wall draws a hard-edged wedge across it), and an environment fill (Blender world = backdrop color at 0.3; threeRoomEnvironmentat 0.3). three adds GTAO (ambient occlusion) where objects nearly touch; Cycles gets it from path tracing. - Dark set (
--scheme dark): the same key; the rim doubles (0.35 → 0.7) to cut the silhouettes out of the dark backdrop; the environment fill doubles (lights.world0.3 → 0.6) so the brand objects keep their token color instead of sinking toward the set. Two materials take the environment at their own strength (env, three branch): the backdrop keeps the light-scheme 0.3, so the set renders close to its token (toss: worst headline frame 10.5:1 against 11.0:1 on the token) rather than a lifted slate; the glass reflects at 2.4, because on a dark set it transmits the dark wall and reads only by what it reflects (with the scene's fill it was a muddy brown/teal ball; lowering the absorption didn't help). Blender readslights.rimandlights.worldbut ignoresenv(Cycles path-traces the glass); the dark Blender branch has not been rendered or verified yet. - Color: Khronos PBR Neutral in both engines (
NeutralToneMapping, applied byOutputPasswhen the composer is on / view transform "Khronos PBR Neutral"). It keeps base colors close to the tokens; AgX and Filmic desaturate brand colors.
Motion (three.js):
- At rest the attraction point drifts on an 8 s figure (±0.22 / 0.1 / 0.18), so the pile churns slowly. About 0.7 s after going live the pile takes one soft breath outward, so it reads as alive before anyone touches it. It starts from the poster pose, so the poster → canvas swap is invisible.
- Pointer: a capsule along the view ray through the pile's depth (radius 0.55) pushes objects; the camera leans toward the pointer (±0.22 / 0.12). Click on the pile: an outward kick (4.5 m/s, 3 m reach), and the spring gathers it again. Touch is ignored (phones get the poster).
api.setSpec(nextSpec): each object shrinks (140 ms), takes the next spec's shape and material, and pops back with a damped spring (500 ms), staggered 22 ms apart; the backdrop color and the light levels (key, rim, environment fill, exposure, the backdrop's own fill) ease over 600 ms. Collision radii scale with the objects, so the pile contracts and re-forms on its own. Use it for a light/dark switch or a gallery of DESIGN.md files; the page updates its own CSS variables and copy. Both specs must have the same body count (every spec fromspec.mjshas 28).- Light/dark switch: build both specs from the same DESIGN.md (
--scheme light,--scheme dark) and bake a poster for each. A scheme control swaps the page's--hero-bg,--hero-text,--hero-brand,--hero-on-brand,--hero-poster(andcolor-scheme) and callsapi.setSpec(otherSpec); the same objects pop back in the other scheme's materials while the set and lights ease, and brand switches keep working in either scheme. Measured on the GPU after every switch (light → dark → another brand → light ...): the canvas backdrop lands within 4/255 of the poster baked from that spec, the headline contrast equals the verify value for that spec, and no object crosses the copy guard. During the 600 ms ease the headline color and the set both pass through mid-grey, so contrast dips for a moment; verify measures settled frames. Keep the switch user-initiated, never automatic.
Motion (Blender loop): each body bobs ±0.05 and rocks ±0.12 rad around the vertical on one 8 s period with its own phase, starting exactly at the settled pose; the camera sways 0.12. Seamless loop, no simulation.
Parameters worth changing in spec.json: pile.spring (higher = tighter pile), pile.damping, pile.orbit.amp (0 = still at rest), pile.pointer.r, exposure, lights.world, materials.backdrop.env and materials.glass.env (dark scheme), quality.ao. Keep pile.planes: removing the copy guard voids the contrast guarantee.
4. Fallback and budget
Poster first, by design:
- The poster is a CSS background on
.hero, the first paint. The canvas or video sits under the copy atopacity: 0and fades in over 600 ms only after it has drawn a frame (data-live). Measured: poster at 181 ms, live scene at 545 ms (three, karrot fixture, with reference workers sharing the machine). - Phones (≤ 720px),
prefers-reduced-motion: reduceand no WebGL2 get only the poster: no three.js download, no video. On phones the poster sits at the bottom (auto 58%) under the copy. - three.js loads with a dynamic
import()after the poster; a failed import leaves the poster and logs one warning. - The canvas pauses offscreen (IntersectionObserver) and in hidden tabs. Pixel ratio is capped at 2.
- A pause button (bottom right, 44px target) appears once the scene or video is live. WCAG 2.2.2: motion that runs longer than 5 s needs a way to stop it. Pause stops the simulation itself, so the pointer and clicks move nothing while paused.
- Quality ladder (frame budget pattern from taste-skill, MIT): after 40 warm-up frames, every 60 frames are averaged. Over 20 ms (under 50 fps): first the AO composer goes (the scene renders straight to the antialiased canvas); still over 20 ms, the pixel ratio drops by 0.75× down to 1; at 1 and still over 33.3 ms (under 30 fps) the scene stops and the page returns to the poster.
- Failures return to the poster: a lost WebGL context (
webglcontextlost) or a shader that fails to compile (renderer.debug.onShaderError) removesdata-live, records the reason indata-fallback, and frees the GPU resources.
Budget (three branch), measured 2026-11-06: three@0.186.1 core (190 KiB gzip over jsDelivr's minified files; the import map remaps three.module's relative three.core.js import to three.core.min.js) + 14 addon modules for geometry, environment and the AO composer (27 KiB) + scene.js and pile.js (12 KiB) = ~229 KiB gzip from the CDN, ~150 KiB gzip bundled and tree-shaken (esbuild). That is twenty times omd-shader-hero's 7 KiB. It is acceptable only because nothing waits for it. Measure your own bundle; never put three.js on the critical path. quality.ao: false in the spec skips the composer at runtime (the addons still load unless you bundle).
Version notes, checked against the 0.186.1 package: PCFSoftShadowMap is removed (the renderer warns and falls back), so use VSMShadowMap + shadow.radius; CapsuleGeometry(radius, height, capSegments, radialSegments, heightSegments); NeutralToneMapping exists. Pin an exact version at least two weeks old.
5. Verify on a real GPU
npm i -D playwright-core && npx playwright-core install chromium
node <skill>/scripts/verify.mjs path/to/hero.html ./hero-verify
A local file is served over http automatically (ES modules don't load from file://). Five passes: desktop GPU 1440×900, phone 390×844 @2x, reduced motion, no WebGL, shader compile failure. Checks:
liveOnDesktop,animates: the canvas or video is on screen and moves;posterBeforeLive: the page's own clock shows the poster painted before the live layer;headlineVisibleAtLoad: the headline is at full opacity on load;contrastHeadline,contrastSubcopy≥ 4.5:1 in every pass, on the worst of 8 frames across the period (three: the simulation re-run from the settled pose to each frozen time, deterministic), measured inside the text's line boxes with the copy hidden;mobilePosterOnly,reducedMotionPosterOnly,noWebGLPosterOnly: no canvas or video in those states;shaderFailPosterOnly: a fifth pass breaks everyshaderSourcecall (context present, compile fails) and expectsdata-fallback="shader-error"and no live layer. "No WebGL" and "WebGL that fails" are different paths;pauseStopsMotion: the pause button is visible on the live pass and, after pressing it, two frames are identical even though the pointer swept across the pile and clicked it in between;pointerKeepsCopyClear(three): an interaction sweep on the real clock — the pointer enters from the right, drags through the pile and on into the copy column, clicks the pile, then wanders over the copy. On every move the leftmost screen x any object reaches is read from the scene (api.leftmost()) and must stay ≥ 8 px right of the headline's line boxes; headline contrast is measured on every sixth frame of the sweep and counts towardcontrastHeadline;- frame time (median, p95) from the scene's own rAF loop over 3 s, and the WebGL renderer string, so you know a real GPU drew it.
Measured on 2026-11-06 (headless Chromium, ANGLE Metal Renderer: Apple M5 Pro, Blender 5.2.2; four reference workers' Chrome sessions were running on the same GPU, so frame times are an upper bound). All five passes and every check passed for all six:
| Fixture | Engine | Worst headline contrast (gpu / phone / reduced / no-GL / shader-fail) | Pointer sweep: nearest object / headline edge | Frame ms (median / p95), AO kept | Poster → live |
|---|---|---|---|---|---|
| toss | three | 13.61 / 15.02 / 13.61 / 13.61 / 13.61 | 715 px / 523 px | 8.3 / 9.5, AO on | 211 → 1062 ms |
| toss | blender | 10.28 / 15.02 / 11.99 / 10.28 / 10.28 | — | video | 172 → 273 ms |
| karrot | three | 13.45 / 15.11 / 13.55 / 13.55 / 13.55 | 717 px / 523 px | 8.3 / 9.2, AO on | 181 → 545 ms |
| karrot | blender | 10.49 / 15.11 / 12.56 / 10.49 / 10.49 | — | video | 149 → 234 ms |
| baemin | three | 13.09 / 14.45 / 13.03 / 13.03 / 13.03 | 717 px / 523 px | 8.3 / 9.1, AO on | 230 → 759 ms |
| baemin | blender | 9.88 / 14.45 / 11.74 / 9.88 / 9.88 | — | video | 137 → 220 ms |
| toss | three, dark | 10.50 / 11.00 / 10.46 / 10.46 / 10.46 | 715 px / 523 px | 8.3 / 10.7, AO on | 83 → 659 ms |
| karrot | three, dark | 15.48 / 16.06 / 15.48 / 15.48 / 15.48 | 717 px / 523 px | 8.3 / 10.0, AO on | 68 → 287 ms |
| baemin | three, dark | 15.12 / 15.70 / 15.22 / 15.22 / 15.22 | 717 px / 523 px | 8.3 / 10.2, AO on | 88 → 325 ms |
The dark rows were measured on 2026-11-09 on the same machine and renderer, with another agent session open on it, so their frame times are also an upper bound. All five passes and every check passed for the three. The same run re-verified light toss with the updated scene.js (explicit backdrop fill, eased light levels): 13.61 / 15.02 / 13.61 / 13.61 / 13.61, unchanged. The light specs from spec.mjs are byte-identical before and after the dark-scheme rules.
Blender timings on the same machine: Cycles poster 1920×1080 at 128 samples + denoise 20–35 s (Metal GPU); EEVEE loop 192 frames at 1280px in 100–122 s.
6. Don't
- Don't use a color that isn't a DESIGN.md token, and never a state color as the brand object.
- Don't put objects behind the copy, and don't remove the copy guard plane to let the pile spread. Don't rescue a failed contrast with a translucent scrim over the scene; move the pile center or the camera instead.
- Don't fade the headline in or wait for the canvas to show it.
- Don't make the idle motion bigger than §3. The pile is lively when touched; at rest it should only drift, or it reads as a screensaver and costs battery.
- Don't add a physics library for this. Twenty-eight capsules need ~5 KiB of code; a WASM engine is megabytes and makes the poster pose depend on its version.
- Don't run Blender through an MCP server for this. The CLI branch is reproducible; an interactive session with arbitrary Python is not.
- Don't load three.js on phones or under reduced motion. The poster is the design there, not a fallback.
7. Attribution
Structure from omd-shader-hero (token readers, poster under every canvas, background on html, first-frame copy, reduced motion as a state, the real-GPU verify loop), which itself comes from the owner's tf-shaders experiment. Market context and gaps: docs/research/2026-11-06-skill-trends.md §4. External references and their licenses: docs/research/2026-11-06-3d-shader-references.md.
Patterns taken (numbers and structure only, no code copied):
- The cursor-pushed floating pile is a common WebGL landing pattern (Lusion's and pmndrs' react-three-fiber demos are the best-known). The simulation here is written for this skill: capsule contacts from Ericson's closest-points-between-segments (Real-Time Collision Detection, 5.1.9), impulse with restitution and friction, hard planes, a fixed step.
- Leonxlnx/taste-skill 2.5 (MIT): the frame-budget ladder (50 fps → lower resolution, 30 fps → still), the forced shader-compile-failure check, the pause control for loops over 5 s.
- MengTo/Skills
webgl-landing-steering(MIT): poster before WebGL, DPR cap, context-loss handling. - CloudAI-X/threejs-skills (MIT) and three.js
llms-full.txt(MIT): the outdated-API list (PCFSoftShadowMap,Clock,examples/jsm),WebGLRendereras the default renderer,setAnimationLoop. - ahujasid/mcp-for-blender (MIT): bpy render settings only; the MCP server itself is not used.
Tips for Omd 3d Hero
- • Start a fresh session after installing so your agent picks up the new skill.
- • This skill ships scripts. Read them before the first run, and run it in a project without secrets first.
- • It bundles reference files. The agent reads them only when needed, so they cost no context until then.
- • It is part of a pack with 40 skills. Install only the ones you need with --skill to keep triggers precise.
- • If two skills overlap, mention the one you want by name.
Questions about Omd 3d Hero
What does the Omd 3d Hero skill do?
A rendered 3D hero from the project's DESIGN.md: a soft-lit studio (cyclorama, contact shadows, ambient occlusion) with a floating pile of brand-colored objects shaped by the radius tokens, and the headline in real DOM text. On desktop the pile is live (the cursor pushes it, a click scatters it, setSpec() morphs it into another brand or between the light and dark schemes) and a hard plane keeps every object out of the copy column. Two engines from one spec: three.js in the browser (default) or headless Blender (Cycles poster + EEVEE loop) from the same settled poses. Poster first: phones, reduced motion and no-WebGL get the still. Never invents colors or shapes; verifies five paths on a real
How do I install Omd 3d Hero?
Run "npx skills add kwakseongjae/oh-my-design --skill omd-3d-hero" in your project. The skills CLI installs it for Claude Code, Cursor, Codex and 70+ other agents. In Claude on the web, upload the skill folder as a ZIP under Customize > Skills.
Does Omd 3d Hero work in Cursor and Codex?
Yes. Omd 3d Hero uses the open SKILL.md format, so it works in Claude Code, Claude, Cursor, OpenAI Codex, Gemini CLI, GitHub Copilot and other agents that support agent skills.
Is Omd 3d Hero free?
Omd 3d Hero is open source on GitHub under the MIT license. It is free to install; check the license before commercial use.
Who made Omd 3d Hero?
Omd 3d Hero is published by kwakseongjae in the kwakseongjae/oh-my-design repository, together with 39 other skills.




