/* Book view (/c/, quran/templates/quran/mushaf.html + mushaf.js) - a
   fixed-height frame showing one Ayah.page_number-grouped page at a
   time (mushaf.js swaps its content), turned via edge-mounted prev/next
   buttons - reading the text is the point, controls stay out of the way
   (page-turn corners on the frame itself, a small icon menu in the
   corner instead of a always-visible toolbar).

   Own file (not input.css/tailwind.css), loaded only from mushaf.html's
   own extra_head - same "plain static CSS, page-specific" pattern
   karaoke.css/player.css already follow, rather than growing the shared
   stylesheet with rules only this one page ever uses. Uses the same
   var(--app-*) theme tokens input.css's own :root defines - those load
   first on every page (tailwind.css, base.html <head>), same
   established assumption karaoke.css/player.css already rely on. */
/* Full-immersion layout (base.html's own main_class block, overridden
   here to this instead of the site-wide "max-w-4xl mx-auto px-4 py-6") -
   site header/footer are gone entirely on this page (own blocks,
   overridden empty), so #main-content itself shouldn't keep reserving
   the same centered/padded column they used to justify; the reading
   frame gets the full viewport instead, own padding (.q-mushaf-frame,
   below) providing the only breathing room from here on. */
.q-mushaf-immersive {
    max-width: none;
    width: 100%;
    margin: 0;
    padding: 0;
}
/* tailwind.css reserves body { padding-bottom: 60px + safe-area } on
   mobile (<1024px) site-wide, to keep page content clear of the fixed
   .q-bottombar nav - but that nav is hidden entirely on this page
   (base.html, q_url == 'mushaf'), so the reserved space is now just
   empty scrollable padding under the frame: the page looked like it
   still scrolled vertically after the frame itself became a real
   100dvh/edge-to-edge fit, "scrolling into nothing". No matching nav to
   compensate for here, so the reservation itself is dropped. */
body:has(.q-mushaf-immersive) {
    padding-bottom: 0;
    /* Browser-chrome-level horizontal scrollbar (reported during a
       page-turn slide) - the mushaf.js carousel's own translateX targets
       nested elements (.q-mushaf-body/.q-mushaf-drag-layer, both already
       overflow:hidden, own rules further below), but the *document's*
       own scrollable width is what an actual browser scrollbar reflects,
       not a nested div's - clipping only at the nested level left this
       still reachable if any of that transform math ever overshoots.
       overflow-y explicit (not left to default alongside overflow-x) -
       the CSS Overflow spec forces the *other* axis to auto the moment
       only one of x/y is set to something other than visible, which
       would turn the whole document into its own accidental vertical
       scroll container instead of the normal one (own past incident:
       reader vertical scroll needing 2 gestures to "escape" it, stuck
       unable to scroll back up on a page tall enough to hit that inner
       scrollbar - never repeat overflow-x alone here without pairing it). */
    overflow-x: hidden;
    overflow-y: visible;
    /* Nothing on this page scrolls vertically anymore, mobile included
       (own fixed-height/paginated screens work, mushaf.js) - without
       this, a vertical-leaning touch drag still triggered the browser's
       own elastic rubber-band bounce at the document edge with nothing
       behind it to actually scroll into (reported: "il y a rien mais ça
       le fait"), since nothing here told it there was nothing left to
       scroll. */
    overscroll-behavior-y: none;
}
/* Own report ("header partagé, même template partout, avec des
   exceptions dedans", mushaf.html): le <header> du site (base.html,
   Accueil/Réglages, header_start réactivé là-bas) est repris tel quel -
   mais #marks-scope (juste au-dessus) est un cadre 100dvh edge-to-edge :
   un header en flux normal le pousserait vers le bas et romprait
   l'immersion. Overlay fixe/transparent à la place, caché par défaut -
   même mécanique data-q-controls-visible que .q-mushaf-topleft/-topright
   (mushaf.js, hover desktop/tap-menu mobile), posée ici sur <html> plutôt
   que .q-mushaf-frame seul (mushaf.js: l'attribut est maintenant posé sur
   les deux, ce header vit hors du cadre). */
body:has(.q-mushaf-immersive) > header {
    position: fixed;
    inset-inline: 0;
    top: 0;
    z-index: 40;
    /* Own report ("le background du header doit suivre le theme
       choisi") - var(--app-bg), plus transparent : mushaf.js pose
       maintenant .q-surah-reading-frame + les mêmes data-q-mushaf-theme/
       -mode sur <html> (own report là-bas), --app-bg (popover.css)
       cascade donc jusqu'ici. */
    background: var(--app-bg);
    /* Own report ("la luminosité aussi") - même combo que .q-mushaf-
       scroll-fade un peu plus bas (déjà ce calcul pour le même
       problème : DOM-order laissait ce genre d'élément passer à côté
       du dim-scrim) : --q-mushaf-dim-amount et -brightness-pct sont
       mutuellement exclusifs (mushaf.js applyMushafBrightness, jamais
       les deux à la fois) donc les multiplier ensemble donne le bon
       résultat dans les deux sens (assombri OU éclairci) en une seule
       règle. Hérité de <html> (own report là-bas) - le header vit hors
       du sous-arbre de frameEl, ne peut pas lire ses styles inline
       directement. */
    filter: brightness(calc((1 - var(--q-mushaf-dim-amount, 0)) * var(--q-mushaf-brightness-pct, 100) / 100));
}
/* Own report ("elargir le header, prendre plus de largeur") - la ligne
   du header (base.html) est bornée à max-w-4xl (56rem) partout par
   défaut, pensée pour les pages au contenu centré classique. #marks-scope
   (own règle plus bas) est lui edge-to-edge (pas de max-width, own
   report "true edge-to-edge") - même largeur ici, plus le padding-inline
   1.5rem qui matche celui du cadre (.q-mushaf-frame, own règle plus bas)
   pour que les icônes tombent visuellement à la même distance du bord
   que le texte du Coran en dessous. */
body:has(.q-mushaf-immersive) > header > div {
    max-width: none;
    /* Own report ("le titre doit etre aligné avec /ar/s/1"): padding
       symétrique (1.5rem des 2 côtés), partagée par les 3 colonnes de la
       grille (1fr auto 1fr) - la retoucher déplace aussi le centrage de
       la colonne titre (own report précédent : essai à 1rem/1.5rem avait
       décalé le titre de ~4px). */
    padding-inline: 1.5rem;
}
/* Own report ("aligner Accueil avec la flèche sans toucher au centrage
   du titre"): marge négative sur le lien lui-même plutôt que sur le
   padding de la ligne - Accueil est déjà le dernier de la ligne côté
   bord (RTL, own comment plus haut sur ce flux naturel), rien d'autre
   ne dépend de sa position pour se caler ; ni le padding de la ligne
   (donc le calcul 1fr/auto/1fr du titre) ni la position des autres
   icônes du groupe (elles restent groupées entre elles à sa gauche) ne
   bougent. -0.5rem = 1.5rem (padding réel) - 1rem ([data-q-mushaf-prev],
   mesuré en direct comme le premier essai). Desktop only (>=1024px) -
   own report ("home mal aligné avec la home page sur mobile") :
   [data-q-mushaf-prev] est display:none sous 1024px (own rule plus
   bas), plus rien à aligner dessus - cette marge négative décalait le
   lien sans raison sur mobile, désaligné vs. le même Accueil sur
   /ar/s et /ar/s/1. */
@media (min-width: 1024px) {
    body:has(.q-mushaf-immersive) > header a[href="/ar/"] {
        margin-inline-start: -0.5rem;
    }
}
/* Own report ("je vois bien le deplacement horizontale"): le vrai
   décalage venait du padding de la ligne (own règle plus haut,
   padding-inline: 1.5rem, forcé sans media query pour matcher le cadre
   de lecture) - base.html lui-même est en px-3 (0.75rem) sous 1024px
   (lg:px-6 seulement à partir de là), donc Accueil tombait 0.75rem plus
   loin du bord ici que sur /ar/, /ar/s, /ar/s/1 en mobile. Même
   technique que la règle desktop ci-dessus (marge négative sur le lien
   seul, jamais le padding de la ligne - le titre resterait décentré
   sinon) mais mobile: -0.75rem = 1.5rem - 0.75rem. */
@media (max-width: 1023.98px) {
    body:has(.q-mushaf-immersive) > header a[href="/ar/"] {
        margin-inline-start: -0.75rem;
    }
}
/* Own report ("les ids doivent etre generiques, meme nommage partout"):
   plus de #q-mushaf-header-right-icons/-left-icons (id page-spécifique
   redondant, mushaf.html) - #q-header-col-start/-col-end (base.html,
   mêmes ids que /ar/s et /ar/s/1) ciblés directement. Own report ("le
   titre doit rester toujours affiché comme avant, seules les icones
   suivent le hover") - opacity posée sur ces 2 colonnes seulement, pas
   sur le <header> entier : la colonne centrale (header_title, base.html)
   n'est jamais ciblée, reste donc toujours opacity:1 - même comportement
   que l'ancien .q-mushaf-title "toujours visible", juste repositionné.
   Accueil (#q-header-col-start) suit maintenant le même hover que les
   icônes Notes/Sourates/Aller à la page/Signets à côté de lui - même
   groupe, même colonne, own report ("a la place du reglage..."). */
body:has(.q-mushaf-immersive) > header #q-header-col-start,
body:has(.q-mushaf-immersive) > header #q-header-col-end {
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.15s ease;
}
html[data-q-controls-visible] body:has(.q-mushaf-immersive) > header #q-header-col-start,
html[data-q-controls-visible] body:has(.q-mushaf-immersive) > header #q-header-col-end,
body:has(.q-mushaf-immersive) > header #q-header-col-start:focus-within,
body:has(.q-mushaf-immersive) > header #q-header-col-end:focus-within {
    opacity: 1;
    pointer-events: auto;
}
/* Own report ("il faut respecter l'ordre comme c'etait"): Partager/
   Thèmes/Lecture/Signet (ex-.q-mushaf-topright) forçaient déjà
   direction:ltr sur leur conteneur pour garder cet ordre visuel exact
   quelle que soit la langue - #q-header-col-end (base.html, partagé
   avec les autres pages) reprend ce même forçage, mais scopé à /ar/c
   seulement (body:has(.q-mushaf-immersive)) pour ne pas affecter les
   autres pages qui utilisent aussi cet id. #q-header-col-start (Accueil +
   Notes/Sourates/Aller à la page/Signets, ex-.q-mushaf-topleft) n'avait
   lui aucun forçage - reste sur le flux RTL naturel, déjà correct par
   défaut, comme sur toutes les autres pages. */
