/* Sanctuary — cadre TUI monochrome posé sur un panorama qui défile.
   0 dégradé, 0 arrondi, 0 couleur : du noir, du blanc, des gris.
   Les traits du cadre sont des bordures CSS (1px), pas des caractères
   ┌ ─ ┐ : comme ça le cadre tient à n'importe quelle largeur. */

@font-face {
    font-family: "Standard Galactic";
    src: url("/fonts/sga/Sga-Regular.woff2") format("woff2");
    font-weight: 400;
    font-style: normal;
    font-display: swap;
}

:root {
    /* Le ciel : les 6 faces du panorama montees en cube.
       --sky-perspective regle le FOV, --sky-size la taille du cube. Il faut
       --sky-size >= 2x --sky-perspective, sinon les faces ne couvrent plus
       l'ecran et on voit le vide dans les coins. Un tour complet en 6 minutes. */
    --sky-perspective: 78vmax;
    --sky-size: 282vmax;
    --sky-duration: 360s;
    /* Les faces debordent de --sky-overlap sur leurs voisines. Sans ca, chaque
       face est antialiasee separement et il reste un cheveu de fond sur les
       aretes du cube. Le debordement ne se voit pas : les bords des faces sont
       identiques au pixel pres (0,2/255 mesure sur les fichiers). */
    --sky-overlap: 10px;
    --sky-face: calc(var(--sky-size) + var(--sky-overlap));

    /* Largeur du cadre : le site est borne a 1024px. */
    --frame-width: min(1024px, calc(100% - 2em));

    --bg: #1a1a1a;
    --bar: rgba(0, 0, 0, 0.6);
    --panel: rgba(0, 0, 0, 0.4);
    --panel-hover: rgba(0, 0, 0, 0.55);
    /* Les quatre couleurs des genres du tableau. Elles sortent du monochrome, par
       décision : un chantier, un besoin, un rendez-vous et une information
       doivent se reconnaître d'un coup d'œil, sans lire le mot. */
    --rouge: #f87171;
    --vert: #4ade80;
    --bleu: #60a5fa;
    --jaune: #facc15;

    /* Le fond des annonces du tableau. C'est le seul endroit à toucher pour les
       assombrir ou les éclaircir. À 0.25, elles sont plus claires que la page
       (--panel, 0.4) : le cadre coloré et ce fond pâle les détachent. */
    --carte: rgba(0, 0, 0, 0.25);
    --field: rgba(0, 0, 0, 0.6);
    --line: rgba(255, 255, 255, 0.35);
    --line-strong: rgba(255, 255, 255, 0.6);
    --fg: #ffffff;
    --fg-soft: #e6e6e6;
    --fg-mute: #b9b9b9;
    /* La seule couleur du site, avec les photos de la Gazette : le jaune vif du
       survol. Jaune pur (255,255,0). */
    --accent: #ffff00;

    /* Les couleurs des trois états de l'accès. Elles sortent du monochrome, par
       décision : un statut vert, orange ou rouge se lit avant la phrase qui le
       suit. Le reste du site garde ses gris. */
    --valid: #4ade80;
    --pending: #fbbf24;
    --invalid: #f87171;

    /* La largeur des deux vignettes — la photo Discord et le skin Minecraft.
       Elles la partagent : c'est ce qui fait tomber les deux listes sur la même
       colonne. C'est la taille de la photo. */
    --portrait: 4rem;

    /* Le trait du cadre, réutilisé partout. */
    --frame: 1px solid var(--line);

    /* L'ombre dure décalée, comme celle du texte en jeu. */
    --text-shadow: 2px 2px 0 rgba(0, 0, 0, 0.7);

    color-scheme: dark;
}

/* Ecran large = FOV large. On rapproche la camera du cube (le champ s'ouvre :
   65 degres sur un ecran etroit, 71 sur du 16/9, 74 sur du 21/9) et on agrandit
   le cube d'autant, sinon il ne remplirait plus l'ecran. Chaque palier garde
   une petite marge au-dessus du minimum theorique. */
@media (min-aspect-ratio: 3/2) {
    :root { --sky-perspective: 74vmax; --sky-size: 312vmax; }
}

