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; } /* --- Livres d'une même série, rassemblés au catalogue (2026-08-22) --- Le bloc se pose là où son premier tome serait tombé dans l'ordre du catalogue : il ne déplace donc rien, il rassemble. Le trait à gauche est ce qui fait lire les cartes comme « dedans » plutôt que comme la suite de la liste. */ .groupe-serie { margin: 0.5rem 0; border-left: 3px solid var(--mb-accent); padding-left: 0.6rem; } /* Le nom mène à la série : c'est le seul endroit du catalogue qui montre aussi ce qui manque. */ .groupe-serie-titre { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.4rem; padding: 0.35rem 0; color: var(--mb-texte); text-decoration: none; font-weight: 600; } .groupe-serie-titre:hover .groupe-serie-nom, .groupe-serie-titre:focus-visible .groupe-serie-nom { text-decoration: underline; } .groupe-serie-nom { min-width: 0; overflow-wrap: anywhere; } .groupe-serie-compte { color: var(--mb-texte-doux); font-size: 0.85rem; font-weight: 400; } /* Une case à cocher parmi des rangées de segments : elle doit se lire comme un réglage, pas comme un filtre de plus. */ .bascule { display: flex; align-items: center; gap: 0.5rem; margin: 0.25rem 0; font-size: 0.9rem; } .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 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. */ /* ⚠️ PAS de « white-space: nowrap » ici, et ce n'est pas un oubli. Un ISBN à tirets mesure 147 px en monospace, là où la colonne des valeurs de la fiche n'en offre que 111 à 375 px et 56 à 320 px : « nowrap » le faisait déborder, et avec lui TOUTE la page — 395 px de contenu pour un écran de 320, mesuré le 2026-08-21. Il annulait au passage le « word-break » dont « .fiche-champs dd » est justement muni pour cela. Le défaut de CSS est exactement ce qu'on veut : le code tient sur une seule ligne quand il y a la place, et ne se coupe qu'à un tiret quand il n'y en a pas — le tiret étant le seul endroit où une coupure se relit. */ .code-isbn { font-family: ui-monospace, "SFMono-Regular", "Menlo", "Consolas", monospace; } /* 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; /* ⚠️ Même piège qu'au lot M : la ligne est elle-même un élément de flex (son
  • l'est), et « min-width: auto » lui interdit de descendre sous sa largeur intrinsèque. Sans cette ligne, un titre de sous-série indenté pousse le compte « 0 sur 2 tomes » hors de l'écran et fait défiler la page horizontalement (mesuré à 320 px : 328 px de large). */ min-width: 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; } /* ------------------------------------------------------------------ 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 `