body:has(.q-mushaf-immersive) > header #q-header-col-end {
    direction: ltr;
}
/* Own report ("sur mobile ça affiche les icônes du Desktop au lieu du
   menu, juste l'icône home"): le hover ci-dessus (opacity/pointer-events)
   ne masque rien sur tactile - un 1er tap sur le <header> déclenche le
   mouseenter synthétique iOS/Android (élément avec listener hover mais
   pas de vrai :hover), qui affichait tout le groupe desktop au lieu du
   menu tap habituel (Sourates/Notes/Aller à la page/Signets/Partager/
   Thèmes/Lecture/Langue/Signet - déjà tous couverts par
   [data-q-mushaf-mobile-menu], own comment plus bas). Accueil reste seul
   visible en permanence (pas de popover, pas de doublon dans le menu
   tap) - même sort que .q-mushaf-topleft/-topright avant lui (own
   comment plus bas, display:none mobile). */
@media (max-width: 1023.98px) {
    body:has(.q-mushaf-immersive) > header #q-header-col-start {
        opacity: 1;
        pointer-events: auto;
    }
    body:has(.q-mushaf-immersive) > header #q-header-col-start button,
    body:has(.q-mushaf-immersive) > header #q-header-col-end {
        display: none;
    }
}
/* .q-mushaf's own max-width: 60rem (below, desktop) still capped the
   reading column even with #main-content's own constraint gone above -
   same override, one level deeper. */
.q-mushaf-immersive .q-mushaf {
    max-width: none;
}
/* The frame itself still looked like a card floating on the page
   (rounded corners/border/shadow, 80-95vh leaving a visible strip of
   page background top/bottom) even once #main-content/.q-mushaf stopped
   constraining its width - defeats the actual point of dropping the
   header/footer. True edge-to-edge here: no border/radius/shadow left
   to read as "a box sitting on a page", full 100vh so nothing of the
   site shows around it at all. #marks-scope (.q-mushaf-frame's own id),
   not just the class - several later theme/dark-mode rules below
   restyle border/box-shadow on ".q-mushaf-frame" combined with just one
   other class/attribute each, tying this rule's own specificity and
   then winning on source order alone; anchoring on the id here beats
   all of those regardless of where they sit in the file. */
.q-mushaf-immersive #marks-scope {
    border: 0;
    border-radius: 0;
    box-shadow: none;
    /* height (not min-height) + overflow:hidden, mobile and desktop
       alike now - a page too tall for one screen paginates internally
       (mushaf.js goToMushafScreen) instead of growing this box past the
       viewport, own ID specificity otherwise silently overriding
       .q-mushaf-frame's plain-class height further below. 100dvh, not
       100vh - the static unit sizes against the browser chrome's
       largest possible state (address bar collapsed), taller than the
       actually-visible area whenever it's showing on mobile - the exact
       shape of the "petit scroll vertical" reported here. */
    height: calc(100dvh - 3.625rem);
    margin-top: 3.625rem;
    overflow: hidden;
}
@media (min-width: 1024px) {
    .q-mushaf-immersive #marks-scope {
        height: calc(100dvh - 4.375rem);
        margin-top: 4.375rem;
    }
}
/* Verse-number badge, copied from karaoke.css's own .q-verse-number
   rule (that file no longer loads here - see mushaf.html's extra_head
   comment) - same small-font/faded/baseline-shifted look every other
   verse-number badge on the site uses. */
.q-verse-number {
    font-size: 0.78em;
    opacity: 0.65;
    position: relative;
    top: -0.1641em;
    margin-inline-start: 0.2em;
    margin-inline-end: 0.2em;
}
.q-mushaf {
    position: relative; /* anchors .q-mushaf-jump-popover below */
    margin: 0 auto;
    /* mushaf.js scrolls here (__qScrollIntoView, block:'start') on every
       page turn - without this, the frame's own top edge lands flush
       against the viewport's top edge, no breathing room above the
       title/topright menu. No sticky header on this site to clear, just
       cosmetic space. */
    scroll-margin-top: 1.5rem;
}
/* Fade mask at .q-mushaf-body's own top inner edge (mushaf.html) - that
   box's overflow:hidden (own comment further below) is what actually
   clips [data-q-mushaf-slot]/.q-mushaf-drag-layer during a page turn, so
   a screen too tall or a drag mid-flight shows its first row sliced
   clean by that edge instead of fading out. This gradient (--app-bg, the
   same themed paper colour as the reading surface - re-declared per-
   theme on .q-mushaf-frame further below, same trick as
   --app-surface/--app-text there) softens it. Positioned against
   .q-mushaf-body itself (own position:relative further below, already
   there for .q-mushaf-drag-layer) so it's clipped by the same
   overflow:hidden box rather than needing its own. pointer-events:none
   so it never blocks the edge buttons/slot under it; last in source
   order (mushaf.html) is what puts it visually above the slot/drag-layer
   siblings, no z-index needed on top of that. */
.q-mushaf-scroll-fade {
    position: absolute;
    top: 0;
    inset-inline: 0;
    height: 1.35rem;
    /* .q-mushaf-dim-scrim ("Intensité" below 100%, mushaf.js) never
       actually darkens this gradient - same DOM-order stacking that
       deliberately keeps topleft/topright/edge buttons above the scrim
       (own comment there) applies to this element too, purely by
       accident (own report: read wrong/mismatched against the dimmed
       page around it once lowered, in every theme). --q-mushaf-dim-
       amount (mushaf.js applyMushafBrightness) mirrors the scrim's own
       opacity value, applied here directly instead of fighting that
       stacking order. */
    filter: brightness(calc(1 - var(--q-mushaf-dim-amount, 0)));
    /* Full opacity right at the clip edge (0%) - a translucent edge
       still let the sliced sliver show through, same hard line just
       dimmed. 3 stops, not 2 - a plain 2-stop fade read as its own
       visible band (linear opacity drop-off isn't how the eye perceives
       fade), the extra 55% mid-stop eases it out instead. */
    background: linear-gradient(to bottom,
        var(--app-bg) 0%,
        color-mix(in srgb, var(--app-bg) 45%, transparent) 55%,
        transparent 100%);
    pointer-events: none;
}
.q-mushaf-scroll-fade-bottom {
    top: auto;
    bottom: 0;
    background: linear-gradient(to top,
        var(--app-bg) 0%,
        color-mix(in srgb, var(--app-bg) 45%, transparent) 55%,
        transparent 100%);
}
/* Desktop only for now (1024px, same lg breakpoint the rest of the site
   uses) - mobile/tablet sizing still undecided, left at its plain
   full-width default rather than guessing a value here too. Wider than
   the single-column width used to be: room for the two-column spread
   below (.q-mushaf-frame's own column-count), not just one block of
   text. */
@media (min-width: 1024px) {
    .q-mushaf {
        max-width: 60rem;
    }
}
.q-mushaf-topright {
    /* Real grid child of .q-mushaf-header now (own rule further below),
       not an absolute offset guessing where the frame's padding put the
       title. grid-row explicit too - the 3 items' auto-placement
       cursor didn't keep them on one shared implicit row on its own
       (confirmed empirically: title landed on row 1, this + topleft
       both landed on row 2 instead), so all 3 pin grid-row:1 outright. */
    grid-column: 3;
    grid-row: 1;
    justify-self: end;
    direction: ltr;
    display: flex;
    justify-content: flex-end;
    gap: 0.125rem;
}
/* Opacity lives on each button, not the row itself (unlike
   .q-mushaf-topleft below) - a group-level opacity:0 would flatten
   every child to invisible regardless of its own opacity, no way for
   the bookmarked-page override right after this to ever win. */
.q-mushaf-topright .q-mushaf-icon-btn {
    opacity: 0;
    transition: opacity 0.15s ease;
}
/* data-q-controls-visible, not :hover - mushaf.js toggles this on
   mousemove/mouseenter and clears it after a short idle timeout even
   while still hovering the frame (plain :hover can't express "idle",
   only "pointer is/isn't over the element"). :focus-visible (per
   button, not :focus-within on the row) is a separate, permanent
   trigger for reaching these by keyboard - deliberately *not*
   :focus-within: a plain mouse click also focuses a <button> in most
   browsers, and :focus-within doesn't care how focus got there or stay
   in sync with the mouse idle timer above it - clicking Signet then
   leaving the mouse alone kept the whole row visible forever, since
   the button never lost focus on its own. :focus-visible's own
   pointer-vs-keyboard heuristic (browser-native) skips that: a mouse
   click generally doesn't trigger it, only real keyboard navigation
   does. */
/* :not(:disabled) - own report: "les q-mushaf-topleft et right doivent
   avoir l'effet disable" - this reveal rule's own specificity (attr +
   3 classes) otherwise always beat .q-mushaf-icon-btn:disabled's own
   opacity:0.5 (class + pseudo-class) further below, so a disabled
   button still snapped back to fully opaque the instant the row
   revealed - exactly when a reader would actually notice it's
   unusable. .q-mushaf-topleft doesn't need the same exclusion - its
   own reveal opacity lives on the row (further below), not stacked on
   the same property of the same button element. */
