> ## Documentation Index
> Fetch the complete documentation index at: https://constanza101-borrissol-28.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Borrissol project structure and file layout

> Directory layout, routing conventions, component categories, and file responsibilities for the Borrissol Astro 6 codebase.

The Borrissol codebase follows the standard Astro project layout with a few deliberate structural choices: all business and SEO configuration is centralised in `src/config/` (split across `site.ts`, `reviews.ts`, and `seasonal.ts`), all translated copy lives in `src/i18n/ui.ts`, all design tokens live in `src/styles/theme.css`, and per-language routing is handled by a single `[lang]/` dynamic route rather than duplicated per-language directories. Configuration files at the root (`astro.config.mjs`, `keystatic.config.ts`, `netlify.toml`) govern the build, CMS schema, and deployment respectively.

## Directory tree

```text expandable theme={null}
keystatic.config.ts         # CMS schema (collections, fields)
  astro.config.mjs            # i18n, redirects, sitemap, integrations
  netlify.toml                # Node version + bot-trap redirects
 
  src/
  ├── assets/images/          # processed by Astro (AVIF/WebP output)
  ├── components/
  │   ├── landings/                    # full-page workshop landings
  │   │   ├── TuftingLanding.astro
  │   │   ├── PunchNeedleLanding.astro
  │   │   ├── FeltreLanding.astro
  │   │   ├── LoomLanding.astro
  │   │   ├── BorlaLanding.astro
  │   │   ├── PelussetesLanding.astro
  │   │   ├── SummerLabLanding.astro
  │   │   ├── TeamBuildingLanding.astro
  │   │   └── TechniqueLanding.astro   # shared skeleton the landings compose
  │   ├── layout/
  │   │   ├── Seo.astro                # all meta, JSON-LD, hreflang
  │   │   ├── Navbar.astro             # incl. language switcher + hash preservation
  │   │   ├── Footer.astro
  │   │   ├── WhatsAppFab.astro 
  │   │   └── CookieBanner.astro
  │   ├── sections/                    # home-page sections
  │   │   ├── HeroSection.astro
  │   │   ├── OfferSection.astro
  │   │   ├── ProcessSection.astro
  │   │   ├── WorkshopsSection.astro
  │   │   ├── TestimonialsSection.astro
  │   │   ├── AboutSection.astro
  │   │   ├── FAQSection.astro
  │   │   ├── CTASection.astro
  │   │   ├── Gallery.astro            # honeycomb tap-to-reveal photo grid
  │   │   ├── PressGrid.astro          # press page grid
  │   │   ├── PressStrip.astro         # press strip on home
  │   │   └── PrivacySection.astro
  │   └── ui/                          # reusable DS primitives
  │       ├── LandingHero.astro        # extracted landing hero
  │       ├── LandingSection.astro     # extracted landing band
  │       ├── FinalCta.astro           # closing CTA block
  │       ├── FeatureCard.astro
  │       ├── TechniqueCard.astro
  │       ├── CourseSchema.astro       # Course JSON-LD per landing
  │       ├── Faq.astro
  │       ├── FaqSchema.astro          # FAQPage JSON-LD
  │       ├── Steps.astro
  │       ├── DetailsList.astro
  │       └── BorlaFunnel.astro
  ├── config.ts               # WhatsApp helpers (WHATSAPP_BASE, waHref())
  ├── config/
  │   ├── site.ts             # SEO + business config (SITE object)
  │   ├── reviews.ts          # Google review data + aggregate rating
  │   ├── seasonal.ts         # time-limited event visibility flags
  │   └── workshops.ts        # tufting formats (name, size, price, level)
  ├── content/
  │   └── blog/               # Keystatic-authored Markdoc posts (.mdoc)
  ├── i18n/
  │   ├── ui.ts               # all copy, 4 languages
  │   └── utils.ts            # getLangFromUrl, useTranslations, getAlternatePath
  ├── layouts/ 
  │   ├── Layout.astro        # base: GA + Consent Mode, skip link, favicons, scroll reveal
  │   ├── HomePage.astro      # home wrapper
  │   └── LandingPage.astro   # workshop-landing wrapper
  ├── pages/
  │   ├── 404.astro
  │   ├── index.astro         # / (CA, default locale)
  │   ├── gallery.astro       # /gallery (CA)
  │   ├── press.astro         # /press (CA)
  │   ├── tufting.astro
  │   ├── punch-needle.astro
  │   ├── felting.astro
  │   ├── loom.astro
  │   ├── borla.astro
  │   ├── summer-lab.astro
  │   ├── team-building.astro   
  │   ├── pelussetes.astro
  │   ├── [lang]/             # dynamic routes — /es, /en, /fr variants
  │   │   ├── index.astro
  │   │   ├── gallery.astro
  │   │   ├── press.astro
  │   │   ├── tufting.astro
  │   │   ├── punch-needle.astro
  │   │   ├── felting.astro
  │   │   ├── loom.astro
  │   │   ├── borla.astro
  │   │   ├── summer-lab.astro
  │   │   ├── team-building.astro
  │   │   └── pelussetes.astro  
  │   └── blog/
  │       ├── index.astro     # /blog
  │       └── [slug].astro    # /blog/post-slug
  └── styles/
      └── theme.css           # design system tokens
```

