Winterfell

How Winterfell was made

One sentence, one sword, six photographs that never happened, and a sheet of frost you have to wipe away yourself. This is the thinking behind the site, the parts that were thrown away, the shaders, and the numbers.

Appel Studios, October 2026. Unofficial fan concept. Not affiliated with HBO or George R. R. Martin.

Frame 0 of the site: a Valyrian-steel longsword has pierced the screen from the upper right; the glass has cracked around the point and the gates of Winterfell are behind it at dusk.
Frame 0. The point is through the glass before you have done anything.
  1. The brief, and the version that was rejected
  2. One idea: it gets colder
  3. Mood board and palette
  4. The sword
  5. Through the glass
  6. The page is a window
  7. Every scene has its object
  8. Scroll turns one scene into the next
  9. Six photographs that never happened
  10. The frost wipe
  11. Snow, grain and the grade
  12. The depth layer
  13. Sound
  14. The reel contract
  15. Engineering and the numbers
  16. Before and after
  17. What is not here yet

The brief, and the version that was rejected

The brief was a flagship portfolio piece: a fan concept for Winterfell that has to stand next to what the studios ship, not next to other fan sites. Twenty-two sections of acceptance criteria, from the one-sentence concept to the security headers. Everything original: no footage, stills, music, marks, typefaces or models from the series, so the credit can actually be claimed.

The first version was built as a real-time castle: procedural block walls, a terrain, a forest, a crypt and a weirwood, all modelled in code and lit in three.js. It worked, it ran at sixty frames, and it was rejected on sight, because it read as a game. The bar was photographs. That version is kept below as the honest before. Everything in it except the snow was deleted.

The rejected first version: a low-detail castle of grey procedural blocks on a dark terrain, with a title over it. It reads as a game level, not a photograph.
Before. The procedural castle, 1440 px wide. Right in every measurable way and wrong in the only one that counts.

There were no paper sketches. The first build was the wireframe, and the lesson it taught set the whole second build: anything that has to read as real in a browser in 2026 is either a photograph or a baked object seen from a camera that never breaks the illusion. The second version was designed in the scene, one decision at a time, and every one of those decisions is written down with its reason in the project's decisions log.

One idea: it gets colder

The sentence a stranger should repeat a week later: the site gets colder the deeper you go. It opens on warm torchlight and late summer at the gates. By the Wall the glass you are looking through has frosted over, and the only thing left to do is wipe it. Every call below follows from that sentence.

SceneTimeLightFrost on the glass
The Gateslate summer, dusktorches, 3200 K0
The Courtyardgrey duskovercast, one brazier0.1
The Great Hallnight, insidehearth and candles, cold windows0.22
The Godswoodnight, moonmoonlight on frost; the one red0.32
The Cryptsno timetorch pools in black0.5
The Walla dawn that never warmsthe ice itself, 10000 K1.0

Three things were locked before any code: the concept above; the opening, a photoreal sword that pierces the screen; and the one interaction built perfectly rather than five built halfway, the frost wipe. The weirwood red is the only saturated colour on the site and it appears only in the godswood.

Mood board and palette

Six square tiles in story order: the gatehouse at dusk, the snowy courtyard, the Great Hall by candlelight, the white heart tree with red leaves under a moon, the torchlit crypts, and the ice wall.
The six scenes as they shipped. The board was made from the scenes, not the other way round: the plates were iterated until they were the board.

Six tokens and one accent, and every pixel on the site is one of them. The ember is a light, not a brand colour: it appears where fire is. Type is IM Fell English SC for display, inscribed with an inner-shadow filter so the titles read as cut into stone lit from above, and Spectral for everything else. Both are open licence and self-hosted. No italics, no monospace, no em dashes anywhere.

The sword

The hero object is a longsword of our own design: a diamond-section blade with a shallow fuller, a slender cross, a leather-wrapped grip and a wheel pommel carrying a direwolf head in relief. It was not sculpted and it was not generated from an image, because both give lumpy geometry at the edge where a sword has to be exact. It is built by a script in Blender 5.2, run headless: every part is lofted from analytic cross-sections, so the edge is an edge.