.q-mushaf-frame[data-q-controls-visible] .q-mushaf-topright .q-mushaf-icon-btn:not(:disabled),
.q-mushaf-topright .q-mushaf-icon-btn:focus-visible:not(:disabled) {
    opacity: 1;
}
/* Signet (bookmark) button stays visible even with the mouse nowhere
   near the frame, once the *current* page is actually bookmarked - a
   real book's own bookmark ribbon still pokes out whether or not
   you're looking at it, this is the same idea. mushaf.js keeps
   aria-pressed in sync with the page (quran-store.js's own
   __qSyncLibraryButtons, called on every turn). */
.q-mushaf-topright .q-lib-toggle-btn[aria-pressed="true"] {
    opacity: 1;
}
/* Own report: "quand un sous menu est ouvert il faut mettre son etat en
   actif" - aucun des boutons Sourates/Signets/Notes/Partager/Thème
   (tous .q-mushaf-icon-btn) ne changeait d'apparence une fois son propre
   popover ouvert, contrairement à data-q-toggle="plus"/#q-surah-duration-
   toggle ailleurs sur le site (même "c'est celui-ci l'actif" - own
   comment popover.css). Même reste-visible que le Signet juste au-dessus
   - sinon l'indicateur actif resterait invisible (opacity:0) dès que la
   souris quitte le cadre alors que le popover, lui, reste ouvert.
   color-mix(...var(--app-link)...), pas --app-link-soft (tailwind.css) -
   ce dernier n'est jamais redéfini par thème de lecture ici, contrairement
   à --app-link (même recette que .q-word-noted plus bas dans ce fichier). */
.q-mushaf-topleft .q-mushaf-icon-btn[aria-expanded="true"]:not(:disabled),
.q-mushaf-topright .q-mushaf-icon-btn[aria-expanded="true"]:not(:disabled) {
    opacity: 1;
    background: color-mix(in srgb, var(--app-link) 16%, transparent);
    border-color: var(--app-link);
    color: var(--app-link);
    font-weight: 600;
}
/* Desktop-only, always - own report: never show this corner ribbon on
   mobile, bookmarked or not. Touch has no hover to begin with (own
   comment on .q-mushaf-topleft/-topright's opacity:0 default further
   below), so without this the rule right above was the one path that
   still poked it into view there once a page got bookmarked - the
   mobile tap menu's own 2nd .q-lib-toggle-btn (mushaf.html) is the only
   bookmark control meant to ever show up on a phone/tablet.
   .q-mushaf-icon-btn[data-q...], not the bare attribute selector - same
   0,1,0 specificity as .q-mushaf-icon-btn's own `display: inline-flex`
   base rule (further below, own report: this element carries that class
   too) meant a tie decided by source order alone, and that rule sits
   *after* this one in the file, winning regardless of the media query.
   The combined selector's extra class bumps this to 0,2,0, unambiguous
   regardless of where either rule sits. */
@media (max-width: 1023px) {
    .q-mushaf-icon-btn[data-q-mushaf-bookmark-btn] {
        display: none;
    }
}
/* Mirror of .q-mushaf-topright above, physical left - no direction:ltr
   needed here the way that one has it (that was for its 2 buttons' own
   flex order/right position, both direction-sensitive; this is a single
   button and `left` isn't direction-dependent to begin with). */
.q-mushaf-topleft {
    /* Real grid child of .q-mushaf-header now (own rule further below) -
       grid-row explicit too, see .q-mushaf-topright's own comment. */
    grid-column: 1;
    grid-row: 1;
    justify-self: start;
    opacity: 0;
    transition: opacity 0.15s ease;
}
/* .q-mushaf-header forces direction:ltr on itself (own rule) so its 3
   grid columns stay pinned to their physical side regardless of the
   current UI language's own direction - but that same forced ltr also
   inherits down into this row's own 3 buttons, which are meant to
   follow whatever the *real* page direction is (own original comment:
   "lays out per the page's own RTL flow"). Restored here, scoped to
   only when the real page is actually RTL (html[dir=rtl], base.html). */
html[dir="rtl"] .q-mushaf-topleft {
    direction: rtl;
}
/* The logo link ("Accueil") lives in .q-mushaf-topleft, always column 1
   (physical left) by the header's own forced direction:ltr above -
   fine for a French/LTR visitor, backwards for the Arabic/RTL default
   (root_redirect sends every visitor to /ar/ first): the logo and its
   own menu cluster (Notes/Sourates/Signets) should sit on the *reading*
   side, physical right in RTL, mirroring .q-mushaf-topright (Partager/
   Thèmes/Jump/Bookmark) to the left instead. Swapping only the macro
   grid-column (a first attempt) wasn't enough on its own: each row's
   own internal alignment was tuned for its *original* side, so moving
   the box alone left its buttons hugging the wrong edge (the one now
   facing the title, not the frame's own outer corner) - direction/
   justify-content need to flip right along with grid-column below. */
html[dir="rtl"] .q-mushaf-topleft {
    grid-column: 3;
    justify-self: end;
    /* ltr, not this file's own rtl override 2 rules up - Accueil (last
       DOM child, own comment further up) needs to land at *this* box's
       own outer edge, its right edge now that the box itself moved to
       the header's right side; direction:ltr puts the last DOM child
       rightmost within its own box, the same "outermost" role just
       carried over to the new side instead of lost. */
    direction: ltr;
}
html[dir="rtl"] .q-mushaf-topright {
    grid-column: 1;
    justify-self: start;
    /* flex-start, not this row's own flex-end (direction stays ltr,
       unchanged - the 5 buttons keep the exact same relative order
       among themselves) - the whole cluster needs to hug this box's
       *left* edge now that the box moved to the header's left side,
       instead of floating in against the title on its old right edge. */
    justify-content: flex-start;
}
/* :has(:focus-visible), not :focus-within (same reasoning as
   .q-mushaf-topright above) - a mouse click also focuses a <button> in
   most browsers, and :focus-within doesn't distinguish that from real
   keyboard navigation, so it kept this row visible indefinitely after
   any click here regardless of the mouse idle timer. */
.q-mushaf-frame[data-q-controls-visible] .q-mushaf-topleft,
.q-mushaf-topleft:has(:focus-visible),
/* Own report: "quand un sous menu est ouvert il faut mettre son etat en
   actif" - contrairement à .q-mushaf-topright (opacity par bouton, own
   comment plus haut), l'opacité ici vit sur la ligne entière : un enfant
   ne peut jamais se rendre visible tout seul si son parent reste à
   opacity:0. Même :has() que le focus-visible juste au-dessus. */
.q-mushaf-topleft:has([aria-expanded="true"]) {
    opacity: 1;
}
.q-mushaf-icon-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    /* Was 2rem, sized for the 18-20px icons that used to be in here -
       bumped along with the icon size increase (mushaf.html, 18/20px to
       22px) to keep the same ~7px margin around the glyph; left at the
       old 2rem this box would only clear ~5px each side, visibly
       shrinking the hover rectangle below (own comment). */
    width: 2.25rem;
    height: 2.25rem;
    /* Rectangular, not circular (999px) - matches .q-mushaf-edge-btn's
       own 8px (mushaf.css, page-turn corner buttons), same shape for
       every icon control on this page. */
    border-radius: 8px;
    color: var(--app-sub);
}
/* Own report: same disabled treatment as every other control here -
   #q-mushaf-script-picker's own hx-disabled-elt (mushaf.html) disables
   .q-mushaf-topleft/-topright's buttons too while its own ajax call is
   in flight (their popovers - Sourates/Notes/Partager/Signets... -
   would otherwise open mid-request showing stale script/marks state). */
.q-mushaf-icon-btn:disabled {
    opacity: 0.5;
    pointer-events: none;
}
.q-mushaf-icon-btn:hover:not(:disabled) {
    /* color-mix against --app-text (not var(--app-bg)) - every one of
       the 6 Mushaf themes sets --app-bg and --app-surface to the exact
       same colour (mushaf.css's own [data-q-mushaf-theme="…"] blocks),
       so a hover background of var(--app-bg) rendered no visible
       change at all against this surface, in any of them. Mixing in
       the *text* colour instead always reads as a highlight regardless
       of theme - darkens under dark text, lightens under light text,
       automatically the right direction either way. Was 8% - read as
       having disappeared entirely once the icon itself grew and left
       less of this rectangle's own margin visible around it (own
       comment on width/height above); 14% stays visible with that
       smaller margin too. */
    background: color-mix(in srgb, var(--app-text) 14%, transparent);
    color: var(--app-text);
}
.q-mushaf-icon-btn:focus-visible {
    outline: 2px solid var(--app-link);
    outline-offset: 2px;
}
/* Signet (bookmark) toggle, .q-lib-toggle-btn (quran-store.js) reused
   as-is from _ayah_card.html's own favoris button - same icon swap
   rule, just duplicated here rather than loading player.css on a page
   that has no audio (this view's own extra_head only pulls in
   karaoke.css/mushaf.css - see mushaf.html's own comment on that). */
.q-lib-toggle-btn .q-icon-lib-on {
    display: none;
    /* rgb(224, 42, 33), not the brighter rgb(251, 69, 58) first tried -
       darkened to clear WCAG AA's 4.5:1 non-text threshold against both
       pure white and pure black (the "Original" theme's own light/dark
       backgrounds, mushaf.html's theme-grid) - the brighter red only
       cleared ~3.5:1 on white. Not verified against the other 5 Mushaf
       theme presets (Tranquille/Papier/Intense/Calme/Attention) - each
       carries its own background/text pair, a flat fill here can't
       track all of them the way --app-text does. */
    fill: rgb(224, 42, 33);
}
.q-lib-toggle-btn[aria-pressed="true"] .q-icon-lib-off {
    display: none;
}
.q-lib-toggle-btn[aria-pressed="true"] .q-icon-lib-on {
    display: block;
}
/* Opened by the search-style icon above (mushaf.js, plain local show/
   hide - not the site-wide data-q-toggle panel mechanism, nothing here
   needs to survive a reload or sync across triggers). Anchored to
   .q-mushaf-frame's own position:relative (same ancestor as
   .q-mushaf-topright, its own trigger button lives there too), not the
   icon button specifically - simpler than measuring the button's own
   position, and there's only ever one of each on this page. */
