Skip to content
alyn
kwakseongjae

Omd Cjk Type Shader skill

One effect, done well: 먹 번짐 — a large Korean, Japanese or Chinese display headline that reads as sumi ink printed on hanji. The heading stays real, selectable DOM text in ink color from the first frame; a WebGL2 layer over it adds what real ink does: a wet, glossy stroke with a soft bloom that shrinks as it dries, ink pooling darker at stroke rims, ends and corners, paper fibers showing through thick strokes, and capillary feathering along fibers at a few edges only. On a dark ground it switches by itself to 금니 on 감지 (gold pigment on indigo paper): no feathering, a dull wet state, settling grains, one slow sheen. Two parameters (bleed, duration), ~5 KiB gzip of library JS, procedural paper t

$npx skills add kwakseongjae/oh-my-design --skill omd-cjk-type-shader
GitHub stars
580
Pack installs
653
Alyn Score
56/99
Updated
14 h ago
License
MIT

Install Omd Cjk Type Shader

Pick your agent. Every command is ready to paste.

$npx skills add kwakseongjae/oh-my-design --skill omd-cjk-type-shader

The 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-cjk-type-shader when the request matches its description.

03

Name it if needed

If it does not trigger, say “use the omd-cjk-type-shader skill”. In Codex, type $omd-cjk-type-shader.

The skill itself

SKILL.md

GitHub

omd-cjk-type-shader — 먹 번짐: CJK display type as sumi ink on hanji

Ink soaking into paper is the one image Korean, Japanese and Chinese writing share. This skill makes a big CJK headline read like it was printed or brushed in sumi on hanji. On a fast skim it should look like a calligraphy poster, not a filter.

How it works. The heading is ordinary DOM text in the ink color, readable and selectable from the first frame. The skill traces every grapheme where the browser laid it out and builds three fields from that mask: distance outside the strokes, distance inside them, and local ink coverage. A WebGL2 layer drawn over the text then adds only what real ink does:

  • wet → dry: the stroke starts glossy-dark with a soft bloom around it, and the bloom shrinks back as the ink dries;
  • pooling: ink collects darker along the stroke rim and at stroke ends and convex corners (low local coverage);
  • kasure: paper fibers show through the middle of thick strokes once dry;
  • nijimi: capillary feathering runs along a few paper fibers at some edges, chosen by low-frequency noise, never as an even ring around every glyph;
  • paper: a procedural hanji texture (SVG turbulence, no image) on the page.

On a dark ground the same signature switches to 금니(金泥) on 감지(紺紙). Goryeo sutra scribes wrote in gold powder mixed with glue on indigo-dyed paper. Pigment doesn't soak in the way ink does, so this variant never feathers and has no bloom. Wet glue reads dull and a little darker; as it dries the grains settle and catch the light (a sparse glint), one slow sheen crosses the line, ends and corners where the brush paused read richer, and thin passes let the indigo show through. It turns on by itself when the heading's color is lighter than the nearest painted background (data-ink-ground="dark" or "light" forces it). Every color it draws is the heading's own color, darker or brighter, or the ground's.

It plays once when the heading enters the viewport, then the render loop stops. Line wrapping, keep-all, font swaps and resizes are re-traced.

Working example: examples/demo.html (single file, Korean + Japanese + Traditional Chinese, on hanji and on a 감지 section). Proof: scripts/verify.mjs.

1. When to use / when not