@media (min-aspect-ratio: 16/9) {
    :root { --sky-perspective: 70vmax; --sky-size: 355vmax; }
}

@media (min-aspect-ratio: 21/9) {
    :root { --sky-perspective: 66vmax; --sky-size: 420vmax; }
}

* { box-sizing: border-box; }

form { margin: 0; }

/* Le cadre occupe au moins toute la hauteur de la fenetre : comme ca le pied de
   page tombe en bas de l'ecran meme quand il y a peu de contenu. dvh suit la
   barre d'adresse des navigateurs mobiles, vh reste en secours. Le ciel est en
   position fixe, il ne compte donc pas dans cette colonne. */
body {
    margin: 0;
    padding: 1em 0;
    /* Le cadre prend la hauteur de la fenêtre, et la page elle-même ne défile
       pas : le défilement se fait dans les conteneurs (les colonnes de
       l'accueil, le dossier). L'en-tête et le pied restent donc toujours en vue. */
    height: 100vh;
    height: 100dvh;
    overflow: hidden;
    display: flex;
    flex-direction: column;
    background: var(--bg);
    color: var(--fg-soft);
    font-family: Consolas, "DejaVu Sans Mono", ui-monospace, monospace;
    font-size: 14px;
    line-height: 1.5;
    text-shadow: var(--text-shadow);
}

/* --------------------------------------------------------------- le ciel */

/* Une couche fixe derriere tout le reste. overflow: hidden borne le cube, qui
   deborde largement de l'ecran. */
.skybox {
    position: fixed;
    inset: 0;
    /* -2 : la couche la plus basse, sous le canvas de la poule. */
    z-index: -2;
    overflow: hidden;
    perspective: var(--sky-perspective);
    perspective-origin: 50% 50%;
    background: var(--bg);
}

/* Le canvas de la poule, cree par le script. Devant le cadre : la poule vole
   par-dessus le site. Il ne prend aucun clic — les evenements sont ecoutes sur
   window, et sans ca il mangerait les liens. */
.chicken-canvas {
    position: fixed;
    inset: 0;
    z-index: 5;
    display: block;
    pointer-events: none;
}

/* Le centre du cube, c'est le centre de l'ecran : la camera est dedans.
   --sky-phase vient du serveur (l'heure courante modulo la duree d'un tour) :
   sans lui, chaque changement de page relancerait la rotation a zero et le ciel
   sauterait. Avec, l'angle suit l'horloge, donc la rotation continue d'une page
   a l'autre. */
.skybox-cube {
    position: absolute;
    top: 50%;
    left: 50%;
    width: 0;
    height: 0;
    transform-style: preserve-3d;
    animation: sky-turn var(--sky-duration) linear infinite;
    animation-delay: calc(-1s * var(--sky-phase, 0));
    will-change: transform;
}

/* Chaque face est centree sur le centre du cube par des marges negatives. Elle
   mesure --sky-face, soit --sky-overlap de plus que le cube, pour recouvrir la
   couture sur les aretes. */
.skybox-face {
    position: absolute;
    top: 0;
    left: 0;
    width: var(--sky-face);
    height: var(--sky-face);
    margin: calc(var(--sky-face) / -2) 0 0 calc(var(--sky-face) / -2);
    background-position: center;
    background-size: 100% 100%;
    background-repeat: no-repeat;
}

/* Les 4 cotes, dans l'ordre du ruban : le bord droit de 0 tombe sur le bord
   gauche de 1, etc. (mesure au pixel sur les fichiers : 0,2/255 d'ecart).
   rotateY(180deg) retourne la face pour qu'elle se lise de l'interieur. */
.sky-0 {
    background-image: url("/images/skybox/sky-0.png");
    transform: translateZ(calc(var(--sky-size) / -2));
}

.sky-1 {
    background-image: url("/images/skybox/sky-1.png");
    transform: rotateY(90deg) translateZ(calc(var(--sky-size) / 2)) rotateY(180deg);
}

.sky-2 {
    background-image: url("/images/skybox/sky-2.png");
    transform: translateZ(calc(var(--sky-size) / 2)) rotateY(180deg);
}

