/* =====================================================================
   Toutes nos couleurs — feuille de style unique.

   Parti pris : la page s'efface. Un projet qui montre des couleurs ne
   peut pas se permettre une interface colorée, elle entrerait en
   concurrence avec les photos. Tout est donc en niveaux de gris, et la
   seule couleur de l'écran vient des participants.
   ===================================================================== */

/* --- Police du site : Social Gothic --------------------------------
   Fournie en OTF (pas d'outil WOFF2 sur place). ~28 Ko par poids, ce qui
   reste raisonnable. `font-display: swap` : le texte s'affiche tout de
   suite dans la police de repli, puis bascule quand Social Gothic est
   chargée — jamais de texte invisible pendant le téléchargement.
   Les pages légales n'utilisent PAS cette police (cf. body.legal-doc). */
@font-face {
    font-family: "Social Gothic";
    src: url("../fonts/social-gothic-regular.otf") format("opentype");
    font-weight: 400;
    font-style: normal;
    font-display: swap;
}
@font-face {
    font-family: "Social Gothic";
    src: url("../fonts/social-gothic-medium.otf") format("opentype");
    font-weight: 500;
    font-style: normal;
    font-display: swap;
}
@font-face {
    font-family: "Social Gothic";
    src: url("../fonts/social-gothic-demibold.otf") format("opentype");
    font-weight: 600;
    font-style: normal;
    font-display: swap;
}
@font-face {
    font-family: "Social Gothic";
    src: url("../fonts/social-gothic-bold.otf") format("opentype");
    font-weight: 700;
    font-style: normal;
    font-display: swap;
}

:root {
    /* Page publique : fond crème doux, texte gris très foncé. Le crème
       plutôt que le blanc pur (trop agressif à côté des cadres de couleur
       vifs), le gris foncé plutôt que le noir (moins tranchant à la
       lecture longue). L'interface reste discrète, comme le veut le CDC :
       « un projet qui montre des couleurs ne peut pas se permettre une
       interface colorée ». Le principe tient en clair comme en sombre. */
    --bg: #eeedea;
    --bg-soft: #ffffff;
    --fg: #1a1a1a;
    --fg-dim: #6a6a6a;
    --border: #d9d4c8;
    --focus: #1a1a1a;

    /* Contexte sombre, gardé pour la navbar, la lightbox et le back-office.
       Ces trois surfaces ont leur propre raison d'être noires : la navbar
       encadre la mosaïque colorée, la lightbox isole la photo, le back-office
       est un outil professionnel. */
    --bg-dark: #0d0d0d;
    --bg-dark-soft: #171717;
    --fg-on-dark: #f2f2f2;
    --fg-dim-on-dark: #8c8c8c;
    --border-on-dark: #2a2a2a;

    --font: "Social Gothic", "Helvetica Neue", Helvetica, Arial, sans-serif;
    /* Police neutre pour les pages de texte : un contenu long et dense se lit
       mieux dans une police système sobre que dans une police d'affichage. */
    --font-reading: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
    --max-width: 720px;
}

/* Pages de texte à lire (mentions légales, CGU, confidentialité, à propos,
   contact, participer) : elles portent class="text-page" sur leur <body>. Tout
   y passe en police système, navbar comprise, pour un rendu neutre et lisible.
   Seule la page « vitrine » d'accueil garde Social Gothic. */
body.text-page {
    font-family: var(--font-reading);
}

*,
*::before,
*::after {
    box-sizing: border-box;
}

/* L'attribut `hidden` doit TOUJOURS l'emporter.
   Le navigateur ne lui applique `display: none` que via sa feuille par
   défaut : n'importe quelle règle d'auteur posant un `display` (un
   `display: flex` sur un conteneur, par exemple) gagne en spécificité et
   laisse l'élément à l'écran alors que le code le croit caché. Le bug est
   silencieux et se rejoue à chaque nouveau composant. On le ferme ici, une
   fois, au lieu de répéter un `.machin[hidden]` par cas. */
[hidden] {
    display: none !important;
}

/* Réserve la gouttière de la barre de défilement en permanence.
   Sans elle, la grille se mesure large (page courte, pas de scrollbar),
   se remplit, déborde, et la scrollbar apparaît en lui retirant ~15px :
   la hauteur de rangée, déjà calculée, ne correspond alors plus à la
   largeur de colonne et les cellules cessent d'être carrées. */
html {
    scrollbar-gutter: stable;
}