The whole sword on a dark ground: dark banded blade, bright steel cross, leather grip, wheel pommel.
The sword, rendered in Cycles from the same script that exports it.
Close on the point: the folded-steel banding runs along the blade and tightens toward the edge.
The point. The banding is a procedural Valyrian-steel pattern: bands along the length, warped, not a granite speckle.
The cross and the grip: planished steel with faint hammer dimples, leather wrapped in a spiral.
The cross is planished, the grip is wrapped. The engraving idea was dropped when it read as scales.
The wheel pommel with a direwolf head in low relief, lit from the side so the relief reads.
The pommel. The relief is our own drawing turned into a height map; it is not any existing sigil.

The materials are procedural Cycles node trees, baked to colour, roughness and normal maps at 4096 px per part, then packed into one glTF with Draco meshes and WebP textures: 3.1 MB with the 4K maps, 1.5 MB with the 2K maps that phones and laptops get. Three traps are worth writing down. Metals bake black in a colour pass unless Metallic is set to zero for that pass and restored after. The GPU bake crashed silently on Metal, so the bake runs on the CPU with full logging. And three.js skips tone mapping when it renders into a render target, which the post stack always does, so the sword's material applies its own ACES curve in the shader, or the blade blows out white.

A 1024 pixel window of the blade's baked colour map: dark steel with pale folded bands running along it.
Blade colour, baked. A 1024 px window of the 4096 by 1024 map, at its true scale.
The same window of the blade's normal map, mostly flat blue with the fuller and the banding as faint relief.
Blade normals, the same window.
The baked normal map of the pommel with the wolf relief visible as colour shifts.
Pommel normals, 512 px, the relief inside.

Through the glass

The screen is the glass. The sword is a world object that flies in along its own axis from seven metres out in the storm, and the point ends 8.5 centimetres in front of a plane half a metre from the lens. That plane is where the two halves of the trick meet.

The first build drew the sword twice: a world canvas behind the page with the blade clipped to behind the glass, and a transparent canvas above the whole interface with the point clipped to in front of it, so the steel could hang over the page's own type. It worked, to the pixel, as long as both cameras were the same camera. It also split the lighting, the depth of field and the post stack in two, and left the glass nothing to refract. The second build put the type in the scene instead, and one canvas draws everything; the section after next says how.

The crack atlas: a crushed white star at the centre, ten radial cracks running out from it that kink and branch and end at different lengths, and short ring cracks between them near the point.
The crack, baked once. A crushed star at the point, ten radial cracks that kink, branch and stop at uneven lengths, ring cracks between them, and clear glass beyond.
The same atlas shown as its tilt field: small wedges of colour around the point where the chips refract, thin coloured bands along every crack, and flat grey everywhere else.
The atlas's red and green channels: the chips near the point and a thin lens along every line refract; the rest of the glass does not.

The crack is a post-processing effect, not geometry, and it was rebuilt once: the first version was a Voronoi cell web across the whole screen, and it read as a honeycomb overlay rather than glass. Impacted glass is a small crushed star at the entry, eight to fourteen radial cracks that kink, branch and end at uneven lengths, a few ring cracks that run from one radial to the next near the point, chipped shards between them, and clear glass beyond about a third of the frame. The atlas stores the refraction tilt (the chips, and a thin lens along every line), the line intensity and the stress whitening at the point; the fracture grows outward from the entry over 0.42 seconds rather than appearing whole; a crack renders as a dark hairline that the light catches in stretches, with the chromatic split only where there is a line to split. The effect is centred on the point where the blade's axis crosses the glass plane, projected with the current frame's camera, so the web stays nailed to the entry wound while the blade keeps moving. Impact is declared when the point touches the glass, not when the blade comes to rest a little further through; the ease that flies the sword inverts in closed form, so that moment is exact.

The sword itself was re-aimed in the same pass. The first version entered side-on from the right, small, and its cross read as a pipe. It now drives point-first at the lens, strongly foreshortened, the point large and close and the length receding up and to the right into the storm, where the world's fog takes the hilt. The key light sits on the lens's side so the flat that faces you is the lit one, and the ripple reads on the near blade.

The page is a window

The second rejection was the one that mattered. Eli's words: the website is flat, there should be floating pieces and fragments around the place, stuff needs to look like it is popping off the page. He was right, and the diagnosis was simple once said: a photograph behind glass is still a photograph. Depth is only felt when things cross the page.

