Ledger & Vine is one HTML file, one stylesheet and one script. The cinema is a photograph pulled apart into parallax planes; the charm is a wine glass that fills itself.
A boutique winery sells restraint: few wines, small numbers, no urgency. The site's job is to make scarcity feel like luxury rather than absence. So the design system is old-world — burgundy so dark it reads as black, cream instead of white, gold reserved for accents — and the typography does the "estate" work: Cormorant Garamond for display (its italic stands in for a script face without the costume-party feel), Inter for body, and DM Mono for the things wineries actually print in fixed-width: lot numbers, pH, ABV, barrel counts. The conceit that ties it together is the ledger — wines named Double Entry and Carried Forward, sections ruled like an account book.
The hero is a six-layer sandwich, back to front: the photograph, a gold "light ray" gradient
wash (mix-blend-mode:screen), a burgundy scrim for text contrast, a far-hills SVG
silhouette, a near vine-rows SVG silhouette, and the wordmark. The SVG silhouettes are two paths
with a few stroked curves following the hillside — drawn to continue the photograph's geometry,
so the seam between photo and vector disappears into the dusk.
Each layer then gets one scrubbed tween. Depth is nothing more than different signs and
magnitudes of yPercent:
gsap.registerPlugin(ScrollTrigger);
const scrub = (sel, vars) => gsap.to(sel, {
ease: 'none', ...vars,
scrollTrigger: { trigger: hero, start: 'top top',
end: 'bottom top', scrub: true }
});
scrub('.ph-photo', { yPercent: 16 }); // deep layer, slowest
scrub('.ph-rays', { yPercent: 26, opacity: 0.15 }); // light thins out
scrub('.ph-hills', { yPercent: -7 }); // far silhouette
scrub('.ph-rows', { yPercent: -16 }); // near rows, fastest
scrub('.hero-copy',{ yPercent: -30, autoAlpha: 0 }); // wordmark lifts away
The photo's container is inset -14% at the top so the tween never exposes an edge.
With JavaScript disabled — or prefers-reduced-motion set — none of these tweens run
and the hero is simply a well-composed still: photograph, rays, silhouettes, wordmark.
Each club tier's glass is a small inline SVG: a bowl path used twice (once as a
clipPath, once stroked as the outline), a stem, a foot — and inside the clip, a
rectangle of wine. The pour is one animated property. The rectangle starts
crushed to the bottom of the bowl and scales up to the tier's fill level, which lives in a CSS
custom property on the card:
<article class="tier" style="--fill:.62"> … </article>
.wine-fill{
transform-box: fill-box;
transform-origin: center bottom;
transform: scaleY(.001); /* empty (JS present) */
transition: transform 2s var(--ease) .25s; /* the decant */
}
.tier.poured .wine-fill{ transform: scaleY(var(--fill)) }
The fill level is doing quiet information design: 3, 6 and 12 bottles a quarter become a third
of a glass, two thirds, and nearly full. A second element — the meniscus ellipse — rides up in
sync with translateY(calc(var(--fill) * -96px)), and a "stream" line runs a short
keyframe animation while the glass fills. An IntersectionObserver adds .poured when
a card scrolls into view (staggered 260 ms per card); hovering a card drains and re-pours
it. Reduced motion? The stylesheet forces every glass to render already filled — composition
intact, theatre skipped.
cubic-bezier(.5,.02,.16,1) — deliberately slow into the curve, like a decant.
Two-second pours, 1.4-second line reveals. Nothing on this site is in a hurry..js class gates every hidden state.
No script → the hero reads, the glasses are full, the booking card is a legible menu of
options. The booking CTA is honest, too: it tells you no table was reserved.Relative paths, no build step, one .nojekyll:
gh repo create bswxyz/ledger-and-vine --public --source . --push
gh api --method POST /repos/bswxyz/ledger-and-vine/pages \
-f 'source[branch]=main' -f 'source[path]=/'
# live at https://bswxyz.github.io/ledger-and-vine/ in ~2 min
Ledger & Vine is a design-showcase concept — the estate, the wines and the club are fictional, and the booking card reserves nothing. See the repository README for the full demo-vs-real map.
← Back to Ledger & Vine