.sky-3 {
    background-image: url("/images/skybox/sky-3.png");
    transform: rotateY(-90deg) translateZ(calc(var(--sky-size) / 2)) rotateY(180deg);
}

/* Dessus et dessous. scaleY(-1) : vus depuis l'interieur du cube, ces deux-la
   sortent en miroir ; sans lui les raccords avec les cotes tombent a l'envers. */
.sky-4 {
    background-image: url("/images/skybox/sky-4.png");
    transform: rotateX(90deg) translateZ(calc(var(--sky-size) / 2)) scaleY(-1);
}

.sky-5 {
    background-image: url("/images/skybox/sky-5.png");
    transform: rotateX(-90deg) translateZ(calc(var(--sky-size) / 2)) scaleY(-1);
}

@keyframes sky-turn {
    from { transform: rotateY(0deg); }
    to { transform: rotateY(360deg); }
}

a { color: var(--fg); }

/* Au survol, le texte des liens et des boutons vire au jaune vif. C'est la seule
   couleur du site en dehors des photos. La regle passe devant les couleurs de la
   nav (0,0,2) et des boutons (0,1,0).

   .button:hover est indispensable pour les vrais <button> : le selecteur
   button.button a la meme specificite que button:hover — un element et une
   classe — et il est ecrit plus bas dans la feuille, donc il gagnerait et le
   bouton resterait blanc. Avec deux classes, on repasse devant. */
a:hover,
button:hover,
.button:hover { color: var(--accent); }

/* ---------------------------------------------------------------- le cadre */

.site-header,
main,
footer {
    width: var(--frame-width);
    margin-inline: auto;
}

.site-header {
    border: var(--frame);
    background: var(--bar);
}

/* Une seule colonne : les blocs s'empilent sur toute la largeur. main prend la
   place qui reste dans la colonne, ce qui pousse le pied de page en bas.

   min-height: 0 est indispensable : sans lui, un enfant plus haut que la place
   disponible fait grandir main au lieu de défiler. Et align-content: stretch fait
   que la page remplit cette hauteur — c'est ce qui donne aux colonnes de
   l'accueil une hauteur finie, donc un défilement interne. */
main {
    flex: 1;
    min-height: 0;
    align-content: stretch;
    border-inline: var(--frame);
    background: var(--panel);
    display: grid;
    grid-template-columns: minmax(0, 1fr);
}

/* Le premier bloc n'a pas besoin de trait au-dessus : le bas du bandeau en fait
   deja un. */
main > :first-child { border-top: 0; }

footer {
    border: var(--frame);
    background: var(--bar);
}

/* ------------------------------------------------------------- barre du haut */

.site-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    flex-wrap: wrap;
    /* Peu d'écart : le logo et la devise forment un seul groupe à gauche. */
    gap: 0.6em 0.75em;
    padding: 0.5em 1em;
}

.mark {
    display: flex;
    align-items: center;
    gap: 0.75em;
    color: var(--fg);
    text-decoration: none;
}

.mark img {
    display: block;
    height: 2em;
    width: auto;
}

/* La devise du site, collée à droite du logo, et les chiffres du serveur dans
   la même ligne, séparés par le même point médian. C'est la feuille de style
   qui met le tout en capitales et l'interlettre : rien à écrire en dur.

   ⚠ DÉMO — les chiffres sont inventés, rien ne les alimente. */
.devise {
    margin: 0;
    color: var(--fg-mute);
    font-size: 0.9em;
    letter-spacing: 0.08em;
    text-decoration: none;
    text-transform: uppercase;
}

/* Le nombre de joueurs en ligne clignote en couleur : il passe du blanc au jaune
   vif et revient, sans jamais s'éteindre. steps(1) et non un fondu : la bascule
   saute d'un coup, sans dégradé — c'est la règle de la maison. Le clignotement
   s'arrête avec le reste des animations. */
.en-ligne {
    color: var(--fg);
    animation: en-ligne 1.6s steps(1, end) infinite;
}

@keyframes en-ligne {
    50% { color: var(--accent); }
}

nav {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 1.2em;
    /* La barre du haut tient trois choses : le logo, la devise, la nav. Cette
       marge prend la place libre, donc la nav part à droite et le logo reste
       collé à la devise. */
    margin-left: auto;
    text-transform: uppercase;
    font-size: 0.9em;
}