.q-mushaf-jump-search-btn {
    display: flex;
    align-items: center;
    flex-shrink: 0;
    border: 0;
    background: transparent;
    padding: 0.5rem 0.625rem;
    color: var(--app-popover-text);
}
.q-mushaf-jump-search-btn:hover,
.q-mushaf-jump-search-btn:focus-visible {
    color: var(--app-link);
}
.q-mushaf-jump-search-btn:focus-visible {
    outline: 2px solid var(--app-link);
    outline-offset: -2px;
}
/* Own report: "Marques et repères" own visible label (_mark_toggles.html)
   dropped in this popover specifically - own aria-labelledby on the
   #mark-toggles fieldset still needs it to exist for its accessible name
   though (CLAUDE.md), so this is the same .sr-only clip technique
   (tailwind.css) scoped to just this popover instead of editing the
   shared partial (still wanted, visible, on every other page that
   includes it). */
#q-mushaf-reading-popover #mark-toggles-label {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border-width: 0;
}
#q-mushaf-reading-popover .q-choice-item:hover,
#q-mushaf-reading-popover .q-choice-item:has(:focus-visible),
#q-mushaf-reading-popover .q-choice-item:focus-visible {
    border-color: transparent;
    background: color-mix(in srgb, var(--app-popover-btn-text) 12%, var(--app-popover-btn-bg));
}
#q-mushaf-reading-popover .q-choice-item:has(:focus-visible),
#q-mushaf-reading-popover .q-choice-item:focus-visible {
    outline: 2px solid var(--app-link);
    outline-offset: 2px;
}
#q-mushaf-reading-popover .q-choice-item:has(:checked),
#q-mushaf-reading-popover .q-choice-item[aria-pressed="true"] {
    background: color-mix(in srgb, var(--app-popover-btn-text) 18%, var(--app-popover-btn-bg));
    color: var(--app-popover-btn-text);
}
/* .q-choice-item:has(:disabled) (tailwind.css) only matches the
   checkbox/label branch - #mark-toggles' as_buttons=True variant here
   (own report) is a real <button disabled>, the disabled thing itself,
   not a wrapper around one. */
#q-mushaf-reading-popover .q-choice-item:disabled {
    cursor: not-allowed;
    opacity: 0.6;
}
/* Own report: own breathing room before the script-picker grid below it
   now (own comment on the markup, mushaf.html) - scoped to this popover
   only, same reasoning as the label rule above (#mark-toggles already
   carries its own "mb-4" on the surah reading page's own Lecture panel,
   don't want to touch that). */
#q-mushaf-reading-popover #mark-toggles {
    margin-bottom: 0.75rem;
}
/* Desktop only, own report - data-q-mushaf-multicolumn="off" itself only
   ever runs @media (min-width:1024px) further below (mobile forces its
   own single-column screen paging regardless, own comment on
   onSlotTouchMove/mushafScreenNext, mushaf.js), so this control already
   did nothing on mobile - hidden now instead of sitting there inert,
   same reasoning .q-mushaf-theme-axis-toggle used to be hidden on mobile
   for (own report: that one's now genuinely functional there instead,
   own comment on screenAxisIsVertical, mushaf.js - not the same
   situation as this one). */
@media (max-width: 1023.98px) {
    .q-mushaf-personalize-multicolumn-toggle {
        display: none;
    }
}
/* Committed state only (mushaf.js applyPersonalizeToFrame) - real page,
   applied the instant every control above changes (own comment further
   up - no more separate staged preview to keep in sync with).
   --q-mushaf-font reuses the site-wide font mechanism's own
   --app-font-arabic custom property (input.css) but overridden here,
   scoped to this frame alone - picking a font here never touches
   quran.font/other pages. */
/* Every choice in the popover needs its own explicit rule here, this one
   included - own report/live repro: without it, picking "Scheherazade"
   silently no-ops on the real page whenever the site-wide font cookie
   (:root[data-font], input.css - a totally separate preference from this
   one) happens to be set to something else, since --app-font-arabic just
   inherits that unrelated value with nothing here to override it back. */
.q-mushaf-frame[data-q-mushaf-font="scheherazade"] {
    --app-font-arabic: "Scheherazade New", serif;
}
.q-mushaf-frame[data-q-mushaf-font="scheherazade-classic"] {
    --app-font-arabic: "Scheherazade Classic", serif;
}
.q-mushaf-frame[data-q-mushaf-font="kfgqpc-hafs"] {
    --app-font-arabic: "KFGQPC Hafs", serif;
}
.q-mushaf-frame[data-q-mushaf-font="kfgqpc-naskh"] {
    --app-font-arabic: "KFGQPC Naskh", serif;
}
.q-mushaf-frame[data-q-mushaf-font="me-quran"] {
    --app-font-arabic: "Me Quran", serif;
}
.q-mushaf-frame[data-q-mushaf-font="pdms-saleem"] {
    --app-font-arabic: "PDMS Saleem", serif;
}
/* .q-mushaf-bold-text .q-arabic-text (font-weight:900) moved to
   popover.css - single source, combined selector with surah-player.css/
   card contexts (own report on the theme consolidation above). */
/* .q-mushaf-custom-spacing (mushaf.js, always added now - no more
   "Personnaliser" master switch to gate it, own report) still gates all
   3, not just var()'s own fallback - line-height specifically needed
   it: .q-mushaf-flow's own base rule (further
   below) sets a direct line-height on the *block*, and an unconditional
   var(--q-mushaf-line-height, inherit) on the nested *inline*
   .q-arabic-text span would have resolved to literal keyword `inherit`
   the instant the custom property was ever unset - overriding that
   block rule with "inherit" (~1) instead of actually falling back to
   it, breaking the default 2.1/1.85 line-height for every reader, not
   just while customizing. Targeting .q-mushaf-flow itself (the block)
   here for the same reason .q-mushaf-no-justify does.
   [data-q-mushaf] prefix isn't decorative on either rule below -
   :root[data-line-spacing="aere"] .q-mushaf-flow *and* its sibling
   :root[...] .q-arabic-text (input.css, site-wide "Interligne aéré")
   both have the exact same specificity (3 classes/attributes either
   way) as the un-prefixed versions, so without the extra selector here
   they win regardless of this slider's own value whenever that
   site-wide setting is on. The .q-mushaf-flow rule alone isn't enough:
   an inline box's own line-height sets its own strut, which grows the
   whole line box past whatever the parent block asked for - the aere
   rule's direct line-height on .q-arabic-text itself was still winning
   that per-line contest even with the block rule fixed (confirmed:
   slider moved the block's computed line-height correctly, page didn't
   move at all with "Interligne aéré" on). line-height has to be
   overridden on the span too, not just letter/word-spacing. */
[data-q-mushaf] .q-mushaf-frame.q-mushaf-custom-spacing .q-mushaf-flow {
    line-height: var(--q-mushaf-line-height);
}
[data-q-mushaf] .q-mushaf-frame.q-mushaf-custom-spacing .q-arabic-text {
    line-height: var(--q-mushaf-line-height);
    word-spacing: var(--q-mushaf-word-spacing);
}
/* .q-mushaf-flow's own base rule (further below) already justifies book
   text unconditionally - adding a class to *enable* justify here would
   have no visible effect (already on), which is exactly why "Justifier
   le texte" OFF never did anything either (mushaf.js only ever toggled
   that redundant class, no override ever ran the *other* direction).
   Inverted: this class explicitly un-justifies instead, only ever added
   while "Justifier le texte" itself is off - justify on leaves the
   page's own default justify untouched. */
.q-mushaf-frame.q-mushaf-no-justify .q-mushaf-flow {
    text-align: start;
}
@media (min-width: 1024px) {
    .q-mushaf-frame[data-q-mushaf-multicolumn="off"] [data-q-mushaf-slot] {
        column-count: 1;
    }
}
/* Swatches keep their own color on hover/focus (excluded above) - a
   ring instead of the generic outline/background, so hovering "jaune"
   doesn't visually turn it into the same grey every other popover
   button hovers to. */
.q-mushaf-highlight-swatch:hover,
.q-mushaf-highlight-swatch:focus-visible {
    box-shadow: 0 0 0 2px var(--app-popover-bg), 0 0 0 4px var(--app-popover-text);
}
[data-q-mushaf-note-save] {
    background: var(--app-link);
    color: var(--app-popover-bg);
}
[data-q-mushaf-note-save]:hover,
[data-q-mushaf-note-save]:focus-visible {
    background: color-mix(in srgb, var(--app-popover-text) 15%, var(--app-link));
}
/* Words currently covered by a saved note (mushaf.js applyNoteHighlights,
   run after every page render) - underline *and* tint together (CLAUDE.md:
   never color alone), so it still reads for colorblind visitors, not just
   a background tint. */
/* Keyboard-only entry point into the Notes feature (mushaf.html/mushaf.js
   - each verse is tabindex=0/role=button, Entrée/Espace opens the note
   form for the whole verse) - a real visible ring, not just the
   browser default (CLAUDE.md: focus visible cohérent, pas trop pâle). */
.q-mushaf-ayah:focus-visible {
    outline: 2px solid var(--app-link);
    outline-offset: 2px;
    border-radius: 4px;
}
/* A word and its own combining marks (waqf/pause dot, tatweel, etc -
   text_marks.py's own render_ayah_word_spans wraps each mark in a
   *separate* <span data-q-mark> nested inside this same .q-word) are two
   distinct inline boxes with no whitespace between them - browsers
   *should* still never break a line between a base glyph and its own
   combining mark (Unicode line-breaking class CM), but splitting them
   into sibling elements like this loses that guarantee in practice: a
   dynamic reflow (multi-column pagination, at exactly the wrong
   font-size/line-height/column width) could still wrap the line
   right there, landing the mark alone at the very start of the next
   line/column - confirmed via a zoomed screenshot showing a waqf circle
   rendered as only its left half, clipped by the column's own edge (a
   symptom of the break, not the actual bug - a stray mark landing
   *inside* a column mid-line would have the same clipped-glyph look,
   just less visible). nowrap makes the whole word one unbreakable unit
   again, mark included - a real Quran word never splits across lines
   in print either way, so this is never a wanted line-break point to
   begin with. */
