Skip to content
+40 754.636.306 Start a project RO
All Case Studies
Web Development Astro v5 Bilingual

Building fikl.com: a bilingual Astro v5 gallery site for contemporary painter Gheorghe Fikl

Fine Art Portfolio / Gheorghe Fikl, Contemporary Painter | Astro v5 + GSAP + Lenis + Cloudflare

Gheorghe Fikl is a Romanian contemporary painter whose work has shown in Bucharest, London, Lisbon, New York, and Paris. The brief was to build him a website that felt like walking into a major gallery: quiet, image-first, in two languages, fast on every device. We delivered a static Astro v5 site with GSAP and Lenis smooth scroll, a custom multi-touch artwork viewer, structured data engineered for the Knowledge Graph, and a bilingual URL strategy that gives English and Romanian audiences the URLs they actually expect.

The Numbers

60+
Pre-rendered Routes
Static across two locales
EN + RO
Locales Bilingual
Prefix-free EN, native RO slugs
<2.5s
LCP Target Met
Hero painting paints fast

Project Snapshot

Client
Gheorghe Fikl
Sector
Fine art
Stack
Astro v5, GSAP, Lenis
Hosting
cPanel + Cloudflare

The Brief: A Gallery-Grade Site for an International Audience

Fikl's audience is not a casual one. Collectors, curators, gallerists, and serious art enthusiasts arrive at the site to evaluate his work for acquisition, exhibition, or critical study. They are international and Romanian-speaking in roughly equal measure. The site has to behave the way a major gallery's front desk behaves: unhurried, quietly authoritative, deferential to the work on the walls.

Before we drew a single screen, we agreed on three words for the brand: elegant, mysterious, timeless. The site had to speak with the quiet confidence of a major gallery. Never loud. Never decorative for its own sake. Every element had to defer to the artwork. We named the emotional goals explicitly. Awe and contemplation, like standing before a painting in a darkened room. Curiosity and discovery, with each series drawing visitors deeper. Confidence and trust, so a first-time visitor would feel within seconds that this is an established artist worth collecting. Those three goals became the editorial brief for every page.

The aesthetic mandate followed from there. We took our cues from Gagosian, David Zwirner, and Pace Gallery: image-first, generous whitespace, restrained typography, warm neutrals rather than cold whites. We agreed on a list of anti-patterns up front and held to it. No marketplace cues. No trendy effects. No over-animated micro-interactions. No busy layouts. Every element had to earn its place.

The constraints behind the surface were less obvious but more demanding. The site had to be bilingual without crippling its search performance. It had to be fast on slow hotel Wi-Fi and provincial Romanian connections. It had to honor accessibility (prefers-reduced-motion respected, ARIA in place, keyboard navigation working) without giving up the deliberate, inhale-and-exhale motion that makes a gallery site feel like a gallery and not a brochure. Every technical decision below is a response to one of those constraints, and every design decision below is the discipline that made the response coherent.

The Aesthetic Mandate: Five Principles That Settled Every Argument

A gallery site is a strange thing to design. Most websites compete for attention. This one had to surrender it. Most websites add elements. This one earned its strength by removing them. Before we wrote a line of code we settled on five principles: short enough to remember, specific enough to settle arguments. Every visual and motion decision on the site can be traced back to one of them.

1. Artwork is sovereign

The paintings are the hero of every page. UI chrome recedes. Navigation is a thin line at the top of the screen. Captions sit at the edges of the frame, never in front of the work. Hover states adjust opacity rather than colour because colour shifts steal focus. When in doubt, we asked which element the visitor's eye should land on first. If the answer was not the painting, we changed something.

2. Quiet confidence

A major gallery does not shout. It does not have to. We removed every element that did not serve either the work or the visitor's task. The homepage does not open with a slogan or a value proposition. It opens with a painting and a line of artist statement. Anything more would have felt like nervousness.

3. Deliberate motion

Animations on the site feel like a slow inhale: purposeful, smooth, unhurried. We standardised on GSAP's power2.out and power3.out easing curves, durations between 350 and 1500 milliseconds. No bounces. No snaps. No playful wiggles. Page transitions use a soft white curtain that fades over six-tenths of a second. Cursor follow uses inertia, not snap. Every animation had to pass a single test: would it feel right in a gallery at midnight, with one visitor in the room?

4. Warm minimalism

