Build Guide

How Tesserae was built

This is the "Tesserae" art direction for Nomadic Owls — one of ten visually distinct builds sharing the same brand content. Tesserae treats the agency's work as light passing through glass: layered, refractive, calm. Below is a specific account of how it was made, not a marketing recap.

Concept & inspiration

The brief was "glassmorphism + a floating 3D glass object." Rather than treat glassmorphism as a texture applied on top of a generic layout, the whole information architecture is built around it: the three service pillars and six disciplines are modeled as physical panes of glass stacked in front of a deep violet-to-blue gradient, each one catching soft colored light from blurred "glow" blobs sitting behind it. The mental image driving the layout was a tessellated window — hence the name — where every panel is a separate tessera (tile) refracting the same light source differently depending on its position and tilt.

Typography & color

Type is Manrope, self-hosted via @fontsource-variable/manrope so the whole variable-font weight axis (200–800) ships in a single woff2 without a Google Fonts request. Manrope's rounded, slightly geometric letterforms read as modern and technical without being cold — display headings sit at 700–800 weight, body copy at 400–500. There is no second typeface: with this much visual texture already coming from blur, glow and gradient, a second display face would compete rather than add.

The palette is a deep violet-blue gradient (#151233#241b4d) with two accent hues pulled straight from the brief: cyan #7dd3fc for interactive/informational accents (eyebrows, links, focus rings) and violet #a78bfa for the softer decorative glow and gradient endpoints. Glass panels use a translucent white fill (rgba(255,255,255,0.07–0.12)) rather than a tinted fill, which keeps the background gradient legible through every panel instead of muddying it.

The glassmorphism + parallax technique

Each glass tile is a .glass element: a soft gradient fill, a 1px semi-transparent border, an inner highlight via inset box-shadow, and backdrop-filter: blur(18px) saturate(140%). Behind each cluster of tiles sits a .glow-field — two or three absolutely positioned circles with filter: blur(70px) and a radial gradient in cyan or violet — which is what actually reads as "colored light hitting glass" rather than a flat blur.

The tilt effect is vanilla JS, no library (src/scripts/tilt.js). Every element with data-tilt gets a pointermove listener that computes the cursor's position within the element as a 0–1 fraction, maps it to a target rotation clamped to ±data-tilt-max degrees, and eases the live rotation toward that target every frame (rx += (target - rx) * 0.08) so the motion is damped instead of snapping. The eased values are written to CSS custom properties (--rx, --ry) which a transform: perspective(900px) rotateX(var(--ry)) rotateY(var(--rx)) rule consumes — the JS never touches transform directly, so the CSS stays declarative and easy to reason about.

The hero object composes two transforms: an outer wrapper carries the cursor tilt (same mechanism as the tiles, smaller max angle), and an inner wrapper runs two independent CSS @keyframes — a vertical bob and a small ±3deg sway — so it never stops moving even when the cursor is idle. All of it is gated behind prefers-reduced-motion: reduce: the JS listener is simply never attached, and the CSS animations are killed with animation: none !important, which leaves every panel at a static, neutral tilt.

A second pass added a scroll-linked complement to the cursor tilt: src/scripts/parallax.js drifts each .glow-blob a few pixels vertically as you scroll, alternating direction per blob so the layers read as independent depths rather than one flat plane. It's a plain scroll listener that only ever writes a transform, throttled to one update per frame with requestAnimationFrame. It's skipped entirely below 641px and under prefers-reduced-motion: reduce — the blobs are the cheapest layer on the page (no backdrop-filter), but mobile gets zero extra animation budget spent on it regardless. Each of the three tile grids (Pillars, Disciplines, Work) also got a thin gradient hairline along the top edge of its cards — violet for Pillars, cyan for Disciplines, a cyan-to-violet blend for Work — so the three grids read as related but distinct instead of three copies of the same card.

Two more second-pass additions: every content tile now gets a hover state — a brighter fill and a wider shadow bloom — that deliberately never touches transform, so it can't fight with the cursor-tilt rotation already running on the same element. And body's background gained a fourth, barely-there layer: a tiled feTurbulence SVG data URI blended in with background-blend-mode: overlay at roughly 3.5% strength. It's static (no JS, no extra compositing layer, painted once with the rest of the background), and it exists purely to break up gradient banding across the large violet-to-blue field and sell the glass panels as sitting on a textured material rather than a flat color.

Generated imagery

Two images were generated with Higgsfield's gpt_image_2 model, both at high quality / 2k resolution:

  • Hero glass owl (1:1) — prompt: "a sculptural translucent glass owl figurine, frosted and clear glass mixed, refracting cool cyan and violet studio light, dramatic rim lighting, isolated on dark background, product photography style." The raw output already had a clean near-black background, but it was additionally run through Higgsfield's image_background_remover model to produce a true alpha-channel cutout (glass-owl-cutout.png), so it sits directly on the page's gradient with no visible seam.
  • Background texture (16:9) — prompt: "abstract macro photography of shattered glass shards and prism facets, deep violet and cyan light refraction, dark background, crystalline geometric texture, subtle bokeh, high contrast dramatic lighting." This one is used at low opacity with mix-blend-mode: screen and a radial mask-image behind the hero copy, purely as atmosphere — it's never meant to be looked at directly.

Both are served through Astro's built-in <Image /> component from astro:assets, which converts them to WebP and generates appropriately-sized output at build time (the hero owl went from a 5.3MB PNG to a ~55KB WebP).

Honest tradeoffs

1. The "3D glass object" is a flat image, not a 3D model. A real glTF/WebGL owl would let it rotate a full 360° convincingly. Because this is a single generated photograph, the animation is deliberately restrained to a small bob and a few degrees of sway/tilt — enough to sell "floating," not enough to expose that it's a flat plane. This was the pragmatic choice given the image-generation budget for this build, but it's the ceiling of what a photo-based hero object can do; a true rotation would need either a 3D render pipeline or several generated angles cross-faded together.

2. backdrop-filter is not free. Six discipline tiles plus three pillar tiles plus the manifesto and contact panels means up to a dozen simultaneously-blurred layers on one page. Desktop compositors handle this fine, but on mobile GPUs repeated large-radius backdrop blurs are a known jank source. The mitigation here is a media query at 640px that drops the blur radius from 18px to 10px and reduces the glow-blob blur from 70px to 40px with lower opacity — it doesn't eliminate the cost, but it meaningfully reduces the compositing work on the devices least able to afford it.

← Back to the site