Skip to main content
The gallery page showcases participant creations and workshop moments at Borrissol Espai Creatiu. Images are displayed in a honeycomb layout — a CSS clip-path grid where diamond-clipped tiles morph into rounded squares on hover or tap, highlighting the selected image while dimming its neighbours. The page is intentionally minimal: a heading, an eyebrow label, and the grid — no sidebar, no filters, no pagination.

Routes

The gallery is served at four URLs, one per locale. All four are statically pre-rendered at build time: The Catalan default route (gallery.astro) hardcodes useTranslations('ca'). The three non-default locale routes ([lang]/gallery.astro) call useTranslations(lang) with the locale derived from getStaticPaths, resolving the page title, meta description, eyebrow label, and image alt text prefix from src/i18n/ui.ts.

SEO metadata

The page title and description are driven by translation keys. In English: Each image receives an auto-numbered alt attribute: ${t('gallery.image.alt')} 1, ${t('gallery.image.alt')} 2, and so on, based on sort order.

Image pipeline

Gallery images live in src/assets/images/gallery/ and are processed by the Astro asset pipeline at build time. The gallery.astro page uses import.meta.glob to load all .jpg files eagerly:
Images are sorted alphabetically by filename so the display order is deterministic. The sorted array is passed as the images prop to Gallery.astro.
To add a new image to the gallery, drop a .jpg file into src/assets/images/gallery/. Name it with a numeric or alphabetic prefix (e.g. 040-new-piece.jpg) to control its position in the grid. The Astro build will automatically generate AVIF and WebP variants.
Gallery.astro renders the honeycomb grid. It accepts a typed images prop:
Inside the component, each image is rendered with <Picture> from astro:assets, outputting responsive <source> elements in AVIF and WebP:
All images are loading="lazy" — the gallery has no above-the-fold hero image.

Honeycomb layout mechanics

The interlocking honeycomb pattern is achieved entirely in CSS using a grid + clip-path: path() approach. There is no canvas, SVG, or layout library involved.

Grid structure

Each photo cell (grid-column: span 2) is a square sized 2 × --cell + gap. Grid rows are only --cell tall, so each cell visually overflows downward by one row. This vertical overflow, combined with the horizontal half-tile offset applied to every nth-child(7n+5) cell, produces the honeycomb interlock.

Diamond clip

The initial clip-path: path(...) clips each tile to a diamond shape using an 8-curve Bézier. The hover/active state morphs the same path to a rounded square. Both shapes share the same 8-control-point structure so the browser can interpolate smoothly.

Breakpoint grid configuration

The --cell CSS custom property controls the tile size at each breakpoint: Each breakpoint has its own hardcoded clip-path: path() string because CSS path() uses absolute pixel coordinates — percentages are not supported — so the path must be rescaled for each tile size.

Tap-to-reveal interaction

No JavaScript framework is used. The interaction is handled by a small inline script (is:inline) injected into the page:
The CSS :has() selector handles the dimming logic: when any tile has .is-active, all other tiles receive filter: brightness(0.55) saturate(0.6). The active tile itself gets filter: brightness(1) saturate(1.15) and morphs to the rounded-square clip path. On hover-capable devices (@media (hover: hover)), the hover state provides the same morph without requiring a tap. The hover and tap states use identical clip-path values.

Accessibility

  • The grid is a <ul role="list"> with <li> cells, making the image count available to screen readers.
  • Each tile is a <button type="button"> with an aria-label set to the image’s alt text.
  • focus-visible outline is applied to focused tiles for keyboard navigation.
  • @media (prefers-reduced-motion: reduce) disables the clip-path morph transition, leaving only the filter transition at a reduced 200ms duration.

Page layout

The gallery page uses the shared Layout.astro wrapper (which injects GA4, consent mode, skip link, hreflang tags, and Open Graph meta) with ogType="website". It includes <Navbar> and <Footer> but no PrivacySection — the privacy accordion is only included on the home page.