Use when:

  • a Korean, Japanese or Chinese display headline (≥ 40px; hero, launch title, campaign line, section opener) should feel inked, calligraphic, "printed on hanji / washi / 宣紙";
  • the brand's ink and paper colors are known (DESIGN.md foreground/canvas, or the heading's computed color and the nearest painted background). Dark grounds get the 금니 variant: use a warm light text color with room to darken (the demo's #d4b062 on #1a2440 is 7.4:1).

Don't use for:

  • body text, labels, buttons, nav, anything below ~32px (pooling and feathering need stroke width to read);
  • Latin-only headlines (it works, but the cultural point is gone);
  • more than one or two headings per page. Boldness in one place (tf-shaders rule).

2. The effect and its two parameters

Param Range Default What it does
bleed 0–1 0.7 How far the capillary feathering travels along fibers (up to 0.12em × bleed, at the edges noise picks) and how big the wet bloom gets. 0.4 = barely there, 0.8 = a wet brush.
duration s 2.6 Time from wet to dry. The bloom peaks early and shrinks; pooling and fiber show-through come in as it dries. Under 1.5s it reads as a flash, over 4s as a loading state.

Everything else is fixed on purpose. Ink = the heading's computed color, darkened to 25% for pools; paper = the page background color. Text ink should be a sumi grey-black, not pure black (the demo uses #3a3530 on #f3eee4, about 10:1), so the darker pools have room to show. One signature, not a control panel.

3. The contract that keeps text text

  • The heading is a normal <h1>/<h2> with its real text. inkBleed() wraps the text in .ink-text and inserts an aria-hidden="true", pointer-events: none layer over it (z-index 2). The layer only darkens, lightens or extends what is already there. It never replaces or re-draws the glyphs, and with the layer removed the heading is still complete.
  • The headline paints on the first frame. No opacity: 0, no clip, no wait for the shader. tf-shaders measured LCP going to 7.8s when hero copy waited for a shader.
  • Selection, copy, find-in-page, translation, screen readers and SEO see plain text. verify.mjs checks that selecting the heading returns its full text in every pass.
  • Leave ~0.4em of clear space around the heading (the demo uses margin-bottom: 0.4em). Feathering and bloom extend up to ~0.3em past the glyphs.
  • Never draw CJK headlines with a canvas/Text shader component. Canvas text loses selection, accessibility, indexing and correct CJK shaping (tf-shaders "Don't" list).

4. CJK typesetting (applies before the shader)

From /hangul (HG rules) for Korean; ja/zh scoped separately so Korean rules don't leak:

.ink:lang(ko) { word-break: keep-all; overflow-wrap: anywhere;  /* HG-10, scoped to :lang(ko) */
                letter-spacing: -0.02em;                          /* HG-7: display never below -0.03em */
                line-height: 1.3; }                               /* HG-5: wrapping display >= 1.3 */
.ink:lang(ja), .ink:lang(zh-Hant) {
  line-break: strict;                                 /* kinsoku: no small kana / closing marks at line start */
  word-break: keep-all; overflow-wrap: anywhere;      /* break only at <wbr> phrase marks you place */
  letter-spacing: 0; line-height: 1.4; }
:lang(ko), :lang(ja), :lang(zh) { font-synthesis: none; } /* no fake bold/italic on CJK */
  • Mark Japanese/Chinese phrase boundaries with <wbr> (BudouX-style: 墨がにじむように、<wbr>名前が<wbr>紙に染みこむ。). In headless Chrome 149, word-break: auto-phrase computed correctly but did not produce phrase breaks in this demo, so don't rely on it.
  • Korean: line breaks only at spaces between 어절, never syllable by syllable. Hand-place display line breaks when the copywriter cares (tf-shaders cjk-type.md).
  • Set lang on the heading (ko, ja, zh-Hant/zh-Hans). It picks the right glyph variants for shared Han code points and scopes the rules above.
  • Fonts: use a face that ships the weight you set (HG-12). The demo uses Noto Serif KR / JP / TC 700 from Google Fonts, which loads only the glyph slices the page uses. A myeongjo/mincho/song face suits ink. Pretendard works too. Trace after document.fonts.ready so the field follows the real face, not the fallback.

5. Library, license, budget

Library: @paper-design/shaders (vanilla ShaderMount), pinned 0.0.82 (2026-10-21; dispose() now releases the WebGL context), WebGL2. Decision record (verified 2026-10-13 from the npm tarballs' LICENSE files, npm metadata and the GitHub license API):

@paper-design/shaders 0.0.81 shaders 4.0.4 (shaders.com)
License Apache-2.0 (LICENSE + NOTICE "Paper Shaders, Copyright 2026 Paper") MIT (© 2026 Shader Effects Inc.); dep typegpu 0.12.3 MIT (Software Mansion)
Redistribution in a skill/template allowed; keep NOTICE if you vendor code allowed
Runtime WebGL2: every current desktop and mobile browser WebGPU only, no fallback (Chrome/Edge, Android 12+ Chrome, Safari 26+, recent Firefox)
JS for this effect 17.5 KiB min / 5.3 KiB gzip (esbuild, ShaderMount only); CDN per-file +esm 16.4 KiB / 5.0 KiB gzip 2,535 KiB min / 710 KiB gzip (esbuild, Shader+MeshGradient from shaders/react)
Telemetry none (its only fetch() reads a local blob's content type) on by default: samples 5% of sessions to shaders.com/api/telemetry; pass disableTelemetry
Custom GLSL + textures yes (ShaderMount(el, frag, uniforms), image uniforms) via its component graph

The effect needs a custom fragment shader and one texture, which is exactly what ShaderMount is, at 1/130th of the WebGPU runtime's weight and with twice the browser reach. The demo imports Paper from jsDelivr (pinned, per-file build) and does not vendor it, so no NOTICE copy is needed. If you vendor Paper's files, copy its NOTICE alongside. If a project already ships shaders, this effect still runs on Paper. Don't load both runtimes on one page.

Budget for one heading:

  • JS: 5.0–5.3 KiB gzip library + 5.3 KiB gzip of effect code (shader, two distance transforms, coverage blur, tracing; measured unminified with comments).
  • GPU: one quad, one texture lookup and 7 value-noise calls per pixel, over the heading box plus a ≤0.32em pad, minPixelRatio: 1, capped at 1920×1080 px. It runs for duration and then setSpeed(0) stops the rAF loop. Paper also pauses it offscreen and in hidden tabs.
  • CPU: per layout change (font swap, resize, re-wrap; debounced 120ms), two Euclidean distance transforms (outside and inside the strokes) and one separable box blur, at half resolution above 64px type.
  • Paper texture: an inline SVG feTurbulence data URI on html (~600 bytes), no image request.
  • Fonts are the real cost. CJK faces are large. Load only the display weight, as unicode-range slices.

6. Fallback and reduced motion

  • No WebGL2 (probe canvas.getContext('webgl2') before mounting; ShaderMount throws if it can't get a context or link the program): no layer is inserted, the heading gets .ink-static (a faint CSS text-shadow). The heading is already in ink on textured paper, so it is complete without the shader. verify.mjs forces this path by stubbing getContext.
  • prefers-reduced-motion: reduce: mount at the dried frame with speed 0. The reader gets the final inked state as one still frame, with no motion and no rAF loop.
  • Shader compile failure (one happened while building this: a precision mismatch with Paper's vertex shader) takes the same .ink-static path. In your own GLSL, declare uniform mediump vec2 u_resolution;, because the precision must match Paper's vertex shader.

7. Verify on a real GPU

next build or a passing lint proves nothing for shaders (tf-shaders rule). Run:

npm i -D playwright-core && npx playwright-core install chromium   # a fresh Playwright Chromium, not your own Chrome
node <skill>/scripts/verify.mjs path/to/page.html ./ink-verify --frames 36

It launches headless Chromium with --enable-gpu --ignore-gpu-blocklist --use-angle=metal (macOS) and runs three passes: GPU, reduced-motion, no-WebGL. It measures the first heading on each ground (data-ink-ground="light" and "dark"). Then it:

  • records the WebGL renderer string, so you know whether a real GPU or SwiftShader drew the frames;
  • steps the shader clock deterministically and saves frames (feed them to ffmpeg for the demo GIF/MP4);
  • proves rendering with pixels: shader-on vs shader-off inside the heading box, and first frame vs dried frame;
  • measures legibility through the whole effect: the glyph cores (text on vs text transparent) as drawn in each sampled frame against the paper behind them, 10th percentile so anti-aliased edges don't decide it, worst frame. Display headings are WCAG large text, so the bar is 3:1;
  • checks that the selected text equals the heading text, the layer is aria-hidden + pointer-events: none, reduced motion is still, and no-WebGL leaves no canvas.

Measured on 2026-11-06 (headless Chromium, ANGLE Metal Renderer: Apple M5 Pro, not SwiftShader), all checks passed:

Ground Heading-box pixels changed by the shader Wet vs dry Glyph legibility (p10, worst frame)
light (sumi on hanji, #3a3530 on #f3eee4) 10.2% 5.9% 8.01:1
dark (금니 on 감지, #d4b062 on #1a2440) 8.8% 8.7% 4.58:1
Look at the frames anyway, at full size: a misaligned field (wrong baseline) shows as pooling offset from the strokes, and a feathering setting that is too strong reads as fur. Only your eyes catch those.

8. Next.js / React port

Keep the same function and lifecycle in a 'use client' leaf: useEffect(() => { let h; document.fonts.ready.then(async () => { const { ShaderMount } = await import('@paper-design/shaders'); h = await inkBleed(ref.current, { bleed, duration }); }); return () => h?.mount.dispose(); }, []). Render the heading server-side as plain text, so the shader only decorates it after hydration. Don't use @paper-design/shaders-react's <ShaderMount> here: it doesn't give you the WebGL2 probe, the fallback class, or control over when the field is re-traced.

9. Attribution

The budget-first framing, the fallback-is-design rule, the first-frame-LCP rule, 어절-not-syllable reveals, keep-all, and the real-GPU screenshot loop come from the owner's tf-shaders experiment (skill/shader-landing/SKILL.md, references/cjk-type.md, scripts/shoot.mjs, RESEARCH.md, 2026-10). Korean type values come from OmD's /hangul skill. The distance transform is Felzenszwalb & Huttenlocher's linear-time EDT, written fresh here. The dark-ground variant follows a pattern stated in Leonxlnx/taste-skill's print3d.js (MIT), "on dark paper the ink is the light": the treatment inverts, it doesn't just recolor. No code was copied; the gold model is written fresh from the 감지금니 practice.

Don't

  • Don't hide the heading until the shader is ready. The text is the content; the ink is the frame.
  • Don't animate syllable by syllable, and don't split Hangul into per-character spans for a reveal.
  • Don't run the loop forever. A dried ink effect costs nothing; a looping one drains batteries.
  • Don't put two runtimes (Paper + shaders) on one page for one heading.
  • Don't feather every edge or draw a uniform halo. An even ring around each glyph reads as a sticker, not ink; feathering belongs to a few edges.
  • Don't use pure black text ink. Pools need a darker value to show, and sumi is grey-black anyway.
  • Don't make the dark-ground variant glow. Gold pigment reflects light, it doesn't emit it: no halo, no bloom, no blur outside the strokes.
  • Don't push bleed until the fibers read as fur. Check the dried frame at your real font size.

Tips for Omd Cjk Type Shader

  • • 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 Cjk Type Shader

What does the Omd Cjk Type Shader skill do?

One effect, done well: 먹 번짐 — a large Korean, Japanese or Chinese display headline that reads as sumi ink printed on hanji. The heading stays real, selectable DOM text in ink color from the first frame; a WebGL2 layer over it adds what real ink does: a wet, glossy stroke with a soft bloom that shrinks as it dries, ink pooling darker at stroke rims, ends and corners, paper fibers showing through thick strokes, and capillary feathering along fibers at a few edges only. On a dark ground it switches by itself to 금니 on 감지 (gold pigment on indigo paper): no feathering, a dull wet state, settling grains, one slow sheen. Two parameters (bleed, duration), ~5 KiB gzip of library JS, procedural paper t

How do I install Omd Cjk Type Shader?

Run "npx skills add kwakseongjae/oh-my-design --skill omd-cjk-type-shader" 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 Cjk Type Shader work in Cursor and Codex?

Yes. Omd Cjk Type Shader 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 Cjk Type Shader free?

Omd Cjk Type Shader is open source on GitHub under the MIT license. It is free to install; check the license before commercial use.

Who made Omd Cjk Type Shader?

Omd Cjk Type Shader is published by kwakseongjae in the kwakseongjae/oh-my-design repository, together with 39 other skills.

Similar skills. From other creators.