How this site
was built
A walkthrough of the concept, the art direction behind every image, the shader technique behind the animated background, the animation system, and how the finished static site would ship.
01 Concept
PRISM Collective is a fictional underground-but-polished electronic music label and festival collective. The brief was neon, glitch, and chromatic-aberration energy — the opposite pace of a slow luxury reveal site. Every decision was made to feel fast and snappy: short easing curves, quick reveals, an infinite marquee, a live "waveform" pulse — nothing lingers.
The reference points were Boiler Room's raw documentary energy and a label's Bandcamp/Resident Advisor presence: bold typographic hierarchy, real event listings, a releases grid, and a booking contact — a site a real label could actually ship.
Palette
Magenta, cyan and acid green are used deliberately clashing rather than harmonized — no gradient blending between them in UI elements, only in the background noise field. Acid green is reserved for state/status signals (eyebrows, "few left" tags, the live-feed dot) so it always reads as "live data," not decoration.
Type pairing
| Role | Font | Why |
|---|---|---|
| Display / headlines | Unbounded (700–900) | Geometric, blocky, slightly technical letterforms with real weight contrast — reads as a rave flyer typeface without being a novelty font. |
| Body / labels / nav / data | JetBrains Mono | A coding monospace doubles as a "systems / signal" voice — event dates, catalog numbers and nav labels read like a tracklist or a terminal. |
02 Generated imagery
Eight images were generated with Higgsfield (nano_banana_pro, 2K) and
converted to WebP (cwebp -q 78 -m 6) to keep the whole asset folder
under 3MB — the raw PNGs came back at roughly 8–10MB each. Every prompt
explicitly excludes text/logos so nothing fights the real typeset UI, and each one
leans on the brand's three clashing neon accents plus visible film grain and
chromatic aberration so the photography and the CSS/shader treatment feel like one
material.
Each image — the prism hero texture, the warehouse crowd, the three roster
portraits, the glitch texture, the festival stage, and the shattered-glass about
texture — was generated from a carefully art-directed brief specifying the lighting
setup, framing, palette, texture treatment, and mood, then iterated until it sat
inside the brand. Direction notes worth keeping: the hero literalizes the brand mark
(a prism splitting light into the three accent colors) and doubles as a release
cover in the Sound grid; the three roster portraits were each directed under a
different lighting setup so the grid doesn't repeat a look, and three source photos
become six roster cards via a CSS hue-rotate() duotone pass; and the
about texture literalizes "broken color" from the tagline. The full prompt library
is proprietary and available to clients on request.
03 The animated background — the required non-generic technique
The brief called for a WebGL or canvas shader background as the site's signature technique. This site uses a raw WebGL fragment shader (no Three.js, no shader libraries) painted onto a single full-screen triangle pair — the smallest possible WebGL setup: one vertex shader that just passes through clip-space coordinates, and one fragment shader doing all the work.
How the noise field works
The fragment shader is a hand-written domain-warped fractal Brownian motion (fBm) field — the same family of technique used for procedural cloud/plasma shaders. The chain is:
- Value noise from a hashed 2D lattice (
hash()→noise()), smoothed with a Hermite curve. - fBm layers five octaves of that noise at increasing frequency /
decreasing amplitude for organic, cloud-like detail (
fbm()). - Domain warping — the coordinates fed into one
fbm()call are themselves offset by the output of two otherfbm()calls (qandrin the code). This is what turns flat noise into the folded, marbled, "liquid" look instead of generic Perlin static. - The final scalar field is mapped through three
mix()steps into the magenta / cyan / acid palette, then dimmed with a vignette and a soft per-pixel scanline + grain pass.
vec2 q = vec2(fbm(p + t), fbm(p - t*1.3));
vec2 r = vec2(fbm(p + 1.4*q + vec2(1.7,9.2) + t*0.9),
fbm(p + 1.4*q + vec2(8.3,2.8) - t*0.7));
float f = fbm(p + 2.2*r);
A uMouse uniform nudges the warp offset slightly toward the cursor for a
subtle parallax without ever breaking the ambient loop. Time drives everything (
uTime), so the field never repeats in an obviously loop-able way over a
normal viewing session.
visibilitychange, and the shader itself is five octaves of 2D noise —
cheap enough to run smoothly on integrated GPUs. A flat radial-gradient fallback
covers browsers with no WebGL context.
.scrim layer
(a soft dark gradient, heavier at the very top/bottom for the nav and footer) sits
between the canvas and the grain overlay. The raw noise field occasionally swings
bright enough to wash out body text; the scrim guarantees a contrast floor everywhere
without dulling the shader's color in the areas that matter most.
04 Animation system
GSAP 3 + ScrollTrigger (CDN) drive every motion moment. In line with
the "fast, not cinematic" brief, most tweens sit at 0.4–0.7s with
power3/power4.out easing rather than the slow multi-second reveals a
luxury site would use.
- Wordmark auto-fit: instead of a static
clamp()font size (which overflowed "COLLECTIVE" on narrow viewports), a small JS routine measures each stacked word's own row and shrinks itsfont-sizeuntil it exactly fills the available width — independently per row, re-run on resize and ondocument.fonts.ready. Each line always spans edge-to-edge regardless of viewport or font-metrics quirks. - Hero entrance: the two wordmark rows slide up from
yPercent(110)inside anoverflow:hiddenmask, staggered with a slight overlap, then the tagline/CTA fade up. - Glitch bursts: a periodic 0.19s timeline nudges the "PRISM" row
with
x/skewXand a magenta/cyantext-shadowsplit, firing once on load and then every 4.2s — a quick flicker, not a constant distraction. - Scroll reveals: section titles wipe in with a
clip-path inset()animation; cards/rows stagger up 30–40px. All usetoggleActions: "play none none reverse"so they replay correctly if a visitor scrolls back up. - Infinite marquee: the ticker's HTML is duplicated once in JS,
then tweened
xby exactly its own scroll width on an infinite linear repeat — the classic seamless-loop trick. - Waveform: a small canvas 2D loop draws ~96 bars driven by three
summed sine waves at different phases/frequencies, colored in a repeating
magenta/cyan/acid cycle, paused via
IntersectionObserverwhen it's off screen. - Stat counters: a plain
gsap.to()tweening a numeric object's value, rendered into the DOM on every tick, fires once viaScrollTrigger.
05 Responsive approach
Two breakpoints (900px, 640px) rework grids from 3→2→1 columns and swap the inline nav for a full-screen link takeover below 640px. Two real bugs surfaced and were fixed during the responsive pass:
- An implicit-grid bug where
.event-loc/.event-statuskept an explicitgrid-column: 2/3placement from the tablet breakpoint even after the mobile breakpoint collapsed the row to one column — the browser was silently generating a second implicit column to satisfy that stale placement. Fixed by resettinggrid-column: 1at the mobile breakpoint. - The scroll-down cue is fixed-position at the bottom of the hero; once the hero tagline/CTA wrap to a taller stack on narrow viewports, it started overlapping the CTA button. Fixed by hiding the cue under 900px, where the CTA itself already implies "keep going."
06 Deployment
The whole project is static — no build step, no framework, no environment variables. To ship it:
- Drag the
prism-collective/folder into Netlify Drop (or connect the repo and set the publish directory to this folder) —index.htmlis picked up automatically as the site root. - No build command is required;
guide/index.htmlandassets/*are served as-is. - Point a custom domain at the Netlify site and Netlify handles TLS automatically.
Because every asset is already a relative path and every third-party dependency (Google Fonts, GSAP) loads from a CDN with no API keys, there is nothing else to configure.