nav a,
nav button {
    color: var(--fg);
    background: none;
    border: 0;
    padding: 0;
    font: inherit;
    text-transform: inherit;
    text-decoration: underline;
    cursor: pointer;
}

.button,
button.button {
    display: inline-block;
    padding: 0.4em 0.9em;
    background: var(--panel-hover);
    border: var(--frame);
    color: var(--fg);
    font: inherit;
    text-transform: uppercase;
    text-decoration: none;
    cursor: pointer;
}

.button:hover { background: var(--bar); }

/* Le bouton de téléchargement : au survol, « Télécharger » cède la place au nom
   de la version. Les deux libellés occupent la même case de grille, donc le
   bouton prend la largeur du plus long et ne bouge pas au passage. */
.download {
    display: inline-grid;
    place-items: center;
}

.download > * { grid-area: 1 / 1; }

.download-version { opacity: 0; }

.download:hover .download-label { opacity: 0; }
.download:hover .download-version { opacity: 1; }

.button-quiet { background: none; }

/* ------------------------------------------------------------------- textes */

h1,
h2 {
    color: var(--fg);
    font-weight: bold;
    line-height: 1.3;
    margin: 0 0 0.6em;
    text-transform: uppercase;
}

h1 { font-size: 1.4em; }
h2 { font-size: 1em; }

p { margin: 0 0 1em; }

/* --------------------------------------------------------- colonnes du milieu */

/* Le message de flash prend toute la largeur du cadre, sous la barre du haut. */
.flash {
    grid-column: 1 / -1;
    margin: 0;
    padding: 0.6em 1em;
    border-bottom: var(--frame);
    background: var(--bar);
}

/* L'erreur se distingue par le pointillé, pas par la couleur. */
.flash-bad { border-bottom-style: dashed; }
.flash-ok { border-bottom-style: solid; }

.actions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.6em;
    margin-bottom: 0;
}

/* Une page à une seule section occupe toute la largeur. Le dossier tient en
   quatre blocs — les deux comptes, le bandeau d'accès, puis le code — séparés
   par un filet comme le reste du cadre. */
.dossier {
    grid-column: 1 / -1;
    min-height: 0;
    /* Le dossier défile dans son cadre : la page, elle, ne bouge pas. */
    overflow-y: auto;
}

.dossier > * {
    padding: 1em;
    border-top: var(--frame);
}

.dossier > :first-child { border-top: 0; }

/* Deux paires de blocs vont sans filet : les deux comptes, qui se lisent comme
   une seule fiche, et le code, qui est la suite du bandeau qui le précède. */
.compte + .compte,
.dossier > .acces-bloc { border-top: 0; }

/* Dans une paire sans filet, l'écart est partagé en deux moitiés égales — 0.4em
   de chaque côté. Chacun gardant son padding, un côté entier des deux côtés
   ferait le double de ce que l'œil lit de part et d'autre d'un filet, et ça se
   voit. Les deux paires tombent donc sur 0.8em, soit la moitié d'un écart
   séparé par un trait. */
.compte-discord { padding-bottom: 0.4em; }
.compte + .compte { padding-top: 0.4em; }
.dossier > .acces-bloc { padding-top: 0.4em; }

/* Les titres de section — « Mon compte », « Statut », « Tableau d'affichage »,
   « Gazette », et les titres d'article de la Gazette. Le trait part du mot et
   court jusqu'au bord droit : c'est lui, la séparation, et il est dessiné en CSS
   plutôt qu'écrit en caractères. */
.titre {
    display: flex;
    align-items: center;
    gap: 0.8em;
    margin: 0;
    color: var(--fg);
    font-size: 0.85em;
    letter-spacing: 0.06em;
    text-transform: uppercase;
}

.titre::after {
    content: "";
    flex: 1;
    border-top: var(--frame);
}

/* Dans le dossier, le titre est un bloc comme les autres : il porte son padding,
   et il remplace le filet de celui qui le suit. */
.dossier > .titre {
    padding: 0.6em 1em 0.3em;
    border-top: 0;
}

.titre + * { border-top: 0; }