.q-word {
    white-space: nowrap;
}
/* padding-block only (never inline - would shove neighbouring words
   apart) - Arabic diacritics/combining marks routinely sit lower than a
   Latin font's own reported descent, so a highlight background sized to
   the text's plain line-box was clipping their bottom edge (screenshot:
   "le bas du mot n'a pas la couleur"). Safe on an inline element: block-
   direction padding paints outside the content box without reserving
   extra space or pushing sibling lines apart, the way it would on a
   block/inline-block element - the already-generous 1.85-2.1
   line-height here easily absorbs this bleed. */
.q-word-noted {
    background: color-mix(in srgb, var(--app-link) 16%, transparent);
    padding-block: 0.2em;
    text-decoration: underline;
    text-decoration-style: dotted;
    text-decoration-color: var(--app-link);
}
/* Color highlights (selection popover's own swatches, quran-store.js's
   `color` field) - fixed pastel background *and* a forced dark ink color,
   not the reading theme's own var(--app-text)/--app-link. A real
   highlighter mark reads the same regardless of which of the Mushaf
   page's many light/dark/custom themes is active (some carry near-white
   text - a pastel background under that would be unreadable), same
   reasoning a paper highlighter works on white or off-white pages alike
   without the reader having to think about it. Own "applied" sRGB values
   (user-supplied), deliberately a shade darker/more saturated than the
   "menu" swatch colors below - the menu's own pastel reads better as a
   small UI chip, this one needs to stay legible as a full-word text
   background. No border-radius here (moved to .q-word-hl-start/-end
   below) - own comment there. */