## Directory and file reference

### `src/assets/images/`

All content images are stored here and run through the Astro asset pipeline at build time. Astro's `<Picture>` component converts sources to **AVIF + WebP** with a responsive `srcset`, and automatically generates the correct `width`/`height` attributes to prevent Cumulative Layout Shift (CLS = 0). Source files should be no larger than approximately 2× the widest responsive variant that will be served.

<Tip>
  The hero image uses `loading="eager"` and `fetchpriority="high"` to optimise LCP (1.8 s on mobile). All other images use `loading="lazy"`.
</Tip>

### `src/components/`

Components are organised into four informal categories:

| Category          | Directory / Files                                                                 | Purpose                                                      |
| ----------------- | --------------------------------------------------------------------------------- | ------------------------------------------------------------ |
| Page sections     | `HeroSection.astro`, `OfferSection.astro`, `WorkshopsSection.astro`, etc.         | Full-width bands that compose the single-page landing layout |
| Shared primitives | `Button.astro`, `Seo.astro`, `Navbar.astro`, `Footer.astro`, `CookieBanner.astro` | Reusable UI building blocks used across multiple pages       |
| Gallery & press   | `Gallery.astro`, `PressGrid.astro`, `PressStrip.astro`                            | Feature-specific display components                          |
| Utilities         | `WhatsAppFab.astro`, `PrivacySection.astro`                                       | Single-purpose functional components                         |

`Seo.astro` is the single place for all `<meta>` tags, JSON-LD structured data blocks (`LocalBusiness`, `FAQPage`, `ImageObject`, `AggregateRating`), Open Graph/Twitter Card tags, hreflang links, and canonical URL. It accepts per-page overrides as props and falls back to the values in `src/config/site.ts`.

### `src/config/`

The config directory holds three separate modules — not one. Each is a single-purpose export:

| File          | Exports                                            | Description                                                                                                                                                                                                                                                                                                      |
| ------------- | -------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `site.ts`     | `SITE: SiteConfig`                                 | Every SEO token and business detail: name, URL, locale, logo path, OG image path, and the full `BusinessConfig` (type, address, geo coordinates, telephone, email, opening hours, price range, social links). `Seo.astro` and the JSON-LD generators consume this object directly.                               |
| `reviews.ts`  | `REVIEWS: Review[]`, `REVIEW_AGGREGATE`            | Real Google review data (reviewer names + i18n keys for review text) and aggregate rating totals (`ratingValue`, `reviewCount`). Shared by both `TestimonialsSection.astro` and the `AggregateRating` JSON-LD node in `Seo.astro`. Update `REVIEW_AGGREGATE` whenever the Google Business Profile totals change. |
| `seasonal.ts` | `summerLab: SeasonalEvent`, `isSummerLabVisible()` | Controls whether time-limited promotional cards are shown. Visibility is evaluated at **build time** (not per request), so a card hides on the first deploy after `endDate`. Bump the dates each year to re-enable the card.                                                                                     |

Update `site.ts` rather than touching individual components when business details change. Never hardcode review counts or aggregate ratings inline — always edit `reviews.ts`.

### `src/content/blog/`

