Lot U — le nombre de pages `Livre.NombrePages` est nullable, sans valeur par défaut : « 0 page » se lirait comme une donnée là où l'on veut dire « on ne sait pas ». Même raison que pour `TypeDocument.NonPrecise` — un défaut qui ne prétend rien n'a rien à reprendre, d'où une migration réduite à un `AddColumn`. Le service refuse un zéro plutôt que de l'écrire ; effacer le champ reste la façon de revenir à « inconnu ». Le préremplissage vient de `dc:format`, que rien ne lisait jusqu'ici. ⚠️ Ce champ n'est pas un nombre mais une phrase décrivant le support, et les notices déjà enregistrées sous Fixtures/ le montrent : « 1 vol. (113 p.) : ill., couv. ill. en coul. ; 18 cm », « 503 p. : couv. ill. ; 17 cm ». Les règles sont donc étroites — un nombre suivi de « p. » ou de « page(s) », rien d'autre — et tout le reste rend `null`. L'erreur n'est pas symétrique : un champ vide se remplit à la main en trois secondes, un chiffre faux s'enregistre sans que personne ne le voie. « 1 vol. » ne vaut pas 1, « 30 cm » ne vaut pas 30, et « (p. 45-90) », qui est une pagination de contribution, ne vaut rien. La valeur reste proposée dans un champ modifiable, et rien n'est déduit pour un ebook. Lot X — éditer une envie, et souhaiter une revue `PUT /api/souhaits/{id}` recalcule la clé d'œuvre et l'auteur normalisé : sans ce recalcul, le rapprochement « déjà au catalogue » continuerait de se faire sur l'ancienne forme, et le signalement mentirait sans le dire. Le filtre sur l'appelant fait partie de la clé de recherche, pas d'une vérification ultérieure — l'envie d'un autre est introuvable (404), jamais refusée (403). ⚠️ Une édition peut heurter l'unicité (utilisateur, œuvre, auteur), ce qu'un ajout ne peut pas : renommer une envie en une autre déjà présente répond par un message lisible, jamais par « UNIQUE constraint failed ». 400 et non 409, contrairement au doublon du catalogue : là-bas posséder deux exemplaires est légitime et l'appel se reconfirme, ici l'index l'interdit et il n'y a rien à confirmer. Le rang n'est pas touché — l'ordre a son propre point d'entrée. ⚠️ La couverture n'est écrite que si la charge utile en porte une. Aucun écran n'offre de champ « URL de couverture » pour une envie (décision actée), donc un remplacement inconditionnel l'aurait effacée à la première faute de frappe corrigée. `RevueSouhaitee` est une table sœur, et non des colonnes de plus sur `LivreSouhaite` : un numéro n'a pas d'auteur et se distingue par son numéro, deux choses que la clé d'unicité des envies de livres ne sait pas exprimer sans devenir fausse pour tout le monde. `NumeroNormalise` est NOT NULL avec un défaut vide — SQLite tient deux NULL pour distincts, et « Médor, sans numéro » s'ajouterait autant de fois qu'on cliquerait. L'ISSN est canonisé avec son tiret, seul code du projet rangé ainsi. Le coût de la table sœur est payé partout où il devait l'être : affichage, `.txt`, `.csv` et instantané hors-ligne `souhaits-revues`. ⚠️ Les revues forment une SECTION à part plutôt que des lignes entrelacées : chaque table numérote son rang indépendamment, et mélanger deux suites sans rapport produirait un ordre que personne n'a choisi. Le `.txt`, groupé par auteur, ne pouvait de toute façon pas les accueillir — elles n'en ont pas, et « Auteur non précisé » désigne des livres dont l'auteur est inconnu. Le CSV gagne une colonne « Type » : sans elle, un tri par titre rendrait revues et livres indiscernables, et la colonne des codes mêlerait ISBN et ISSN en silence. `ServiceRenormalisation` connaît la nouvelle table, avec la règle de collision déjà en place. ⚠️ L'ISSN y est canonisé à part : `Renormaliser` n'applique rien quand la clé ne bouge pas, un ISSN mal formé sur une ligne au titre inchangé y échapperait. `RevueSouhaitee` ne porte PAS de `CoverUrl` : rien à ajouter au garde de `GET /api/couvertures`. Vérifié en exécution : ISSN « 24666718 » rangé « 2466-6718 », édition de l'envie d'un autre en 404, et les deux exports portant bien les deux moitiés. 602 tests au vert (552 au départ), aucun avertissement de compilation. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1605 lines
39 KiB
CSS
1605 lines
39 KiB
CSS
html, body {
|
|
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
|
}
|
|
|
|
h1:focus {
|
|
outline: none;
|
|
}
|
|
|
|
a, .btn-link {
|
|
color: #0071c1;
|
|
}
|
|
|
|
.btn-primary {
|
|
color: #fff;
|
|
background-color: #1b6ec2;
|
|
border-color: #1861ac;
|
|
}
|
|
|
|
.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
|
|
box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb;
|
|
}
|
|
|
|
.content {
|
|
padding-top: 1.1rem;
|
|
}
|
|
|
|
.valid.modified:not([type=checkbox]) {
|
|
outline: 1px solid #26b050;
|
|
}
|
|
|
|
.invalid {
|
|
outline: 1px solid red;
|
|
}
|
|
|
|
.validation-message {
|
|
color: red;
|
|
}
|
|
|
|
#blazor-error-ui {
|
|
color-scheme: light only;
|
|
background: lightyellow;
|
|
bottom: 0;
|
|
box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
|
|
box-sizing: border-box;
|
|
display: none;
|
|
left: 0;
|
|
padding: 0.6rem 1.25rem 0.7rem 1.25rem;
|
|
position: fixed;
|
|
width: 100%;
|
|
z-index: 1000;
|
|
}
|
|
|
|
#blazor-error-ui .dismiss {
|
|
cursor: pointer;
|
|
position: absolute;
|
|
right: 0.75rem;
|
|
top: 0.5rem;
|
|
}
|
|
|
|
.blazor-error-boundary {
|
|
background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
|
|
padding: 1rem 1rem 1rem 3.7rem;
|
|
color: white;
|
|
}
|
|
|
|
.blazor-error-boundary::after {
|
|
content: "An error has occurred."
|
|
}
|
|
|
|
.loading-progress {
|
|
position: absolute;
|
|
display: block;
|
|
width: 8rem;
|
|
height: 8rem;
|
|
inset: 20vh 0 auto 0;
|
|
margin: 0 auto 0 auto;
|
|
}
|
|
|
|
.loading-progress circle {
|
|
fill: none;
|
|
stroke: #e0e0e0;
|
|
stroke-width: 0.6rem;
|
|
transform-origin: 50% 50%;
|
|
transform: rotate(-90deg);
|
|
}
|
|
|
|
.loading-progress circle:last-child {
|
|
stroke: #1b6ec2;
|
|
stroke-dasharray: calc(3.141 * var(--blazor-load-percentage, 0%) * 0.8), 500%;
|
|
transition: stroke-dasharray 0.05s ease-in-out;
|
|
}
|
|
|
|
.loading-progress-text {
|
|
position: absolute;
|
|
text-align: center;
|
|
font-weight: bold;
|
|
inset: calc(20vh + 3.25rem) 0 auto 0.2rem;
|
|
}
|
|
|
|
.loading-progress-text:after {
|
|
content: var(--blazor-load-percentage-text, "Loading");
|
|
}
|
|
|
|
code {
|
|
color: #c02d76;
|
|
}
|
|
|
|
.form-floating > .form-control-plaintext::placeholder, .form-floating > .form-control::placeholder {
|
|
color: var(--bs-secondary-color);
|
|
text-align: end;
|
|
}
|
|
|
|
.form-floating > .form-control-plaintext:focus::placeholder, .form-floating > .form-control:focus::placeholder {
|
|
text-align: start;
|
|
}
|
|
/* ------------------------------------------------------------------
|
|
MaBibli — interface du catalogue (phase 3)
|
|
|
|
Conçue mobile d'abord : une seule colonne, cibles tactiles d'au moins
|
|
2,75 rem, aucune règle de largeur en dessous du point de rupture.
|
|
Le PC n'ajoute qu'une largeur maximale et une grille de cartes.
|
|
------------------------------------------------------------------ */
|
|
|
|
:root {
|
|
--mb-accent: #1b3a5c;
|
|
--mb-bord: #dcdcdc;
|
|
--mb-fond-doux: #f4f5f7;
|
|
--mb-texte-doux: #5c6470;
|
|
--mb-danger: #a4242c;
|
|
}
|
|
|
|
body {
|
|
background: #fff;
|
|
color: #1d2129;
|
|
}
|
|
|
|
.titre-page {
|
|
font-size: 1.4rem;
|
|
margin: 0 0 0.75rem;
|
|
}
|
|
|
|
/*
|
|
Ligne d'en-tête d'une fiche : le texte à gauche, les actions groupées à DROITE (lot T).
|
|
|
|
C'est la disposition déjà tenue par la bibliographie et la liste des auteurs. Reléguées en
|
|
bas de l'écran, les actions d'une saga de vingt tomes ne se découvraient qu'après avoir
|
|
déroulé toute la liste.
|
|
|
|
⚠️ `min-width: 0` sur la colonne de texte : sans lui, un titre long refuse de rétrécir et
|
|
pousse les actions hors de l'écran — le piège déjà rencontré au lot M1.
|
|
*/
|
|
.entete-fiche {
|
|
display: flex;
|
|
align-items: flex-start;
|
|
justify-content: space-between;
|
|
gap: 0.5rem;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.entete-fiche-texte {
|
|
flex: 1 1 12rem;
|
|
min-width: 0;
|
|
}
|
|
|
|
.entete-fiche-actions {
|
|
flex: 0 0 auto;
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
justify-content: flex-end;
|
|
align-items: center;
|
|
gap: 0.4rem;
|
|
}
|
|
|
|
.message-discret {
|
|
color: var(--mb-texte-doux);
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.message-erreur {
|
|
background: #fdecec;
|
|
border-left: 3px solid var(--mb-danger);
|
|
color: #7a1a20;
|
|
padding: 0.6rem 0.75rem;
|
|
border-radius: 4px;
|
|
margin: 0.75rem 0;
|
|
}
|
|
|
|
.message-avertissement {
|
|
background: #fff8e1;
|
|
border-left: 3px solid #c99700;
|
|
color: #6b5200;
|
|
padding: 0.6rem 0.75rem;
|
|
border-radius: 4px;
|
|
margin: 0.5rem 0;
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.compteur {
|
|
color: var(--mb-texte-doux);
|
|
font-size: 0.85rem;
|
|
margin: 0.75rem 0 0.25rem;
|
|
}
|
|
|
|
/* --- Champs de saisie --- */
|
|
|
|
.barre-recherche {
|
|
margin-bottom: 0.75rem;
|
|
}
|
|
|
|
.champ {
|
|
display: block;
|
|
margin-bottom: 0.9rem;
|
|
border: 0;
|
|
padding: 0;
|
|
}
|
|
|
|
.champ-libelle {
|
|
display: block;
|
|
font-size: 0.85rem;
|
|
font-weight: 600;
|
|
margin-bottom: 0.3rem;
|
|
}
|
|
|
|
.champ-aide,
|
|
.obligatoire {
|
|
font-weight: 400;
|
|
color: var(--mb-texte-doux);
|
|
}
|
|
|
|
.champ-saisie {
|
|
width: 100%;
|
|
/* 16px minimum : en dessous, iOS zoome au focus. */
|
|
font-size: 1rem;
|
|
padding: 0.7rem 0.75rem;
|
|
border: 1px solid var(--mb-bord);
|
|
border-radius: 8px;
|
|
background: #fff;
|
|
}
|
|
|
|
.champ-saisie:focus {
|
|
outline: 2px solid var(--mb-accent);
|
|
outline-offset: -1px;
|
|
}
|
|
|
|
/* --- Segments (filtres, format, statut) --- */
|
|
|
|
.segments {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 0.35rem;
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
|
|
.segment {
|
|
flex: 1 1 auto;
|
|
min-height: 2.5rem;
|
|
padding: 0.4rem 0.7rem;
|
|
font-size: 0.85rem;
|
|
border: 1px solid var(--mb-bord);
|
|
border-radius: 999px;
|
|
background: #fff;
|
|
color: #1d2129;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.segment-actif {
|
|
background: var(--mb-accent);
|
|
border-color: var(--mb-accent);
|
|
color: #fff;
|
|
}
|
|
|
|
.segments-compacts .segment {
|
|
min-height: 2.2rem;
|
|
font-size: 0.8rem;
|
|
}
|
|
|
|
.segment.statut-a-lire.segment-actif { background: #6b4fa8; border-color: #6b4fa8; }
|
|
.segment.statut-en-cours.segment-actif { background: #b06b00; border-color: #b06b00; }
|
|
.segment.statut-lu.segment-actif { background: #1f7a44; border-color: #1f7a44; }
|
|
|
|
/* Type de document : même vocabulaire que les pastilles de statut, deux couleurs distinctes
|
|
des trois du statut et de celle du prêt, pour qu'on reconnaisse ce qu'on filtre (A2, IDEES.md). */
|
|
/* Le segment « Prêtés » reprend la couleur de l'étiquette de prêt, comme les segments de
|
|
statut reprennent celle de leur pastille : le filtre et la carte doivent se répondre. */
|
|
.segment.segment-prete.segment-actif { background: #8a5a00; border-color: #8a5a00; }
|
|
|
|
.segment.type-roman.segment-actif { background: #2a6f97; border-color: #2a6f97; }
|
|
.segment.type-bd.segment-actif { background: #a13d63; border-color: #a13d63; }
|
|
|
|
/* --- Boutons --- */
|
|
|
|
.bouton {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
min-height: 2.75rem;
|
|
padding: 0.6rem 1rem;
|
|
border-radius: 8px;
|
|
border: 1px solid var(--mb-bord);
|
|
background: #fff;
|
|
color: #1d2129;
|
|
font-size: 0.95rem;
|
|
text-decoration: none;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.bouton:disabled {
|
|
opacity: 0.55;
|
|
cursor: default;
|
|
}
|
|
|
|
.bouton-principal {
|
|
background: var(--mb-accent);
|
|
border-color: var(--mb-accent);
|
|
color: #fff;
|
|
}
|
|
|
|
.bouton-discret {
|
|
background: var(--mb-fond-doux);
|
|
}
|
|
|
|
.bouton-danger {
|
|
background: var(--mb-danger);
|
|
border-color: var(--mb-danger);
|
|
color: #fff;
|
|
}
|
|
|
|
.actions-formulaire {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 0.5rem;
|
|
margin: 1rem 0;
|
|
}
|
|
|
|
/* Cette barre ne porte que des ACTIONS : la navigation est dans le menu du bandeau.
|
|
Depuis que ce menu est passé en haut (2026-08-20), plus rien ne l'accompagne en bas de
|
|
l'écran : elle s'y pose donc directement, sans décalage à réserver. */
|
|
.actions-flottantes {
|
|
position: fixed;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 0.5rem;
|
|
padding: 0.6rem 1rem calc(0.6rem + env(safe-area-inset-bottom));
|
|
background: rgba(255, 255, 255, 0.96);
|
|
border-top: 1px solid var(--mb-bord);
|
|
}
|
|
|
|
/* Base de 8rem : au-delà de trois entrées la barre passe sur deux lignes plutôt que de
|
|
comprimer chaque libellé sur trois lignes de texte. Mesuré sur 375 px de large. */
|
|
.actions-flottantes .bouton {
|
|
flex: 1 1 8rem;
|
|
}
|
|
|
|
/* --- Couvertures --- */
|
|
|
|
.couverture {
|
|
position: relative;
|
|
flex: 0 0 auto;
|
|
overflow: hidden;
|
|
border-radius: 6px;
|
|
background: var(--mb-fond-doux);
|
|
border: 1px solid var(--mb-bord);
|
|
}
|
|
|
|
.couverture-petite { width: 3.5rem; height: 5.25rem; }
|
|
.couverture-moyenne { width: 6rem; height: 9rem; }
|
|
|
|
.couverture-substitut {
|
|
position: absolute;
|
|
inset: 0;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 1.5rem;
|
|
font-weight: 700;
|
|
color: #9aa2ad;
|
|
}
|
|
|
|
.couverture-image {
|
|
position: absolute;
|
|
inset: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
object-fit: cover;
|
|
}
|
|
|
|
/* Posée par l'attribut onerror : les couvertures OpenLibrary renvoient
|
|
des 502 intermittents, l'image cassée laisse place au substitut. */
|
|
.couverture-image.image-cassee {
|
|
display: none;
|
|
}
|
|
|
|
/* --- Liste de livres --- */
|
|
|
|
.liste-livres,
|
|
.liste-candidats {
|
|
list-style: none;
|
|
margin: 0;
|
|
padding: 0;
|
|
}
|
|
|
|
.carte-livre,
|
|
.carte-candidat {
|
|
display: flex;
|
|
gap: 0.75rem;
|
|
padding: 0.75rem 0;
|
|
border-bottom: 1px solid var(--mb-bord);
|
|
}
|
|
|
|
.carte-corps {
|
|
min-width: 0;
|
|
flex: 1;
|
|
}
|
|
|
|
.carte-titre {
|
|
display: block;
|
|
font-weight: 600;
|
|
color: #1d2129;
|
|
text-decoration: none;
|
|
margin: 0 0 0.15rem;
|
|
}
|
|
|
|
.carte-auteur {
|
|
margin: 0 0 0.25rem;
|
|
color: var(--mb-texte-doux);
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.carte-details {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 0.4rem;
|
|
align-items: center;
|
|
margin: 0 0 0.5rem;
|
|
font-size: 0.8rem;
|
|
color: var(--mb-texte-doux);
|
|
}
|
|
|
|
.etiquette {
|
|
background: var(--mb-fond-doux);
|
|
border-radius: 4px;
|
|
padding: 0.1rem 0.4rem;
|
|
}
|
|
|
|
/* --- Choix entre plusieurs notices --- */
|
|
|
|
/* Les candidats partagent titre et auteur : ce sont l'éditeur et l'année
|
|
qui les départagent, donc ils sont mis en avant visuellement. */
|
|
.candidat-distinction {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 0.5rem;
|
|
margin: 0 0 0.4rem;
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.candidat-editeur {
|
|
font-weight: 600;
|
|
}
|
|
|
|
.candidat-annee {
|
|
background: #eef2f7;
|
|
border-radius: 4px;
|
|
padding: 0.05rem 0.4rem;
|
|
}
|
|
|
|
/* --- Formulaire --- */
|
|
|
|
.formulaire-apercu {
|
|
display: flex;
|
|
justify-content: center;
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
.zone-danger {
|
|
margin-top: 2rem;
|
|
padding-top: 1rem;
|
|
border-top: 1px solid var(--mb-bord);
|
|
}
|
|
|
|
/* --- PC : simple élargissement, la structure mobile ne change pas. --- */
|
|
|
|
@media (min-width: 48rem) {
|
|
.actions-flottantes {
|
|
position: static;
|
|
padding: 0;
|
|
margin-top: 1.5rem;
|
|
background: none;
|
|
border-top: 0;
|
|
}
|
|
|
|
.actions-flottantes .bouton {
|
|
flex: 0 0 auto;
|
|
}
|
|
}
|
|
|
|
/* --- Prêts --- */
|
|
|
|
/* Le prêt est une information du foyer, pas de l'utilisateur : sa couleur est
|
|
volontairement différente de celles des statuts de lecture, qui sont personnels. */
|
|
.etiquette-prete {
|
|
background: #fdecd8;
|
|
color: #7a4a00;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.bloc-prets {
|
|
margin-top: 1.5rem;
|
|
padding-top: 1rem;
|
|
border-top: 1px solid var(--mb-bord);
|
|
}
|
|
|
|
.titre-section {
|
|
font-size: 1.1rem;
|
|
margin: 0 0 0.5rem;
|
|
}
|
|
|
|
.titre-sous-section {
|
|
font-size: 0.95rem;
|
|
margin: 1.25rem 0 0.35rem;
|
|
color: var(--mb-texte-doux);
|
|
}
|
|
|
|
.bandeau-pret {
|
|
background: #fff8ef;
|
|
border-left: 3px solid #c98a2e;
|
|
border-radius: 4px;
|
|
padding: 0.6rem 0.75rem;
|
|
margin: 0.5rem 0;
|
|
font-size: 0.95rem;
|
|
}
|
|
|
|
.pret-duree {
|
|
color: var(--mb-texte-doux);
|
|
font-size: 0.85rem;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.avertissement-fort {
|
|
display: block;
|
|
margin-top: 0.35rem;
|
|
font-weight: 600;
|
|
}
|
|
|
|
/* L'historique se lit, il ne se manipule pas : rien n'y est cliquable. */
|
|
.liste-historique {
|
|
list-style: none;
|
|
margin: 0;
|
|
padding: 0;
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.liste-historique li {
|
|
padding: 0.4rem 0;
|
|
border-bottom: 1px solid var(--mb-bord);
|
|
}
|
|
|
|
.historique-dates {
|
|
display: block;
|
|
color: var(--mb-texte-doux);
|
|
font-size: 0.85rem;
|
|
}
|
|
|
|
/* Bouton d'action au sein d'une carte : il ne doit pas peser autant qu'un bouton de page,
|
|
tout en gardant une cible tactile utilisable. */
|
|
.bouton-compact {
|
|
min-height: 2.25rem;
|
|
padding: 0.35rem 0.8rem;
|
|
font-size: 0.85rem;
|
|
}
|
|
|
|
/* --- Scan du code-barres --- */
|
|
|
|
.scanner-cadre {
|
|
position: relative;
|
|
background: #000;
|
|
border-radius: 8px;
|
|
overflow: hidden;
|
|
aspect-ratio: 4 / 3;
|
|
}
|
|
|
|
.scanner-video {
|
|
width: 100%;
|
|
height: 100%;
|
|
object-fit: cover;
|
|
display: block;
|
|
}
|
|
|
|
/* Le viseur matérialise la bande réellement décodée : viser ailleurs ne sert à rien. */
|
|
.scanner-viseur {
|
|
position: absolute;
|
|
left: 5%;
|
|
right: 5%;
|
|
top: 27.5%;
|
|
height: 45%;
|
|
border: 2px solid rgba(255, 255, 255, 0.85);
|
|
border-radius: 6px;
|
|
box-shadow: 0 0 0 100vmax rgba(0, 0, 0, 0.35);
|
|
}
|
|
|
|
.scanner-mesure {
|
|
font-variant-numeric: tabular-nums;
|
|
}
|
|
|
|
/* --- Agrandissement de la couverture --- */
|
|
|
|
/* Le déclencheur recouvre exactement la vignette. Il n'existe que quand il y a
|
|
une image : sans couverture, le substitut à initiale reste un décor inerte. */
|
|
.couverture-declencheur {
|
|
position: absolute;
|
|
inset: 0;
|
|
padding: 0;
|
|
border: 0;
|
|
background: none;
|
|
cursor: zoom-in;
|
|
}
|
|
|
|
/* Image cassée (404/502 intermittent) : le onerror du <img> désactive ce bouton.
|
|
Curseur neutre, pour ne plus promettre un agrandissement qui n'ouvrirait rien. */
|
|
.couverture-declencheur:disabled {
|
|
cursor: default;
|
|
}
|
|
|
|
.calque-couverture {
|
|
position: fixed;
|
|
inset: 0;
|
|
z-index: 50;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 1.5rem;
|
|
background: rgba(0, 0, 0, 0.82);
|
|
cursor: zoom-out;
|
|
}
|
|
|
|
.calque-couverture:focus {
|
|
outline: none;
|
|
}
|
|
|
|
/* Bornée par la fenêtre : sur mobile la couverture occupe la largeur,
|
|
sur PC elle ne dépasse pas la hauteur utile. */
|
|
.calque-image {
|
|
max-width: 100%;
|
|
max-height: 100%;
|
|
object-fit: contain;
|
|
border-radius: 6px;
|
|
background: #fff;
|
|
cursor: default;
|
|
}
|
|
|
|
.calque-image.image-cassee {
|
|
display: none;
|
|
}
|
|
|
|
.calque-fermer {
|
|
position: absolute;
|
|
top: calc(0.5rem + env(safe-area-inset-top));
|
|
right: 0.5rem;
|
|
min-width: 2.75rem;
|
|
min-height: 2.75rem;
|
|
border: 0;
|
|
border-radius: 999px;
|
|
background: rgba(255, 255, 255, 0.15);
|
|
color: #fff;
|
|
font-size: 1.1rem;
|
|
cursor: pointer;
|
|
}
|
|
|
|
/* --- Statut de lecture en lecture seule --- */
|
|
|
|
/* Pastille pleine, aux couleurs des segments actifs, pour ne pas se confondre avec
|
|
l'étiquette pâle du prêt : le statut est personnel, le prêt est celui du foyer. */
|
|
.etiquette-statut {
|
|
color: #fff;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.etiquette-statut.statut-a-lire { background: #6b4fa8; }
|
|
.etiquette-statut.statut-en-cours { background: #b06b00; }
|
|
.etiquette-statut.statut-lu { background: #1f7a44; }
|
|
|
|
/* Pastille du type de document sur la carte du catalogue : mêmes couleurs que le
|
|
filtre (A2, IDEES.md), pour reconnaître d'un coup d'œil ce qui a été filtré. */
|
|
.etiquette-type {
|
|
color: #fff;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.etiquette-type.type-roman { background: #2a6f97; }
|
|
.etiquette-type.type-bd { background: #a13d63; }
|
|
|
|
/* --- Fiche du livre en lecture seule --- */
|
|
|
|
.couverture-grande { width: 8rem; height: 12rem; }
|
|
|
|
.fiche-entete {
|
|
display: flex;
|
|
gap: 1rem;
|
|
align-items: flex-start;
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
.fiche-infos {
|
|
min-width: 0;
|
|
flex: 1;
|
|
}
|
|
|
|
.fiche-titre {
|
|
font-size: 1.15rem;
|
|
margin: 0 0 0.25rem;
|
|
}
|
|
|
|
.fiche-auteurs {
|
|
margin: 0 0 0.5rem;
|
|
color: var(--mb-texte-doux);
|
|
}
|
|
|
|
/* Deux colonnes « libellé / valeur » qui restent lisibles à 320 px. */
|
|
.fiche-champs {
|
|
margin: 0;
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.fiche-champs div {
|
|
display: flex;
|
|
gap: 0.5rem;
|
|
padding: 0.15rem 0;
|
|
}
|
|
|
|
.fiche-champs dt {
|
|
flex: 0 0 5rem;
|
|
color: var(--mb-texte-doux);
|
|
}
|
|
|
|
.fiche-champs dd {
|
|
margin: 0;
|
|
min-width: 0;
|
|
word-break: break-word;
|
|
}
|
|
|
|
/* Le statut reste manipulable hors édition : c'est le geste le plus fréquent
|
|
de l'application, l'enfouir derrière « Éditer » serait un recul. */
|
|
.bloc-statut {
|
|
margin-top: 1.25rem;
|
|
padding-top: 1rem;
|
|
border-top: 1px solid var(--mb-bord);
|
|
}
|
|
|
|
/* --- Auteurs --- */
|
|
|
|
/* Le nom d'un auteur est un point d'entrée, pas une décoration : il doit se voir comme
|
|
cliquable, et rester une cible atteignable au pouce dans une liste dense. */
|
|
.lien-auteur {
|
|
color: var(--mb-accent);
|
|
text-decoration: none;
|
|
padding: 0.1rem 0;
|
|
}
|
|
|
|
.lien-auteur:hover,
|
|
.lien-auteur:focus {
|
|
text-decoration: underline;
|
|
}
|
|
|
|
.liste-auteurs,
|
|
.liste-rapprochements {
|
|
list-style: none;
|
|
margin: 0;
|
|
padding: 0;
|
|
}
|
|
|
|
.carte-auteur-ligne {
|
|
display: flex;
|
|
gap: 0.5rem;
|
|
align-items: center;
|
|
min-height: 2.75rem;
|
|
padding: 0.4rem 0;
|
|
border-bottom: 1px solid var(--mb-bord);
|
|
}
|
|
|
|
/*
|
|
Trois colonnes, dont une seule est élastique.
|
|
|
|
⚠️ C'est la largeur FIGÉE des deux colonnes extrêmes qui fait tout l'intérêt : sans elle,
|
|
chaque ligne plaçait son compteur et ses boutons à un endroit différent, au gré de la
|
|
longueur du nom, et l'œil devait relire chaque ligne entière. Trois chiffres suffisent au
|
|
compteur — personne ne possède mille livres d'un même auteur.
|
|
*/
|
|
.auteur-compte {
|
|
flex: 0 0 2.5rem;
|
|
text-align: right;
|
|
font-variant-numeric: tabular-nums;
|
|
font-size: 0.85rem;
|
|
font-weight: 600;
|
|
color: var(--mb-texte-doux);
|
|
}
|
|
|
|
.auteur-nom {
|
|
flex: 1 1 auto;
|
|
min-width: 0;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
|
|
.auteur-actions {
|
|
flex: 0 0 auto;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: flex-end;
|
|
gap: 0.4rem;
|
|
}
|
|
|
|
/* Le champ de renommage s'insère dans la colonne d'actions : il doit pouvoir rétrécir, sinon
|
|
il pousse « Enregistrer » hors de l'écran sur un téléphone. */
|
|
.champ-auteur-renomme {
|
|
min-width: 0;
|
|
max-width: 10rem;
|
|
}
|
|
|
|
.rapprochements {
|
|
margin-bottom: 1.5rem;
|
|
padding: 0.75rem;
|
|
border: 1px solid var(--mb-bord);
|
|
border-radius: 8px;
|
|
background: var(--mb-fond-doux);
|
|
}
|
|
|
|
.carte-rapprochement {
|
|
padding: 0.5rem 0;
|
|
border-top: 1px solid var(--mb-bord);
|
|
}
|
|
|
|
.rapprochement-noms {
|
|
margin: 0;
|
|
font-size: 0.95rem;
|
|
}
|
|
|
|
/* ─────────────────────────────────────────────────────────────────────────────
|
|
Liste d'envies et bibliographie par auteur.
|
|
Mobile d'abord : une colonne, l'action à droite du texte, rien qui déborde.
|
|
───────────────────────────────────────────────────────────────────────────── */
|
|
|
|
.bloc-souhait-ajout {
|
|
background: var(--mb-fond-doux);
|
|
border-radius: 8px;
|
|
padding: 0.75rem;
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
/* Le champ ISBN et son bouton sur une ligne : le bouton ne doit pas se rétrécir
|
|
au point de devenir intouchable au pouce quand le champ est large. */
|
|
.ligne-isbn {
|
|
display: flex;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.ligne-isbn .champ-saisie {
|
|
flex: 1;
|
|
min-width: 0;
|
|
}
|
|
|
|
.ligne-isbn .bouton {
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.actions-export {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 0.5rem;
|
|
margin-bottom: 0.75rem;
|
|
}
|
|
|
|
.liste-souhaits,
|
|
.liste-bibliographie {
|
|
list-style: none;
|
|
margin: 0;
|
|
padding: 0;
|
|
}
|
|
|
|
.carte-souhait,
|
|
.carte-oeuvre {
|
|
display: flex;
|
|
gap: 0.75rem;
|
|
align-items: flex-start;
|
|
padding: 0.75rem 0;
|
|
border-bottom: 1px solid var(--mb-bord);
|
|
}
|
|
|
|
/* ⚠️ La ligne de bibliographie, elle, est centrée : la case à cocher est à gauche du titre
|
|
et les boutons à droite, sur la même ligne. Alignés en haut, la case flottait au-dessus du
|
|
texte et les boutons pendaient sous lui — c'est ce qui rendait la liste illisible à coup
|
|
d'œil, alors qu'on la parcourt précisément pour cocher. */
|
|
.carte-oeuvre {
|
|
align-items: center;
|
|
}
|
|
|
|
/* La carte partagée d'un livre possédé sert de corps à la ligne : elle porte déjà sa propre
|
|
bordure et son propre rembourrage, qu'il faut neutraliser ici pour ne pas doubler le trait. */
|
|
.carte-oeuvre > .carte-livre {
|
|
flex: 1;
|
|
min-width: 0;
|
|
padding: 0;
|
|
border-bottom: 0;
|
|
}
|
|
|
|
.oeuvre-case {
|
|
flex: 0 0 auto;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
/* Cible tactile : la case native fait 1rem, on double la zone cliquable autour. */
|
|
padding: 0.4rem;
|
|
margin: -0.4rem 0;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.oeuvre-case input {
|
|
width: 1.1rem;
|
|
height: 1.1rem;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.oeuvre-actions {
|
|
flex: 0 0 auto;
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
justify-content: flex-end;
|
|
align-items: center;
|
|
gap: 0.4rem;
|
|
}
|
|
|
|
/*
|
|
Barre d'actions de la sélection, EN HAUT de la liste et collante : on coche en descendant,
|
|
et une barre en bas d'écran obligeait soit à redescendre pour valider, soit — pire —
|
|
recouvrait les dernières lignes qu'on cherchait justement à cocher.
|
|
*/
|
|
.barre-selection {
|
|
position: sticky;
|
|
top: 0;
|
|
z-index: 5;
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
margin: 0.75rem 0;
|
|
padding: 0.6rem 0.75rem;
|
|
background: var(--mb-fond-doux);
|
|
border: 1px solid var(--mb-bord);
|
|
border-radius: 6px;
|
|
}
|
|
|
|
.selection-compte {
|
|
font-size: 0.85rem;
|
|
font-weight: 600;
|
|
color: var(--mb-texte-doux);
|
|
}
|
|
|
|
/* Libellé réservé aux lecteurs d'écran : la case à cocher n'a plus de texte visible à côté
|
|
d'elle, mais elle doit continuer à dire ce qu'elle sélectionne. */
|
|
.lecture-ecran {
|
|
position: absolute;
|
|
width: 1px;
|
|
height: 1px;
|
|
padding: 0;
|
|
margin: -1px;
|
|
overflow: hidden;
|
|
clip: rect(0, 0, 0, 0);
|
|
white-space: nowrap;
|
|
border: 0;
|
|
}
|
|
|
|
/* Le corps prend la place restante et « min-width: 0 » l'autorise à rétrécir :
|
|
sans lui, un titre long pousse le bouton hors de l'écran sur mobile. */
|
|
.carte-souhait .carte-corps,
|
|
.carte-oeuvre .carte-corps {
|
|
flex: 1;
|
|
min-width: 0;
|
|
}
|
|
|
|
.carte-souhait .bouton,
|
|
.carte-oeuvre .bouton {
|
|
flex-shrink: 0;
|
|
align-self: center;
|
|
}
|
|
|
|
/* Les deux actions d'une envie, empilées : « Éditer » au-dessus de « Retirer ».
|
|
Elles ne s'étalent pas en largeur — sur un téléphone, la colonne d'actions doit rester
|
|
étroite pour laisser le titre respirer. */
|
|
.souhait-actions {
|
|
flex: 0 0 auto;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-self: center;
|
|
gap: 0.35rem;
|
|
}
|
|
|
|
.souhait-note {
|
|
margin: 0.25rem 0 0;
|
|
color: var(--mb-texte-doux);
|
|
font-size: 0.9rem;
|
|
font-style: italic;
|
|
}
|
|
|
|
/* Le grisage des livres déjà possédés : c'est l'information principale de l'écran
|
|
de bibliographie. L'opacité seule suffirait visuellement, mais elle ne dit rien
|
|
à un lecteur d'écran — d'où l'étiquette « Déjà chez vous » qui l'accompagne
|
|
toujours dans le balisage. */
|
|
.oeuvre-possedee {
|
|
opacity: 0.55;
|
|
}
|
|
|
|
.oeuvre-possedee .carte-titre {
|
|
font-weight: 400;
|
|
}
|
|
|
|
.oeuvre-titre-bouton {
|
|
width: 100%;
|
|
padding: 0;
|
|
border: 0;
|
|
background: transparent;
|
|
text-align: left;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.detail-oeuvre {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
align-items: flex-start;
|
|
gap: 0.5rem;
|
|
margin: 0.25rem 0 0;
|
|
}
|
|
|
|
.etiquette-possede {
|
|
background: #e3ece3;
|
|
color: #2f5d34;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.etiquette-souhaite {
|
|
background: #e6e6f5;
|
|
color: #3b3b7a;
|
|
font-weight: 600;
|
|
}
|
|
|
|
/* --- Mise à jour de l'application --- */
|
|
|
|
/* Injecté par js/mise-a-jour.js, hors composants Blazor : ce bandeau doit pouvoir s'afficher
|
|
même si l'application WebAssembly n'a pas démarré. D'où une règle globale et non scopée.
|
|
Il existe parce que les empreintes des assets WASM sont désactivées (CLAUDE.md) : le service
|
|
worker est le seul cache-busting du projet, et sa mise à jour attendrait sinon la fermeture
|
|
de tous les onglets. */
|
|
#mb-maj {
|
|
position: fixed;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
z-index: 1100;
|
|
display: flex;
|
|
gap: 0.75rem;
|
|
align-items: center;
|
|
justify-content: center;
|
|
flex-wrap: wrap;
|
|
padding: 0.7rem 1rem;
|
|
background: #1b3a5c;
|
|
color: #fff;
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
#mb-maj button {
|
|
min-height: 2.5rem;
|
|
padding: 0.4rem 0.9rem;
|
|
border: 0;
|
|
border-radius: 6px;
|
|
background: #f5d76e;
|
|
color: #4a3800;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
}
|
|
|
|
/* Un ISBN se relit chiffre à chiffre : une chasse fixe évite de confondre 1 et l,
|
|
0 et O, et rend les tranches séparées par des tirets alignées d'une ligne à l'autre. */
|
|
.code-isbn {
|
|
font-family: ui-monospace, "SFMono-Regular", "Menlo", "Consolas", monospace;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
/* Titre d'écran et son bouton de réglages sur la même ligne : le bouton « Filtrer » du
|
|
catalogue appartient au titre, pas au contenu. */
|
|
.entete-page {
|
|
display: flex;
|
|
align-items: baseline;
|
|
justify-content: space-between;
|
|
gap: 0.75rem;
|
|
}
|
|
|
|
.entete-page .titre-page {
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
|
|
/* --- Liste d'envies : ordre choisi --- */
|
|
|
|
/* Colonne de déplacement à gauche de chaque envie. Les flèches sont larges à dessein :
|
|
c'est le seul moyen de réordonner au doigt, le glisser-déposer HTML5 n'existant pas
|
|
sur mobile. */
|
|
.rang-actions {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
gap: 0.15rem;
|
|
flex: 0 0 auto;
|
|
}
|
|
|
|
.rang-actions .bouton {
|
|
padding: 0.15rem 0.5rem;
|
|
line-height: 1.1;
|
|
}
|
|
|
|
.rang-numero {
|
|
font-size: 0.75rem;
|
|
color: var(--mb-texte-doux);
|
|
font-variant-numeric: tabular-nums;
|
|
}
|
|
|
|
/* La carte saisie s'efface pendant le glissé : sans ce repère, on ne sait plus laquelle
|
|
on déplace une fois le curseur loin de son point de départ. */
|
|
.carte-glissee {
|
|
opacity: 0.4;
|
|
}
|
|
|
|
/* Trois manières de chercher, sur une ligne : réutilise l'apparence des segments du
|
|
catalogue plutôt que d'inventer un troisième style de bascule. */
|
|
.onglets-secondaires {
|
|
display: flex;
|
|
gap: 0.25rem;
|
|
margin: 0.75rem 0;
|
|
}
|
|
|
|
.onglets-secondaires .segment {
|
|
flex: 1 1 0;
|
|
}
|
|
|
|
/* --- Sagas, cycles et séries --- */
|
|
|
|
.liste-series,
|
|
.liste-tomes,
|
|
/*
|
|
Arbre des séries : une simple imbrication de listes, indentée par un liseré vertical.
|
|
|
|
⚠️ Volontairement dépouillé — pas de carte, pas de couverture, pas de tome. L'arbre répond
|
|
à « qui contient quoi », et l'écran d'une série répond au reste. La version précédente
|
|
dépliait la liste complète des livres de chaque nœud, ce qui noyait la hiérarchie qu'elle
|
|
était censée montrer.
|
|
*/
|
|
.arbre,
|
|
.arbre-enfants {
|
|
list-style: none;
|
|
margin: 0;
|
|
padding: 0;
|
|
}
|
|
|
|
.arbre-enfants {
|
|
/* Le liseré remplace les traits d'un arbre dessiné : il suffit à rattacher visuellement
|
|
une descendance à son parent, sans pseudo-éléments à positionner. */
|
|
margin-left: 0.7rem;
|
|
padding-left: 0.8rem;
|
|
border-left: 2px solid var(--mb-bord);
|
|
}
|
|
|
|
.arbre-ligne {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
min-height: 2.25rem;
|
|
padding: 0.2rem 0;
|
|
}
|
|
|
|
.arbre-plier,
|
|
.arbre-puce {
|
|
flex: 0 0 1.5rem;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
height: 1.5rem;
|
|
padding: 0;
|
|
font-size: 0.9rem;
|
|
color: var(--mb-texte-doux);
|
|
background: transparent;
|
|
border: 0;
|
|
}
|
|
|
|
.arbre-plier {
|
|
cursor: pointer;
|
|
}
|
|
|
|
.arbre-titre {
|
|
flex: 1 1 auto;
|
|
min-width: 0;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
font-weight: 600;
|
|
color: #1d2129;
|
|
text-decoration: none;
|
|
}
|
|
|
|
.arbre-titre:hover,
|
|
.arbre-titre:focus-visible {
|
|
text-decoration: underline;
|
|
}
|
|
|
|
.arbre-compte {
|
|
flex: 0 0 auto;
|
|
font-size: 0.8rem;
|
|
color: var(--mb-texte-doux);
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.arbre-actions {
|
|
flex: 0 0 auto;
|
|
display: flex;
|
|
gap: 0.25rem;
|
|
}
|
|
|
|
.liste-sous-series {
|
|
list-style: none;
|
|
margin: 0.5rem 0;
|
|
padding: 0;
|
|
}
|
|
|
|
.carte-serie {
|
|
padding: 0.7rem 0;
|
|
border-bottom: 1px solid var(--mb-bord);
|
|
}
|
|
|
|
/* Ligne entièrement cliquable (lot V) : le curseur doit le dire, sans quoi seul le titre a
|
|
l'air de mener quelque part. Le survol marque la ligne visée, utile à la souris comme au
|
|
clavier lorsque le lien de titre prend le focus. */
|
|
.carte-cliquable {
|
|
cursor: pointer;
|
|
}
|
|
|
|
.carte-cliquable:hover,
|
|
.carte-cliquable:focus-within {
|
|
background: var(--mb-fond-doux);
|
|
}
|
|
|
|
.liste-sous-series {
|
|
margin: 0.35rem 0 0 1rem;
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.liste-sous-series li {
|
|
padding: 0.2rem 0;
|
|
display: flex;
|
|
gap: 0.5rem;
|
|
align-items: baseline;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.carte-tome {
|
|
display: flex;
|
|
gap: 0.6rem;
|
|
align-items: flex-start;
|
|
padding: 0.6rem 0;
|
|
border-bottom: 1px solid var(--mb-bord);
|
|
}
|
|
|
|
/* Le tome absent est estompé, comme une œuvre déjà possédée l'est dans la bibliographie —
|
|
mais l'opacité ne dit rien à un lecteur d'écran, d'où l'étiquette qui l'accompagne. */
|
|
.tome-manquant .carte-titre {
|
|
font-weight: 400;
|
|
color: var(--mb-texte-doux);
|
|
}
|
|
|
|
.etiquette-manquant {
|
|
background: #f3e2e2;
|
|
color: #7a2f2f;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.actions-tome {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 0.4rem;
|
|
margin-top: 0.35rem;
|
|
}
|
|
|
|
/* Rattacher un livre à une place : la recherche s'ouvre SOUS le tome concerné, pour qu'on voie
|
|
ce qu'on est en train de compléter. */
|
|
.recherche-livre {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.3rem;
|
|
margin-top: 0.5rem;
|
|
padding: 0.5rem;
|
|
background: var(--mb-fond-doux);
|
|
border-radius: 6px;
|
|
}
|
|
|
|
.recherche-livre .bouton {
|
|
text-align: left;
|
|
}
|
|
|
|
.fiche-serie {
|
|
margin: 0.2rem 0;
|
|
font-size: 0.9rem;
|
|
color: var(--mb-texte-doux);
|
|
}
|
|
|
|
/* --- Revues et numéros --- */
|
|
|
|
.liste-numeros {
|
|
list-style: none;
|
|
margin: 0.5rem 0;
|
|
padding: 0;
|
|
}
|
|
|
|
.carte-numero {
|
|
display: flex;
|
|
gap: 0.6rem;
|
|
align-items: flex-start;
|
|
justify-content: space-between;
|
|
padding: 0.6rem 0;
|
|
border-bottom: 1px solid var(--mb-bord);
|
|
}
|
|
|
|
/* Numéro, date et note tiennent sur une ligne dès qu'il y a la place, et se replient sinon :
|
|
la saisie d'un numéro est répétitive, on ne veut pas la faire défiler. */
|
|
.champs-numero {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 0.75rem;
|
|
}
|
|
|
|
.champs-numero .champ {
|
|
flex: 1 1 8rem;
|
|
}
|
|
|
|
/* --- Rôles des auteurs (scénario, dessin, couleurs) --- */
|
|
|
|
.role-auteur {
|
|
color: var(--mb-texte-doux);
|
|
font-size: 0.85em;
|
|
}
|
|
|
|
/* Un nom, sa rangée de rôles : la ligne se replie sur les écrans étroits plutôt que de
|
|
comprimer les boutons, qui doivent rester atteignables au pouce. */
|
|
.ligne-role {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
align-items: center;
|
|
gap: 0.4rem 0.75rem;
|
|
margin-top: 0.4rem;
|
|
}
|
|
|
|
.nom-role {
|
|
font-weight: 600;
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
/* ------------------------------------------------------------------
|
|
Menu principal (bandeau)
|
|
|
|
⚠️ Ces règles sont ICI, en feuille GLOBALE, et non dans
|
|
`Layout/MainLayout.razor.css`. Deux raisons :
|
|
|
|
1. `NavLink` est un composant : l'isolation CSS ne pose son attribut de
|
|
portée que sur le balisage du composant courant, jamais sur ce que rend
|
|
un composant enfant. Déclarés en feuille scopée, `.menu-lien` n'atteignait
|
|
AUCUN des six liens — le `<nav>` prenait son fond sombre, les liens
|
|
restaient en bleu souligné par défaut.
|
|
2. La navigation ne doit dépendre ni du bundle d'isolation ni de son cache.
|
|
|
|
Le repli sans style reste une suite de liens lisibles les uns sous les
|
|
autres : c'est tout l'intérêt du gabarit retenu le 2026-08-20.
|
|
------------------------------------------------------------------ */
|
|
|
|
/*
|
|
Bandeau et menu collent ENSEMBLE en haut de l'écran (lot R) : sur PC, la rangée des six
|
|
destinations défilait avec la page et devenait inatteignable dès qu'on descendait dans une
|
|
longue liste.
|
|
|
|
⚠️ Un seul conteneur collant, et non deux éléments collants superposés : ceux-ci auraient
|
|
obligé à écrire en dur la hauteur du bandeau dans le `top` du menu, hauteur qui varie avec
|
|
la pastille hors-ligne et le nom d'utilisateur.
|
|
*/
|
|
.entete {
|
|
position: sticky;
|
|
top: 0;
|
|
z-index: 10;
|
|
}
|
|
|
|
.menu {
|
|
display: none;
|
|
flex-direction: column;
|
|
background: #16304c;
|
|
border-bottom: 1px solid rgba(255, 255, 255, 0.15);
|
|
}
|
|
|
|
/*
|
|
Téléphone : le menu déployé occupe l'écran ENTIER.
|
|
|
|
Déployé sous le bandeau, il partageait l'écran avec la liste qu'on quittait, et l'on
|
|
choisissait sa destination au milieu d'autre chose. En plein écran, les six destinations se
|
|
lisent d'un coup d'œil.
|
|
|
|
⚠️ Le calque garde une porte de sortie explicite — la croix ci-dessous, Échap, et le clic
|
|
hors des liens (posés dans MainLayout.razor), en plus de la fermeture sur LocationChanged.
|
|
Sans elles, un plein écran est un piège.
|
|
*/
|
|
.menu-ouvert {
|
|
display: flex;
|
|
position: fixed;
|
|
inset: 0;
|
|
z-index: 50;
|
|
overflow-y: auto;
|
|
padding-bottom: 2rem;
|
|
/* Le calque prend le focus à l'ouverture : sans cela Échap ne lui parviendrait pas. Le
|
|
contour du navigateur n'apprend rien ici, la croix étant visible. */
|
|
outline: none;
|
|
}
|
|
|
|
.menu-entete {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 0.5rem;
|
|
padding: 0.6rem 0.75rem;
|
|
border-bottom: 1px solid rgba(255, 255, 255, 0.15);
|
|
}
|
|
|
|
.menu-titre {
|
|
color: #e8edf3;
|
|
font-size: 0.85rem;
|
|
font-weight: 600;
|
|
letter-spacing: 0.04em;
|
|
text-transform: uppercase;
|
|
}
|
|
|
|
/* Cible tactile de 2.25rem, comme les boutons du bandeau : on la touche au pouce. */
|
|
.menu-fermer {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 2.25rem;
|
|
height: 2.25rem;
|
|
padding: 0;
|
|
font-size: 1.15rem;
|
|
line-height: 1;
|
|
color: #fff;
|
|
background: transparent;
|
|
border: 1px solid rgba(255, 255, 255, 0.35);
|
|
border-radius: 6px;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.menu-fermer:hover,
|
|
.menu-fermer:focus-visible {
|
|
background: rgba(255, 255, 255, 0.15);
|
|
}
|
|
|
|
.menu-lien {
|
|
display: block;
|
|
padding: 0.7rem 1rem;
|
|
color: #e8edf3;
|
|
text-decoration: none;
|
|
font-size: 0.95rem;
|
|
font-weight: 500;
|
|
border-left: 4px solid transparent;
|
|
}
|
|
|
|
.menu-lien:hover,
|
|
.menu-lien:focus-visible {
|
|
background: rgba(255, 255, 255, 0.1);
|
|
color: #fff;
|
|
text-decoration: none;
|
|
}
|
|
|
|
/* NavLink pose « active » tout seul ; « Catalogue » utilise NavLinkMatch.All,
|
|
sans quoi il resterait allumé sur toutes les pages — son href est la racine. */
|
|
.menu-lien.active {
|
|
color: #fff;
|
|
font-weight: 600;
|
|
background: rgba(255, 255, 255, 0.16);
|
|
border-left-color: #f5d76e;
|
|
}
|
|
|
|
/*
|
|
À partir de 40rem la rangée tient sans se comprimer, et la bascule n'a plus
|
|
de raison d'être. Le point de rupture est répété dans `MainLayout.razor.css`
|
|
(qui y masque la bascule) — les deux doivent rester identiques.
|
|
*/
|
|
@media (min-width: 40rem) {
|
|
.menu {
|
|
display: flex;
|
|
flex-direction: row;
|
|
justify-content: center;
|
|
flex-wrap: wrap;
|
|
gap: 0.25rem;
|
|
padding: 0 0.5rem;
|
|
}
|
|
|
|
/*
|
|
⚠️ Le plein écran est ANNULÉ ici : sans cela, un menu ouvert au doigt puis un
|
|
agrandissement de la fenêtre laisserait un calque plein écran sur un PC, sans bascule
|
|
pour le refermer (elle est masquée à partir de cette largeur).
|
|
*/
|
|
.menu-ouvert {
|
|
position: static;
|
|
overflow-y: visible;
|
|
padding-bottom: 0;
|
|
}
|
|
|
|
.menu-entete {
|
|
display: none;
|
|
}
|
|
|
|
.menu-lien {
|
|
padding: 0.6rem 0.9rem;
|
|
border-left: 0;
|
|
border-bottom: 3px solid transparent;
|
|
}
|
|
|
|
.menu-lien.active {
|
|
background: rgba(255, 255, 255, 0.12);
|
|
border-bottom-color: #f5d76e;
|
|
}
|
|
}
|
|
|
|
/* --- Attente (lot L1) -------------------------------------------------
|
|
Un appel lent doit se voir : une recherche BnF coûte ~1 s par page, et
|
|
jusqu'à dix pages pour les nouveautés. Sans indicateur, l'écran se lit
|
|
comme planté et l'on reclique, ce qui relance l'appel.
|
|
------------------------------------------------------------------- */
|
|
|
|
.attente {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
color: var(--mb-texte-doux);
|
|
font-size: 0.9rem;
|
|
margin: 0.75rem 0;
|
|
}
|
|
|
|
.attente-rondelle {
|
|
flex: 0 0 auto;
|
|
width: 1rem;
|
|
height: 1rem;
|
|
border: 2px solid var(--mb-bord);
|
|
border-top-color: var(--mb-accent);
|
|
border-radius: 50%;
|
|
animation: attente-tourne 0.8s linear infinite;
|
|
}
|
|
|
|
@keyframes attente-tourne {
|
|
to { transform: rotate(360deg); }
|
|
}
|
|
|
|
/* Une animation perpétuelle est précisément ce que ce réglage demande d'éviter.
|
|
L'indicateur reste visible — c'est lui qui porte l'information, pas sa rotation. */
|
|
@media (prefers-reduced-motion: reduce) {
|
|
.attente-rondelle {
|
|
animation: none;
|
|
border-top-color: var(--mb-accent);
|
|
border-right-color: var(--mb-accent);
|
|
}
|
|
}
|
|
|
|
/* --- Articles à la une d'un numéro (lot O2) ---------------------------
|
|
Une couleur à part, parce que ce n'est pas un thème : un titre d'article
|
|
est unique à sa parution et ne se réutilise jamais, là où un thème est un
|
|
vocabulaire commun au catalogue.
|
|
------------------------------------------------------------------- */
|
|
|
|
.etiquette-une {
|
|
background: #eef2f7;
|
|
color: #24405e;
|
|
font-style: italic;
|
|
}
|
|
|
|
/* --- Créer depuis une liste : champ + bouton sur une ligne (lot M1) ----
|
|
⚠️ min-width: 0 sur le champ est ce qui fait tenir la ligne à 320 px : sans
|
|
lui, un élément de formulaire refuse de rétrécir sous sa largeur intrinsèque
|
|
et pousse le bouton hors de l'écran. Le bouton, lui, ne rétrécit pas et son
|
|
libellé ne se coupe pas — un bouton tronqué ne se lit plus.
|
|
------------------------------------------------------------------- */
|
|
|
|
.ligne-creation {
|
|
display: flex;
|
|
gap: 0.5rem;
|
|
align-items: stretch;
|
|
margin-bottom: 0.75rem;
|
|
}
|
|
|
|
.ligne-creation .champ-saisie {
|
|
flex: 1 1 auto;
|
|
min-width: 0;
|
|
}
|
|
|
|
.ligne-creation .bouton {
|
|
flex: 0 0 auto;
|
|
white-space: nowrap;
|
|
}
|