← The library

Noray

Portfolio for a designer who codes — three pages, one particle sky

Full template Members only Open the demo Unlock the library
Preview of the Noray template: a cloud of glowing particles forming a sparkle over a deep blue night sky, the italic heading “I build web experiences people remember” and two buttons.
  • 3,200 particles, 30 kBThe night sky is raw WebGL, written by hand — the drifting background sheets included. The original needs Three.js, React Three Fiber and a post-processing pass: 3.6 MB of JavaScript.
  • 19 animations measuredRead straight out of the original's bundle — durations, curves, offsets, thresholds — then rebuilt without GSAP, without ScrollTrigger, without Lenis.
  • 0 external requestsNo dependency, no CDN, no build step. Open the file and it works.

What it is

The portfolio of a creative developer, in three pages. The home page: a full-height hero, a statement, three projects with two sticky rails, a pinned scene where nine services scroll past in three panels, a four-step process, an oversized closing call, a footer. The about page: the story, the tools in a full negative section, the experience. The work page: the three projects as a full-screen carousel. Behind all three, the same field of particles, reshaping itself section by section.

The design follows that of a working design engineer's site, of which nothing has been kept — neither the images nor the code. The original is a Next.js app carrying Three.js, React Three Fiber, GSAP with ScrollTrigger and SplitText, Lenis and opentype.js: 3.6 MB of JavaScript for the home page alone. Here the same page runs on 42 kB.

That's the whole point: this template isn't here to be admired, it's here to be read, broken and rebuilt.

What you change first

The colours — four lines

Right at the top of assets/css/style.css:

--night: #070b1a;   /* the background, and the glow rising from the bottom */
--halo:  #243c7e;   /* the light behind the page */
--spark: #4a5a9e;   /* the particles */
--ink:   #f4f6fa;   /* the text */

One thing to grasp before touching anything else: the particle colour exists twice. A shader cannot read a CSS variable, so --spark is repeated in field.js as three numbers between 0 and 1. Change one, change both.

The name

Search for “Noray” in index.html. No CSS class carries the brand name, so a find-and-replace can't break anything. One extra place: sampleGlyph('N') in field.js — that single letter is the shape the footer particles gather into.

The sizes

The type scale is declared once, then redeclared at every breakpoint. So a heading never gets its own media query: you change --h2 and the whole page follows.

The 19 effects, and how they’re built

The original doesn't hide its settings: they sit in its bundle as named constants and plain GSAP calls. Everything below comes from there, then measured in the browser — never guessed by eye.

EffectWhereHow
Lines rising into place26 blocks of textthe text is split into real lines by measuring, each one masked; yPercent 320 → 0 with a 10° tilt, 0.4 s, 0.07 s apart
Text that prints itself4 headingssplit into characters, opacity 0.1 → 1 driven by scroll; JS writes one progress value, clamp() does the rest
Steps that brightenthe processopacity follows the distance from the middle of the screen — the step in the centre is the one you can read
Pinned sceneservicessame mechanic, but three screens instead of six: one screen of scrolling per panel. At six the section takes twice as long as it should to go past
Live coverswork, work pageeach cover loads the real neighbouring site in a frame as the card comes into view — rendered at 1440 wide then scaled, so it serves its desktop layout. The still image stays underneath as fallback and placeholder
Twin railsselected workthe project nearest the middle of the screen names itself on the left and offers its link on the right
Particle fieldthe whole page3,200 points in raw WebGL; shapes computed as superquadrics, not modelled — a star, a cut diamond, a flame, a letter
Field stepping backreading sectionsnot in the original: over a section marked data-field-quiet the canvas drops to 30% opacity. A dense cloud between the eye and a paragraph looks good for a second and is unreadable after that
Star fieldthe whole page45% of the points sit on a far sheet drawn in its own pass: it does not turn with the shape, or half the stars would swing behind the camera twice per turn
Drifting backgroundthe whole pagea full-screen rectangle, two octaves of fractal noise sliding between --night and --halo; scrolling down speeds it up, scrolling back slows it
Letter made of particlesfooterthe glyph is drawn once into a 2D canvas and its pixels read back: 30 lines instead of a 90 kB font library
Six turns of scrollthe whole pagethe cloud rotates −2160° from top to bottom, and keeps turning for a moment after the page stops
Weight under the pointerlogo, menu, footer linksRed Hat Display is a variable font: the wght axis follows the cursor, 400 → 900 within 80 px
Custom cursoreverywherefour bars in mix-blend-difference that pull back into a ring over a link
Folding menutop rightclip-path rather than a height, plus inert so the closed panel leaves the tab order
Full-size peekproject coversa cover opens the neighbouring site live in a native <dialog>: it scrolls and clicks for real. Same-origin relative address, loaded on click and never on page load; the still image stays as the fallback
Page transitionbetween the three pagesthe content leaves in 0.36 s, the cloud scatters, then the link goes through. Only a plain navigation is intercepted — with scripting off, a link stays a link
Negative sectionabout pagethe sky really does flip: field.js interpolates both shaders — particles included — towards their pale variant. A CSS sheet doubles it, and carries the section on its own if WebGL is missing
Projects carouselwork pagea real horizontal scroller with scroll-snap: it slides, swipes and answers the arrow keys before any script runs. The script adds the perspective, the pointer drag, the vertical wheel turned sideways, and the counter

What you should know

  • No external requests. Every resource is local, fonts included. The original pulls its bundles and its fonts from its own CDN.
  • The sky is CSS, not canvas. The gradient is a real background; the canvas only draws the particles. With JavaScript off, with WebGL missing, or with the context lost, there is still a night sky.
  • A lost WebGL context paints white. A laptop waking from sleep is enough. The canvas listens for webglcontextlost, hides itself, and rebuilds everything when the context returns.
  • Scroll reveals can't hide the content. The hidden state only exists once the JavaScript has run. Checked by removing the <script> tags: all 26 blocks stay visible and the three service panels stack up one under the other.
  • Zero inline styles, zero inline scripts. The template honours the site's strict security policy, checked by serving it with that policy in front — not assumed.
  • Checked at 390, 1440 and 2560 px, on load and after scrolling all the way down, with no horizontal overflow. The bench ships with it: node _mesures/banc.mjs.

What isn’t perfect

Better said here than left for you to find out.

  • The three project covers are abstract renders, not screenshots. The original's covers are real brand logotypes: impossible to reuse, and a generated image is not allowed to invent one. The project name is written in HTML over the image instead — which at least makes it translatable.
  • The videos of the original's work page are not reused. It plays three animated captures of 3–4 MB each as WebGL textures. They belong to its clients and cannot be redistributed: the carousel shows the same still covers as the home page. You lose the movement inside the cards, you save 11 MB.
  • The cards do not deform the way the original's do. There, a shader bends the plane as it slides. Here it is a CSS perspective plus a shear proportional to speed: the curved wall is there, the elasticity is not.
  • “See project” points at the project's own anchor. The three projects are invented, so there is no honest destination. Swap the three hrefs for your own URLs.
  • No post-processing. The original runs the scene through a bloom pass. Here the glow lives in the point's own shader — a core, a halo, an additive blend. The difference shows mostly on the largest points, a little less diffuse.
  • The language switch is decorative. The original has a full Spanish version; a one-page template has one language.
  • The contact form sends nothing. It's a native <dialog> and the send button hands the fields to the visitor's mail client. Where to plug your own endpoint in is marked in main.js.

It’s waiting for you in the library.

Noray comes with the subscription, alongside the rest of the library and the full course.

Immediate access · No commitment · Cancel in one click