# Oddity > Oddity is a browser studio for designing animated WebGL backgrounds — > gradients, photos and print screens (halftone, ASCII, dither) stacked as > one composition — and shipping them into a real site. Everything renders > client-side; there is no account and no server-side rendering step. Studio: https://oddity.design/studio Human docs: https://oddity.design/docs ## Two ways to put a look in a page 1. **Angular component** (typed, editable in your codebase) npm install oddity-angular # requires Angular 22 or newer import { IOddityConfig, OddityCanvas } from "oddity-angular"; @Component({ selector: "hero-section", imports: [OddityCanvas], template: `
`, }) export class HeroSection { readonly look: IOddityConfig = { /* fields below */ }; } 2. **Embed** (any framework, or none) The `look` attribute is the hash from a studio URL — everything after the `#` in https://oddity.design/studio#... — so a link IS a look, and the same string drives both paths. Two optional attributes carry what a hash cannot. `image` takes a data URI (the export inlines an uploaded photo there). `video` takes a URL the HOST page serves — relative to that page, not to Oddity — and its presence is what declares the source to be footage: ## Rules both paths follow - The canvas fills its nearest positioned ancestor. Give that ancestor `position: relative`; the canvas takes no space of its own. - It sits behind sibling content and never receives pointer events, so it cannot eat a click. - It pauses when scrolled offscreen and when the tab is hidden — footage included: a clip nobody is looking at stops decoding, it does not merely stop being drawn. - `prefers-reduced-motion: reduce` stops the animation; the look still renders, as its resting frame. Footage holds on its first frame. - Photos you uploaded in the studio travel inline as data URIs. Footage does not travel at all: the studio's code export hands over the FILE and points at it — `video="./clip.mp4"` in an embed, `imageSrc` plus `imageKind: "vid"` in a config. Serve it yourself; cross-origin needs `Access-Control-Allow-Origin`, since the frames become a WebGL texture. ## Writing a config by hand `IOddityConfig` is a partial: **every field is optional and anything you omit takes the default below.** A config therefore reads as the diff from a plain gradient, which is also exactly what the studio's "Copy Angular component" emits. Two fields decide what is in the composition at all, and both are strings of single-character codes: fxOrder: which stages are stacked, bottom to top. Layer codes are "G" Color, "I" Image, "P" Pattern; effect codes are listed per stage below. hidden: codes present in the stack but not drawn. A stage's own fields do nothing unless its code is in `fxOrder`. ## Effect stages and their fields ### Waves — code "w" (Distort) waveMode: 0 waveSide: 0 waveAmount: 0.3 waveFrequency: 8 waveFalloff: 0.5 wavePhase: 0 waveX: 0.5 waveY: 0.5 ### Glass — code "g" (Distort) glassMode: 0 glassAmount: 0.5 glassCount: 24 glassRatio: 1 glassAngle: 0 glassProfile: 3 glassBevel: 0.04 glassShadow: 0.3 glassHighlight: 0.3 ### Kaleidoscope — code "k" (Distort) kaleidoSides: 6 kaleidoAngle: 0 kaleidoX: 0.5 kaleidoY: 0.5 kaleidoZoom: 1 kaleidoMirror: true kaleidoSpin: 0 ### Flow — code "f" (Distort) flowAmount: 0 flowScale: 3 flowDetail: 3 flowAngle: 0 flowBias: 0 ### Shear — code "z" (Distort) shearBands: 12 shearAmount: 0 shearAngle: 0 shearSeed: 0 shearSpread: 0.5 ### Mirror — code "y" (Distort) mirrorCols: 1 mirrorRows: 1 mirrorX: 0 mirrorY: 0 ### Light — code "l" (Light & lens) lightMode: 0 lightAmount: 0.5 lightSize: 0.5 lightX: 0.5 lightY: 0.5 lightCount: 1 lightFollow: 0 light2X: 0.25 light2Y: 0.7 light3X: 0.75 light3Y: 0.7 light4X: 0.5 light4Y: 0.85 lightColor: "ffffff" lightColor2: "ffffff" lightColor3: "ffffff" lightColor4: "ffffff" lightGain: 1 lightGain2: 1 lightGain3: 1 lightGain4: 1 lightSpread: 1 lightSpread2: 1 lightSpread3: 1 lightSpread4: 1 ### Lens — code "o" (Light & lens) aberration: 0 softness: 0 lensDistort: 0 ### Spectra — code "b" (Light & lens) spectraAmount: 0 spectraScale: 2.5 spectraSpread: 0.5 spectraWidth: 0.35 ### Iris — code "n" (Light & lens) irisAmount: 0 irisCells: 5 irisBend: 0.6 irisRings: 5 irisWidth: 0.3 irisShape: 0 irisPalette: 0 ### Threshold — code "x" (Color & tone) thresholdLevel: 0.5 thresholdSoft: 0.02 thresholdInvert: false ### Duotone — code "u" (Color & tone) duotoneShadow: "1b2a4a" duotoneLight: "f0d9b5" duotoneSteps: 1 duotoneBalance: 0 duotoneAmount: 0 ### Grade — code "e" (Color & tone) gradeExposure: 0 gradeContrast: 1 gradeSaturation: 1 gradeTemp: 0 gradeTint: 0 ### Pixelate — code "p" (Pixels & print) pixelate: 0 pixelateRatio: 1 pixelateGap: 0 pixelateGroutColor: "" ### Dither — code "d" (Pixels & print) ditherAlgo: 0 ditherMatrix: 4 ditherScale: 4 ditherLevels: 4 ditherStrength: 1 ditherColorMode: 0 ditherThreshold: 0.5 ditherGamma: 1 ditherInvert: false ditherRatio: 1 ### CRT — code "v" (Pixels & print) crtCurve: 0 crtScanSize: 3 crtScanDepth: 0 crtMask: 0 crtBoost: 0 ### Halftone — code "h" (Pixels & print) halftoneSize: 8 halftoneStyle: 0 halftoneStagger: false halftoneDotScale: 0.85 halftoneColor: 1 halftonePaper: 0.2 halftonePaperColor: "ffffff" halftoneEmboss: 0 halftoneWind: 0 halftoneWindAngle: 0 halftoneWobble: 0 halftoneGlint: 0 halftoneJitter: 0 halftoneJitterSeed: 0 halftoneAngle: 0 halftoneGamma: 1 halftoneInvert: false halftoneRatio: 1 halftoneInkColor: "" ### ASCII — code "a" (Pixels & print) asciiSize: 16 asciiRatio: 1 asciiStyle: 0 asciiColor: 1 asciiBackground: 0.2 asciiExposure: 1 asciiFill: 1 asciiDensity: 64 asciiScatter: 0 asciiGap: 0 asciiAngle: 0 asciiThreshold: 0.5 asciiGamma: 1 asciiInvert: false asciiInkColor: "" asciiPaperColor: "000000" asciiOpacity: 1 asciiBlend: 0 ### Contours — code "c" (Pixels & print) contourLevels: 8 contourWeight: 2 contourDash: 0 contourGlow: 0 contourColor: 1 contourPaper: 0.2 contourInkColor: "" contourPaperColor: "" ### Hatch — code "t" (Pixels & print) toneGeoSize: 36 toneGeoLow: 0.4 toneGeoHigh: 0.6 toneGeoInvert: false toneGeoRules: 3 toneGeoCross: 0 toneGeoAngle: 0 toneGeoGrid: 0 toneGeoJitter: 0 toneGeoJitterSeed: 0 toneGeoWeight: 0.5 toneGeoColor: 1 toneGeoPaper: 0.2 toneGeoInkColor: "" toneGeoPaperColor: "ffffff" ### Grain — code "r" (Texture) grain: 0 grainSize: 1 ### Paper — code "s" (Texture) paperAmount: 0 paperColor: "" paperFibre: 1 paperVignette: 1 ### Pointer — code "m" (Interact) pointerMode: 0 pointerAmount: 0.5 pointerSize: 0.4 pointerFade: 0.5 pointerDamping: -1 pointerFlip: false ## Frame, canvas and layers Everything no stage's key list claims: the frame, the color/image/pattern layers and animation. **A field named after a stage belongs to that stage** — `contour*` is Contours, `toneGeo*` is Hatch (those keys predate the rename) — and only takes effect when that stage's code is in `fxOrder`. fxOrder: "G" hidden: "" shape: 4 stops: [{"color":"1e1b4b","pos":0},{"color":"4338ca","pos":25},{"color":"7c6ff0","pos":50},{"color":"cbb0f7","pos":75},{"color":"faf9f6","pos":100}] seed: 128 warp: 0.8 noiseScale: 1.2 detail: 3 contrast: 1 angle: 45 centerX: 0.5 centerY: 0.5 gradientFollow: 0 gradientOpacity: 1 gradientBlend: 0 oklab: false imageSrc: "" imageKind: "img" imageOpacity: 1 imageBlend: 0 imageBlur: 0 imageFit: 0 imageFocalX: 0.5 imageFocalY: 0.5 imageZoom: 1 patternSource: "l" patternLayout: "b" patternCols: 6 patternRows: 6 patternGap: 8 patternRadius: 16 patternGapColor: "" patternSeed: 1 animate: false animSpeed: 1 aspect: "1x1" sizeAnchor: 1 ## Getting a config you did not write Design the look at https://oddity.design/studio, then Export → Copy Angular component — the component above, config filled in Copy embed code — the two-line embed Copy look — a link that reopens the exact composition An agent asked to "use this background" is usually being handed one of those three. All of them describe the same composition.