body {
    margin: 0;
    background: var(--bg);
    color: var(--fg);
    font-family: var(--font);
    font-size: 16px;
    line-height: 1.6;
    -webkit-font-smoothing: antialiased;
}

a {
    color: var(--fg);
    text-underline-offset: 3px;
}

/* Visible au clavier uniquement : un contour permanent parasiterait la
   grille, mais sans lui la navigation au clavier devient impossible. */
:focus-visible {
    outline: 2px solid var(--focus);
    outline-offset: 2px;
}

.visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

/* --- En-tête ------------------------------------------------------- */

.site-header {
    display: flex;
    flex-wrap: wrap;
    gap: 1rem 2rem;
    align-items: baseline;
    justify-content: space-between;
    padding: 1.5rem 1.5rem 1rem;
    /* La navbar garde la police d'affichage (Social Gothic) sur TOUTES les
       pages, y compris celles en `body.text-page` qui passent leur corps de
       texte en police système : c'est un élément d'identité, constant d'une
       page à l'autre. Sans cette ligne, elle hériterait de --font-reading. */
    font-family: var(--font);
    /* Reste sombre malgré le fond clair de la page : la navbar encadre la
       mosaïque, c'est ce contraste qui la fait ressortir. */
    background: var(--bg-dark);
    color: var(--fg-on-dark);
}

/* Dans la navbar, les liens reprennent le contexte sombre : les règles
   globales `a { color: var(--fg) }` viseraient un texte foncé illisible. */
.site-header a {
    color: var(--fg-on-dark);
}

.site-nav a {
    color: var(--fg-dim-on-dark);
}

.site-nav a:hover,
.site-nav a[aria-current="page"] {
    color: var(--fg-on-dark);
}

.site-title {
    margin: 0;
    font-size: 1.1rem;
    font-weight: 600;
    letter-spacing: 0.02em;
}

.site-title a {
    text-decoration: none;
}

.site-nav {
    display: flex;
    gap: 1.5rem;
    font-size: 0.9rem;
}

/* .site-nav a est stylé dans le bloc `.site-header a` plus haut : la navbar
   reste en contexte sombre. On garde ici les seules propriétés qui ne sont
   pas des couleurs. */
.site-nav a {
    text-decoration: none;
    transition: color 0.15s;
}

/* --- La grille (CDC §2) --------------------------------------------
   Grille de carrés. Une photo occupe 1x2 (portrait) ou 2x1 (paysage),
   un cadre de couleur occupe 1x1. Aucune bordure, aucun espacement :
   les images se touchent, c'est explicitement demandé.

   --cols et --cell sont posés par grid.js : la hauteur d'une rangée doit
   valoir la largeur d'une colonne pour que les cellules soient carrées,
   et le CSS seul ne sait pas exprimer cette dépendance.

   `dense` comble les trous. Sans lui, une grille mêlant des emprises
   1x2, 2x1 et 1x1 en laisse partout. Contrepartie assumée : un cadre
   peut occasionnellement glisser en amont pour boucher un vide, et donc
   s'éloigner de sa photo. Adjacence stricte et pavage plein sont
   géométriquement incompatibles.
------------------------------------------------------------------- */

.grid {
    display: grid;
    grid-template-columns: repeat(var(--cols, 4), 1fr);
    grid-auto-rows: var(--cell, 25vw);
    grid-auto-flow: row dense;
    gap: 0;
}

.cell {
    position: relative;
    overflow: hidden;
}

.cell--landscape {
    grid-column: span 2;
    grid-row: span 1;
}

.cell--portrait {
    grid-column: span 1;
    grid-row: span 2;
}

/* Une photo carrée occupe 1x1, soit exactement l'emprise d'un cadre de
   couleur : à cette taille, les deux ne se distinguent plus que par leur
   contenu. C'est une conséquence assumée de la réintroduction du carré.
   Les 3 étapes d'accroche partagent la même emprise 1x1. */
.cell--square,
.cell--color,
.cell--step {
    grid-column: span 1;
    grid-row: span 1;
}

.cell--photo {
    padding: 0;
    border: 0;
    cursor: zoom-in;
    background: var(--bg-soft);
}

.cell--photo img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

/* Le nom de la couleur, posé sur le carré de couleur unie. */
.color-frame {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    height: 100%;
    padding: 0.75rem;
    text-align: center;
}

.color-frame__name {
    font-size: clamp(0.7rem, 1.6vw, 1rem);
    font-weight: 500;
    line-height: 1.25;
    overflow-wrap: anywhere;
}