So the glass breaks for real. At the strike the sheet bursts into a hundred and seventy shards, most of them within a hand of the point, that fly outward and at the lens; a quarter keep going past the camera, and the rest hang in the air around the carved name. Each shard is a thin extruded sliver of real glass: three's transmission pass refracts the scene behind it, dispersion splits the colours on the brightest edges, a faint green sits in its thickness, and a pushed Fresnel edge makes a sliver read as an edge of glass rather than as nothing.

WINTERFELL itself is in the scene: the ten letters extruded from the IM Fell English outlines as pale weathered granite, bevelled wide enough for the bevel to catch the light, soot in the cut and frost on the faces that look up, hanging on the blade's own line a hand short of the guard so the steel comes through the type, as the reference does (one object mid-action punching through the headline). The page's own h1 stays for readers and search, visually hidden while the scene runs. Behind and around the name floats the debris of the place, in a strict order: the sword first, the shards second, and the debris third. Granite chips and forged iron (a bent nail, a hinge plate, a chain link, a band), built and baked in Blender like the sword, instanced in three bands: ten chips right before the lens, small and soft and at the frame's edges; a sparse middle band; and far specks in the gate's air. The name and the copy lane stay clear. Embers rise from the torches. The pointer's parallax is more than doubled. As you scroll, the lens flies two and a half metres forward through the field before the whiteout takes you to the courtyard.

One canvas means one depth of field: focus sits between the point and the name, the near chips and the deep gate go soft, and the shards, the debris, the embers and the storm all share the composer's bloom, grain and vignette. The first attempt kept a second, transparent canvas over the page for anything in front of the glass and spent a day on its consequences (nothing to refract, a depth of field that turned the page black, a seam at the glass plane to hold to the pixel); putting the type in the scene removed all three at once. Every one of these layers is a pure function of time, so the reel's last frame still equals its first.

Every scene has its object

The opening set the grammar and the other five scenes follow it: a photograph with depth at the back, the scene's name cut in stone in the middle of the air, one object caught in the act going through the letters, and the place's own debris floating at every depth between them and the lens. The names are not typed into the scene by hand. Each scene's h2 is laid out by the browser in a hidden box at a giant size, so a phone wraps it the way its CSS would, every character's line box is read back, and the letters are cut at those positions a couple of metres into the scene. They arrive out of the dark behind, one after another, turning as they come, and when the lens flies on they open at the middle and swing past it. The h2 stays in the page for readers, search and the static version.

The objects are the scenes' own. In the courtyard the glass that broke at the gates comes together again as the gate itself: a portcullis of jagged ice being hauled up through THE COURTYARD, which the lens flies under. In the Great Hall an iron chandelier, knocked swinging, goes through THE GREAT HALL with its twelve candles lit, its ring in front of the letters on one side and behind them on the other. In the godswood a gust tears the heart tree's leaves loose and pours them through THE GODSWOOD and past the lens, crimson and translucent, the only red on the site. In the crypts a torch turns end over end through THE CRYPTS and is the only light the letters get. At the Wall the word is cut in the ice, under the frost, and the wipe uncovers it.

The courtyard in grey dusk and snow: THE COURTYARD carved in stone hangs in the air, with a portcullis of jagged ice bars and teeth rising through it.
The courtyard. The opening's shards as the gate, on their way up.
The Great Hall by candlelight: THE GREAT HALL carved in warm stone, an iron chandelier ring swinging through it with twelve lit candles.
The Great Hall. The chandelier is a Blender bake; the flames are drawn on its wicks.
The godswood under a full moon: crimson leaves blow through THE GODSWOOD carved in pale stone, the white heart tree behind.
The godswood. Two hundred leaves on one curved path, each on a helix that opens as it nears the lens.
The crypts in the dark: THE CRYPTS lit warm from below by a torch turning through the letters, red leaves falling in the torchlight.
The crypts. One torch, one light, the leaves still falling.

Scroll turns one scene into the next