.q-word-highlight-yellow,
.q-word-highlight-green,
.q-word-highlight-blue,
.q-word-highlight-pink,
.q-word-highlight-purple {
    color: #1a1a1a;
    padding-block: 0.2em;
}
.q-word-highlight-yellow { background: #fedc00; }
.q-word-highlight-green { background: #83df76; }
.q-word-highlight-blue { background: #8db9ff; }
.q-word-highlight-pink { background: #fc94b4; }
.q-word-highlight-purple { background: #caacff; }
/* Only the outer edge of a highlighted run gets rounded - the reading
   order's first word's own leading (right, this content is always RTL)
   corner, the run's last word's own trailing (left) corner - every
   corner in between (including the .q-word-gap bridge spans, own
   comment on applyNoteHighlights) stays square, so a multi-word
   highlight reads as one continuous rounded bar rather than a chain of
   individually-rounded segments. mushaf.js adds *both* classes to a
   lone single-word highlight (its own first AND last span at once),
   which is exactly a fully-rounded pill - same as before this dual-
   class split existed. */
.q-word-hl-start {
    border-top-right-radius: 3px;
    border-bottom-right-radius: 3px;
}
.q-word-hl-end {
    border-top-left-radius: 3px;
    border-bottom-left-radius: 3px;
}
.q-mushaf-highlight-swatch.q-word-highlight-yellow { background: #fcdc3f; }
.q-mushaf-highlight-swatch.q-word-highlight-green { background: #99dc82; }
.q-mushaf-highlight-swatch.q-word-highlight-blue { background: #95b9ff; }
.q-mushaf-highlight-swatch.q-word-highlight-pink { background: #fc9bb4; }
.q-mushaf-highlight-swatch.q-word-highlight-purple { background: #c5affb; }
.q-mushaf-selection-divider {
    align-self: stretch;
    height: 1px;
    margin: 0.125rem 0;
    background: var(--app-popover-divider);
}
/* Destructive action (mushaf.html data-q-mushaf-clear-note, hidden
   until an existing note/highlight is found for the current selection)
   - --app-danger is a real global token (tailwind.css :root), not one
   of .q-mushaf-frame's own overridden --app-* set, so this stays a
   recognizable red regardless of which reading theme is active. */
[data-q-mushaf-clear-note] {
    color: var(--app-danger);
}
/* Date group header ("il y a 2 jours" etc - mushaf.js's own
   Intl.RelativeTimeFormat, not a translated string) inside the
   bookmarks list - a plain <li>, not a <button>, so it's inert/
   unfocusable, same as a real list's own section divider. */
.q-mushaf-bookmarks-group {
    padding: 0.5rem 0.5rem 0.125rem;
    font-size: calc(0.6875rem * var(--q-ui-scale, 1));

    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.02em;
    color: var(--app-popover-text);
}
.q-mushaf-bookmarks-group:first-child {
    padding-top: 0;
}
.q-mushaf-bookmarks-empty {
    margin: 0;
    padding: 0.5rem 0.25rem;
    font-size: calc(0.8125rem * var(--q-ui-scale, 1));

    line-height: 1.5;
    color: var(--app-popover-text);
}
.q-mushaf-search-loading {
    margin: 0.5rem 0 0;
    font-size: calc(0.75rem * var(--q-ui-scale, 1));

    color: var(--app-popover-text);
    opacity: 0.75;
}
.q-mushaf-search-recent-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-top: 0.75rem;
    font-size: calc(0.75rem * var(--q-ui-scale, 1));

}
.q-mushaf-search-recent-head button {
    color: var(--app-link);
}
.q-mushaf-search-recent-head button:hover,
.q-mushaf-search-recent-head button:focus-visible {
    text-decoration: underline;
}
/* "tous les résultats" (not a top-10 cap - views.py's own
   MUSHAF_SEARCH_LIMIT), so this is the element that scrolls, fixed
   height, rather than letting the popover itself grow past the
   viewport. */
.q-mushaf-search-results {
    max-height: 18rem;
    overflow-y: auto;
}
/* Visible page indicator (replaces the old toolbars' own "/ 604" text) -
   small and muted, sitting inside the frame's own bottom edge (its
   parent .q-mushaf-frame is position:relative), same "Page N" template
   mushaf.js also announces via aria-live on this same element. */
.q-mushaf-status {
    /* Real grid child of .q-mushaf-footer now (own rule further below). */
    grid-column: 2;
    justify-self: center;
    text-align: center;
    font-size: calc(0.75rem * var(--q-ui-scale, 1));

    line-height: 1.5rem;
    color: var(--app-sub);
}
/* Same bottom row as .q-mushaf-status above, right side instead of
   centered - "N pages left in this surah" (mushaf.js). Same hover-
   reveal treatment as the corner menus (opacity gated on
   [data-q-controls-visible]) rather than always-on like the status
   line itself: this is a nice-to-know, not something worth permanently
   competing with the page text for attention. No :focus-visible
   fallback here (unlike the corner buttons) - there's nothing inside
   it to focus, it's plain text; opacity:0 alone doesn't remove it from
   the accessibility tree either way (unlike display:none), so a
   screen-reader user still reaches it regardless of the mouse-only
   reveal. */
.q-mushaf-surah-remaining {
    /* Real grid child of .q-mushaf-footer now (own rule further below). */
    grid-column: 3;
    justify-self: end;
    font-size: calc(0.75rem * var(--q-ui-scale, 1));

    line-height: 1.5rem;
    color: var(--app-sub);
    opacity: 0;
    transition: opacity 0.15s ease;
}
.q-mushaf-frame[data-q-controls-visible] .q-mushaf-surah-remaining {
    opacity: 1;
}
/* Fixed "page" - a real border/shadow/min-height so it reads as a
   physical sheet, not just another content block; min-height (not
   height) so a page with fewer/shorter ayahs doesn't visually collapse
   into a sliver next to ones packed with a full page of text. This is
   the mobile/default sizing - single flowing column, free to grow past
   75vh if a page's text needs it (no vertical-scroll concern there,
   the whole document just scrolls). Desktop overrides this to a fixed
   height below, once the 2-column layout takes over. position:relative
   anchors the edge prev/next buttons below. */
.q-mushaf-frame {
    position: relative;
    /* --q-ui-scale (multiplies every UI text font-size - popovers,
       button labels, dialog titles, status line, sliders, etc, never
       the actual Quran text itself - .q-mushaf-flow/.q-mushaf-surah-
       title/.q-arabic-text keep their own separate --q-mushaf-fit-scale/
       --q-arabic-scale multipliers, untouched by this one) - single
       source now, popover.css's own :root default (own report: "renomme
       le en quelque chose pourque ca marche pour tout... a utiliser dans
       popover.css par exemple en commun"), no longer redeclared per
       frame. */
    border: 1px solid var(--app-border);
    border-radius: 12px;
    background: var(--app-surface);
    /* color/--font-quran (explicit re-resolve-here, not left to inherit
       from body - own report: "il faut utiliser un seul code de theme
       pour toute les pages") moved to popover.css's own shared combined
       rule (.q-mushaf-frame/.q-surah-reading-frame/[data-q-ayah-card]),
       single source now for all 3 contexts instead of being redeclared
       per frame. */
    box-shadow: 0 2px 12px rgb(0 0 0 / 6%);
    /* Was 3.5rem - only needed that wide back when .q-mushaf-edge-btn sat
       absolutely positioned *inside* this padding zone, overlapping the
       text (old comment above, kept for the history). It's a real flex
       child of .q-mushaf-body now (own rule below), no longer living in
       this padding at all - so this can shrink, giving the header/body/
       footer stack more real height and the body more width, and pulling
       the title/footer closer to the frame's own top/bottom edge. */
    padding: 1.5rem;
    min-height: 80vh;
    /* Real header/body/footer stack (.q-mushaf-header/-body/-footer,
       own rules further below) instead of a title/status each guessing
       an absolute offset into the padding above/below - `gap` gives
       both the header-to-body and body-to-footer space the exact same
       value by construction, nothing left to eyeball. Mobile and
       desktop both flow this way; only the body's own column count and
       the frame's fixed height/overflow differ per breakpoint below. */
    display: flex;
    flex-direction: column;
    gap: 1rem;
}
/* The 6 Mushaf themes x light/dark (--app-bg/-text/-popover tokens/
   -mushaf-danger), the Tranquille --app-sub-mix override, and the
   "Contraste élevé" --app-popover-border fix all used to be duplicated
   here AND in surah-player.css, byte-for-byte identical values - own
   report: "au lieu de dupliquer les theme pour chaque page mets les
   dans un fichier unique... meme si il sont deja dupliquer dans
   l'existant, factorise les". Single source now: popover.css (loaded
   on every page, this one included), one combined selector per rule
   covering .q-mushaf-frame/.q-surah-reading-frame/every
   [data-q-ayah-card] card at once instead of a 3rd copy for the new
   card-wide use (own report: "meme dans les pages... ayah, root,
   lemme, mot, recherche"). */
/* Real header row - title + both corner icon groups (mushaf.html
   .q-mushaf-header, wraps .q-mushaf-title/-topleft/-topright as actual
   children now, not 3 independently absolute-positioned guesses). 3
   columns so the title stays centered regardless of the two icon
   groups' own (different) widths - the outer 2 columns are equal `1fr`
   shares, so whichever group is narrower just leaves more of its own
   column's space unused rather than pulling the title off-center. */
.q-mushaf-header {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: start;
    /* Column 1 is the physical *right* edge under the page's own RTL
       (Arabic default) - forces it back to the physical left/right the
       old absolute left:/right: offsets always used regardless of
       page direction (same reasoning .q-mushaf-topright's own
       direction:ltr already applied to its internal icon order, just
       one level up here for which grid column lands on which side). */
    direction: ltr;
    /* mushaf.js's mouseenter/mouseleave (controls-visible toggle) binds
       to this element only, not the whole frame - but .q-mushaf-frame's
       own padding-top (1.5rem, further below) sits above it, outside
       this box, so a mouse moving up off the header toward the frame's
       real top edge exits the hitbox mid-gap and the controls hide
       before the pointer ever reaches the edge. padding-top grows the
       box upward by that same 1.5rem, margin-top cancels the resulting
       visual shift - hitbox now reaches the frame's top edge, content
       position unchanged. */
    padding-top: 1.5rem;
    margin-top: -1.5rem;
    /* Same gap, left/right - .q-mushaf-frame's own padding-inline
       (1.5rem, further below) sits outside this grid too, so the
       topleft/topright button groups' own hitbox (grid-column
       1/3, own rules further below) stops short of the frame's real
       side edges as well. */
    padding-inline: 1.5rem;
    margin-inline: -1.5rem;
}
/* Mobile tap menu (mushaf.html, <1024px only) - replaces the desktop
   corner icons' hover reveal, which never fires on touch (mushaf.js
   headerEl mouseenter/mouseleave). `:not([hidden])` on each of the 3
   pieces below, not a plain unconditional `display` on the bare class -
   this file loads after tailwind.css's own preflight `[hidden]` reset
   (staticsrc/input.css import order), so an unconditional `display`
   here would risk winning the cascade over `[hidden]` on source-order
   alone and never actually hide; the explicit :not() guard makes the
   3 states unambiguous regardless of load order. Disabled outright at
   >=1024px (own rule further below) - desktop keeps the existing hover
   corner icons untouched, this overlay only ever exists as a mobile
   presentation of the same 8 actions, not a 2nd control surface. */
/* .q-mushaf-mobile-menu-backdrop's own rule + desktop disable moved to
   popover.css (own report: "il utilise la meme structure et fichier
   css" - surah_detail.html doesn't load mushaf.css at all, own comment
   further up this file on why the shared popover chrome already lives
   there instead; #q-mushaf-surah-popover's own mobile sheet reuses this
   same backdrop class now too). */
/* .q-mushaf-mobile-menu-logo retiré (own report "deux home qui se
   superposent") - #q-header-col-start's Accueil (base.html, own rule
   further above) reste déjà affiché en permanence sur mobile. */
/* .q-mushaf-mobile-menu's own :focus (openMobileMenu, mushaf.js, focuses
   this container itself so screen readers announce the opened menu -
   no outline on that programmatic focus) and box rules moved to
   popover.css (own report: "meme structure et meme class css" -
   #q-mushaf-surah-popover reuses this exact class now too, surah_
   detail.html does not load mushaf.css). */
/* Full-width row, label one side + icon the other - each just forwards
   its click to the matching desktop trigger (mushaf.js
   data-q-mushaf-mobile-forward), same theme-driven colors as every
   other popover button (--app-popover-btn-*, re-resolved per theme on
   .q-mushaf-frame further above) so this overlay repaints along with
   whatever "Thèmes et réglages" preset/mode the reader currently has,
   never a hardcoded light/dark pair of its own. */
.q-mushaf-mobile-pill {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    width: 100%;
    border: 0;
    border-radius: 14px;
    padding: 0.875rem 1rem;
    background: var(--app-popover-btn-bg);
    color: var(--app-popover-btn-text);
    font-size: calc(0.9375rem * var(--q-ui-scale, 1));
    text-align: start;
}
/* Notes/Signets pills, hidden when there's nothing to show them for
   (mushaf.js updateMobileListVisibility) - explicit [hidden] override,
   not left to the bare attribute alone: same 0,1,0-vs-0,1,0 cascade tie
   the bookmark ribbon's own display:none fix further above ran into,
   the class rule right above wins on source order alone without this. */
.q-mushaf-mobile-pill[hidden] {
    display: none;
}
.q-mushaf-mobile-pill:focus-visible {
    outline: 2px solid var(--app-link);
    outline-offset: 2px;
}
/* Partager/Signet - single-tap actions, not a browsable list, same
   reasoning as Apple Books' own bottom icon row (reference screenshot,
   own report) keeping these apart from the pill list above. */
.q-mushaf-mobile-bottomrow {
    display: flex;
    gap: 0.5rem;
    margin-top: 0.25rem;
}
.q-mushaf-mobile-icon {
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    border: 0;
    border-radius: 14px;
    padding: 0.75rem;
    background: var(--app-popover-btn-bg);
    color: var(--app-popover-btn-text);
}
.q-mushaf-mobile-icon:focus-visible {
    outline: 2px solid var(--app-link);
    outline-offset: 2px;
}
/* List/drill-down, 2 views of the same .q-mushaf-mobile-menu (mushaf.js
   mobileDrillInto/restoreMobileDrilldown) - both need the same flex
   column + gap the outer menu itself gave its direct children before
   this split (own report: 1 tap-target list, [hidden] toggling between
   them further below), now that those direct children are these 2
   wrapper divs instead of the pills/back-button+slot themselves. */
[data-q-mushaf-mobile-list],
[data-q-mushaf-mobile-drilldown] {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    /* Own report ("le menu doit grandir avec son contenu, pas
       scroller") - ex-flex:1 1 auto/min-height:0/overflow-y:auto
       (own report précédent) étirait cette liste pour remplir le
       max-height:70vh de .q-mushaf-mobile-menu et scrollait dès que le
       contenu (Notes/Signets, pills conditionnelles - own comment sur
       [hidden] plus bas) dépassait cette hauteur fixe, même quand la
       vraie liste (souvent plus courte, ces pills masquées) tenait déjà
       largement dedans. Bloc simple maintenant - suit son propre
       contenu, .q-mushaf-mobile-menu (popover.css, own report là-bas)
       porte seul le scroll de secours si jamais ça déborde vraiment. */
}
[data-q-mushaf-mobile-list][hidden],
[data-q-mushaf-mobile-drilldown][hidden] {
    display: none;
}
/* Own report ("l'input monte quand le résultat s'affiche - regarde
   comme c'est fait pour le popover Sourates, lui il est fixe") - le menu
   mobile (.q-mushaf-mobile-menu, popover.css) est position:fixed ancré en
   bas et grandit vers le HAUT avec son contenu ; une liste de résultats
   en max-height grandit de 0 vers son plafond au fil des frappes, donc le
   sheet grandit vers le haut et l'input (en tête) monte. Même recette que
   #q-mushaf-surah-popover .q-popover-list (popover.css, own report
   identique "l'input change de position a chaque fois qu'on filtre") :
   hauteur FIXE sur la liste (pas max-height), un viewport réservé qui ne
   change pas avec le nombre de résultats - les résultats scrollent dedans,
   la ligne de recherche au-dessus ne bouge plus. Seulement quand la liste
   est visible (:not([hidden])) pour ne pas réserver 50vh à vide avant la
   première recherche. max-height:none annule le plafond 18rem de la règle
   de base plus haut. */
@media (max-width: 1023.98px) {
    /* Vue drilldown de jump uniquement (:has(#q-mushaf-jump-popover)) -
       hauteur fixe + son propre scroll, pour que le sheet ne change plus
       de hauteur quand les zones sous l'input s'échangent. Le form est
       déjà position:sticky top:0 dans cette boîte (popover.css), il reste
       donc collé en haut pendant que le contenu défile. Sourates a déjà
       sa liste 50vh, pas concernée. */
    [data-q-mushaf-mobile-drilldown]:has(#q-mushaf-jump-popover) {
        height: 55vh;
        overflow-y: auto;
    }
    /* Les résultats grandissent DANS ce scroll box, pas leur propre 2e
       scroll (annule le plafond 18rem de la règle de base plus haut). */
    [data-q-mushaf-mobile-drilldown] .q-mushaf-search-results {
        max-height: none;
        overflow-y: visible;
    }
}
/* Ex-.q-mushaf-title retiré (own report: "il faut que ca soit pareille" -
   titre déplacé dans le vrai header partagé, header_start/mushaf.html,
   même position que /ar/s et /ar/s/1 plutôt qu'une position custom ici). */
/* Real footer row - status + surah-remaining (mushaf.html
   .q-mushaf-footer), mirrors .q-mushaf-header above. Empty first
   column (nothing right-aligned on that side today) still keeps the
   status line mathematically centered against the surah-remaining
   column on the other side, same 1fr/auto/1fr trick. */
.q-mushaf-footer {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: end;
    /* Same RTL column-mirroring fix as .q-mushaf-header above - the old
       .q-mushaf-surah-remaining always sat at the physical right
       (right: 3.5rem), regardless of page direction. */
    direction: ltr;
}
/* Real body row - edge-mounted page-turn buttons + the page content
   itself, side by side (mushaf.html .q-mushaf-body) instead of the
   buttons floating absolutely on top of the text. Fills whatever
   height header+footer don't take (flex:1, frame's own rule above). */
.q-mushaf-body {
    display: flex;
    /* stretch (default) - not center: the slot needs the row's full
       height to stay a definite size (own comment on [data-q-mushaf-slot]
       below), or the desktop column-count rule has no height to
       compute column breaks against and the text collapses into one
       overflowing column instead. The edge buttons get their own
       align-self:center below instead. */
    flex: 1 1 auto;
    min-height: 0;
    /* Real breathing room between the edge buttons and the text now -
       as flex siblings with no gap they sat flush against the slot
       (the old absolute-positioned button used to float *inside* the
       frame's own padding instead, which put empty space between it
       and the text for free; that's gone now it's a real flex child). */
    gap: 1rem;
    /* Anchors .q-mushaf-drag-layer (mushaf.js, mobile carousel swipe
       further below) - that layer is position:absolute against this
       box specifically (not .q-mushaf-frame), so it lines up with the
       slot's own rect even though the frame also has header/footer
       above/below it. */
    position: relative;
}
/* Carousel drag/slide (mushaf.js ensureDragLayer/autoSlideTransition) -
   translateX-drags the real [data-q-mushaf-slot] and its own 2nd layer,
   so the next/prev screen visibly slides in instead of jumping. Own
   children here are only the 2 edge buttons + the slot + the spinner
   (mushaf.html) - none of them need to render past this box's own
   edges, so clipping unconditionally (not just mobile) is what keeps
   the sliding content from visibly crossing over the edge buttons on
   desktop instead of disappearing behind them. */
.q-mushaf-body {
    overflow: hidden;
}
/* The 2nd layer a carousel drag reveals (mushaf.js ensureDragLayer) -
   a sibling of the real slot, same box, empty/invisible at rest.
   pointer-events:none - always a preview only, the actual touch target
   throughout a drag stays the real [data-q-mushaf-slot] underneath.
   Shares that same attribute (mushaf.js gives it its own
   data-q-mushaf-slot too) purely so every column-layout rule keyed off
   that attribute elsewhere in this file (mobile/desktop column-count,
   etc.) applies to it identically without a 2nd copy of each rule. */
.q-mushaf-drag-layer {
    position: absolute;
    inset: 0;
    overflow: hidden;
    pointer-events: none;
    visibility: hidden;
}
.q-mushaf-drag-layer[data-q-active] {
    visibility: visible;
}
/* Desktop only - the edge buttons (still visible there, own rule below)
   ate into the extra width this reclaims from the frame's own side
   padding. Mobile hides those buttons entirely (own rule below) and
   swipes instead, so there's nothing to compensate for there - it can
   just keep the frame's plain padding, real breathing room on the
   sides instead of text running right up to the edge. */
@media (min-width: 1024px) {
    .q-mushaf-body {
        margin-inline: -1rem;
    }
}
@media (prefers-color-scheme: dark) {
    .q-mushaf-frame {
        box-shadow: 0 2px 12px rgb(0 0 0 / 30%);
    }
}
:root[data-theme="dark"] .q-mushaf-frame,
:root[data-theme="dim"] .q-mushaf-frame {
    box-shadow: 0 2px 12px rgb(0 0 0 / 30%);
}
:root[data-theme="light"] .q-mushaf-frame,
:root[data-theme="sepia"] .q-mushaf-frame,
:root[data-theme="cream"] .q-mushaf-frame {
    box-shadow: 0 2px 12px rgb(0 0 0 / 6%);
}
/* Fills whatever width .q-mushaf-body's edge buttons don't take -
   height comes from the row's own default align-items:stretch (no
   explicit height needed), which is already a definite size the
   desktop column-count rule below can clip against. */
[data-q-mushaf-slot] {
    flex: 1 1 0%;
    min-width: 0;
    /* Small top nudge (own report: text sitting flush against the top
       edge/fade mask above) - text still top-aligned overall (own
       comment further below, column-fill:auto, "like a real page of
       print"), just starting a little lower instead of touching the
       body's own top edge. */
    padding-top: 1rem;
    /* Tells the browser up front that horizontal panning here is the
       page-turn carousel's own gesture (mushaf.js touchmove), not iOS
       Safari's own edge-swipe-back/forward navigation - preventDefault()
       on touchmove (mushaf.js) only stops the ordinary rubber-band
       scroll, the system back-swipe recognizer pre-empts it regardless
       when a touch starts near the physical screen edge, reported as the
       *whole* page (browser chrome included) sliding away instead of
       just the carousel turning. pan-y dropped - both breakpoints now
       paginate into fixed-height screens (mushaf.js/mushaf.css), nothing
       here ever scrolls vertically to hand that axis to native scroll
       for; leaving it declared native still let a vertical-leaning touch
       trigger the browser's own rubber-band bounce over empty space
       (own comment on body:has(.q-mushaf-immersive) further above).
       pinch-zoom stays (base.html's own viewport meta allows it
       site-wide, no user-scalable=no) - neither pan axis is native now,
       only zoom. */
    touch-action: pinch-zoom;
    /* Layout/paint/style containment - this is a flex sibling of the
       edge prev/next buttons (.q-mushaf-body's own flex row), and
       measured (console [Q-PERF] logging, live Safari test on Al-Baqara)
       a ~600-700ms single frame-gap the instant the pointer entered
       either button, settling to normal after - Safari re-laying-out the
       whole flex container (including this multi-column sibling, the
       single most expensive thing on the page) on a plain :hover
       background-color change that can't possibly affect this element's
       own size. Chrome never showed the same spike - not present there.
       `contain` tells the engine this subtree's layout/paint/counters
       can't leak out and don't need recomputing when an unrelated
       sibling's style changes, which is exactly the case here. No size
       containment (this element is flex/height-sized by its parent, not
       by its own content, so layout containment alone is safe and
       doesn't need `contain: size` to avoid a 0-size collapse). */
    contain: layout paint style;
}
/* mushaf.js sets aria-busy while a page/surah fetch is in flight (already
   there for screen readers) - some surahs are large (Al-Baqara's own
   fragment alone is ~500KB) and a fresh, not-yet-prefetched fetch for one
   can take long enough to read as a dead pause between the out/in halves
   of a page-turn transition ("un grand espace") rather than a page still
   loading. A dimmed slot reads as "working on it", not "broken". Same
   treatment (own report) while the "Style d'écriture arabe" radiogroup's
   own ajax round-trip (mushaf.html) is in flight - htmx toggles
   .htmx-request on this element itself via its own hx-indicator
   targeting [data-q-mushaf-slot] directly, no mushaf.js wiring needed for
   that half. */
[data-q-mushaf-slot][aria-busy="true"],
[data-q-mushaf-slot].htmx-request {
    opacity: 0.4;
}
@media (prefers-reduced-motion: no-preference) {
    [data-q-mushaf-slot] {
        transition: opacity 0.15s ease;
    }
}
/* Sits over the dimmed slot above (.q-mushaf-body's own position:relative)
   while a page/surah fetch is in flight - the dimming alone read as
   "greyed out", not clearly "loading", on a slow/not-yet-prefetched
   neighbour (own comment on the HTML, mushaf.html). Sibling selector, not
   a JS-toggled class - same [aria-busy] mushaf.js already sets/clears for
   the dimming, one less thing to keep in sync. */
.q-mushaf-spinner {
    display: none;
    position: absolute;
    inset: 0;
    align-items: center;
    justify-content: center;
    pointer-events: none;
    color: var(--app-text);
}
[data-q-mushaf-slot][aria-busy="true"] ~ .q-mushaf-spinner,
[data-q-mushaf-slot].htmx-request ~ .q-mushaf-spinner {
    display: flex;
}
/* Static ring, no spin, when motion is unwanted (CLAUDE.md) - still
   reads as a loading glyph on its own, just not an animated one. */
@media (prefers-reduced-motion: no-preference) {
    .q-mushaf-spinner svg {
        animation: q-mushaf-spin 0.8s linear infinite;
    }
}
@keyframes q-mushaf-spin {
    to { transform: rotate(360deg); }
}
/* One Mushaf page's content, auto-flowed into CSS columns - 2 on
   desktop, 1 (screen-width, mushaf.js ebookApplyColumnWidth) on mobile -
   paged horizontally either way instead of the page growing tall and
   the browser scrolling vertically. No visible rule/border between
   columns - just the gap, same as the reference screenshots.
   column-fill:auto (not the "balance" default) so the first column
   fills before overflow starts the second, like a real page of print
   instead of columns evened out to equal length. This page's own
   #marks-scope rule (higher-specificity ID, further above) is what
   actually caps .q-mushaf-frame's height + hides overflow now - nothing
   to duplicate here. */
[data-q-mushaf-slot] {
    column-fill: auto;
    /* A page whose text needs more room than one screen's worth of
       column(s) holds overflows into further off-flow "screens" beyond
       this box - hidden here (blocks the reader's own wheel/touch/
       keyboard scroll), paged through via scrollLeft instead (mushaf.js
       fitMushafPage/goToMushafScreen). */
    overflow: hidden;
}
@media (max-width: 1023.98px) {
    [data-q-mushaf-slot] {
        column-count: 1;
        /* Explicit real length, not the browser default ("normal", ~1em
           in most engines) - ebookScreenStride (mushaf.js) reads this
           back via getComputedStyle to compute each screen's scrollLeft
           target; "normal" isn't a parseable length there (treated as 0
           in the JS math) while the real rendered gap wasn't actually 0
           - screen N+1's target undershot the real column boundary by
           that gap, compounding every screen forward (reported: a
           sliver of the previous screen still visible after "suivant").
           A real value here also matters on its own past just being
           parseable - 0 read as consecutive screens' text sitting flush
           against each other with no breathing room, "collé" (own
           report), unlike a real page turn's own DRAG_GAP_PX gap
           (mushaf.js) between outgoing/incoming layers. */
        column-gap: 2rem;
    }
}
@media (min-width: 1024px) {
    [data-q-mushaf-slot] {
        column-count: 2;
        column-gap: 10rem;
    }
}
/* Mobile - swipe (mushaf.js touchstart/touchend on the slot) replaces
   the edge buttons rather than sitting next to them. #q-mushaf ancestor
   (not just the plain class) - the later, unconditional
   .q-mushaf-edge-btn rule further below sets its own display, same
   specificity as a bare class selector here would have, and being
   lower in the file it would otherwise win the tie and keep the
   buttons visible on mobile regardless of this media query. */
@media (max-width: 1023.98px) {
    #q-mushaf .q-mushaf-edge-btn {
        display: none;
    }
}
/* Desktop's hover-reveal corner icon groups (own report: "les éléments
   du menu Desktop sont cachés mais ils prennent de l'espace") - mobile
   never fires hover (headerEl mouseenter/mouseleave, mushaf.js) so these
   stayed permanently opacity:0 there, but still full-size grid children,
   wasting header height/width for nothing. The mobile tap menu further
   below (data-q-mushaf-mobile-menu) already covers every one of their
   actions (Sourates/jump/notes/bookmarks/marks/theme/share/bookmark/
   Accueil) - display:none here removes them from layout entirely rather
   than just hiding them, so the header shrinks to the title's own
   height instead of the invisible icons'. */
@media (max-width: 1023.98px) {
    .q-mushaf-topleft,
    .q-mushaf-topright {
        display: none;
    }
    /* .q-mushaf-surah-remaining's own opacity:0 default (above) only ever
       lifts on [data-q-controls-visible], set by headerEl's mouseenter/
       mouseleave (mushaf.js) - a hover that never fires on a touch-only
       device, so this sat permanently invisible on mobile, no mobile
       equivalent covering the info the way the tap menu covers
       topleft/topright's own actions above. Always-on here instead of
       hidden - there's no other way to see it on mobile. Own report:
       right next to .q-mushaf-status (same row, grid-column:3) cramped
       both against the narrow viewport - own row below it instead,
       spanning every column so justify-self:center actually centers
       against the full footer width, not just its own 1fr column.
       .q-mushaf-footer's grid-template-columns is unchanged (desktop
       still needs the 3-column split for the side-by-side layout) - grid
       auto-flow just adds the extra row underneath on its own once this
       spans past column 3. */
    .q-mushaf-surah-remaining {
        opacity: 1;
        grid-column: 1 / -1;
        justify-self: center;
    }
}
/* Page-turn controls, 2 axes (desktop only, mobile stays display:none
   below/swipe-only - horizontal is the only axis it ever gets, own
   comment on setAxis mushaf.js). Same 2 buttons, same flex-sibling
   layout/size *in both axes* (own report: "le texte reste dans sa
   place il bouge pas" - the reading column itself never resizes or
   moves between axes) - switching axis (quickrow radiogroup only,
   these buttons never double as that toggle themselves) only ever
   changes each button's visual side (order, further below) and its
   icon's rotation, never their position model. No border/background/
   shadow at rest (reference screenshot) - just a faint chevron,
   near-invisible until you look for it. */
.q-mushaf-edge-btn {
    flex: 0 0 auto;
    align-self: center;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.25rem;
    height: 4.5rem;
    border-radius: 8px;
    border: none;
    background: transparent;
    color: var(--app-sub);
    opacity: 0.55;
}
/* Horizontal (default) - margin faces each button's own screen edge:
   "précédent" (data-q-mushaf-prev, inline-start = right in RTL) pushes
   off the right edge, "suivant" (inline-end = left) off the left. */
.q-mushaf-edge-btn-start {
    margin-inline-start: 0.5rem;
}
.q-mushaf-edge-btn-end {
    margin-inline-end: 0.5rem;
}
/* Vertical (own report: reuse "précédent"'s exact right/centered slot
   for "scroll up" and "suivant"'s exact left/centered slot for "scroll
   down") - order alone swaps which button visually sits on which side,
   DOM/click-handler identity never changes (both stay real
   single-purpose nav, own earlier correction); margin has to follow
   suit or it'd still push against the *old* screen edge after order
   flips which one is visually first/last. */
.q-mushaf-frame[data-q-mushaf-axis="vertical"] .q-mushaf-edge-btn-start {
    order: 1;
    margin-inline-start: 0;
    margin-inline-end: 0.5rem;
}
.q-mushaf-frame[data-q-mushaf-axis="vertical"] .q-mushaf-edge-btn-end {
    order: -1;
    margin-inline-end: 0;
    margin-inline-start: 0.5rem;
}
[dir="rtl"] .q-mushaf-frame:not([data-q-mushaf-axis="vertical"]) .q-mushaf-edge-btn svg {
    /* Original RTL mirror, horizontal axis only. */
    transform: scaleX(-1);
}
/* Vertical - same svg (mushaf.html), rotated 90deg: "précédent"'s left
   chevron (‹) turns to point up ("scroll up", own report), "suivant"'s
   right chevron (›) turns to point down ("scroll down") - exactly the
   mapping asked for, no separate icon markup needed. No [dir="rtl"]
   mirror here (own rule above, scoped out) - up/down doesn't flip for
   RTL the way left/right does. */
.q-mushaf-frame[data-q-mushaf-axis="vertical"] .q-mushaf-edge-btn svg {
    transform: rotate(90deg);
}
.q-mushaf-edge-btn:hover:not(:disabled) {
    /* color-mix against --app-text, not var(--app-bg) - see
       .q-mushaf-icon-btn:hover's own comment above for why. */
    background: color-mix(in srgb, var(--app-text) 8%, transparent);
    color: var(--app-text);
    opacity: 1;
}
.q-mushaf-edge-btn:focus-visible {
    outline: 2px solid var(--app-link);
    outline-offset: 2px;
}
.q-mushaf-edge-btn:disabled {
    opacity: 0.25;
    cursor: not-allowed;
}
/* Continuous book text, every page - justify, not .ayah-block's plain
   right-align (unused on this page now, every ayah renders through this
   flow paragraph instead). Verse links stay inline (default <a> display,
   not .ayah-block's own display:block) so ayahs run into each other and
   wrap naturally instead of one per line. */
.q-mushaf-flow {
    text-align: justify;
    font-size: calc(1.375rem * var(--q-mushaf-fit-scale, 1));
    line-height: 2.1;
}
/* Per-surah heading (_mushaf_page.html) - sized/spaced to land its own line-height on the
   exact same grid the ayat below it use, not a plain margin. Needs 2
   things the plain text-base/mb-4 version didn't have, both flagged by
   testing this side by side with real ayah text: 1) font-size has to
   scale with --q-arabic-scale (the A/A stepper) the same way a real
   ayah's own .q-arabic-text does - text-base is a flat 1rem regardless
   of that slider, so line-height was multiplying against the *wrong*
   number the moment a reader touched that stepper; 2) the box needs a
   real height (2 rows: 1 for the title text, 1 blank spacer under it),
   not a fixed-px margin - mb-4's 1rem has nothing to do with whatever
   the ayat's own line-height actually computes to, so the ayat text
   right after it essentially never started on that grid's own row
   boundary. --q-title-size/--q-title-line are the 2 local knobs each
   override below only has to touch once - font-size/height both read
   off them instead of repeating the same calc() several times over. */
.q-mushaf-surah-title {
    --q-title-size: 1.375rem;
    --q-title-line: 2.1;
    display: flex;
    align-items: flex-start;
    justify-content: center;
    margin: 0;
    font-weight: 600;
    font-size: calc(var(--q-title-size) * var(--q-arabic-scale, 1));
    line-height: var(--q-title-line);
    height: calc(2 * var(--q-title-size) * var(--q-arabic-scale, 1) * var(--q-title-line));
}
@media (min-width: 1024px) {
    .q-mushaf-surah-title {
        --q-title-size: 1.25rem;
        --q-title-line: 1.85;
    }
}
.q-mushaf-surah-title-spaced {
    height: calc(3 * var(--q-title-size) * var(--q-arabic-scale, 1) * var(--q-title-line));
    padding-top: calc(var(--q-title-size) * var(--q-arabic-scale, 1) * var(--q-title-line));
}
/* Flash on the exact verse a word search jumped to (mushaf.js
   flashHighlight, removed again after 4s) - a background tint plus a
   ring, not color alone (CLAUDE.md), so it still reads for colorblind
   visitors. Transition-based, not a keyframe animation, so
   prefers-reduced-motion's blanket transition-duration:0 override
   (input.css's own media query / [data-motion="reduit"]) already
   applies to it with no extra rule needed here. */
.q-mushaf-search-hit {
    background: color-mix(in srgb, var(--app-link) 18%, transparent);
    outline: 2px solid var(--app-link);
    outline-offset: 2px;
    border-radius: 4px;
    transition: background 0.6s ease, outline-color 0.6s ease;
}
/* Tighter than the shared 1.375rem/2.1 above - that pair left the
   longest real Mushaf page (552, 1518 chars) still spilling into a 3rd
   column even at 95vh (.q-mushaf-frame's own desktop rule). Scoped to
   desktop only, placed after the base rule above so it actually wins
   the cascade (equal specificity, source order decides) - mobile's
   single flowing column just grows past 75vh/no scroll concern, no
   reason to shrink its reading comfort to solve a 2-column-only
   problem. ~20% less vertical space per line (1.25 x 1.85 vs.
   1.375 x 2.1), more headroom than any further vh bump could
   realistically buy this close to 100vh. */
@media (min-width: 1024px) {
    .q-mushaf-flow {
        font-size: 1.25rem;
        line-height: 1.85;
    }
}