MDX files authored via the Keystatic CMS panel at `/keystatic`. Posts are processed through Astro Content Collections (`getCollection('blog')`). The blog is intentionally **Catalan-only** to keep the editorial workload sustainable — `/es/blog`, `/en/blog`, and `/fr/blog` all 301-redirect to `/blog`.

### `src/i18n/`

| File       | Exports                                                                               | Description                                                                                                                                                                                                  |
| ---------- | ------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `ui.ts`    | Translation map object                                                                | All visible copy for every UI string in all four languages (Catalan, Spanish, English, French). Zero hardcoded strings are allowed in components — every user-facing string references a key from this file. |
| `utils.ts` | `getLangFromUrl`, `useTranslations`, `getAlternatePath` (also called `localizedPath`) | Helper functions for extracting the active locale from the current URL, retrieving the correct translation map, and generating localised hrefs for the language switcher.                                    |

### `src/layouts/`

`Layout.astro` is the single shared shell for every page. It injects:

* Google Analytics 4 (`gtag.js`) with **Consent Mode v2 Advanced** — GA loads on every visit in cookieless mode by default; cookies are only granted after the user accepts the cookie banner.
* A skip-to-content link as the first child of `<body>` for keyboard accessibility.
* Preconnect hints for third-party domains.
* The scroll-reveal `IntersectionObserver` script (respects `prefers-reduced-motion`).
* Self-hosted Roboto WOFF2 font declarations with `font-display: swap`.

### `src/pages/`

Astro's file-based router maps files to URLs directly. The routing strategy for Borrissol:

* **Catalan default** — `index.astro`, `gallery.astro`, `press.astro`, and all workshop pages (`tufting.astro`, `punch-needle.astro`, `felting.astro`, `loom.astro`, `borla.astro`, `summer-lab.astro`, `team-building.astro`, `pelussetes.astro`) sit at the top level and are served at their respective root paths. There is no `/ca/` prefix.
* **Other languages** — mirrored files inside `[lang]/` use `getStaticPaths` to generate the `/es`, `/en`, and `/fr` variants at build time.
* **Blog** — `blog/index.astro` and `blog/[slug].astro` are Catalan-only. Non-Catalan blog URLs 301-redirect to the Catalan equivalents in `astro.config.mjs`.
* **Seasonal pages** — `summer-lab.astro` is always built and served; the home-page promotional card that links to it is conditionally rendered based on `isSummerLabVisible()` from `src/config/seasonal.ts`.

### `src/styles/theme.css`

The single source of truth for the entire design system. Contains all CSS custom properties on `:root`:

* **Colors** — `--color-white`, `--color-light`, `--color-mid`, `--color-muted`, `--color-black`, plus semantic aliases (`--bg`, `--fg`, `--fg-muted`, `--border-hairline`).
* **Typography** — `--font-size-h1` through `--font-size-h4`, `--font-size-p1` through `--font-size-p3`, `--font-size-ui`, weight, line-height, and letter-spacing tokens.
* **Spacing** — `--space-xs` (8 px) through `--space-xl` (80 px).
* **Border radius** — `--radius-pill`, `--radius-md`, `--radius-sm`, `--radius-xs`.
* **Icons** — stroke width, color, and size tokens for each context (inline, button, nav, feature, hero).
* **Utility classes** — `.text-h1`–`.text-h4`, `.text-p1`–`.text-p3`, `.text-ui`, `.text-muted`, `.eyebrow`, `.card`, `.media`, `.btn`, `.btn-primary`, `.btn-secondary`, `.btn-tertiary`, `.btn-sm`, `.badge`, `.badge-light`, `.input`, `.checkbox`.

<Warning>
  Never hardcode a color, spacing value, font size, or border radius anywhere in the codebase. Always reference a token from `theme.css`. If a value isn't tokenized yet, add it to `theme.css` first, then use it.
</Warning>

### `keystatic.config.ts`

Defines the Keystatic CMS schema — the `blog` collection, its fields (title, publish date, description, body MDX), and the storage strategy (local Git-backed files in `src/content/blog/`). In production, Keystatic authenticates via Keystatic Cloud; serverless function invocations only occur when the editor uses the `/keystatic` UI — regular visitors load fully static pages at zero function cost.

### `astro.config.mjs`

The Astro build configuration. Key settings:

* **`site`** — set to `https://borrissol.com` for correct sitemap and canonical URL generation.
* **`adapter`** — `@astrojs/netlify` for hybrid rendering (static pages + Netlify functions for the Keystatic panel).
* **`i18n`** — `defaultLocale: 'ca'`, locales `['ca', 'es', 'en', 'fr']`, `prefixDefaultLocale: false` (Catalan served at root without `/ca/` prefix).
* **`redirects`** — 301 rules for legacy `/ca/*` URLs and cross-language blog redirects (all `/<lang>/blog` → `/blog`).
* **`integrations`** — `@astrojs/sitemap` (with hreflang cross-references between language variants), `@astrojs/react` (required by Keystatic), `@astrojs/markdoc`, `@keystatic/astro`.

### `netlify.toml`

Controls the Netlify build and edge behaviour:

* **`[build]`** — build command `npm run build`, publish directory `dist`.
* **`[build.environment]`** — pins `NODE_VERSION = "22.12.0"` to match the Astro 6 requirement.
* **Bot-trap redirects** — a series of forced-404 `[[redirects]]` rules that short-circuit common WordPress/PHP/admin-tool scanner probes (`/wp-admin/*`, `/wp-login.php`, `/.env`, `/.git/*`, etc.) at the Netlify edge layer. This prevents bot requests from invoking Astro SSR and consuming function credits.

## URL routing reference

| URL pattern                                 | File                                                                     | Notes                                                                            |
| ------------------------------------------- | ------------------------------------------------------------------------ | -------------------------------------------------------------------------------- |
| `/`                                         | `src/pages/index.astro`                                                  | Catalan home (default locale, no prefix)                                         |
| `/es`, `/en`, `/fr`                         | `src/pages/[lang]/index.astro`                                           | Non-Catalan home pages via `getStaticPaths`                                      |
| `/gallery`                                  | `src/pages/gallery.astro`                                                | Catalan gallery                                                                  |
| `/es/gallery`, `/en/gallery`, `/fr/gallery` | `src/pages/[lang]/gallery.astro`                                         | Localized gallery pages                                                          |
| `/press`                                    | `src/pages/press.astro`                                                  | Catalan press page                                                               |
| `/es/press`, `/en/press`, `/fr/press`       | `src/pages/[lang]/press.astro`                                           | Localized press pages                                                            |
| `/blog`                                     | `src/pages/blog/index.astro`                                             | Blog index (Catalan only)                                                        |
| `/blog/[slug]`                              | `src/pages/blog/[slug].astro`                                            | Individual blog posts (Catalan only)                                             |
| `/tufting`                                  | `src/pages/tufting.astro` + `src/pages/[lang]/tufting.astro`             | Tufting workshop landing (CA root + ES/EN/FR via `[lang]`)                       |
| `/punch-needle`                             | `src/pages/punch-needle.astro` + `src/pages/[lang]/punch-needle.astro`   | Punch needle workshop landing                                                    |
| `/felting`                                  | `src/pages/felting.astro` + `src/pages/[lang]/felting.astro`             | Felting workshop landing                                                         |
| `/loom`                                     | `src/pages/loom.astro` + `src/pages/[lang]/loom.astro`                   | Loom weaving workshop landing                                                    |
| `/borla`                                    | `src/pages/borla.astro` + `src/pages/[lang]/borla.astro`                 | Borla workshop landing                                                           |
| `/summer-lab`                               | `src/pages/summer-lab.astro` + `src/pages/[lang]/summer-lab.astro`       | Summer Lab seasonal offering (visibility controlled by `src/config/seasonal.ts`) |
| `/team-building`                            | `src/pages/team-building.astro` + `src/pages/[lang]/team-building.astro` | Team building group format landing                                               |
| `/pelussetes`                               | `src/pages/pelussetes.astro` + `src/pages/[lang]/pelussetes.astro`       | Pelussetes workshop landing                                                      |
| `/keystatic`                                | Keystatic integration                                                    | CMS panel (auth-gated in production, open in dev)                                |
| `/ca`, `/ca/*`                              | —                                                                        | 301 → equivalent root URL (legacy redirect)                                      |
| `/es/blog`, `/en/blog`, `/fr/blog`          | —                                                                        | 301 → `/blog` (blog is intentionally CA-only)                                    |