/* --- Lightbox (CDC §2) ---------------------------------------------
   Fond noir, clic pour revenir. C'est une <dialog> : le navigateur gère
   le piège au clavier et la fermeture par Échap, qu'il aurait fallu
   réécrire à la main sinon.
------------------------------------------------------------------- */

.lightbox {
    width: 100vw;
    max-width: 100vw;
    height: 100vh;
    max-height: 100vh;
    padding: 0;
    border: 0;
    background: #000;
    cursor: zoom-out;
}

.lightbox::backdrop {
    background: #000;
}

.lightbox__inner {
    display: flex;
    flex-direction: column;
    gap: 1rem;
    align-items: center;
    justify-content: center;
    width: 100%;
    height: 100%;
    padding: 2rem 1rem;
}

.lightbox__img {
    max-width: 100%;
    max-height: calc(100vh - 8rem);
    object-fit: contain;
}

.lightbox__caption {
    display: flex;
    gap: 0.75rem;
    align-items: center;
    /* La lightbox est sur fond noir (CDC §2), donc contexte sombre pour la
       légende : sans quoi elle deviendrait un gris foncé invisible. */
    color: var(--fg-dim-on-dark);
    font-size: 0.85rem;
}

.lightbox__swatch {
    width: 0.9rem;
    height: 0.9rem;
    border-radius: 50%;
}

/* Le lien vers Instagram est poussé à droite de la légende : c'est une
   action, pas une information sur la photo, et le crédit de l'auteur doit
   rester ce qu'on lit en premier. */
.lightbox__instagram {
    margin-left: auto;
    padding: 0.3rem 0.7rem;
    border: 1px solid var(--fg-dim-on-dark);
    border-radius: 999px;
    color: inherit;
    text-decoration: none;
    white-space: nowrap;
}

.lightbox__instagram:hover,
.lightbox__instagram:focus-visible {
    border-color: #fff;
    color: #fff;
}

/* --- Pages de contenu ----------------------------------------------- */

.page {
    max-width: var(--max-width);
    margin: 0 auto;
    padding: 2rem 1.5rem 6rem;
}

.page h1 {
    margin-top: 0;
    font-size: 1.6rem;
}

.page h2 {
    margin-top: 1rem;
    font-size: 1.1rem;
}

.page h3 {
    margin-top: 1.75rem;
    font-size: 0.95rem;
    color: var(--fg-dim);
}

.page :is(p, li) {
    color: #2b2b2b;
}

.lede {
    font-size: 1.05rem;
    color: var(--fg) !important;
}

.muted {
    color: var(--fg-dim) !important;
    font-size: 0.9rem;
}

hr {
    margin: 2.5rem 0;
    border: 0;
    border-top: 1px solid var(--border);
}

/* Le tableau des données de la politique de confidentialité. Il défile dans
   son propre conteneur : sans ça, ses trois colonnes forcent la page entière
   à défiler latéralement sur un téléphone. */
.page table {
    display: block;
    width: 100%;
    overflow-x: auto;
    border-collapse: collapse;
    margin: 1.5rem 0;
    font-size: 0.85rem;
}

.page :is(th, td) {
    padding: 0.6rem 0.75rem;
    border: 1px solid var(--border);
    text-align: left;
    vertical-align: top;
}

.page th {
    background: var(--bg-soft);
    font-weight: 600;
    white-space: nowrap;
}

.page td {
    color: #2b2b2b;
}

/* --- Formulaire ------------------------------------------------------ */

.field {
    margin-bottom: 0.25rem;
}

.field label {
    display: block;
    margin-bottom: 0.35rem;
    font-size: 0.9rem;
}

.field .hint {
    margin: 0.35rem 0 0;
    color: var(--fg-dim);
    font-size: 0.8rem;
}

input[type="text"],
input[type="email"],
input[type="password"],
input[type="file"] {
    width: 100%;
    padding: 0.3rem 0.75rem;
    border: 0px solid var(--border);
    border-radius: 3px;
    background: var(--bg-soft);
    color: var(--fg);
    font-family: inherit;
    font-size: 0.95rem;
}

input[type="text"]:focus,
input[type="email"]:focus,
input[type="password"]:focus {
    border-color: var(--fg-dim);
}