/* Une moitié : la vignette, puis sa liste. Alignées par le haut. Les deux
   moitiés s'empilent, donc leurs vignettes de même largeur font tomber les deux
   listes sur la même colonne. */
.compte {
    display: flex;
    align-items: flex-start;
    gap: 1.5em;
}

/* Les deux listes prennent la place qui reste, pour que leurs colonnes
   s'étalent au lieu de se serrer. */
.compte > .jeu,
.compte > .infos {
    flex: 1;
    min-width: 0;
}

/* La photo de profil Discord. Elle garde ses couleurs, et prend exactement la
   largeur du skin — c'est ce qui fait tomber les deux listes sur la même
   colonne. */
.avatar {
    flex: 0 0 auto;
    display: block;
    width: var(--portrait);
    height: var(--portrait);
    border: var(--frame);
    border-radius: 0;
    background: var(--field);
    object-fit: cover;
}

/* Le skin, rendu en pied par mc-api.io, chargé directement par le navigateur.

   On demande un rendu à size=256 et on l'affiche à la largeur de la photo : le
   navigateur le réduit, ce qui tombe juste sur un écran à densité double. La
   hauteur suit le rapport du personnage, donc rien n'est déformé. */
.skin {
    flex: 0 0 auto;
    display: block;
    width: var(--portrait);
    height: auto;
    border: var(--frame);
    border-radius: 0;
    background: var(--field);
}

/* Les infos perso, repliées par défaut. C'est le panneau qui cache les
   valeurs : rien à déguiser dans le texte, il suffit qu'il soit fermé. */
.infos summary {
    color: var(--fg-soft);
    cursor: pointer;
    font-size: 0.85em;
    text-transform: uppercase;
}

/* Les deux listes partagent la même première colonne, calée sur le libellé le
   plus long : « TIME IN SANCTUARY », 17 caractères, donc 18ch pour garder un
   caractère de marge. Sans ça chaque grille règle son `auto` sur son propre
   libellé — 14 caractères côté infos perso sans cette ligne, 16 côté jeu — et les
   deux colonnes de valeurs ne tombent pas l'une sous l'autre. La police est à
   chasse fixe, donc le ch est exact. */
.infos dl,
.jeu {
    display: grid;
    grid-template-columns: 18ch minmax(0, 1fr);
    gap: 0.3em 1.2em;
    margin: 0.9em 0 0;
}

/* La liste du jeu n'est pas dans un panneau replié : pas de marge haute. */
.jeu { margin: 0; }

/* Les libellés des deux listes, en capitales et en gris sourd : c'est le nom du
   champ, la valeur reste en clair à côté. */
.infos dt,
.jeu dt {
    color: var(--fg-mute);
    text-transform: uppercase;
}

.infos dd,
.jeu dd { margin: 0; }

/* La ligne des droits : l'état, un tiret long, puis ce que ça autorise, alignée
   à gauche comme le reste. Sans marge basse : le bloc a déjà son padding. */
.message {
    margin: 0;
    color: var(--fg-soft);
}

/* Ce qui suit le bandeau — les boutons Discord ou le formulaire — garde son
   écart, lui. */
.message + * { margin-top: 1em; }

/* Le bloc du bandeau respire moins que les autres : il ne porte qu'une ligne. Son
   bas se cale sur la demi-mesure de la paire qu'il forme avec le code. */
.dossier > .bandeau { padding: 0.6em 1em 0.4em; }

/* L'état de l'accès, en tête de cette ligne. Plus clair que le message et en
   capitales : c'est lui qu'on lit en premier. */
.statut {
    color: var(--fg);
    text-transform: uppercase;
}

/* Valide, en attente, invalide. La classe est posée par la vue, et c'est le seul
   endroit de la feuille qui sorte des gris. Le tiret et la phrase derrière
   gardent la couleur du message. */
.statut-accepted { color: var(--valid); }
.statut-pending { color: var(--pending); }
.statut-rejected { color: var(--invalid); }

/* Le libellé du code, au-dessus de lui. Même corps que le titre du panneau
   infos perso. Pas de marge haute : le bloc a son padding. */
.acces-label {
    margin: 0 0 0.5em;
    color: var(--fg-soft);
    font-size: 0.85em;
    text-transform: uppercase;
}

