Tavotto
AI drew your research figure. Tavotto makes it publication-ready: edit matplotlib figures on a canvas, lay out in mm, check journal rules. Free, open source.
翻译准备中
尚未检测到更新。GitHub 发布版本后会显示在这里。
个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证Ten animated shader visualisations, designed in the browser and exported as code, 4K stills or seamless loops.
翻译准备中
取自仓库 README
正在加载完整 README。 以下为已保存的摘录。
Eleven animated WebGL visualisations, designed in the browser and taken away as code, as a 4K still,
or as a seamless loop — and shown applied to real artwork rather than only full-bleed on a black
stage.
One fragment shader over two triangles. No three.js, no gradient library, no stack of blurred
divs — a few kilobytes against ~700KB for a three.js-backed alternative.查看完整 README ↗Eleven animated WebGL visualisations, designed in the browser and taken away as code, as a 4K still, or as a seamless loop — and shown applied to real artwork rather than only full-bleed on a black stage.
Live demo: shader.plaimaker.space
One fragment shader over two triangles. No three.js, no gradient library, no stack of blurred divs — a few kilobytes against ~700KB for a three.js-backed alternative.
Every visualisation is the same three moves in a different order: bend the coordinate, cut it up, read colour last. That ordering is the whole effect. Animating colours, or translating blurred shapes, gives you something that slides; moving the coordinates the colour is sampled from gives you something that flows, and that is the part CSS cannot do.
src/lib/shaders/ is the single source of truth for the GLSL. The live preview, the still
exporter, the video recorder, the brand mockups and the copy-paste output all read the same
assembled string, so the code a visitor takes away is byte-for-byte the shader they were looking
at.
fract(); dividing the other way gives depth, and walking the ramp along it is what makes the
gradient read as a trail receding rather than as a pattern sliding up the screen.Every visualisation shares one library of colour ramps (src/lib/shaders/ramps.ts), so switching
visualisation keeps the palette you have built. Randomise rerolls the motion and where the
colours sit along the ramp, but never the colours themselves; Reset returns a shader to its slider defaults.
A gradient judged full-bleed is a different thing from a gradient judged as the bottom half of a poster, and the second is what most of these end up being. The Brand applied view puts the live shader inside thirteen real pieces of artwork — a poster pair, a three-card Instagram carousel, a spread, a landing hero (full bleed, the artwork is the page), a landing section (the artwork as one panel beside the copy), a cover, an Instagram story, the thumbnail a Figma file shows in the browser, a newsletter, a social profile, a phone's lock and home screens, a pricing page and an event ticket — with editable type over it. The ticket leans toward the pointer. Change a colour and it changes in the poster. All mockups shows every layout at once.
The Instagram cards each treat the artwork differently on purpose: one cut in half by a header, one read through frosted glass, one reduced to a border around a floating card. A gradient that only works full-bleed fails two of those, and it is worth knowing that before it is a grid of nine.
Each layout reveals on a staggered timeline — the cards rise, then the headline and the wordmark arrive — in a few CSS keyframes rather than an animation library. Replaying it restarts the CSS animations in place rather than remounting the stage, because a replay must not cost a WebGL context.
Each layout is drawn at true size and scaled to fit, so a 38px headline stays a headline instead of resizing into a caption on a narrow screen. Every canvas on the page runs off one shared clock: independent clocks started at different mount times would show the same shader at different moments, and a poster pair whose halves disagree reads as a bug rather than as a brand.
Code — React (TSX), vanilla JS, a self-contained HTML page, raw GLSL, or the CSS fallback. Self-contained and dependency-free; a browser without WebGL gets a still gradient, not a black box.
Stills — PNG at 1920×1080, 4K, square, vertical or ultrawide, rendered fresh at that size rather than upscaled from the preview. While a mockup is on stage, the export can instead be the composited piece: artwork, scrims and copy, without any of the studio's own chrome.
Video — a seamless loop, MP4 (H.264) or WebM, 5–20 seconds at 24, 30 or 60 fps. Seamless
by construction: for a recording, every time term in the shader is swapped for one that
closes on a whole number of cycles over the chosen duration, and the noise drift walks a circle
through the field instead of a straight line — so t = 0 and t = duration render identical
frames.
Recording runs in real time (10 seconds of video takes 10 seconds) because MediaRecorder
timestamps frames by the wall clock. What the recorder does control precisely is which
instant each frame shows: the stream is captured manually and frame i is drawn at exactly
i / fps on the shader's clock, so frame timing survives a dropped rAF tick.
Share — the whole parameter set is packed into the URL hash, so a link is the gradient. Nothing is stored server-side.
Requires Node.js 22 or newer. The project uses pnpm.
git clone https://github.com/OmarMota/shader-studio.git
cd shader-studio
pnpm install
pnpm dev # http://localhost:3000
There are no environment variables to set.
| Command | What it does |
|---|---|
pnpm dev | Start the Next.js dev server |
pnpm build | Production build (standalone output); postbuild copies static assets in |
pnpm start | Serve the production build |
pnpm lint | ESLint (Next.js core-web-vitals + TypeScript rules) |
pnpm typecheck | tsc --noEmit |
src/
├── app/
│ ├── layout.tsx # fonts, metadata
│ ├── page.tsx # renders <Studio />
│ ├── globals.css # Tailwind v4, theme tokens, the reveal keyframes
│ └── icon.svg
├── components/
│ ├── Studio.tsx # the app: state, toolbar, side panel, views
│ ├── ShaderCanvas.tsx # one live WebGL canvas on the shared clock
│ ├── BrandStage.tsx # the nine "Brand applied" layouts
│ ├── GalleryView.tsx # every mockup at once, from one offscreen renderer
│ ├── ExportDialog.tsx # Code / Image / Video export
│ ├── RampEditor.tsx # the draggable gradient-stop editor
│ ├── AboutDialog.tsx
│ ├── ShaderGlyph.tsx # the per-visualisation picker glyphs
│ ├── VisualisationIcon.tsx, ShaderIcon.tsx
│ └── ui.tsx # shared controls (buttons, selects, sliders, modal)
└── lib/
├── shaders/
│ ├── common.ts # vertex shader, uniforms, shared GLSL, buildFrag
│ ├── index.ts # the registry: SHADERS, switch / randomise helpers
│ ├── ramps.ts # the shared colour-ramp library
│ └── wave.ts, rail.ts, fog.ts, halftone.ts, prism.ts,
│ cascade.ts, aurora.ts, paper.ts, morph.ts, ascii.ts,
│ grain.ts
├── renderer.ts # one GL context, a cached program per shader
├── clock.ts # the single clock every canvas reads
├── export-code.ts # React / JS / HTML / GLSL / CSS output
├── export-media.ts # PNG stills and seamless video loops
├── mockup-export.ts # composited mockup stills and videos
├── share.ts # compact URL-hash encoding
└── icons.ts, icon-painter.ts
Nothing outside src/lib/shaders/ needs to change: the picker, the panel, the share link, all
the exporters and the brand view are written against the registry in src/lib/shaders/index.ts.
src/lib/shaders/<name>.ts exporting a ShaderDef (prism.ts is a compact
example):
params: ShaderParam[] — the controls. Each key becomes a uniform float u_<key>,
generated, so a control and its uniform cannot drift apart. Give a param a roll: [min, max]
range if Randomise should move it; give it options: [...] to render a picker instead of
a slider (the shader receives the chosen index).void main() — passed through buildFrag(params, BODY) to produce frag.
buildFrag prepends the shared uniform block and helper library from common.ts (the ramp,
noise, turns(), drift(), sharpness(), slat(), grain()…).id, name, a one-line tagline, a longer note for the About dialog, usesBackground,
and defaultRamp — the id of a ramp from ramps.ts it opens on.src/lib/shaders/index.ts: import it and append it to the end of
SHADERS. The compact share link stores a shader as its index in that list, so reordering or
inserting breaks existing links.src/components/ShaderGlyph.tsx (a case in
glyph() and an entry in FLOW). Without one it falls back to a plain dot.The shader body has three rules to keep:
turns() or drift(). That is what closes the animation into
a seamless loop for video export. A raw u_time silently breaks recording for that shader.fwidth without an
extension, so each body works out analytically how many repeats it packs into a pixel and passes
it through sharpness() into slat(). Skip it and fine detail turns to moiré.Neither typecheck nor build looks inside the GLSL string: a broken shader builds fine and then
falls back to a flat CSS gradient in the browser. Check the browser console, and see
AGENTS.md for how to compile shaders headlessly.
Renderer.resize skips its work when the size is unchanged. Anything creating a renderer must
size the canvas through it, not by assigning canvas.width first — the viewport and the
u_resolution uniform are set in the same place, and a frame that divides by a zero resolution
comes out as one flat colour.decodeParams reads that
as the wave rather than as today's default.next.config.ts builds with output: "standalone", and postbuild copies .next/static (and
public/, if present) into .next/standalone, so the result runs anywhere with
node .next/standalone/server.js. The live site builds from main on a Node host.
The config also overrides the year-long s-maxage Next puts on a prerendered page with
s-maxage=60, stale-while-revalidate=300, so a CDN with no purge hook picks up a new deploy
within the minute. Hashed assets under /_next/static keep their immutable year.
MediaRecorder for video, Canvas 2D for composited mockupsContributions are welcome — new visualisations especially. See CONTRIBUTING.md for setup, conventions and how to open a pull request.
MIT © 2026 Omar Mota