/* Le piège à robots. Invisible pour un humain, rempli par un script qui
   remplit tout ce qu'il trouve. `aria-hidden` et `tabindex` le retirent
   aussi des lecteurs d'écran et de la navigation au clavier : un piège
   qui attrape un utilisateur non-voyant serait un bug, pas une défense. */
.hp-field {
    position: absolute;
    left: -9999px;
    width: 1px;
    height: 1px;
    overflow: hidden;
}

.checkbox {
    display: flex;
    gap: 0.75rem;
    align-items: flex-start;
    padding-bottom: 0.9rem;
}

.checkbox input {
    flex-shrink: 0;
    margin-top: 0.3rem;
}

.checkbox label {
    font-size: 0.88rem;
    line-height: 1.5;
}

.btn {
    padding: 0.7rem 1.4rem;
    border: 1px solid var(--fg);
    border-radius: 3px;
    background: var(--fg);
    color: var(--bg);
    font-family: inherit;
    font-size: 0.95rem;
    font-weight: 500;
    cursor: pointer;
    transition: opacity 0.15s;
}

.btn:hover:not(:disabled) {
    opacity: 0.85;
}

.btn:disabled {
    opacity: 0.4;
    cursor: not-allowed;
}

.btn--ghost {
    background: transparent;
    color: var(--fg);
    border-color: var(--border);
}

.btn--small {
    padding: 0.4rem 0.8rem;
    font-size: 0.85rem;
}

/* Une action (donc un <button>, pas un lien : elle ne mène nulle part) qui
   se lit comme un lien, dans le fil d'une phrase. */
.link-button {
    padding: 0;
    border: 0;
    background: none;
    color: inherit;
    font: inherit;
    text-decoration: underline;
    cursor: pointer;
}

/* Rattachée à la case « se souvenir » qui la précède : on la remonte dans
   l'espace que la case laisse sous elle. */
.remember-note {
    margin: -0.5rem 0 0.9rem 1.75rem;
}

/* --- Choix de la photo : le label EST le bouton ------------------------
   Le champ natif est masqué à l'œil sans quitter l'arbre d'accessibilité ni
   l'ordre de tabulation : un `display: none` l'aurait rendu inatteignable au
   clavier. C'est le label associé qui reçoit le clic, comportement natif de
   HTML, donc aucun JavaScript n'est nécessaire pour ouvrir le sélecteur. */
/* Sélecteurs qualifiés à dessein : `input[type="file"]` et `.field label`
   existent déjà plus haut et l'emporteraient sinon en spécificité, quel que
   soit l'ordre des règles. Sans cela le champ « masqué » gardait sa largeur
   de 100 %, donc une zone cliquable invisible en travers de la page. */
input[type="file"].file-input {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    border: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
}

/* Ici le label n'annonce plus un champ, il agit : il reprend donc la mise en
   forme d'un bouton, et non celle d'une étiquette. */
.field label.file-button {
    display: inline-block;
    margin-bottom: 0;
    font-size: 0.95rem;
    text-align: left;
}

/* Le champ étant invisible, son anneau de focus le serait aussi. On le
   reporte sur le label, sinon la navigation au clavier se perdrait sur un
   bouton qui ne réagit pas. */
.file-input:focus-visible + .file-button {
    outline: 2px solid var(--focus);
    outline-offset: 2px;
}

.alert {
    margin-bottom: 1.5rem;
    padding: 0.9rem 1rem;
    border: 1px solid var(--border);
    border-left-width: 3px;
    border-radius: 3px;
    background: var(--bg-soft);
    font-size: 0.9rem;
}

.alert--error {
    border-left-color: #e05252;
}

.alert--success {
    border-left-color: #4caf7d;
}

/* --- Pixel picker ---------------------------------------------------- */

.picker {
    margin-bottom: 2rem;
    padding: 1rem;
    border: 1px solid var(--border);
    border-radius: 3px;
    background: var(--bg-soft);
}

.picker__head {
    display: flex;
    gap: 1rem;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 0.75rem;
}