/* La précision qui suit le libellé, après le tiret. En bas de casse : une phrase
   entière en capitales serait lourde à lire. Plus sourde que le libellé, pour ne
   pas lui voler la vedette. */
.acces-label .acces-note {
    text-transform: none;
    color: var(--fg-mute);
}

/* Le code et son bouton de copie, sur la même ligne. Pas de marge haute : c'est
   le libellé ci-dessus qui fait l'espace. */
.acces {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 1em;
    margin-top: 0;
}

/* Le bouton de copie se cale sur la taille du bouton de téléchargement : celui-ci
   vit dans la nav, qui est à 0.9em, alors que celui-ci est dans la page, à 1em.
   Sans cette ligne les deux mêmes boutons ne font pas la même taille — 12.6px
   contre 14px. Si la nav change de taille, celle-ci suit. */
.copy {
    white-space: nowrap;
    font-size: 0.9em;
}

.apply {
    display: flex;
    flex-direction: column;
    gap: 0.5em;
}

.apply > * { margin-bottom: 0; }

/* Le champ reste court : une ligne de saisie, pas un bloc de la largeur du
   cadre. */
.apply input { max-width: 24em; }

label {
    color: var(--fg);
    font-size: 0.9em;
    text-transform: uppercase;
}

input {
    width: 100%;
    padding: 0.35em 0.5em;
    border: var(--frame);
    border-radius: 0;
    background: var(--field);
    color: var(--fg);
    font: inherit;
    text-shadow: none;
}

/* Le code de personnage, en alphabet galactique, dans un cadre. Pas de
   transcription : les lettres sont dessinees, c'est tout.

   Dans la rangée souple, un élément prend la largeur de son contenu : la
   bordure épouse donc le code au lieu de barrer toute la ligne. Le bouton de
   copie reste en dehors du cadre. */
.code {
    margin: 0;
    padding: 0.3em 0.8em;
    border: var(--frame);
    font-family: "Standard Galactic", monospace;
    font-size: 2em;
    line-height: 1.4;
    letter-spacing: 0.04em;
    color: var(--fg);
}

.field-error {
    margin: 0;
    color: var(--fg);
    font-weight: bold;
}

/* Le code de parrainage reste en lettres : ce n'est pas le code de personnage. */
.share-code {
    margin: 0.4em 0;
    color: var(--fg);
    font-size: 1.4em;
    letter-spacing: 0.06em;
}

.note {
    margin: 0.6em 0 0;
    color: var(--fg-mute);
    font-size: 0.85em;
}

/* ---------------------------------------------------- accueil, deux colonnes */

/* La Gazette a gauche, le tableau d'affichage a droite : c'est lui qui garde la
   grande colonne, donc la plus large. Le filet vertical est sur le bord droit de
   la colonne de gauche, comme partout ailleurs dans le cadre. */
.accueil {
    grid-column: 1 / -1;
    min-height: 0;
    display: grid;
    /* Le nombre d'or : la Gazette prend 1.618 part, le tableau d'affichage 1.
       Soit 61,8 % pour la gauche et 38,2 % pour la droite — la proportion se lit
       comme un rectangle d'or. Sur 1024 px de cadre : environ 630 px et 390 px.

       Deux rangées : la bande d'intendance prend sa hauteur, les deux colonnes
       se partagent le reste — c'est ce reste qui défile. */
    grid-template-columns: minmax(0, 1.618fr) minmax(0, 1fr);
    grid-template-rows: auto minmax(0, 1fr);
}

/* L'intendance : la bande du haut, qui traverse les deux colonnes. Pas de filet
   sous elle — l'écart suffit à la séparer des colonnes, comme pour les autres
   paires de blocs. Son bas est réduit d'autant : 0.4em au lieu de 1em, donc un
   écart de 1.4em avec le haut des colonnes au lieu de 2em. */
.intendance {
    grid-column: 1 / -1;
    padding: 1em 1em 0.4em;
}

.intendance-corps {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 1.2em;
}

/* Le carré du jour : le chiffre en gros, le mois dessous. */
.calendrier {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.1em;
    width: 4.2em;
    height: 4.2em;
    margin: 0;
    background: var(--carte);
    border: var(--frame);
}

