The concept
Harbor Light is one of two churches in this showcase set, and the brief was explicit that it needed to be visually distinct from its counterpart — that one goes bold and contemporary; this one goes calm and luminous. So the whole site is organized around one idea: light through glass. Not a literal stained-glass window (that reads as "church clip art" fast), but the effect of light — dawn/dusk gradient washes, a soft prism-of-color accent used only a few times, and photography that leans on natural window light rather than a stage and a worship band.
The copy voice follows the same instinct: warm and direct, never corporate. Section headings like "Come as you are, leave lighter" and body copy like "no awkward stand-up-and-introduce-yourself moments, we promise" are doing the work of making the place feel like somewhere a first-time visitor would actually want to walk into.
Palette & type
Every color in the system is deliberately desaturated. The brief called for "luminous rather than saturated," so nothing here is a pure, punchy hue — golds, blues, and blush are all pulled toward pale/dusty versions of themselves.
Type is a two-family pairing: Fraunces (a variable, optical-size serif with real warmth in its italic cut) carries every headline, and Inter handles body copy, labels, and UI text so it never competes with the serif. The "leave lighter" hero line deliberately uses Fraunces' italic — it's the single most expressive type moment on the page, so it only happens once.
Generating the imagery
Every photo on the site was generated through the Higgsfield MCP using
Nano Banana Pro at 2K resolution, then converted to WebP
(cwebp -q 82 -m 6) to bring the asset folder down from roughly 73MB of PNG
to about 2.3MB total. Each image was art-directed specifically for this palette —
the warm gold, dusty blue, and blush tones were named in every brief on purpose, so
the generated photography would sit naturally inside the CSS color system instead of
fighting it.
Every shot — the golden-hour sanctuary hero, the dusk exterior, the community circle, the prism close-up, the clasped hands, the still-life journal, the golden-hour gathering, and the kids-and-family scene — was generated from a carefully art-directed brief specifying the lighting, lens character, color grade, composition, and mood, then iterated until it matched the brand. Several were regenerated when a first pass drifted off-brief — the sanctuary hero, for instance, initially came back with literal religious iconography the brief explicitly avoided, and was re-directed until the room read as pure architectural light and space. The full prompt library behind the set is proprietary and available to clients on request.
Motion & build techniques
Everything runs on GSAP 3 loaded straight from the jsDelivr CDN, plus the ScrollTrigger
plugin — no bundler, no npm install. A single js/main.js file registers
ScrollTrigger once and wires up every animation declaratively.
Gradient-sky drift
The hero's .hero-sky layer is an oversized --sky-dawn
linear-gradient animated purely in CSS — background-position drifting
between two points over 26s, ease-in-out alternate. No JS needed for
this one; it's the cheapest possible always-on ambient motion.
Hero parallax
A ScrollTrigger-scrubbed timeline (scrub: 0.6) moves the hero photo
yPercent/scale and fades the text block to 20% opacity as
you scroll past it, so the hero dissolves into the welcome strip instead of
hard-cutting.
Light-ray sweep
repeating-conic-gradient thin gold wedges with
mix-blend-mode: screen, tweened back and forth on
background-position — an animated light-ray texture rather than a
static PNG overlay.
Prism accent motif
Blurred, low-opacity radial-gradient circles (.prism-glow) placed
behind section heads, each given an independent slow drift
(sine.inOut, 10-14s, yoyo). This is the "stained-glass prism motif
used sparingly" from the brief — refracted color as atmosphere, never a literal
window.
Reveal-on-scroll system
Every content block carries a .reveal class
(opacity:0; translateY(34px)); one ScrollTrigger per element handles
single blocks, and a data-stagger wrapper batches grouped items
(service-time cards, ministry cards, event rows) with
gsap.utils.toArray() + stagger: 0.12 — declarative, no
per-section animation code duplicated.
Stat animation
The "12 yrs" tag in the About section tweens a plain JS number object
(gsap.to(obj, {val:12, onUpdate...})) and writes it into the DOM on
each frame, triggered once when it scrolls into view.
Scroll-aware navigation
One ScrollTrigger.create({start:60, onUpdate}) toggles an
.is-scrolled class that swaps the nav from transparent/white-on-photo
to a blurred paper background with ink text. The "Contact" pill button gets the
same light/dark swap explicitly — worth calling out because it was a real bug
caught in QA: the ghost-button style defaulted to dark ink text even when the nav
was transparent over a photo, which made it unreadable at the top of the page.
Responsive approach
Layout is CSS Grid/Flexbox throughout, with breakpoints at 1080px (two-column
sections collapse to one) and 720px (nav collapses to a full-screen mobile
menu, grids go to one column, service-time cards and ministry cards stack). Below 720px
the desktop-only decorative elements — the vertical "SCROLL" cue, the event location
column — are hidden rather than squeezed, since they're not load-bearing content.
The mobile menu itself is a simple full-screen panel using a CSS
transform: translateY(-100%) → translateY(0) transition — deliberately not
GSAP, since a single toggle didn't need a JS animation library.
Deployment
This is a fully static site — no server-side code, no build step, no environment variables. To ship it:
- Drag the
harborlight-church/folder straight into Netlify's manual deploy dropzone, or - Connect the repo in Netlify and set the base directory to
harborlight-church/with an empty build command (there's nothing to compile) and publish directory.
Every asset path in index.html and guide/index.html is
relative, so the folder also works opened directly from disk or served from any static
host without configuration.