95 lines
2.8 KiB
Plaintext
95 lines
2.8 KiB
Plaintext
---
|
|
/**
|
|
* BaseLayout — coquille HTML commune à tous les sites.
|
|
*
|
|
* Props :
|
|
* - title (string, requis) : <title> + og:title
|
|
* - description (string) : meta description + og:description
|
|
* - lang (string, 'fr') : attribut lang du <html>
|
|
* - ogImage (string) : URL absolue de l'image OG
|
|
* - canonical (string) : URL canonique
|
|
*
|
|
* Slots : default (contenu du <main>), header, footer, head (metas en plus).
|
|
*
|
|
* Le THÈME n'est pas importé ici : le site importe son theme/*.css dans son
|
|
* propre layout, à côté de <BaseLayout> (cf. README).
|
|
* Gestion clair/sombre : data-theme posé avant le premier paint depuis
|
|
* localStorage('tf-theme') ; helper global window.tfSetTheme('dark'|'light'|'auto').
|
|
*/
|
|
import '../tokens.css';
|
|
import '../reset.css';
|
|
|
|
interface Props {
|
|
title: string;
|
|
description?: string;
|
|
lang?: string;
|
|
ogImage?: string;
|
|
canonical?: string;
|
|
}
|
|
|
|
const { title, description, lang = 'fr', ogImage, canonical } = Astro.props;
|
|
---
|
|
|
|
<html lang={lang}>
|
|
<head>
|
|
<meta charset="utf-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
<title>{title}</title>
|
|
{description && <meta name="description" content={description} />}
|
|
{canonical && <link rel="canonical" href={canonical} />}
|
|
<meta property="og:title" content={title} />
|
|
{description && <meta property="og:description" content={description} />}
|
|
<meta property="og:type" content="website" />
|
|
{ogImage && <meta property="og:image" content={ogImage} />}
|
|
<script is:inline>
|
|
(() => {
|
|
try {
|
|
const t = localStorage.getItem("tf-theme");
|
|
if (t === "dark" || t === "light") document.documentElement.dataset.theme = t;
|
|
} catch {}
|
|
document.documentElement.dataset.js = "";
|
|
})();
|
|
</script>
|
|
<slot name="head" />
|
|
</head>
|
|
<body>
|
|
<a class="tf-skip-link" href="#main">Aller au contenu</a>
|
|
<slot name="header" />
|
|
<main id="main"><slot /></main>
|
|
<slot name="footer" />
|
|
<script is:inline>
|
|
window.tfSetTheme = (mode) => {
|
|
const root = document.documentElement;
|
|
try {
|
|
if (mode === "auto") {
|
|
delete root.dataset.theme;
|
|
localStorage.removeItem("tf-theme");
|
|
} else {
|
|
root.dataset.theme = mode;
|
|
localStorage.setItem("tf-theme", mode);
|
|
}
|
|
} catch {}
|
|
};
|
|
</script>
|
|
</body>
|
|
</html>
|
|
|
|
<style>
|
|
.tf-skip-link {
|
|
position: absolute;
|
|
inset-inline-start: var(--space-md);
|
|
inset-block-start: var(--space-md);
|
|
z-index: var(--z-toast);
|
|
padding: var(--space-xs) var(--space-md);
|
|
background: var(--primary);
|
|
color: var(--on-primary);
|
|
border-radius: var(--radius-md);
|
|
text-decoration: none;
|
|
transform: translateY(-200%);
|
|
}
|
|
|
|
.tf-skip-link:focus-visible {
|
|
transform: none;
|
|
}
|
|
</style>
|