.calendrier-jour {
    color: var(--fg);
    font-size: 2em;
    line-height: 1;
}

.calendrier-mois {
    color: var(--fg-mute);
    font-size: 0.8em;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}

/* L'heure du serveur, à droite du carré : la date en petit, l'heure en grand. */
.horloge { margin: 0; }

.horloge-date {
    display: block;
    color: var(--fg-mute);
    font-size: 0.85em;
    letter-spacing: 0.06em;
    text-transform: uppercase;
}

.horloge-heure {
    display: block;
    color: var(--fg);
    font-size: 1.4em;
    line-height: 1.2;
}

/* L'annonce du serveur : elle prend la place qui reste, séparée par un filet. */
.annonce-serveur {
    flex: 1;
    min-width: 15em;
    margin: 0;
    padding-left: 1.2em;
    border-left: var(--frame);
    color: var(--fg-soft);
}

/* Chaque colonne défile pour son compte : on peut lire la Gazette sans perdre le
   haut du tableau d'affichage. min-height: 0 leur donne une hauteur finie, sans
   quoi elles s'allongeraient au lieu de défiler. */
.gazette {
    min-height: 0;
    padding: 1em;
    overflow-y: auto;
    border-right: var(--frame);
}

.affichage {
    min-height: 0;
    padding: 1em;
    overflow-y: auto;
}

.accueil h2.titre { margin-bottom: 0.9em; }

/* Une annonce du tableau : la face du joueur qui a publié à gauche, le texte à
   droite, dans le même bloc. Le cadre prend la couleur de l'annonce, le fond est
   assombri d'un cran par rapport à la page. */
.annonce {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    gap: 0.9em;
    padding: 0.9em 1em;
    background: var(--carte);
    border: 1px solid var(--annonce);
}

.annonce + .annonce { margin-top: 0.8em; }

/* Les quatre genres, quatre couleurs fixes. La carte s'en sert pour son cadre et
   pour le genre écrit en haut ; le reste du texte garde ses gris. */
.annonce-rouge { --annonce: var(--rouge); }
.annonce-vert { --annonce: var(--vert); }
.annonce-bleu { --annonce: var(--bleu); }
.annonce-jaune { --annonce: var(--jaune); }

/* L'avis épinglé se détache du reste : c'est celui que l'équipe met en avant. Un
   cran plus sombre que les autres, et son cadre porte sa couleur. */
.annonce-epinglee { background: var(--panel); }

/* La face, rendue par mc-api.io à partir du pseudo de l'auteur. Carrée, sans
   arrondi, et demandée en 96 px pour rester nette sur un écran dense. */
.annonce-face {
    display: block;
    width: 3em;
    height: 3em;
    border: var(--frame);
    border-radius: 0;
    background: var(--field);
}

/* La colonne de texte, à droite de la face. min-width: 0 la laisse se rétrécir
   au lieu de pousser la face hors de la carte. */
.annonce-corps { min-width: 0; }

.annonce-titre {
    margin: 0 0 0.4em;
    font-size: 1em;
}

/* Le genre — chantier, rendez-vous, besoin — dit en un mot de quoi il s'agit, et
   prend la couleur de l'annonce. Sur sa propre ligne, au-dessus du sujet. */
.annonce-genre {
    margin: 0 0 0.15em;
    color: var(--annonce);
    font-size: 0.85em;
    letter-spacing: 0.06em;
    text-transform: uppercase;
}

.annonce-texte {
    margin: 0 0 0.7em;
    color: var(--fg-soft);
}

.annonce-pied {
    margin: 0;
    color: var(--fg-mute);
    font-size: 0.85em;
}

/* Un article de la Gazette. Son titre porte le trait qui le sépare du précédent,
   exactement comme les titres de section du dossier : pas de filet en plus. */
.article { padding-bottom: 0.9em; }

.article summary {
    padding: 0.7em 0;
    cursor: pointer;
    list-style: none;
}

/* Le petit triangle du navigateur n'a pas sa place ici : le trait du titre sépare
   déjà, et il décalerait le mot de quelques pixels. */
.article summary::-webkit-details-marker { display: none; }