The scenes do not cut to each other. Twelve sparks rise out of the courtyard's air, ride the lens through the whiteout and land on the chandelier's twelve wicks, where they become the hall's candle flames. When the hall is left behind the flames lift off the wicks, swirl in front of the lens and cool into twelve leaves, still lit by the fire for a moment, that join the godswood's gust. Twelve of them are let go before the cut, fall slowly through the crypts in the torchlight, and at the Wall rime grows over each one until it breaks into a burst of real ice that goes back through THE WALL. Each of these is a pure function of the scroll position and the reel's periodic clock, so scrolling up plays it backwards and the reel still loops on itself.

The hall just after the whiteout: sparks flying in toward the chandelier's candles as the carved letters assemble.
Sparks from the courtyard, a moment before they become the flames.
The hall behind the lens: twelve leaves glowing ember red swirl in front of the camera, the chandelier small behind them.
The flames, cooling into leaves on the way to the godswood.

Six photographs that never happened

Nothing modelled in real time was going to read as a photograph at 4K, so the environments are photographs. They were generated from our own prompts (a named camera, lens, film stock and ISO on every one, so the model reaches for optics rather than illustration), one 16:9 and one 9:16 composition per scene, because a phone deserves its own framing and not a crop. Twelve plates at 3840 by 2160.

A flat photograph dies the moment the camera moves. Each plate gets a depth map from Depth Anything V2 Large, run locally on the Mac, and is shown as a dense displaced mesh: the depth drives the relief, the depth's gradient gives a normal for a touch of relighting, and distance fades into fog. The camera is allowed to dolly, drift and tilt within the depth budget, and never further, because a plate has no back. The walk between scenes is a push-in through a brief, thin mist of snow, well under a beat, so what carries one scene into the next is always seen going through it and the frame is never empty. The frost on the glass follows the same idea: none at the gates, in the courtyard or in the hall, a creep at the edges in the godswood, a frame in the crypts, and all of it at the Wall.

The godswood plate: a bone-white weirwood with red leaves beside a dark steaming pool under a full moon, frost on everything.
The godswood plate. The one red on the site.
The depth map of the godswood plate: the tree bright and near, the forest and sky falling to black.
Its depth, from Depth Anything V2 Large.
The crypts plate: a vaulted granite passage lit by torches, seated stone kings in alcoves, the far end black.
The crypts plate.
The depth map of the crypts plate: the near pillars bright, the passage receding to black.
Its depth. The passage is where the dolly goes.

The frost wipe

The frost is the concept made touchable: the glass gets colder scene by scene, and at the Wall it has frozen over completely. You drag to clear it and it refreezes slowly behind your hand. On a phone it is a finger; on a desk it is the mouse; in the reel it is a scripted finger driving the same code.

It is one fragment shader on the post stack. A coverage field decides, per pixel, at what cold level that spot freezes: the edges of the glass first and the centre last, shaped by slow noise and by the crystals themselves so the front advances as feathers rather than a fog. The crystals are generated macro photographs of hoar frost, two of them, star growth for the broad structure and fern fronds rotated for the veins, mirror-repeated so no seam shows, and everything is measured against the short side of the screen so a phone gets the same ferns as a monitor. Where there is frost, the scene behind is refracted by the crystal's gradient, blurred to milk between the ridges, and lit with a small specular so the ridges catch the light.

A macro photograph of hoar frost: six-pointed star crystals grown on glass, white on grey.
Crystal A, the stars.
A macro photograph of frost ferns: feather-shaped crystals branching across glass.
Crystal B, the ferns.

The wipe lives in a ping-pong render target: the red channel is cleared glass, painted from the pointer's trail with a soft brush and refreezing at 0.045 per second, and the green channel is breath, which blooms where a pointer rests and fades. How much of the Wall you have revealed is not read back from the GPU; it is the work the pointer has done there, accumulated on the CPU, and it is what fades in the Wall's one line of copy and unlocks the ending.

The Wall mid-wipe on a phone: frost ferns cover the glass except for a cleared sweep where the blue ice wall shows through.
Ten seconds into the reel, the wipe at its peak.
A phone at the Wall after a synthetic finger wiped an S across the glass: the ice shows through the swept path, the frost intact around it.
The same code under a synthetic finger on a 390 by 844 touch profile in the test run.

Snow, grain and the grade

