/* ========================================================================== layout.css — Structure de page -------------------------------------------------------------------------- Conteneurs, en-tête collant, navigation, sections, grilles, pied de page. Ce fichier positionne ; components.css habille. ========================================================================== */ @layer layout { /* ====================================================================== CONTENEUR Un seul conteneur réutilisé partout : la largeur de contenu est décidée à un seul endroit. ====================================================================== */ .container { inline-size: 100%; max-inline-size: var(--width-content); margin-inline: auto; padding-inline: var(--gutter); } /* ====================================================================== LIEN D'ÉVITEMENT Premier élément focusable de la page. Permet à un utilisateur au clavier ou au lecteur d'écran de sauter la navigation pour atteindre directement le contenu. Critère WCAG 2.4.1 (Contourner des blocs). Masqué visuellement hors focus, mais jamais display:none — sinon il ne serait pas focusable. ====================================================================== */ .skip-link { position: fixed; inset-block-start: var(--space-xs); inset-inline-start: var(--space-xs); z-index: var(--z-skip-link); padding: var(--space-xs) var(--space-md); background-color: var(--color-ink); color: var(--color-ink-inverse); font-family: var(--font-mono); font-size: var(--text-sm); text-decoration: none; border-radius: var(--radius-sm); transform: translateY(-200%); transition: transform var(--transition-fast); } .skip-link:focus { transform: translateY(0); color: var(--color-ink-inverse); } /* ====================================================================== EN-TÊTE COLLANT ====================================================================== */ .site-header { position: sticky; inset-block-start: 0; z-index: var(--z-sticky); background-color: var(--color-bg-translucent); border-block-end: 1px solid var(--color-line); /* Flou d'arrière-plan : le contenu qui défile dessous reste deviné sans nuire à la lisibilité du menu. Dégradation propre si non supporté (le fond translucide suffit). */ backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); } .site-header__inner { display: flex; align-items: center; justify-content: space-between; gap: var(--space-md); min-block-size: var(--header-height); } .site-header__brand { display: inline-flex; align-items: center; gap: var(--space-xs); font-family: var(--font-mono); font-size: var(--text-sm); font-weight: var(--weight-bold); color: var(--color-ink); text-decoration: none; letter-spacing: -0.01em; white-space: nowrap; } .site-header__brand:hover { color: var(--color-ink); } .site-header__logo { inline-size: 1.6rem; block-size: 1.6rem; flex-shrink: 0; } /* ====================================================================== NAVIGATION PRINCIPALE ====================================================================== */ .site-nav__list { display: flex; align-items: center; gap: var(--space-lg); list-style: none; margin: 0; padding: 0; } .site-nav__link { display: inline-block; font-family: var(--font-mono); font-size: var(--text-sm); color: var(--color-ink-faint); text-decoration: none; padding-block: var(--space-2xs); border-block-end: 2px solid transparent; transition: color var(--transition-fast), border-color var(--transition-fast); } .site-nav__link:hover { color: var(--color-ink); } /* Section courante. aria-current n'est pas qu'un sélecteur pratique : il annonce la position aux lecteurs d'écran. Le repère visuel repose sur la couleur ET sur une bordure — jamais sur la couleur seule (WCAG 1.4.1, information non véhiculée par la couleur uniquement). */ .site-nav__link[aria-current="true"] { color: var(--color-ink); border-block-end-color: var(--color-accent); } /* ====================================================================== BOUTON MENU (mobile) ====================================================================== */ .nav-toggle { display: none; /* Affiché uniquement sous le point de rupture */ align-items: center; gap: var(--space-xs); min-block-size: var(--tap-target); padding-inline: var(--space-xs); font-family: var(--font-mono); font-size: var(--text-sm); color: var(--color-ink-soft); } .nav-toggle__icon { inline-size: 1.15rem; block-size: 1.15rem; stroke: currentcolor; stroke-width: 2; fill: none; } /* Les deux tracés de l'icône se transforment en croix à l'ouverture */ .nav-toggle__line { transform-origin: center; transition: transform var(--transition-base), opacity var(--transition-fast); } .nav-toggle[aria-expanded="true"] .nav-toggle__line--top { transform: translateY(5px) rotate(45deg); } .nav-toggle[aria-expanded="true"] .nav-toggle__line--middle { opacity: 0; } .nav-toggle[aria-expanded="true"] .nav-toggle__line--bottom { transform: translateY(-5px) rotate(-45deg); } /* ====================================================================== RÉGIONS DE CONTENU ====================================================================== */ .masthead { padding-block: var(--space-3xl) var(--space-2xl); border-block-end: 1px solid var(--color-line); } .section { padding-block: var(--space-3xl); border-block-start: 1px solid var(--color-line); } /* ====================================================================== GRILLE AUTO-ADAPTATIVE auto-fit + minmax : le nombre de colonnes s'ajuste seul à la largeur disponible, sans media query. Le min() interne évite le débordement sur les très petits écrans (une colonne ne peut pas être plus large que son conteneur). ====================================================================== */ .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(17.5rem, 100%), 1fr)); gap: var(--space-md); } .grid--wide { grid-template-columns: repeat(auto-fit, minmax(min(22rem, 100%), 1fr)); } /* Empilement vertical régulier */ .stack > * + * { margin-block-start: var(--space-md); } /* ====================================================================== PIED DE PAGE ====================================================================== */ .site-footer { border-block-start: 1px solid var(--color-line); padding-block: var(--space-xl) var(--space-2xl); } .site-footer__inner { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: center; gap: var(--space-sm) var(--space-lg); font-family: var(--font-mono); font-size: var(--text-sm); color: var(--color-ink-faint); } .site-footer__links { display: flex; flex-wrap: wrap; gap: var(--space-md); list-style: none; margin: 0; padding: 0; } /* ====================================================================== BOUTON RETOUR EN HAUT ====================================================================== */ .to-top { position: fixed; inset-block-end: var(--space-lg); inset-inline-end: var(--space-lg); z-index: var(--z-overlay); display: grid; place-items: center; inline-size: var(--tap-target); block-size: var(--tap-target); background-color: var(--color-surface); border: 1px solid var(--color-line-strong); border-radius: var(--radius-full); box-shadow: var(--shadow-md); color: var(--color-ink-soft); /* Masqué par défaut : révélé par JS après défilement. visibility (et non display) pour permettre la transition. */ opacity: 0; visibility: hidden; transform: translateY(0.5rem); transition: opacity var(--transition-base), transform var(--transition-base), visibility var(--transition-base); } .to-top[data-visible="true"] { opacity: 1; visibility: visible; transform: translateY(0); } .to-top:hover { color: var(--color-accent-dark); border-color: var(--color-accent); } /* ====================================================================== POINT DE RUPTURE MOBILE Une seule media query pour toute la mise en page : le reste est fluide. Écrite en syntaxe de plage (width <= 46rem), lisible et sans le traditionnel « max-width: 45.999rem ». ====================================================================== */ @media (width <= 46rem) { .nav-toggle { display: inline-flex; } .site-header__inner { position: relative; } /* Le menu devient un panneau déroulant sous l'en-tête */ .site-nav { position: absolute; inset-block-start: 100%; inset-inline: calc(var(--gutter) * -1); display: none; background-color: var(--color-surface); border-block-end: 1px solid var(--color-line); box-shadow: var(--shadow-md); } .site-nav[data-open="true"] { display: block; } .site-nav__list { flex-direction: column; align-items: stretch; gap: 0; padding: var(--space-xs) var(--gutter) var(--space-md); } .site-nav__link { /* Hauteur de cible tactile conforme (44px minimum) */ display: flex; align-items: center; min-block-size: var(--tap-target); padding-inline-start: var(--space-xs); border-block-end: 1px solid var(--color-line); border-inline-start: 2px solid transparent; } /* Le repère de section active passe du bas au côté */ .site-nav__link[aria-current="true"] { border-block-end-color: var(--color-line); border-inline-start-color: var(--color-accent); background-color: var(--color-accent-glow); } .masthead { padding-block: var(--space-2xl) var(--space-xl); } .section { padding-block: var(--space-2xl); } .to-top { inset-block-end: var(--space-md); inset-inline-end: var(--space-md); } } }