The single most consequential colour decision on the project was choosing a grey scale with a yellowish undertone instead of a cool, blue-leaning one. The difference is invisible to most clients in a brief and obvious in the room. Cool greys read as clinical, corporate, technological. Warm greys read as paper, plaster, gallery wall. The lightest tone on the site (#FAFAF8) is barely off-white. The darkest (#0D0C0B) is barely black. Every shade in between carries the same warmth. The single accent (a coral-rust #dd4b39) appears only on interactive elements, never decoratively. Most pages on the site contain no accent colour at all.

5. Typography as architecture

We used a single typeface (Roboto) in three weights, and built hierarchy from spacing and weight rather than size alone. Headings step down by weight before they step down by size. Captions sit in uppercase with generous letter-spacing, wide enough to read as architectural marks rather than as words. Body text runs at light 300 weight to keep prose pages feeling unhurried. Romanian diacritics (ă, î, ș, ț) get the same care as the Latin base set, served from a separate Unicode-range subset so that a single curator browsing in Romanian does not pay for glyphs they will never see, and so the diacritics never render as the wrong weight on a slow connection.

The custom cursor deserves its own note. We replaced the default arrow with a small dot and a circle that uses mix-blend-mode: difference, meaning the cursor adapts to whatever sits behind it without ever needing to be explicitly themed. On a white gallery page it appears as a dark mark. Over a dark painting it appears as a pale one. The cursor itself becomes a piece of typography that respects the work, rather than a chrome element fighting with it.

From our experience

The hardest part of all five principles was the discipline of saying no. We kept an explicit list of things the site would not be (no marketplace cues, no glassmorphism, no neon gradients, no novelty effects, no busy layouts) pinned next to the design tokens for the entire project. Most of the design work, in the end, was honoring it.

Architecture: Astro v5, Cloudflare, Static-First

We chose Astro v5 with full static output. Fikl's portfolio updates seasonally, not hourly, so there is nothing to gain from server-side rendering and a great deal to gain from shipping plain HTML through a CDN. The build produces ~60 pre-rendered pages that Cloudflare caches at the edge.

The high-level picture:

  • Astro v5 with content collections. Every series, exhibition, and image is described in MDX with a strict schema and validated at build time.
  • GSAP + ScrollTrigger for scroll-driven reveals, plus Lenis for inertial smooth scrolling.
  • Astro View Transitions for soft fades between pages instead of jarring full reloads.
  • Sharp for build-time image optimization. Every painting served as WebP at four responsive widths.
  • Apache + Cloudflare for hosting and caching, with a hand-tuned .htaccess for redirects, compression, and cache headers.
  • TypeScript throughout, with path aliases that keep imports legible (@components, @i18n, @scripts).

There is no database, no API, no server-side runtime. The site that loads in your browser is the same set of files we generated at build time, served from disk and cached aggressively. That choice cut the entire class of "the database is slow today" problems out of the project on day one, and made the rest of the work about craft, not infrastructure.

Bilingual URLs Without Page Duplication Pain

The first hard problem was language. English audiences expect URLs like /works/ and /about/. Romanian audiences expect /lucrări/ (transliterated to /lucrari/ for ASCII URLs) and /despre/. Generic auto-translated slugs read like an afterthought. They break the spell of a careful site.

The constraint was twofold. English had to keep its prefix-free URLs because years of inbound links (exhibition reviews, press features, gallery sites) point at /works/, not /en/works/. We were not going to discard that link equity. Romanian needed natural-language slugs because anything else would read as a translation, not as a Romanian site. And Google had to understand that the two versions belong together but serve different audiences.

The solution is a small data structure that does most of the heavy lifting. Internal page names are mapped to locale-specific URL segments in a single routeMap:

export const routeMap = {
  en: { works: 'works',   about: 'about',  exhibitions: 'news',     contact: 'contact' },
  ro: { works: 'lucrari', about: 'despre', exhibitions: 'articole', contact: 'contact' },
};

That tiny object is the spine of every link, every sitemap entry, and every hreflang tag on the site. A getAlternateUrl resolver computes the matching URL on the alternate locale for any given page, and a single SEOHead component emits the three alternate-language link tags every page needs: one for each language plus an x-default fallback to English. We built each locale as its own page file rather than relying on dynamic routing tricks. It is slightly more verbose to scaffold, but it leaves Astro's static generator with no ambiguity and leaves future editors with a structure that is obvious at a glance.

The result is the kind of detail visitors do not notice and search engines reward. A Romanian collector clicking through from a Bucharest art journal lands on fikl.com/ro/lucrari/baroque/: a URL written in their language. An international curator clicking through from a London review lands on fikl.com/works/baroque/. Both pages know about each other. Google indexes them as a set, with no duplicate-content penalties and no clever query-string hacks. Hreflang (the signal that tells Google which version of the page belongs to which audience) validates clean across every page.

Lazy-Booting GSAP and Lenis: Keeping First Paint Honest

The second hard problem was motion. Gallery-paced motion is a design constraint before it is an engineering one. Every easing curve, every duration, every transition had to pass the midnight-gallery test. But the libraries that make that motion possible are heavy. Loading GSAP, ScrollTrigger, and Lenis eagerly on a gallery site pushes out the moment the visitor sees the artwork. On a gallery site, that moment is the entire product.

We were not willing to give up the motion. Scroll-driven reveals, magnetic-cursor hover, smooth-scrolled inertia, and view-transition curtains are part of how the site communicates its quiet confidence. We were not willing to penalize first paint either.

The fix is a two-tier boot. The critical-path entry script is tiny (a few dozen lines) and handles only what is needed before the first animation could possibly fire. Everything else is deferred. We dynamically import the heavy animation engine inside requestIdleCallback, the browser's "tell me when you're free" API, with a 1.5-second timeout fallback for browsers that do not support it. ScrollTrigger only registers itself when the browser is idle. Lenis only starts when the page has settled. The comment that opens the entry file says it plainly:

// Critical-path entry: only what's needed before/at first paint.
// Heavy init (ScrollTrigger, Lenis, scroll-driven animations) is moved to
// boot.ts and dynamic-imported from inside `requestIdleCallback` so it
// doesn't push out LCP or inflate Total Blocking Time on first load.

The corollary problem is cleanup. View Transitions keep the same browser tab and JavaScript context across page navigations, so animations that are not torn down cleanly accumulate. Every page becomes a little slower than the last. We register every animation with a cleanup function, and on every page navigation we call them all, kill any leftover GSAP timelines, and remove every ScrollTrigger before the new page binds its own. Memory stays flat. The fiftieth page navigation feels exactly like the first.

The visible result is the answer to the brief's quietest constraint. The artwork appears almost instantly. The animation system spins up behind the scenes, never racing the paint pipeline. The page feels both fast and alive, and stays that way no matter how long the visitor stays.

An LCP-First Image Pipeline for a Painting-Heavy Site

The third hard problem was images. Every page on the site has a painting at the top. Largest Contentful Paint (the moment your hero image actually appears on screen) is therefore the single most important performance metric on a gallery site. If LCP is slow, nothing else matters. The artwork-is-sovereign principle becomes a network-pipeline problem.

The constraints were familiar but unforgiving. We could not serve the largest desktop variant to mobile visitors because that wastes about a megabyte per page-view, and mobile data is not free. We could not use JavaScript to swap images based on screen size because that hurts SEO and accessibility, and it cannot run before the browser has parsed and executed the script (which is the worst possible time to discover what image to fetch). The hero image had to start downloading the moment the HTML hit the browser, and it had to be the right hero image for the device.

We pre-generate four WebP variants of every painting at build time using Sharp, Astro's native image-optimization backend. The base layout exposes a small lcpImage prop that emits <link rel="preload" as="image"> tags with media-query-gated imagesrcset attributes. The browser reads those preload tags before it parses the body, picks the right variant for the current viewport, and starts downloading it immediately, beating the body parser to the <img> tag by several hundred milliseconds on a typical connection. The same Sharp variants then power the rendered <picture> element, so the transformation never runs twice and the cache keys line up perfectly.

Every other image on the page (thumbnails, secondary works, exhibition covers) uses lazy loading with a small fade-in transition that masks the network. Only the LCP painting gets the priority treatment, because only the LCP painting needs it.

The qualitative result is that the site feels right immediately. The visitor lands on the homepage and the hero painting is there before they have finished scrolling. They click into a series and the first artwork is already painted on screen. Mobile visitors do not pay desktop bandwidth costs. Lighthouse Performance scores stay high without the team having to think about performance on every new page. The architecture is the optimization.

Animation That Defers to the Artwork

Beyond the heavy lifting, three smaller animation systems give the site its texture. Each is disciplined by the deliberate-motion principle.

View Transitions with rapid-click resilience

View Transitions turn page navigations into soft fades instead of full reloads. A fixed-position white curtain fades in for 0.4 seconds while the next page fetches, then fades out for 0.6 seconds once the new content is in place: the same gallery-curtain feel a visitor would get walking from one room to the next. There is a subtle engineering detail underneath. Rapid clicks (a curator who decides mid-fade to go somewhere else) used to leave the site stuck on a half-faded overlay because the original animation's promise never resolved. We split the visual layer (driven by GSAP, which gracefully overrides itself when interrupted) from the timing layer (a plain setTimeout, which always resolves), and rapid-click jank disappeared.

Declarative animation triggers

Declarative animation triggers let the design team work without touching JavaScript. Every animatable element on the site is annotated with a data-animate attribute: fade-in, text-reveal, image-reveal, clip-reveal, parallax, stagger-children. The animation engine reads those attributes on every page and binds the right behavior. Adding a new fade-in to a marketing block is a one-attribute change, not a code change. Designers stay in design tools. Engineers stay out of their way.

ArtworkViewer: pinch, swipe, keyboard, scroll-aware

ArtworkViewer is the site's most complex single component: a custom multi-image viewer that handles pinch-zoom on tablet, keyboard arrow navigation on desktop, swipe gestures on phones, and a thumbnail strip that auto-scrolls to keep the current image visible. The detail we are proudest of is small. While a visitor is pinching or dragging an artwork, smooth scroll pauses so the page does not fight their gestures. It is the kind of detail that nobody notices when it works, and that everybody notices when it does not.

Content Collections and a Connected JSON-LD Entity Graph

Behind the scenes, the site is held together by two quietly powerful systems.

The first is Astro v5 content collections. Every series, every exhibition, every individual painting is described in MDX with a strict schema. Image references are validated at build time. A typo in a filename fails the build instead of shipping a 404. Adding a new painting to a series is a matter of editing one file and adding one image. The homepage poster, the works grid, the prev/next series-nav thumbnail, and the artwork viewer all read from the same canonical list. There is exactly one source of truth for the content of each series, and it lives next to the prose that describes it.

This system also encodes a process lesson the project taught us early. The English and Romanian content files briefly drifted. A few paintings dropped from one locale, a few never propagated to the other. We solved it not by adding tooling but by adding a convention: English is canonical, Romanian mirrors English's image list exactly, and only the prose (medium, description, tags, body) is localized. The drift problem was not a code problem and would not have been solved by code.

The second system is JSON-LD structured data. The homepage emits a connected @graph: a Person typed as VisualArtist (with verified social profiles linked via sameAs), a WebSite declaring both supported languages, and a WebPage describing the homepage itself. Series detail pages emit a CreativeWorkSeries with a hasPart array of VisualArtwork objects: every painting on the page individually described with its medium, dimensions, year, and image URL. Breadcrumbs are explicit. The /about/ page uses AboutPage linked back to the artist entity. The /works/ page uses CollectionPage.

The point of this scaffolding is not to game search engines. The point is that Google now understands Fikl is an artist entity, not just a website. He shows up in Knowledge Graph features. His paintings are eligible to appear in Google Images with the rich metadata that drives clicks from collectors: title, year, dimensions, medium, all in the slot the algorithm wants them. The structured data does the work of making the site legible to the systems that surface it. For the deeper rationale on entity-graph SEO, see our llms.txt AI search visibility case study.

Deployment, Caching, and Legacy URL Migration

The site is hosted on cPanel with a hand-tuned .htaccess and fronted by Cloudflare's CDN. There is no Node runtime in production, no Lambda, no edge functions. Just static HTML, CSS, JavaScript, and image files served from disk and cached at the edge.

A few details earn their keep. We 301-redirect every legacy /en/ URL to its new prefix-free English equivalent, preserving the inbound link equity from years of press coverage and exhibition reviews that point at the old structure. HTTPS is enforced. The www variant redirects to apex. The canonical URL is unambiguous. Hashed assets are cached for a year with immutable headers. HTML is cached for an hour, which is short enough that content updates appear quickly without forcing a global cache purge. Compression is on for text, JSON, and JSON-LD. Standard security headers are in place: X-Content-Type-Options, X-Frame-Options, Referrer-Policy. None of this is novel. All of it is correct.

Results in Production

Real-world outcomes, where we have measurements:

  • Fast first paint on both locales. The hero painting appears within Google's "good" LCP threshold on representative connections, on both English and Romanian homepages. Live Lighthouse audit numbers available on request.
  • Clean hreflang validation. Every page declares its alternate-language counterpart correctly. Search Console reports no hreflang errors.
  • Rich-result eligibility. Person, CreativeWorkSeries, VisualArtwork, BreadcrumbList, and WebSite schemas all validate without warnings in Google's Rich Results Test.
  • Stable performance across navigations. Because animations and ScrollTriggers are torn down on every page transition, the site feels exactly the same on the fiftieth click as on the first.
  • Pinch-zoom that works at gallery openings. Curators flipping through the artwork viewer on iPads at exhibition previews can pinch into a painting at 4x to inspect a detail without the page hijacking the gesture.

The qualitative result is the one that matters most. The site disappears in front of the work. Visitors stop thinking about the website and start thinking about the paintings, which is, in the end, what the brief asked for.

Lessons We Carry Forward

Four takeaways are worth carrying to every project after this one.

  1. Design principles are decision-savers, not decoration. The five principles we agreed on at the start of the project (artwork is sovereign, quiet confidence, deliberate motion, warm minimalism, typography as architecture) settled at least one argument every week of the build. Writing them down in plain language, before any pixels, was the highest-leverage hour of the project.
  2. Bilingual content drift is a process problem, not a code problem. When the English and Romanian image lists briefly diverged, the fix was not tooling. It was an explicit convention: one language is canonical for structure, the other mirrors it, and only prose is localized. Process beats automation when humans are doing the localizing.
  3. Defer everything you can. The single biggest perceived-performance lever on this project was moving GSAP and Lenis behind requestIdleCallback and dynamic-importing them after first paint. The pattern is general. Every site that ships heavy motion benefits from the same discipline. If a script does not have to run before first paint, it should not.
  4. Schemas pay for themselves. Astro's image() content type caught broken image references at build time and saved the project from at least one production-shipped 404. Every minute spent defining the schema returned itself many times over in problems that never reached the visitor.
Our approach

The brief's quietest constraint was the loudest discipline. "Make a site that disappears in front of the work" is easy to say and very hard to do. Every animation, every colour, every URL had to be small enough to forget. The craft is in the absence.

The Full Stack

  • Astro v5 (static output, content collections, MDX, View Transitions)
  • GSAP + ScrollTrigger (scroll-driven reveals, page transitions)
  • Lenis (inertial smooth scroll, integrated with GSAP's ticker)
  • Sharp (build-time WebP image optimization)
  • TypeScript (strict mode, path aliases)
  • Apache + Cloudflare (hosting, CDN, caching, 301 redirects)

Frequently Asked Questions

Why Astro v5 instead of WordPress for an artist site?

A contemporary painter's portfolio updates seasonally, not hourly. There is nothing to gain from server-side rendering, and a great deal to gain from shipping plain HTML through a CDN. Astro v5 with full static output produces ~60 pre-rendered pages that Cloudflare caches at the edge. There is no database, no API, no server-side runtime. That choice cut the entire class of "the database is slow today" problems out of the project on day one. For a deeper comparison, see our WordPress alternatives 2026 guide.

How does the bilingual URL strategy work without breaking SEO?

English keeps prefix-free URLs (fikl.com/works/) so years of inbound links from press features, exhibition reviews, and gallery sites still resolve. Romanian gets natural-language slugs (fikl.com/ro/lucrari/) so it reads as a Romanian site, not a translated English site. A small routeMap maps internal page names to locale-specific URL segments. A single SEOHead component emits hreflang link tags for each language plus an x-default fallback to English. Search Console reports zero hreflang errors.

What is the LCP-first image strategy and why does it matter?

Largest Contentful Paint is the moment the hero image actually appears on screen. On a gallery site, that moment is the entire product. We pre-generate four WebP variants of every painting at build time using Sharp. The base layout emits <link rel="preload" as="image"> tags with media-query-gated imagesrcset attributes. The browser reads those preload tags before it parses the body, picks the right variant for the current viewport, and starts downloading it immediately. Mobile visitors do not pay desktop bandwidth costs.

How do you preserve animation polish without hurting first paint?

Two-tier boot. The critical-path entry script is tiny and handles only what is needed before the first animation could possibly fire. Everything else is deferred. We dynamically import the heavy animation engine (GSAP, ScrollTrigger, Lenis) inside requestIdleCallback with a 1.5-second timeout fallback. The artwork appears almost instantly. The animation system spins up behind the scenes, never racing the paint pipeline.

Can other galleries or artists use this same approach?

Yes. The pattern generalizes to any image-first portfolio site: galleries, foundations, studios, photographers, sculptors, designers. The core decisions (static-first hosting, content collections with strict schemas, lazy animation boot, LCP-first image pipeline, connected JSON-LD entity graph) all transfer. The aesthetic principles are equally portable. The only thing that must be built fresh is the brand: colour palette, typography stack, motion vocabulary, and copy. If you are a gallery, foundation, studio, or artist looking for a site that respects the work, our corporate website development service starts here.

Want a site that respects the work?

Galleries, foundations, studios, artists. Fast, bilingual, search-engine-aware, and quiet enough to let the art lead.