The storm is a GPU particle system of flakes, seeded, drawn as short shutter streaks from each flake's position this frame to its position last frame, so they read as snow on film rather than dots. No two flakes are the same shape: a few lobes on the outline, phased by each flake's seed. Near the lens a flake is out of focus, so it grows into a wide, faint disc, the lens's bokeh; far away it is a small crisp fleck. The flakes swirl around the pointer through the same trail buffer the frost reads, and their density and the dust in the air change per scene. Film grain is a periodic hash whose seed is the reel's time, so the loop seals; bloom is small; depth of field is reserved for the two upper tiers; the vignette is 0.3. Every lit material carries the ACES curve in its own shader, and the finished frame gets one more grade on top, per scene: a black point, an S-curve in display space and a split tone (cold shadows, the torches' warmth kept in the highlights). The first version of this site had lifted blacks and read as a hazy beige photograph; the grade is what made the gates a night. The scene's own fog only starts at depth, so the hilt and the far debris sink into the air of each place, night blue in the godswood and black in the crypts, while the name and the point stay clean.

The depth layer

Spectacle alone is a demo reel. Under it there are forty-seven lore entries, a Stark family tree and a map of the North, all fed from one typed content layer: JSON in the repository, validated at build against the TypeScript types, read through a ContentSource interface with a local adapter today and a hosted-CMS adapter stub for tomorrow. The lore is prerendered to static HTML at build, so every entry has its own title, description and share card, is crawlable, and opens with a cross-document view transition that feels like turning a page rather than a reload. The six scenes are deep-linkable paths as well, and the address follows the scroll.

The lore archive: a list of entries with eras and kinds, searchable, in the site's type on charcoal.
The lore archive.
The Stark family tree: generations drawn with lines, one person's panel open.
The Starks, with Eddard open.
The map of the North: a parchment map with the Wall across the top, Winterfell at the centre, pins and labels.
The North. Pan and zoom, pins with notes.

Sound

Off by default, remembered, started only by a gesture. One ambient bed per scene (wind and a campfire at the gates, chimney wind in the courtyard, the hearth and a room tone in the hall, a low drone in the godswood, dark air and a fire loop in the crypts, hurricane gusts and an ice field at the Wall), each a twenty-second loop cut from the Sonniss GDC library and levelled to minus 27 LUFS, crossfaded as the story moves. Short hits for the moments: the blade's flight, the pierce (four layered transients built to land on the same frame), the crack's ticks, a torch, a gust, the door. A looping rub while a finger wipes the frost, its level following how hard the finger moves. Every meaningful sound has a text caption in a live region, and the mix runs through a compressor so it never clips.

The reel contract

The showreel is not a separate render. With ?reel=1 the site becomes a deterministic fifteen-second film at thirty frames per second: time comes only from performance.now(), which the capture tool replaces with a stepped clock; the scroll, the finger and the sword are pure functions of that time; and frame 450 must equal frame 0, so the loop is seamless when it plays on a phone. Getting the seam to close meant removing every piece of history from the reel path: no damped camera, no running noise clock, and the previous frame's camera for the snow streaks computed from the same pure functions rather than remembered. The measured seam is a mean absolute difference of 0.04 on a 0 to 255 scale between the last frame and the first.

Reel frame at 0 seconds: the sword through the glass at the gates.
0.0 s
Reel frame at 5.3 seconds: THE GREAT HALL carved over three lines with the lit chandelier ring through it.
5.3 s
Reel frame at 10 seconds: the frosted glass partly wiped, THE WALL cut in ice showing through, the meter counting what is clear.
10.0 s
Reel frame at 14.9 seconds: back at the gates, the sword through the glass, one frame before the loop.
14.9 s

Engineering and the numbers

The site detects the GPU and picks a tier before anything loads. Tier 1 is a phone or an integrated GPU: 2K sword maps, a 512 px environment, a 1024 px crack atlas, medium plates, eight thousand flakes, no depth of field. Tier 2 adds depth of field and twenty thousand flakes. Tier 3, a discrete or Apple GPU on a large screen, gets the 4K maps, the full plates and forty-eight thousand flakes. A governor watches the frame time and steps the resolution down twice before it turns the frost's refraction off. Rendering pauses when the tab is hidden, and every geometry, texture and render target is disposed when its component leaves.

The first paint is a real image: the gates plate at 1280 px, 117 KB, declared in the HTML with a high fetch priority, so the largest contentful paint is the loader's backdrop at the first second rather than the title after every asset has landed. The loader's rising snow moves by transform, never by height, so it is not a layout shift. Three.js, the renderer and the scene boot behind a dynamic import, so the first bundle is the interface alone.

WhatTier 1 (phone)Tier 3 (desktop)
Interface bundle (main), gzipped40 KB40 KB
three.js, the renderer, React and the scene, gzipped, behind the dynamic import466 KB466 KB
A lore page, all of its script, gzipped90 KB90 KB
The sword (Draco, WebP)1.37 MB3.11 MB
Environment map0.44 MB1.74 MB
Crack atlas (lossless WebP)0.44 MB0.54 MB
Frost crystals, two1.0 MB3.2 MB
The chandelier (Draco, WebP), loaded a scene ahead0.85 MB2.58 MB
A scene's fragments, loaded a scene ahead0.4 to 1.1 MB1.7 to 3.0 MB
A plate, per scene, streamed0.56 MB1.7 to 2.0 MB
Fonts, three files102 KB102 KB
Sound, all nineteen cues, on demand2.2 MB2.2 MB
Lighthouse 13, local buildDesktopMobile (throttled)
Performance10095
Accessibility100100
Best practices100100
SEO100100
Largest contentful paint0.6 s2.4 s
Cumulative layout shift0.0020.001
Total blocking time30 ms140 ms

The honest reading of that table: the static layer scores where a static site scores, and the WebGL layer costs blocking time at boot, because compiling the post stack's shaders and uploading textures is work the main thread has to do once, and Lighthouse's mobile run draws WebGL on a throttled CPU. The carved headlines were one new cost this pass added: cut all at once at boot they blocked the main thread for over half a second, so each glyph is now extruded once and kept, the cache warms one glyph at a time after the visitor's first move, and a scene's name is assembled from it only as the scene comes near. The other was the boot itself. A profile showed the first frames drawn behind the loader compiling every shader synchronously and uploading every texture in one task. Now the scene stays hidden while it loads, every program compiles in parallel once the files are in, the textures go up one per frame, and only then is the scene shown and the loader told; phones get the same models with textures a quarter the size, because a fragment there is a few dozen pixels wide. Mobile went from 81 to the mid nineties with nothing taken out of the picture. In a headless Chrome on an M4 the site holds sixty frames per second at 1440 by 900 with every effect on, and the phone tier holds it on a 390 by 844 profile at a device scale of three. The checks that produced these numbers, the screenshot matrix at four viewports, the synthetic finger, the keyboard walk, the still and static modes, and the reel's loop seam, are scripts in the repository and ran before this page was published.

Under the WebGL there is semantic HTML: real headings, one landmark per region, every scene's copy in the page before any script runs, skip links to the content and past the intro, keyboard navigation (j and k walk the journey, Escape closes the menu), captions for sound, a reduced-motion mode that honours the system setting and can be chosen from the menu, and a static mode that shows the plates as stills with every word in place. Each page has its own title, description, canonical URL, share card and structured data; there is a sitemap, a robots file, a web manifest, and the usual security headers with a content security policy that allows no inline script.

Before and after

Before: the rejected procedural block castle.
Before. Procedural castle, real-time lit. 1440 by 900.
After: the gates of Winterfell at night, WINTERFELL carved in stone in the air before them, the sword through the name and the glass bursting around it.
After. The same viewport, the same scroll position.

The difference is not polish on the same approach. It is a different approach: a camera that never breaks a photograph, one baked object that is allowed to be close, and effects that live on the glass rather than in the world.

What is not here yet

Some of the brief's checklist is deliberately not in this version, and saying so is part of the case study. There is no composed score, only the beds. There is no direwolf character and no ravens; a baked object that close to the lens has to be perfect, and the wolf's head that was built read as a skull, so it was held back. The courtyard's gate and the Wall's ice are made from the opening's shards and the godswood's leaves rather than from new sculpted pieces, which is the idea, and also the budget. The content layer is CMS-ready but not CMS-hosted, because a hosted CMS is an account to create and pay for. There are no analytics, so the privacy page is one paragraph long. The textures ship as WebP inside the glTF rather than KTX2, because the Khronos encoder is a separate install. And the phone testing here is a simulator and a profile; the real-device pass is the next thing on the list.