.picker__filename {
    overflow: hidden;
    color: var(--fg-dim);
    font-size: 0.8rem;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.picker__canvas-wrap {
    position: relative;
    line-height: 0;
}

.picker__label {
    display: block;
    margin-right: 0.5rem;
    color: var(--fg-dim);
    font-size: 0.8rem;
}

.picker__formats {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
    align-items: center;
    margin-bottom: 0.75rem;
}

.picker__formats .btn.is-active {
    border-color: var(--fg);
    background: var(--fg);
    color: var(--bg);
}

.picker__formats .btn.is-active .muted {
    color: var(--bg) !important;
    opacity: 0.6;
}

.picker__zoom {
    display: flex;
    gap: 0.75rem;
    align-items: center;
    margin-top: 0.75rem;
}

.picker__zoom input[type="range"] {
    flex-grow: 1;
    accent-color: var(--fg);
}

/* --- Rognage ---------------------------------------------------------
   Le cadre est positionné en POURCENTAGES du conteneur : il suit donc la
   photo quelle que soit la largeur d'écran, sans le moindre recalcul.
   C'est aussi ce qui permet d'envoyer des fractions au serveur, qui les
   applique à l'original en pleine définition.
------------------------------------------------------------------- */

.cropper__stage {
    position: relative;
    overflow: hidden;
    line-height: 0;
    touch-action: none;   /* sinon le navigateur fait défiler au lieu de déplacer le cadre */
    user-select: none;
}

.cropper__canvas {
    display: block;
    width: 100%;
    height: auto;
    cursor: crosshair;
}

.cropper__shade {
    position: absolute;
    background: rgba(13, 13, 13, 0.72);
    pointer-events: none;   /* le clic doit atteindre le canvas en dessous */
}

/* Le cadre sert à choisir la couleur (clic) ET à se déplacer (glissement).
   Le curseur annonce l'action principale, le picker ; il ne bascule sur la
   main qu'une fois le déplacement réellement engagé. Un `grab` permanent
   laisserait croire que le cadre ne sait que bouger. */
.cropper__frame {
    position: absolute;
    border: 1px solid rgba(255, 255, 255, 0.9);
    box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.4);
    cursor: crosshair;
}

.cropper__frame.is-dragging {
    cursor: grabbing;
}

/* Repères aux angles : sans eux, sur une photo claire, le cadre disparaît. */
.cropper__frame::before,
.cropper__frame::after {
    content: "";
    position: absolute;
    width: 14px;
    height: 14px;
    border: 2px solid #fff;
}

.cropper__frame::before {
    top: -1px;
    left: -1px;
    border-right: 0;
    border-bottom: 0;
}

.cropper__frame::after {
    right: -1px;
    bottom: -1px;
    border-top: 0;
    border-left: 0;
}

.picker__result {
    display: flex;
    gap: 1rem;
    align-items: center;
    margin-top: 0.75rem;
}

.picker__swatch {
    flex-shrink: 0;
    width: 3rem;
    height: 3rem;
    border: 1px solid var(--border);
    border-radius: 3px;
    background: #000;
}

.picker__fields {
    flex-grow: 1;
}

.grid-empty {
    padding: 5rem 1.5rem;
    color: var(--fg-dim);
    text-align: center;
}

/* --- Les 3 étapes, intégrées à la mosaïque -------------------------
   Chaque étape est une cellule 1x1 de la grille, au même titre qu'un cadre
   de couleur : même emprise, aucune bordure, collée à ses voisines. La
   grille impose déjà la dimension carrée (--cell, cf. .grid) ; il ne reste
   qu'à centrer le contenu et le calibrer pour qu'il tienne. Les couleurs
   sont tirées au sort par le serveur à chaque visite.
------------------------------------------------------------------- */

.cell--step {
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: flex-start;
    padding: 1rem;
    text-align: left;
    /* Couleur de fond posée en inline par le JS ; `color: inherit` respecte
       la couleur de texte contrastée choisie, sans que la règle globale
       `a { color: var(--fg) }` ne reprenne la main. */
    color: inherit;
    text-decoration: none;
    cursor: pointer;
    transition: filter 0.15s;
}

.cell--step:hover,
.cell--step:focus-visible {
    /* Surtout pas de transform : déplacer une cellule creuserait un trou
       dans la mosaïque. On éclaircit légèrement le carré à la place. */
    filter: brightness(1.12);
}

.step__text {
    font-size: clamp(0.85rem, 1.9vw, 1.05rem);
    font-weight: 600;
    line-height: 1.35;
}

@media (max-width: 640px) {
    .site-header {
        padding: 1rem;
    }

    .site-nav {
        gap: 1rem;
        font-size: 0.85rem;
    }

    /* Cellules étroites sur mobile : on resserre le contenu pour qu'il tienne
       dans le carré plutôt que de déborder (masqué par overflow: hidden). */
    .cell--step {
        padding: 0.5rem;
    }

    .step__text {
        font-size: 0.66rem;
        line-height: 1.2;
    }
}
