Skip to content
A blue flower dissolved into an ordered dither: the photograph survives only as a grid of dots, dense in the petals and open in the background

A portfolio with no pages: how the FREITAG site is built

Leal built the FREITAG portfolio as a single document: one WebGL scene, four overlays, and no page loads at all. Projects are places inside a dome you fly through. Every state still pushes its own URL, so a single project can be sent to a single client and the back button behaves.

Published · 3 min read

In short

  • The scene mounts once and never unloads. The four sections are overlays above it, which is why movement between them feels continuous.
  • No page loads does not have to mean no addresses. pushState and popstate give every state a real URL, so links, refresh and the back button all keep working.
  • Each surface effect uses a different tool for a reason: a 132 by 165 pixel canvas for the ordered dither, a GLSL shader on the GPU for the halftone over moving imagery.
  • The site has no build step. Modules load through an importmap, so what you write is what runs.
  • The opening felt slow because of a delay we had written, not because of weight. Measuring found three seconds that were loading nothing.

FREITAG is an AI-driven audiovisual production arm, and their portfolio does not have a home page. It has a dome.

The whole studio is one navigable space. Projects are places inside it, and you fly between them. There is no transition between pages because the site has none: four overlays sit above a scene that never unloads.

This is how it is put together.

One document, four overlays

The scene is a WebGL canvas built with three.js. It mounts once and stays mounted. Studio, Index and Contact are overlays drawn above it, and a project opens as another layer, so the dome is always there underneath, holding its position and its momentum.

That is what makes movement feel continuous. Nothing is being torn down and rebuilt, so nothing has to be re-entered. The cost of a normal page change, which is throwing away everything and starting over, simply never happens.

It also holds 60 fps while you move through it, without dropping a frame, across a continuous pass that opens one project after another. That is the number a scene like this lives or dies by: a portfolio you fly through is only pleasant while it stays smooth.

URLs without pages

A site with no page loads usually loses the thing links are for. This one does not.

Every state pushes its own address with the History API, and reads it back on popstate:

const target = "/" + routeForState() + location.search; if (location.pathname !== target) history.pushState({}, "", target);

So a single project has a URL you can send to a single client, refreshing lands you back in the same place, and the browser's back button behaves the way people expect. The experience is continuous and the addresses are still real.

The surface is computed, not filtered

The visual language leans on effects that are calculated at runtime, and each one is a different tool for a different reason.

The entry screen is an ordered Bayer dither drawn in a 2D canvas. The buffer is deliberately tiny, 132 by 165 pixels, and then scaled up: the coarseness is the point, and computing it small is what keeps it cheap.

The About page uses a halftone written as a GLSL shader, running on the GPU, because it has to survive over moving imagery rather than a still.

Type scrambles before it settles, and line breaks are handled with SplitType so each line can be animated on its own. GSAP drives the timelines that tie all of it together.

No build step

There is no bundler and no package.json. The browser loads ES modules through an importmap, straight from a CDN: three.js, GSAP, SplitType.

For a site of this size that is a real simplification, not a stunt. It is 2,420 lines of JavaScript and 1,149 of CSS across 122 files. What you write is what runs, and there is no compile stage between a change and seeing it.

The gate is a choice

The site opens on a threshold: a dithered portrait, the client logos, and a button that says Enter. You arrive somewhere before you arrive at the work.

That is deliberate. A portfolio everyone reaches through a link the agency sends can afford a front door, and the door does something a grid cannot: it sets the register before the first project appears.

What it does not cost is the part underneath. The scene is legible to a machine even though it is pixels, because the canvas declares what it holds and the brand line exists in the markup as well as in 3D. Accessibility, best practices and SEO all come back at 100.

The team publishes it

Cases, images, the opening film and client logos all come from Sanity. Adding a project is filling a form, not a deploy.

That is the part that decides whether a site like this survives its first year. A portfolio nobody can update becomes a screenshot of the day it launched.

What we measured

Frame rate through the 3D scene, with no dropped frames
60 fps
Held across a continuous pass through the dome, opening projects along the way, on a desktop browser.
Lighthouse scores for accessibility, best practices and SEO
100 out of 100, on all three
Lighthouse on the published site. Three separate audits, all returning full marks, on a portfolio whose main surface is a WebGL scene.
Size of the site, with no build step
2420 lines of JavaScript, plus 1,149 of CSS across 122 files
Counted in the repository, without the dependencies, which load from a CDN and are never copied in.

Questions

Why not use a framework?
The site is one scene and four overlays, with no shared state to speak of and no page transitions to orchestrate. A framework would add a build step and a dependency tree to solve problems this site does not have.
Does a single-document site still work with links and the back button?
Yes, when it pushes its own state. This one writes the address on every change and reads it back on popstate, so a project URL can be shared, a refresh returns to the same place, and going back does what it looks like it does.
Why compute the dither in a tiny canvas instead of using an image?
Because the coarseness is the effect. Working at 132 by 165 pixels and scaling up gives large, even dots and costs almost nothing to compute, and it stays crisp at any screen size instead of being a fixed asset that has to be exported again for every crop.

Work behind this

  • FREITAG® · An agency built the way it makes.

Read as plain text

Related