/* Le titre de l'article ouvert passe en jaune, comme les liens : on voit d'un
   coup d'œil lequel est déplié. Le survol le colore aussi, ouvert ou non. La
   couleur se pose sur le titre lui-même, pas sur le summary : le titre a la
   sienne, il ne l'hérite pas. */
.article summary:hover .titre,
.article[open] > summary .titre { color: var(--accent); }

/* La photo, sa légende, le récit, puis la date et l'auteur. */
.article-photo {
    display: block;
    width: 100%;
    height: auto;
    border: var(--frame);
}

.article-legende {
    margin: 0.5em 0 1em;
    color: var(--fg-mute);
    font-size: 0.85em;
}

.article-texte { margin: 0 0 0.9em; }

.article-pied {
    margin: 0;
    color: var(--fg-mute);
    font-size: 0.85em;
}

.date {
    color: var(--fg-mute);
    font-size: 0.85em;
}

/* La date, en tête du titre d'article. Elle garde le gris des dates mais reprend
   la taille du titre : sans ça, dans un titre déjà à 0.85 em, elle vaudrait
   0.72 em — illisible. */
.titre .date { font-size: 1em; }

/* ------------------------------------------------- accueil sur écran étroit */

/* Sur un écran large, les onglets n'existent pas : les deux colonnes sont là. */
.onglet,
.onglet-bouton { display: none; }

/* Quand la place manque, une seule colonne, et les deux espaces deviennent deux
   onglets. Le panneau non choisi est masqué par le sélecteur de frère : les
   boutons radio précèdent les deux sections, donc ils peuvent les viser. */
@media (max-width: 860px) {
    /* Deux colonnes pour la rangée d'onglets, et les panneaux prennent toute la
       largeur : ils passent sous les onglets. La rangée des onglets prend sa
       hauteur, celle des panneaux prend le reste — c'est elle qui défile. */
    .accueil {
        grid-template-columns: 1fr 1fr;
        /* Trois rangées : l'intendance, les onglets, puis les panneaux — c'est
           cette dernière qui défile. */
        grid-template-rows: auto auto minmax(0, 1fr);
    }

    .affichage,
    .gazette { grid-column: 1 / -1; }

    /* Une seule colonne : plus de filet vertical, et les onglets séparent. */
    .gazette { border-right: 0; }

    /* Le titre de la colonne ferait doublon avec l'onglet. Seulement celui de la
       colonne : les titres d'article, eux, restent — c'est ce sur quoi on clique
       pour ouvrir. */
    .accueil h2.titre { display: none; }

    .onglet-bouton {
        display: block;
        padding: 0.5em 1em;
        border-bottom: var(--frame);
        color: var(--fg-mute);
        font-size: 0.85em;
        letter-spacing: 0.06em;
        text-transform: uppercase;
        cursor: pointer;
    }

    .onglet:checked + .onglet-bouton {
        background: var(--panel-hover);
        color: var(--fg);
    }

    #onglet-gazette:checked ~ .affichage { display: none; }
    #onglet-affichage:checked ~ .gazette { display: none; }
}
/* ------------------------------------------------------ barre du bas */

footer {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 0.4em 1em;
    padding: 0.5em 1em;
    color: var(--fg-mute);
    /* Taille calculée pour que les deux morceaux tiennent sur une ligne : ils
       mesurent 81,1 em, donc 0,85 em de corps pour 1024 px de cadre. */
    font-size: 0.85em;
}

/* Au-dessus de 1056 px de fenêtre, le cadre fait ses 1024 px et la ligne ne
   casse plus. En dessous, le cadre se rétrécit : on laisse le pied casser
   plutôt que de le voir déborder. */
@media (min-width: 1056px) {
    footer { white-space: nowrap; }
}

footer p { margin: 0; }

/* Qui a demandé à ne pas bouger n'a pas de ciel qui tourne, ni de compteur qui
   clignote. */
@media (prefers-reduced-motion: reduce) {
    .skybox-cube { animation: none; }

    .en-ligne { animation: none; }
}

@media (max-width: 480px) {
    .site-header { font-size: 0.9em; }
    .mark img { height: 1.6em; }
    nav { justify-content: flex-end; }
}
