Shader Backgrounds
Decorative WebGL hero backdrops for pages in the web app.
The shader library lives in apps/web/modules/shaders. Cal Event pages load the saved configuration with the event data and render it through EventShaderBackground; the renderer never queries the database or changes the document theme.
Production contract
import { ShaderHero } from "@calcom/web/modules/shaders/shader-hero";
<ShaderHero id="contour" theme="dark" paletteId="dusk" />The parent must be relative; put page content in a relative z-10 sibling. The decorative hero is absolute, 720px tall, pointer-events-none, and aria-hidden. Its fixed artwork scale, per-effect masks, grain, and motion settings are owned by the renderer.
themeis the resolved"light" | "dark"value. Passundefinedwhile it is unknown: the shell renders, but no effect mounts.paletteIdselects a named preset shared by both themes. Each preset uses its former dark-mode colors. Omit it to use the shader's named default, independent of theme (for example, Aurora uses Borealis). There is no separate “Shader default” picker option.colors, when supplied, overrides the resolved palette. It must be three validated six-digit hex colors, including for effects that use only two stops.- Unknown shader IDs render no decoration. Validate the saved
effectbefore passing it; never temporarily substitute another shader. - Unknown
paletteIdvalues also render no decoration at runtime — relevant to editor presets and docs, not persisted event configuration.
Theme ownership and first paint
The consuming page owns theme policy. On public Cal Event routes, the existing BookingPageProviderTheme applies the document class through the nonce-bearing next-themes initialization script. It currently follows system preference. This page theme may differ from the signed-in application's theme.
EventShaderBackground reads forcedTheme ?? resolvedTheme from useGetTheme and passes a supported light/dark value after hydration. Provider changes update canvas contrast and grain without changing the selected colors. Direct HTML-class edits outside the provider do not update the shader theme. The provider's pre-hydration script and theme-aware CSS are responsible for the initial page shell. Do not fetch the saved shader selection in a client effect.
The canvas is transparent while its code loads and stays hidden until its first draw with the final theme and palette. Failed imports, initialization, drawing, or context loss leave the page usable without decoration. Context loss does not automatically retry; remount to retry. No poster, artificial gradient, or default-palette placeholder is supplied. WebGL artwork itself cannot be server rendered.
Palette and theme changes update uniforms without resetting the artwork. Animations stop offscreen, when the document is hidden, and for reduced motion; still frames redraw for palette/theme and container-size changes. The library never sets or persists a theme.
Event settings integration
Cal Events save this configuration under CalEventDetails.appearance.background:
{
kind: "shader",
effect: "aurora",
colors: ["#43D7AC", "#78BAFF", "#C2A3F3"]
}Shader: persist a stable effect slug, not picker order. SHADER_CATALOG is the full registry for rendering saved events; SHADER_DISPLAY_ORDER is only what the editor offers for new picks. Retiring a shader from the picker does not invalidate stored IDs — keep its implementation in the catalog.
Palettes: persist the picked color values, not palette IDs. Named presets in SHADER_PALETTES seed the editor; add, remove, or change them without affecting saved events.
The pure shader-catalog and shader-palettes modules are safe to import on the server. Validate the saved effect with isShaderId; treat missing or invalid shader configuration as no decoration. Pass the same saved colors in both themes to ShaderHero via colors, or omit colors for the shader default.
The public page uses saved colors; the editor preview uses unsaved form values through the same adapter. Earlier two-palette configurations retain their light colors on read and become one array on the next background save. This compatibility rule is separate from the dark-origin named presets. Keep the page content outside shader loading and error boundaries.
Preview ownership
Each shader appears once below, rendered through ShaderHero with its production framing, mask, and shared named default palette. Preview containers preserve the full 720px hero height. The docs adapter reads its own theme provider; no docs-specific sizing or theme logic belongs in ShaderHero.
Effects
| Id | Label | Colors | Default palette (both themes) |
|---|---|---|---|
recess | Recess | 2 | Terra |
halftone | Halftone | 2 | Orchard |
strata | Strata | 2 | Petrol |
aurora | Aurora | 3 | Borealis |
tide | Tide | 3 | Lagoon |
contour | Contour | 3 | Dusk |
satin | Satin | 2 | Rosé |
veil | Veil | 3 | Orchard |
resonance | Resonance | 3 | Petrol |
lumen | Lumen | 3 | Dusk |
facet | Facet | 2 | Mono |
API
| Export | Module | Notes |
|---|---|---|
ShaderHero | shader-hero | { id; theme; paletteId?; colors? } |
ShaderId, ShaderTheme, SHADER_CATALOG | shader-catalog | Server-safe IDs and metadata |
getShaderDefaultColors | shader-catalog | (id) => ShaderColors |
SHADER_PALETTES | shader-palettes | Twelve presets |
getShaderPaletteColors | shader-palettes | (id) => ShaderColors |
isShaderId, resolveShaderColors | shader-catalog | Validation and pure palette resolution |
isShaderPaletteId | shader-palettes | Validate editor/runtime preset IDs |
ShaderColors | shader-colors | [string, string, string] |
The hero band is 720px tall.
Call sites
| File | Notes |
|---|---|
apps/web/modules/events/views/event-public-view.tsx | Renders the saved background through EventShaderBackground |
Example URLs
| Path | Notes |
|---|---|
/{event-slug} | Public Cal Event page with its saved background |