---
/**
* Layout commun à toutes les pages rubis.pro/*.
*
* Reçoit en props les meta SEO essentiels. Importe le CSS racine (qui inline
* les tokens + base @rubis/ui), monte SiteHeader + SiteFooter, et expose un
* pour le contenu de la page.
*
* Convention : zéro JS hydraté côté public sauf nécessité explicite — le
* SiteHeader + SiteFooter sont rendus côté serveur uniquement (pas de
* client:load) pour rester sur le bundle minimal.
*/
import "../styles/app.css";
import { SiteHeader } from "../components/SiteHeader";
import { SiteFooter } from "../components/SiteFooter";
/**
* URLs hashées (au build) des deux woff2 latin que la quasi-totalité du
* contenu utilise — Inter (body) + Bricolage Grotesque (display). Le
* suffix `?url` Vite retourne le path final après hashing, donc le
* preload reste valide même après un rebuild qui change le hash.
*
* Préchargées en HEAD pour casser la chaîne `HTML → CSS → fonts` du
* critical path (cf. audit Lighthouse network-dependency-tree, ~50 ms
* gagnés sur le LCP). On NE preload PAS les latin-ext / vietnamese /
* cyrillic / greek : poids inutile pour 99% du trafic FR/latin.
*/
import interLatinWoff2 from "@fontsource-variable/inter/files/inter-latin-wght-normal.woff2?url";
import bricolageLatinWoff2 from "@fontsource-variable/bricolage-grotesque/files/bricolage-grotesque-latin-wght-normal.woff2?url";
const SITE_URL = "https://rubis.pro";
/**
* OG image par défaut (1200×630) servie depuis apps/landing/public/.
* Utilisée pour les partages sociaux quand aucune image n'est fournie
* par la page (cas de la home + pages légales). Les articles de blog
* ont leur propre hero qui prend précédence.
*/
const DEFAULT_OG_IMAGE = `${SITE_URL}/og-default.png`;
interface Props {
title: string;
description: string;
/** Path absolu de la page courante (ex. "/blog/foo"). Default = location actuelle. */
pathname?: string;
/** OG image absolue (1200×630 idéal). Fallback sur og-default.png si non fournie. */
ogImage?: string;
/** OG type. Default "website". Mettre "article" sur les pages de blog. */
ogType?: "website" | "article";
/** Si true → noindex (legal/test). */
noindex?: boolean;
/** Header solide (bordure + fond) plutôt que transparent (par défaut). */
solidHeader?: boolean;
/** JSON-LD structured data — passé en string déjà sérialisé OU en object. */
jsonLd?: object | object[];
}
const {
title,
description,
pathname,
ogImage,
ogType = "website",
noindex = false,
solidHeader = false,
jsonLd,
} = Astro.props;
/**
* Suffixe brand intelligent :
* - Si le titre est court (<45 chars) ET ne contient pas "Rubis", on ajoute
* " — Rubis" pour la cohérence brand dans les SERP.
* - Sinon (titre long ou déjà brandé), on laisse tel quel : Google tronque
* à ~60 chars dans le snippet, autant garder le message-clé en entier.
*
* Le branding reste assuré par og:site_name + JSON-LD publisher + le hostname
* rubis.pro visible dans la SERP — pas besoin de le marteler dans le title.
*/
const fullTitle =
title.length < 45 && !title.includes("Rubis") ? `${title} — Rubis` : title;
const resolvedOgImage = ogImage ?? DEFAULT_OG_IMAGE;
const url = `${SITE_URL}${pathname ?? Astro.url.pathname}`;
const robots = noindex ? "noindex,nofollow" : "index,follow,max-image-preview:large";
const jsonLdArray = jsonLd ? (Array.isArray(jsonLd) ? jsonLd : [jsonLd]) : [];
---
{fullTitle}
{/* Open Graph */}
{/* Twitter Card — toujours summary_large_image puisqu'on a maintenant
un og-default.png 1200×630 servi par défaut sur les pages sans hero. */}
{/* Preload des fonts critiques (latin uniquement) — coupe la chaîne
HTML→CSS→woff2 en faisant démarrer le téléchargement au plus tôt.
crossorigin="anonymous" est obligatoire pour matcher la requête
woff2 que Vite émet derrière (sans cred), sinon le browser refait
un round-trip et le preload est ignoré. */}
{/* Favicons — set aligné sur apps/web (mêmes assets sur rubis.pro et
app.rubis.pro, cohérence visuelle quand l'user passe de la landing
à l'app). SVG hand-coded en priorité (1 KB), PNG en fallback iOS.
`favicon.ico` (legacy IE/Edge bookmark) gardé pour les bookmark bars. */}
{/* RSS auto-discovery — exposés sur toutes les pages pour que les
lecteurs RSS détectent les deux flux quel que soit le point d'entrée. */}
{/* JSON-LD structured data */}
{
jsonLdArray.map((data) => (
))
}