How This Site Was Built
This is the "Mirage" art direction for Nomadic Owls — one of ten visually distinct builds sharing the same real agency content. Below is a specific account of the concept, the effects, the type and color decisions, the generated imagery, and where the approach cuts corners.
Y2K optimism, reframed through a desert nomad lens
The brief was "Y2K Retro-Futurism / Desert Chrome": early-2000s chrome-and-gradient optimism, but instead of a generic metallic sphere on a white studio backdrop, the hero object is a liquid-chrome owl that looks like it's evaporating into dune sand — a mirage on the horizon, which is also a nod to Nomadic Owls being a fully remote, nomadic studio. The whole site is built around that one image: a sunset gradient mesh stands in for the sky the owl is reflecting, and the retro-OS taskbar nav plus foil trading cards for the six disciplines carry the "early internet, but polished" feeling through the rest of the page.
Sunset chrome on a near-black base
The palette is hot pink #ff5f9e → orange #ff8a3d → deep purple
#5b2a86, with warm sand #e8c9a0 as the one grounding neutral and
near-white chrome #f4f2f8 for text and metal highlights. All of it sits on a
near-black ink base (#150a22) rather than white — a busy, colorful gradient
needs a dark anchor or it reads as noise, and it also makes the chrome/sand text pop the
way actual metal catches light against a dark sky.
Type is two families, both self-hosted via @fontsource (no third-party
font requests): Unbounded for display headings — it's bold and
geometric enough to carry the Y2K feeling without tipping into novelty — and
Space Mono everywhere else, including body copy. Using a monospace
font for paragraphs is an intentional, slightly uncomfortable choice: it reads like a
terminal or an old OS dialog, which fits the "retro tech" concept, at the cost of
slightly denser reading rhythm than a humanist sans would give. I compensated with a
1.65 line-height and capped measure (~46–60ch) on paragraphs.
Four independently-drifting radial gradients, not one CSS gradient
The animated background (.mesh in global.css) is four
radial-gradient() layers stacked in one background-image,
each with its own size and position. Because background-position accepts a
comma-separated value per layer, a single keyframe animation can drift all four blobs
independently just by listing four position pairs per keyframe step. The whole thing is
blurred by 70px and given a spinning conic-gradient overlay
(mix-blend-mode: screen) underneath for extra depth. It's cheap — no JS,
no canvas — but the blur radius is real work for the GPU on some hardware, which is the
first tradeoff noted below.
Cursor-driven CSS custom properties, not a JS animation library
The six discipline "trading cards" use the generated holographic foil texture as a
background, hue-rotated per card (filter: hue-rotate(var(--hue))) so one
source image reads as six distinct foil colors instead of a repeated tile. A small
pointermove listener per card writes four custom properties —
--rx, --ry, --mx, --my — which drive
both a rotateX/rotateY tilt transform and a
radial-gradient(circle at var(--mx) var(--my), …) shine layer in
mix-blend-mode: overlay. The shine tracks the cursor; the tilt responds to
it. No animation library, just custom properties updated on every pointer event and read
straight by CSS.
A fixed overlay and a lightweight DOM-node trail
The scanline effect is one fixed, full-viewport repeating-linear-gradient
(1px light line, 2px gap) at mix-blend-mode: overlay and roughly 50%
opacity — subtle enough to read as texture rather than distraction. The cursor sparkle
trail spawns a small radial-gradient <span> at the pointer position
on a throttled pointermove (roughly every 55ms), lets a CSS
animationend event remove it, and never accumulates more than a handful of
nodes at once.
All of this checks prefers-reduced-motion up front: under reduced motion,
the sparkle-trail listener is never attached, the gradient mesh and conic sweep get
their animation duration clamped to effectively zero, the button and card shimmer
transitions are removed, and blinking cursors stop blinking.
Two images, both gpt_image_2 via Higgsfield
Hero image (src/assets/chrome-owl.webp), generated at 3:4
— the CLI's aspect-ratio flag only accepts 1:1, 4:3, 3:4, 16:9, 9:16, 3:2, 2:3,
so the brief's suggested 4:5 wasn't available and 3:4 was the closest portrait ratio:
"A liquid chrome sculpture of an owl, mirror-like reflective mercury surface reflecting a hot pink and orange sunset gradient sky, floating above desert sand dunes at dusk, deep purple horizon, dramatic studio lighting, hyperreal 3D render, glossy specular highlights, Y2K aesthetic"
Card background texture (src/assets/foil-texture.webp),
generated at 1:1:
"Abstract holographic foil texture pattern, iridescent pearlescent rainbow sheen, liquid metallic swirls in pink orange and purple, close-up macro texture, Y2K chrome aesthetic, seamless glossy surface"
Both were downloaded as PNG and converted to WebP with sharp at build time
(resized to a 1200px-wide max, quality 80–82), then served through Astro's
astro:assets pipeline for responsive srcset output.
Two things I'd flag to a reviewer
1. Backdrop blur and gradient blur stack up. The glass panels
(.panel, used for the hero copy, pillar windows, cards, contact block) use
backdrop-filter: blur(18px) on top of a 70px-blurred animated background.
That's two blur passes running continuously behind a lot of the page. It's smooth on
recent hardware but is the single heaviest cost in the design — on very low-end mobile
GPUs it could drop frames. A production follow-up would reduce blur radius or swap the
animated mesh for a static gradient below a viewport-width breakpoint.
2. Monospace body copy is a legibility tradeoff, not a free win. Space Mono for paragraphs reinforces the terminal/Y2K-OS concept, but monospace text has less contrast between letterforms than a well-hinted humanist sans, so it reads slightly slower at long lengths. I kept every paragraph short and capped its measure to offset this rather than pretending it isn't a cost.