:root {
  --bg: #f7f5f0;
  --surface: #ffffff;
  --border: #e5e0d5;
  --text: #2b2620;
  --muted: #7a7364;
  --primary: #1f6f5c;
  --primary-dark: #175647;
  --accent: #b98b2a;
  --accent-light: #d6ab5c;
  --danger: #b04a3d;
  --warn: #c08a2e;
  --good: #2f7d4f;
  --radius: 12px;
  --shadow: 0 1px 3px rgba(0,0,0,0.06), 0 1px 2px rgba(0,0,0,0.04);
}

/* The default theme follows the phone's own light/dark setting, which is why
   a fresh install can open dark. A reader who prefers otherwise pins it from
   the settings, and that choice arrives as data-scheme on <html>. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme]):not([data-scheme="light"]),
  :root[data-theme="default"]:not([data-scheme="light"]) {
    --bg: #16150f;
    --surface: #201e17;
    --border: #35311f;
    --text: #ece7d9;
    --muted: #9a917c;
    --primary: #3fae91;
    --primary-dark: #2f8a72;
    --accent: #d6ab5c;
    --accent-light: #e8c78a;
    /* The semantic three, brightened for a dark ground like the named dark
       themes already do - left at their light-theme values they were a dark
       red and a dark green sitting on a dark surface. */
    --danger: #e0705f;
    --warn: #e0b45c;
    --good: #6fbf8a;
    --shadow: 0 1px 3px rgba(0,0,0,0.4);
  }
}

:root:not([data-theme])[data-scheme="dark"],
:root[data-theme="default"][data-scheme="dark"] {
  --bg: #16150f;
  --surface: #201e17;
  --border: #35311f;
  --text: #ece7d9;
  --muted: #9a917c;
  --primary: #3fae91;
  --primary-dark: #2f8a72;
  --accent: #d6ab5c;
  --accent-light: #e8c78a;
  --danger: #e0705f;
  --warn: #e0b45c;
  --good: #6fbf8a;
  --shadow: 0 1px 3px rgba(0,0,0,0.4);
}

/* Explicit calming themes - chosen deliberately, so they hold regardless of
   the system's light/dark preference (unlike the default theme above). */
:root[data-theme="sepia"] {
  --bg: #f4ecd8;
  --surface: #fbf3e3;
  --border: #e3d5b8;
  --text: #4a3b28;
  --muted: #8a7859;
  --primary: #8a5a2c;
  --primary-dark: #6e4720;
  --accent: #b9862f;
  --accent-light: #d1a55c;
  --danger: #a1483a;
  --warn: #b98b2a;
  --good: #5c7a4a;
  --shadow: 0 1px 3px rgba(0,0,0,0.08);
}
:root[data-theme="night"] {
  --bg: #0f1520;
  --surface: #182333;
  --border: #2a3a52;
  --text: #dbe6f5;
  --muted: #8fa3bf;
  --primary: #5b8fd6;
  --primary-dark: #3f6cad;
  --accent: #d6ab5c;
  --accent-light: #e8c78a;
  --danger: #e07a6c;
  --warn: #e0b45c;
  --good: #6fbf8a;
  --shadow: 0 1px 3px rgba(0,0,0,0.5);
}
:root[data-theme="forest"] {
  --bg: #0e1912;
  --surface: #152318;
  --border: #28402e;
  --text: #e3ede5;
  --muted: #93ab98;
  --primary: #4caf7d;
  --primary-dark: #3a8d63;
  --accent: #c9a24a;
  --accent-light: #ddbf72;
  --danger: #e08670;
  --warn: #d9ad5c;
  --good: #7fd19d;
  --shadow: 0 1px 3px rgba(0,0,0,0.5);
}
/* A rich maroon-and-gold "embroidered Mushaf cover" palette - the priciest
   theme, deliberately more ornate than the calmer color-only themes above. */
:root[data-theme="embroidered"] {
  --bg: #1a0e0f;
  --surface: #2b1416;
  --border: #4a2226;
  --text: #f3e3c8;
  --muted: #b89a7a;
  --primary: #8c1f2b;
  --primary-dark: #6e1620;
  --accent: #d4af37;
  --accent-light: #e8cd6f;
  --danger: #e0574a;
  --warn: #e0b45c;
  --good: #6fbf8a;
  --shadow: 0 1px 3px rgba(0,0,0,0.5);
}
/* ثلث الليل - the last third of the night.
   ---------------------------------------------------------------------
   Not a dark palette but a lit room, and the lighting is the feature. Two
   kinds of light, which is what makes it read as a place rather than a
   colour scheme: a cold crescent high on the right, and warm lanterns hung
   low on chains. The cold and the warm arguing with each other is the whole
   effect - a uniformly dark screen has no light in it at all.

   Four layers, and which layer a thing lives on is the design:

     body::before   the sky, the arch, and the lanterns themselves - all
                    BEHIND the cards
     html::after    the falloff: everything multiplied by how much light
                    actually reaches it
     body::after    moon, stars and the glow pools, added on top

   The lanterns hang behind the content on purpose. Hung in front they would
   sit across the text - which is what they do in every illustration of this
   idea - and there is no gap between the cards wide enough to thread them
   through. Behind, they glow through the dark glass the cards are made of,
   so they light the card they hang over and never cover a letter of it. */

:root[data-theme="lastthird"] {
  /* The sky is cold and the lamps are warm, so the surfaces sit between:
     indigo-black grounds, brass for anything interactive, and text that is
     warm off-white rather than blue-white - which is the other half of not
     being أزرق ليلي. */
  --bg: #05060e;
  --surface: #0c0e19;
  --border: #262433;
  --text: #cbc6bd;
  --muted: #938d83;
  --primary: #b99a5e;
  --primary-dark: #94794a;
  --accent: #e8c98a;
  --accent-light: #f5e2b8;
  --danger: #bb7566;
  --warn: #c2a266;
  --good: #78a58b;
  --shadow: 0 1px 3px rgba(0,0,0,0.8);
}

/* The wallpaper stands down: this theme brings its own sky, and a green
   mushaf tile over a night sky is two skies at once. (:root[data-bg=...]
   is (0,2,0) and comes later in the file, so the element takes this to
   (0,2,1) rather than reaching for !important.) */
html:root[data-theme="lastthird"] { background-image: none; }


/* ---- the sky, and the lanterns hanging in it ---- */
:root[data-theme="lastthird"] body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background-repeat: no-repeat;
  background-image:
    /* One lamp, hung from the middle of the arch on a wire that runs all
       the way down to the middle of the screen. The wire and the lamp are
       separate layers on purpose: the lamp is a fixed size, and the wire
       has to stretch from the top of the viewport to wherever the middle
       of it lands - which no single drawing can do, because scaling one
       SVG scales the lamp along with its chain. */
    linear-gradient(rgba(201,168,106,0.55), rgba(201,168,106,0.55)),
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='80' height='100' viewBox='0 0 80 100'%3E%3Cdefs%3E%3ClinearGradient id='gl' x1='0' y1='0' x2='0' y2='1'%3E%3Cstop offset='0' stop-color='%23ffe0a4' stop-opacity='0.45'/%3E%3Cstop offset='0.5' stop-color='%23ffc266' stop-opacity='0.9'/%3E%3Cstop offset='1' stop-color='%23ff9d3c' stop-opacity='0.55'/%3E%3C/linearGradient%3E%3C/defs%3E%3Ccircle cx='40' cy='7' r='5' fill='none' stroke='%23c9a86a' stroke-width='2.2'/%3E%3Cpath d='M40 13l20 15H20Z' fill='%23c9a86a'/%3E%3Crect x='17' y='28' width='46' height='6' rx='3' fill='%23d9b878'/%3E%3Cpath d='M22 34h36l5 44H17Z' fill='url(%23gl)'/%3E%3Cellipse cx='40' cy='58' rx='11' ry='18' fill='%23ffd894' fill-opacity='0.45'/%3E%3Cellipse cx='40' cy='58' rx='6.5' ry='12' fill='%23fff5da' fill-opacity='0.97'/%3E%3Cpath d='M22 34L17 78M58 34l5 44M40 34v44' stroke='%23a8894f' stroke-opacity='0.9' stroke-width='1.8' fill='none'/%3E%3Crect x='14' y='78' width='52' height='7' rx='3.5' fill='%23d9b878'/%3E%3Cpath d='M40 85v6' stroke='%23a8894f' stroke-width='2.4'/%3E%3Ccircle cx='40' cy='94' r='4' fill='%23c9a86a'/%3E%3C/svg%3E"),
    /* The arch, above the sky and below the lamp. It has to sit in this
       same stack: the sky gradient below is opaque, so an arch on a lower
       layer was painted over and invisible. */
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='400' height='320' viewBox='0 0 400 320' preserveAspectRatio='none'%3E%3Cpath d='M0 0h400v320h-30V160c0-72-76-118-170-118S30 88 30 160v160H0Z' fill='%23090a14'/%3E%3C/svg%3E"),
    /* the moon's own halo, and the sky */
    radial-gradient(circle 200px at 86% 13%, rgba(216,202,164,0.15), rgba(216,202,164,0.04) 44%, transparent 72%),
    radial-gradient(ellipse 160% 90% at 50% 0%, #101430, #070914 52%, #05060e 100%);
  background-position: 50% 0, 50% 50%, top center, 0 0, 0 0;
  /* The wire runs from the very top of the screen. It used to start at the
     arch's peak so it met the curve drawn there; that curve is gone, so a
     wire starting in mid-air would just look cut. It still stops 48px short
     of centre, where the lamp's hook ring lands once the lamp is centred,
     so the two meet instead of the wire running down through the glass. */
  background-size: 2px calc(50% - 48px), 84px 105px, 100% 320px, 100% 100%, 100% 100%;
}

/* ---- the falloff ---- */
/* A page cannot turn the phone's brightness down - nothing on the web can -
   but it can multiply the whole interface by how much light reaches it.
   White means untouched, and each light source punches its own bright spot
   through: `lighten` keeps whichever source reaches a given pixel most, so
   the moon and the lamp light the room together rather than the last one
   in the list winning. The floor is 0.72 because past that the ayah text
   stops being readable, which is where the simulation has to give way. */
:root[data-theme="lastthird"]::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 9998;
  pointer-events: none;
  mix-blend-mode: multiply;
  background-image:
    radial-gradient(circle 210px at 86% 12%, #ffffff, #cfd0d4 55%, #b7b9bf 100%),
    radial-gradient(circle 230px at 50% 50%, #ffffff, #cfcabf 55%, #b7b9bf 100%),
    linear-gradient(#b7b9bf, #b7b9bf);
  background-blend-mode: lighten, lighten, normal;
}

/* ---- the light that reaches the front ---- */
/* Moon, stars and the lamps' glow, added rather than painted: `screen` only
   ever brightens, and the sky behind is black, so nothing outside the light
   itself is touched. This layer is exempt from the falloff below it - these
   are the sources the falloff is measured from. The glows are diffuse light
   and not objects, so they pass over text without hiding it; the lantern
   bodies stay behind the cards where they belong. */
:root[data-theme="lastthird"] body::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 9999;
  mix-blend-mode: screen;
  pointer-events: none;
  background-repeat: no-repeat;
  background-image:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120' viewBox='0 0 120 120'%3E%3Cdefs%3E%3Cmask id='m'%3E%3Crect width='120' height='120' fill='white'/%3E%3Ccircle cx='74' cy='48' r='40' fill='black'/%3E%3C/mask%3E%3C/defs%3E%3Ccircle cx='58' cy='58' r='40' fill='%23efe4bd' fill-opacity='0.62' mask='url(%23m)'/%3E%3C/svg%3E"),
    radial-gradient(circle 30px at 50% 50%, rgba(255,238,196,0.72), rgba(255,214,138,0.30) 60%, transparent 88%),
    radial-gradient(circle 120px at 50% 50%, rgba(255,196,110,0.26), rgba(255,196,110,0.06) 45%, transparent 74%),
    radial-gradient(circle at 12% 16%, rgba(238,234,214,0.85) 1.3px, transparent 1.6px),
    radial-gradient(circle at 33% 7%,  rgba(238,234,214,0.55) 1px,   transparent 1.3px),
    radial-gradient(circle at 58% 22%, rgba(238,234,214,0.7) 1.2px,  transparent 1.5px),
    radial-gradient(circle at 22% 38%, rgba(238,234,214,0.45) 1px,   transparent 1.3px),
    radial-gradient(circle at 91% 34%, rgba(238,234,214,0.6) 1.1px,  transparent 1.4px),
    radial-gradient(circle at 46% 52%, rgba(238,234,214,0.35) 1px,   transparent 1.3px),
    radial-gradient(circle at 72% 67%, rgba(238,234,214,0.5) 1.1px,  transparent 1.4px),
    radial-gradient(circle at 8%  74%, rgba(238,234,214,0.4) 1px,    transparent 1.3px),
    radial-gradient(circle at 37% 88%, rgba(238,234,214,0.3) 1px,    transparent 1.3px),
    radial-gradient(circle at 86% 92%, rgba(238,234,214,0.45) 1.1px, transparent 1.4px);
  background-position: 88% 11%, 0 0, 0 0, 0 0, 0 0, 0 0, 0 0, 0 0, 0 0, 0 0, 0 0, 0 0, 0 0;
  background-size: 92px 92px, 100% 100%, 100% 100%, 100% 100%, 100% 100%, 100% 100%, 100% 100%, 100% 100%, 100% 100%, 100% 100%, 100% 100%, 100% 100%, 100% 100%;
  /* Not a blink - flames and stars breathe. One composited opacity change
     per frame for the whole layer. */
  animation: nightBreath 7s ease-in-out infinite alternate;
}
@keyframes nightBreath {
  from { opacity: 0.82; }
  to { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  :root[data-theme="lastthird"] body::after { animation: none; opacity: 1; }
}

/* ---- the surfaces ---- */
/* Dark glass, and this is the idea working or not working: the lanterns and
   the moon live behind the content, and on a phone the content is nearly
   the whole screen - opaque cards would bury every one of them and leave a
   two-centimetre strip of sky at the top. Letting the night through is what
   makes the lamps light anything. */
:root[data-theme="lastthird"] .panel,
:root[data-theme="lastthird"] .stat-card,
:root[data-theme="lastthird"] .settings-group {
  background: color-mix(in srgb, var(--surface) 58%, transparent);
}
:root[data-theme="lastthird"] .panel,
:root[data-theme="lastthird"] .challenge-card {
  box-shadow: inset 0 1px 0 rgba(232,201,138,0.10), 0 1px 2px rgba(0,0,0,0.6);
}
/* backdrop-filter goes on .tabs and NEVER on .app-header: any value but
   none makes an element a containing block for its fixed descendants, and
   on phones .tabs is `position: fixed; bottom: 0` *inside* the header - so
   blurring the header re-anchored the whole tab bar to the header's 55px
   box and parked it at the top of the screen. */
:root[data-theme="lastthird"] .app-header {
  background: color-mix(in srgb, var(--bg) 80%, transparent);
}
:root[data-theme="lastthird"] .tabs {
  background: color-mix(in srgb, var(--surface) 74%, transparent);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
}
/* The challenge card is painted in --primary to match whatever theme is on.
   Here that made it the brightest thing on a black page. */
:root[data-theme="lastthird"] .challenge-card {
  /* Its own gradient goes at the BACK of the same stack rather than in a
     `background` shorthand, which would reset background-image and take the
     lamp and its pool with it. */
  background-color: transparent;
  background-image:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='64' viewBox='0 0 24 64'%3E%3Cdefs%3E%3ClinearGradient id='f' x1='0' y1='0' x2='0' y2='1'%3E%3Cstop offset='0' stop-color='%23ffe0a4' stop-opacity='0.55'/%3E%3Cstop offset='0.5' stop-color='%23ffc266' stop-opacity='0.95'/%3E%3Cstop offset='1' stop-color='%23ff9d3c' stop-opacity='0.6'/%3E%3C/linearGradient%3E%3C/defs%3E%3Cpath d='M12 0v26' stroke='%23c9a86a' stroke-opacity='0.6' stroke-width='1.3' fill='none'/%3E%3Cpath d='M12 28l6 6H6Z' fill='%23c9a86a'/%3E%3Crect x='4.5' y='34' width='15' height='2.6' rx='1.3' fill='%23d9b878'/%3E%3Cpath d='M6.5 36.6h11l1.6 19H4.9Z' fill='url(%23f)'/%3E%3Cellipse cx='12' cy='45' rx='3.1' ry='5.6' fill='%23fff3d2' fill-opacity='0.95'/%3E%3Cpath d='M6.5 36.6L4.9 55.6M17.5 36.6l1.6 19' stroke='%23a8894f' stroke-opacity='0.9' stroke-width='1.1' fill='none'/%3E%3Crect x='3.4' y='55.6' width='17.2' height='3' rx='1.5' fill='%23d9b878'/%3E%3Ccircle cx='12' cy='61' r='1.7' fill='%23c9a86a'/%3E%3C/svg%3E"),
    radial-gradient(ellipse 62% 48% at 16% 0%,
      rgba(255,198,116,0.17), rgba(255,198,116,0.05) 45%, transparent 78%),
    linear-gradient(150deg, rgba(20,18,30,0.6), rgba(9,10,18,0.6));
  background-repeat: no-repeat, no-repeat, no-repeat;
  background-position: left 14px top 0, 0 0, 0 0;
  background-size: 20px 46px, auto, auto;
  border: 1px solid color-mix(in srgb, var(--primary) 36%, transparent);
  color: var(--text);
}
:root[data-theme="lastthird"] .challenge-card.done {
  background-image:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='64' viewBox='0 0 24 64'%3E%3Cdefs%3E%3ClinearGradient id='f' x1='0' y1='0' x2='0' y2='1'%3E%3Cstop offset='0' stop-color='%23ffe0a4' stop-opacity='0.55'/%3E%3Cstop offset='0.5' stop-color='%23ffc266' stop-opacity='0.95'/%3E%3Cstop offset='1' stop-color='%23ff9d3c' stop-opacity='0.6'/%3E%3C/linearGradient%3E%3C/defs%3E%3Cpath d='M12 0v26' stroke='%23c9a86a' stroke-opacity='0.6' stroke-width='1.3' fill='none'/%3E%3Cpath d='M12 28l6 6H6Z' fill='%23c9a86a'/%3E%3Crect x='4.5' y='34' width='15' height='2.6' rx='1.3' fill='%23d9b878'/%3E%3Cpath d='M6.5 36.6h11l1.6 19H4.9Z' fill='url(%23f)'/%3E%3Cellipse cx='12' cy='45' rx='3.1' ry='5.6' fill='%23fff3d2' fill-opacity='0.95'/%3E%3Cpath d='M6.5 36.6L4.9 55.6M17.5 36.6l1.6 19' stroke='%23a8894f' stroke-opacity='0.9' stroke-width='1.1' fill='none'/%3E%3Crect x='3.4' y='55.6' width='17.2' height='3' rx='1.5' fill='%23d9b878'/%3E%3Ccircle cx='12' cy='61' r='1.7' fill='%23c9a86a'/%3E%3C/svg%3E"),
    radial-gradient(ellipse 62% 48% at 16% 0%,
      rgba(255,198,116,0.17), rgba(255,198,116,0.05) 45%, transparent 78%),
    linear-gradient(150deg, rgba(16,26,22,0.6), rgba(7,13,11,0.6));
  border-color: color-mix(in srgb, var(--good) 40%, transparent);
}
:root[data-theme="lastthird"] .challenge-card h2,
:root[data-theme="lastthird"] .challenge-card p { color: var(--text); }
:root[data-theme="lastthird"] .challenge-card .btn.accent,
:root[data-theme="lastthird"] .btn.accent {
  /* Nothing man-made out-shines the lamps in this room. */
  background: color-mix(in srgb, var(--accent) 60%, #0c0e19);
  color: #100c04;
  border-color: transparent;
}

/* ---- a lantern inside every card ---- */
/* Each card carries its own lamp, hung in its top-left corner, and is lit
   from under it.
   Two separate things, deliberately:
     ::before  the lamp itself - inside the card, but on the negative layer
               of the card's own stacking context, so it is painted over
               the card's face and under every word on it;
     the card's own background-image - a warm pool spilling down from the
               top edge. A background image paints BEHIND the text, which
               is why the light passes through the card and lights it
               rather than washing over what is written on it. A screen-
               blended overlay would have lifted the dark card more than
               the pale text and quietly cut the contrast; this cannot.
   The glass the cards are made of is what lets the light look like it came
   from the lamp and not from inside the card. */
:root[data-theme="lastthird"] .panel,
:root[data-theme="lastthird"] .stat-card,
:root[data-theme="lastthird"] .challenge-card {
  /* The lamp is a background layer, not a ::before, and that is not a
     stylistic choice. A background paints behind the element's text by
     definition - which is the whole requirement - and it needs no stacking
     context to do it. The ::before version needed `isolation: isolate` on
     the card to sit under the text, and isolation traps a position:fixed
     DESCENDANT in the card's stacking context: the typing field pins itself
     above the keyboard at z-index 45 to clear the tab bar at 40, and once
     trapped it no longer outranked the bar at all - it was painted under
     the footer, which is exactly what got reported. No stacking context, no
     trap. */
  background-image:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='64' viewBox='0 0 24 64'%3E%3Cdefs%3E%3ClinearGradient id='f' x1='0' y1='0' x2='0' y2='1'%3E%3Cstop offset='0' stop-color='%23ffe0a4' stop-opacity='0.55'/%3E%3Cstop offset='0.5' stop-color='%23ffc266' stop-opacity='0.95'/%3E%3Cstop offset='1' stop-color='%23ff9d3c' stop-opacity='0.6'/%3E%3C/linearGradient%3E%3C/defs%3E%3Cpath d='M12 0v26' stroke='%23c9a86a' stroke-opacity='0.6' stroke-width='1.3' fill='none'/%3E%3Cpath d='M12 28l6 6H6Z' fill='%23c9a86a'/%3E%3Crect x='4.5' y='34' width='15' height='2.6' rx='1.3' fill='%23d9b878'/%3E%3Cpath d='M6.5 36.6h11l1.6 19H4.9Z' fill='url(%23f)'/%3E%3Cellipse cx='12' cy='45' rx='3.1' ry='5.6' fill='%23fff3d2' fill-opacity='0.95'/%3E%3Cpath d='M6.5 36.6L4.9 55.6M17.5 36.6l1.6 19' stroke='%23a8894f' stroke-opacity='0.9' stroke-width='1.1' fill='none'/%3E%3Crect x='3.4' y='55.6' width='17.2' height='3' rx='1.5' fill='%23d9b878'/%3E%3Ccircle cx='12' cy='61' r='1.7' fill='%23c9a86a'/%3E%3C/svg%3E"),
    radial-gradient(ellipse 62% 48% at 16% 0%,
      rgba(255,198,116,0.17), rgba(255,198,116,0.05) 45%, transparent 78%);
  background-repeat: no-repeat, no-repeat;
  /* physical left - the app is RTL, so this is the corner an Arabic line
     ends at, the emptiest part of a card's first row */
  background-position: left 14px top 0, 0 0;
  background-size: 20px 46px, auto;
}
/* The ayah is the one thing in the app the lamps are actually for, so its
   pool is warmer and reaches further down the panel. Its ::before/::after
   are the corner marks, so the lamp hangs off the panel around it. */
:root[data-theme="lastthird"] .ayah-display {
  background-image: radial-gradient(ellipse 72% 60% at 16% 0%,
    rgba(255,198,116,0.18), rgba(255,198,116,0.05) 48%, transparent 80%);
}


/* The ayah panel is what the lamps are for. The gold mushaf watermark is
   drawn for a lit page; on near-black it stops being a watermark and starts
   being a picture behind the ayah. */
:root[data-theme="lastthird"] .ayah-display {
  background-color: rgba(8,10,20,0.7);
  background-image: none;
  box-shadow: 0 0 42px rgba(232,201,138,0.10) inset;
  outline-color: color-mix(in srgb, var(--accent) 34%, transparent);
}
:root[data-theme="lastthird"] .ayah-display::before,
:root[data-theme="lastthird"] .ayah-display::after {
  border-color: color-mix(in srgb, var(--accent) 40%, transparent);
  opacity: 0.6;
}

/* The pull-to-reload badge. Parked above the top edge and brought down by
   the thumb; it is the only moving part of the gesture, which is why the
   page itself never moves. */
.pull-refresh {
  position: fixed;
  top: -44px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 9990;
  width: 38px;
  height: 38px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 999px;
  background: var(--surface);
  border: 1px solid var(--border);
  box-shadow: 0 2px 10px rgba(0,0,0,0.18);
  color: var(--muted);
  opacity: 0;
  pointer-events: none;
  transition: opacity .15s ease, color .15s ease, border-color .15s ease;
}
.pull-refresh svg { width: 19px; height: 19px; }
.pull-refresh.visible { opacity: 1; }
/* Far enough: let go and it reloads. */
.pull-refresh.ready { color: var(--primary); border-color: var(--primary); }
.pull-refresh.spinning svg { animation: pullSpin .9s linear infinite; }
@keyframes pullSpin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  .pull-refresh.spinning svg { animation: none; }
}

.points-note {
  margin: 2px 0 0;
  font-size: 0.78rem;
  text-align: center;
}
.points-note.hidden { display: none; }

/* Completing the ayah the reciter stopped in the middle of. */
.listen-finish, .listen-write-row {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  justify-content: center;
  width: 100%;
}
.listen-finish.hidden, .listen-write-row.hidden { display: none; }
.listen-write-row textarea {
  flex: 1 1 180px;
  min-width: 0;
  font-family: var(--ayah-font-family, 'Amiri', serif);
  font-size: 1.05rem;
  line-height: 1.9;
  text-align: center;
  padding: 9px 12px;
  border-radius: 10px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
  /* The height is set from the content in JS; without these the browser's
     own resize handle and scrollbar fight it every keystroke. */
  resize: none;
  overflow-y: hidden;
  outline: none;
}
.listen-write-row textarea:focus { border-color: var(--primary); }
/* Above the keyboard rather than under it, the way the typing round in الحفظ
   already works: the row leaves the flow while the field has focus, so what
   is being written stays on screen. Only on a phone - on a desktop there is
   no keyboard to be covered by. */
@media (max-width: 820px) {
  .listen-write-row.keyboard-active {
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    z-index: 45;
    background: var(--surface);
    border-top: 1px solid var(--border);
    box-shadow: 0 -4px 16px rgba(0,0,0,0.12);
    margin: 0;
    padding: 10px 16px calc(10px + env(safe-area-inset-bottom, 0px));
    flex-wrap: nowrap;
    align-items: flex-end;
  }
}

/* The seam, shown rather than hidden: the reciter's half is set back and
   the person's half is forward, so where one stopped and the other carried
   on is legible at a glance instead of being a guess. Colour alone was not
   enough - muted against text is a shade apart on a light ground and
   invisible at a glance - so the half that was recalled also carries a
   line under it. */
.word.seam-reciter { color: var(--muted); opacity: 0.62; }
.word.seam-you {
  color: var(--text);
  box-shadow: inset 0 -2px 0 color-mix(in srgb, var(--primary) 45%, transparent);
  border-radius: 2px;
}
/* And the join itself is drawn, not implied. The first word the person
   supplied carries a bar on the edge facing the reciter's half - inline-start,
   so it lands on the right in this RTL text and stays there if the line
   wraps. A width the underlines don't have and a colour the text doesn't,
   because "where did he stop?" is the one thing this screen has to answer. */
.word.seam-start {
  border-inline-start: 3px solid var(--accent);
  border-radius: 2px;
  padding-inline-start: 10px;
  margin-inline-start: 7px;
  /* Taller than the glyphs so it reads as a rule between the two halves and
     not as an ornament on the word it happens to be attached to. The
     negative block margin gives that height back, so the line spacing of
     the ayah is the same with the bar as without it. */
  padding-block: 4px;
  margin-block: -4px;
}
/* Neither recited nor written: no claim either way. */
.word.seam-untouched { color: var(--muted); opacity: 0.45; }
/* Owed: a word the writing left open - never written, or written wrong. It
   is covered and has to be answered before the grading row opens, so the
   tile carries only which of the two it is, never the word itself. The
   .masked rules give it its cover; these two only tint it. */
.word.masked.seam-owed-wrong {
  background: color-mix(in srgb, var(--danger) 14%, transparent);
  border-bottom-color: color-mix(in srgb, var(--danger) 55%, transparent);
}
.word.seam-wrong {
  color: var(--danger);
  box-shadow: inset 0 -2px 0 color-mix(in srgb, var(--danger) 70%, transparent);
  text-decoration: underline;
  text-decoration-style: wavy;
  text-underline-offset: 5px;
}

/* التفسير, on the reading box rather than in the row of buttons under it.
   It belongs to the ayah, and as an icon on the frame's left edge it stops
   competing with the controls that belong to the drill. Physical left: the
   app is RTL, so this is the far end of every line of text. */
.ayah-side-btn {
  position: absolute;
  top: 8px;
  left: 8px;
  z-index: 2;
  width: 34px;
  height: 34px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  border: 1px solid var(--border);
  background: color-mix(in srgb, var(--surface) 85%, transparent);
  color: var(--primary);
  cursor: pointer;
  padding: 0;
}
.ayah-side-btn svg { width: 17px; height: 17px; }
@media (hover: hover) { .ayah-side-btn:hover { background: var(--surface); } }

/* The two readings of one ayah, side by side in the same sheet. */
.gloss-tabs { display: flex; gap: 6px; margin-bottom: 12px; }
.gloss-tabs .btn { flex: 1; padding: 7px 10px; font-size: 0.85rem; }
.gloss-tab.active { background: var(--primary); color: #fff; border-color: var(--primary); }
.gloss-preamble { margin: 0 0 10px; }
.gloss-source { margin: 12px 0 0; font-size: 0.76rem; text-align: left; }

/* The footer follows the route: it belongs under the dashboard, not under
   every tab. */
.app-footer.hidden { display: none; }

/* The address lives in the button's href, not printed under it: a raw URL
   set as body text is something to read and re-type rather than something
   to press. */
.contact-link { display: block; margin-top: 8px; text-align: center; text-decoration: none; }

/* The completion screen. Quiet rather than confetti-loud: finishing the
   Qur'an is not a level-up. */
.khatm-panel { text-align: center; }
.khatm-panel.hidden { display: none; }
.khatm-mark {
  font-size: 2.6rem;
  color: var(--accent);
  line-height: 1;
  margin-bottom: 6px;
}
.khatm-panel h2 { font-size: 1.5rem; margin-bottom: 8px; }
.khatm-lead { font-size: 1.05rem; margin: 0 0 6px; }
.khatm-since { margin: 0 0 14px; font-size: 0.85rem; }
.khatm-hadith {
  font-family: var(--ayah-font-family, 'Amiri', serif);
  font-size: 1.2rem;
  line-height: 1.9;
  color: var(--primary);
  margin: 0 0 10px;
}
.khatm-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  justify-content: center;
  margin-top: 14px;
}

* { box-sizing: border-box; }

/* No grey/blue flash box over a button when it's tapped - the browser's
   default tap highlight, which has nothing to do with this app's colours.
   The property inherits, so setting it once covers everything. */
html { -webkit-tap-highlight-color: transparent; }

/* No scrollbar track drawn down the side of a sheet. On the page a visible
   bar is a useful affordance; inside a modal it is a hairline against the
   sheet's own edge, and it was reading as a stray border - most of all in
   settings, where the sheet is tall and the bar ran its whole height. The
   content still scrolls; only the furniture is gone. */
.settings-panel, .info-modal, .voice-modal, .modal-sheet,
.info-modal-body, #info-modal-body, .mushaf-reader {
  scrollbar-width: none;
  -ms-overflow-style: none;
}
.settings-panel::-webkit-scrollbar, .info-modal::-webkit-scrollbar,
.voice-modal::-webkit-scrollbar, .modal-sheet::-webkit-scrollbar,
.info-modal-body::-webkit-scrollbar, #info-modal-body::-webkit-scrollbar,
.mushaf-reader::-webkit-scrollbar { width: 0; height: 0; }

/* No zooming - the meta viewport says so, and this says it again to the
   touch handler, which is what actually stops a two-finger pinch and the
   double-tap zoom on Android. Panning is left alone (pan-x pan-y), so the
   page, the ayah panels and the reader all still scroll and swipe. Text
   size belongs to the setting in الإعدادات, which scales the whole
   interface together instead of leaving half of it off the screen. */
html { touch-action: pan-x pan-y; }

html, body {
  margin: 0;
  color: var(--text);
  font-family: 'Tajawal', system-ui, sans-serif;
  min-height: 100%;
}

/* What made the bottom bar look like it drifted away from the content was
   the page bouncing under the finger, not where anything was laid out: an
   overscroll carries the fixed bar with it, and lets go of it somewhere
   else. Stopping the bounce keeps the bar on the edge where it belongs -
   vertically only, so the browser's own back-swipe is untouched. The credit
   line, meanwhile, goes back to sitting right under the content: pinning it
   to the bottom of the screen opened a gulf between it and a short tab. */
html, body { overscroll-behavior-y: none; }

/* Everything in this file is sized in rem, so one step here lifts the whole
   interface together - labels, chips, buttons and the ayah text - instead
   of a dozen scattered bumps that would drift apart. Arabic needs a little
   more height than Latin at the same nominal size to stay comfortable, and
   at 16px the smaller labels were reading cramped. */
html { font-size: 17px; }

/* Nothing in this app is meant to scroll sideways - the strips that do
   (the wird week, the learn mode switch) scroll inside themselves. `clip`
   rather than `hidden` on purpose: `hidden` would turn the root into a
   scroll container and break the sticky header on wide screens. */
html { overflow-x: clip; }

/* The pattern belongs on the ROOT element, not on body. body's box is only
   as tall as its content, so a background painted there stopped partway
   down the page and left the rest bare (visible on short tabs like the
   review one). A background on the root paints the whole canvas however
   tall the viewport is; body stays transparent so it doesn't cover it. */
html { background-color: var(--bg); }
body { background-color: transparent; }

/* The pattern belongs to the page, not to the paper on it: pinned to the
   viewport, so the cards slide over a sky that stays where it is instead of
   the whole thing moving as one sheet. */
html { background-attachment: fixed; }

/* ---------- The page holds still while a modal is open ----------
   A modal is a room, not a layer of paint: scrolling inside one used to run
   out of content and hand the gesture on to the page underneath, so closing
   the modal revealed a tab scrolled somewhere else entirely. Two halves to
   stopping that, and both are needed: the page behind stops scrolling at
   all (below), and each modal's own scroller refuses to pass a gesture on
   once it reaches its end (overscroll-behavior, further down). The scroll
   position is kept, not reset - overflow:hidden freezes the document where
   it stands, and it resumes there when the modal closes.
   scrollbar-gutter keeps the desktop scrollbar's width reserved, so the
   page doesn't jump sideways the moment it stops scrolling. */
html { scrollbar-gutter: stable; }
html.modal-open, html.modal-open body { overflow: hidden; }

/* Subtle Islamic geometric pattern (8-point star tiling) behind everything -
   the default background choice (see BACKGROUNDS in app.js); the other
   purchasable patterns below replace it via [data-bg] on the root. */
/* No full-size gradient layer in front of the tile: a first layer sized
   `auto` covers the whole page opaquely and hid the pattern entirely (the
   reason "زخرفة ذهبية" looked like it did nothing at all). */
/* An open mushaf, tiled - the app's own motif, the same drawing the ayah
   panel carries as a watermark. It replaces the eight-point star these two
   backgrounds used to share. */
:root:not([data-bg]), :root[data-bg="default"] {
  background-image:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='60' height='60' viewBox='0 0 160 160'%3E%3Cg fill='none' stroke='%231f6f5c' stroke-opacity='0.13' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M80 55C68 45 50 42 33 45v66c17-3 35 0 47 10 12-10 30-13 47-10V45c-17-3-35 0-47 10Z'/%3E%3Cpath d='M80 55v66'/%3E%3Cpath d='M45 64h22M45 84h18M93 64h22M93 84h18'/%3E%3Ccircle cx='80' cy='80' r='70'/%3E%3C/g%3E%3C/svg%3E");
  background-size: 60px 60px;
}
/* These three are meant to actually read as a pattern in the narrow gaps
   between opaque panel cards (not the whole page), so - unlike the default
   above - they use noticeably higher contrast and smaller, denser tiles;
   the first attempt at this was too subtle to see there. */
:root[data-bg="geometric-gold"] {
  background-image:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='56' height='56' viewBox='0 0 160 160'%3E%3Cg fill='none' stroke='%23b98b2a' stroke-opacity='0.45' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M80 55C68 45 50 42 33 45v66c17-3 35 0 47 10 12-10 30-13 47-10V45c-17-3-35 0-47 10Z'/%3E%3Cpath d='M80 55v66'/%3E%3Cpath d='M45 64h22M45 84h18M93 64h22M93 84h18'/%3E%3Ccircle cx='80' cy='80' r='70'/%3E%3C/g%3E%3C/svg%3E");
  background-size: 56px 56px;
}
:root[data-bg="waves"] {
  background-image:
    repeating-linear-gradient(135deg, rgba(31,111,92,0.16) 0px, rgba(31,111,92,0.16) 3px, transparent 3px, transparent 16px),
    repeating-linear-gradient(45deg, rgba(31,111,92,0.13) 0px, rgba(31,111,92,0.13) 3px, transparent 3px, transparent 22px);
}
/* Interlaced arabesque: two crossing lattices at 60 degrees, which is what
   makes the woven look without drawing a single knot by hand. */
:root[data-bg="arabesque"] {
  background-image:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='80' height='80' viewBox='0 0 80 80'%3E%3Cg fill='none' stroke='%231f6f5c' stroke-opacity='0.3' stroke-width='1.6' stroke-linejoin='round'%3E%3Cpath d='M40 6 62 18v24L40 54 18 42V18Z'/%3E%3Cpath d='M40 18 51 24v12l-11 6-11-6V24Z'/%3E%3Cpath d='M40 6v12M62 18 51 24M62 42 51 36M40 54V42M18 42l11-6M18 18l11 6'/%3E%3C/g%3E%3Cg fill='%23b98b2a' fill-opacity='0.3'%3E%3Ccircle cx='40' cy='30' r='2.4'/%3E%3C/g%3E%3C/svg%3E");
  background-size: 80px 80px;
}
/* Palm fronds: a leaf drawn once - midrib and ribs either side - and tiled
   in alternating rows so it reads as fronds rather than as a stamp. */
:root[data-bg="lanterns"] {
  background-image:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='88' height='88' viewBox='0 0 88 88'%3E%3Cg fill='none' stroke='%23b98b2a' stroke-opacity='0.34' stroke-width='1.6' stroke-linejoin='round' stroke-linecap='round'%3E%3Cpath d='M44 14v6'/%3E%3Cpath d='M38 20h12l-2 4H40Z'/%3E%3Cpath d='M40 24h8l4 14-8 12-8-12Z'/%3E%3Cpath d='M40 24 36 38M48 24l4 14M36 38h16'/%3E%3Cpath d='M44 50v6'/%3E%3Cpath d='M41 56h6'/%3E%3C/g%3E%3Cg fill='%23b98b2a' fill-opacity='0.22'%3E%3Cpath d='M40 26h8l3 11-7 10-7-10Z'/%3E%3C/g%3E%3C/svg%3E");
  background-size: 88px 88px;
}
:root[data-bg="stars-scatter"] {
  background-image:
    radial-gradient(circle at 10% 20%, rgba(185,139,42,0.55) 1.8px, transparent 1.8px),
    radial-gradient(circle at 70% 60%, rgba(185,139,42,0.45) 1.4px, transparent 1.4px),
    radial-gradient(circle at 40% 80%, rgba(185,139,42,0.5) 1.6px, transparent 1.6px),
    radial-gradient(circle at 90% 15%, rgba(185,139,42,0.4) 1.4px, transparent 1.4px),
    radial-gradient(circle at 25% 45%, rgba(185,139,42,0.35) 1.4px, transparent 1.4px);
  background-size: 70px 70px, 46px 46px, 60px 60px, 50px 50px, 36px 36px;
}

h1, h2 { font-family: 'Tajawal', sans-serif; margin: 0; }

/* viewport-fit=cover lets the page reach the very top of the screen, which
   is what puts the app's own colour behind the status bar instead of a white
   band - but the header then has to keep its own contents out from under the
   clock and the notch. Hence the top inset here (zero on everything that
   doesn't have one), and the side insets for landscape, where the notch eats
   into one edge. */
.app-header {
  position: sticky;
  top: 0;
  z-index: 10;
  background: var(--surface);
  border-bottom: 1px solid var(--border);
  padding: max(12px, calc(env(safe-area-inset-top, 0px) + 2px)) calc(20px + env(safe-area-inset-right, 0px)) 12px calc(20px + env(safe-area-inset-left, 0px));
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 12px;
}

.brand {
  display: flex;
  align-items: center;
  gap: 8px;
}
.brand-icon { font-size: 1.5rem; }
/* Room above the letters for the vowels: at a tight line height the shadda
   and damma on the بّ ride into the header's edge. */
/* The app's name in the same hand as the ayat it carries - it is the one
   word on the page that should look like the book. */
.brand h1 {
  font-family: var(--ayah-font-family, 'Amiri', serif);
  font-size: 1.45rem;
  line-height: 1.6;
  color: var(--primary);
}

.tabs {
  display: flex;
  gap: 4px;
  background: var(--bg);
  border-radius: 999px;
  padding: 4px;
}

.tab-btn {
  display: flex;
  align-items: center;
  gap: 6px;
  border: none;
  background: transparent;
  padding: 8px 16px;
  border-radius: 999px;
  cursor: pointer;
  font-family: inherit;
  font-size: 0.85rem;
  color: var(--muted);
  transition: all .15s ease;
  white-space: nowrap;
}
.tab-icon { position: relative; display: inline-flex; line-height: 1; }
.tab-icon svg { width: 18px; height: 18px; }
.tab-btn.active,
:root[data-route="dashboard"] .tab-btn[data-tab="dashboard"],
:root[data-route="learn"] .tab-btn[data-tab="learn"],
:root[data-route="browse"] .tab-btn[data-tab="browse"],
:root[data-route="review"] .tab-btn[data-tab="review"] {
  background: var(--primary);
  color: white;
}

.icon-only-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  border: none;
  background: var(--bg);
  color: var(--muted);
  border-radius: 50%;
  cursor: pointer;
  transition: all .15s ease;
}
.icon-only-btn svg { width: 19px; height: 19px; }
@media (hover: hover) { .icon-only-btn:hover { color: var(--primary); background: var(--border); } }

/* The header scrolls away on phones (it isn't sticky there - see the
   media query below), which used to take settings with it. This is the
   same gear held at the same spot it occupies in the header, so it reads
   as staying put rather than as a new button appearing; it fades and
   lifts back out when the header itself comes back into view. Sits above
   the fullscreen reader (z-index 400) so it's reachable there too, and
   is hidden while any modal that should own the screen is open. */
.settings-fab {
  position: fixed;
  top: calc(10px + env(safe-area-inset-top, 0px));
  left: 14px;
  z-index: 410;
  background: var(--surface);
  border: 1px solid var(--border);
  box-shadow: var(--shadow);
  opacity: 0;
  transform: translateY(-10px) scale(0.9);
  pointer-events: none;
  transition: opacity .22s ease, transform .22s ease, top .22s ease, left .22s ease;
}
.settings-fab.visible {
  opacity: 1;
  transform: none;
  pointer-events: auto;
}
@media (prefers-reduced-motion: reduce) {
  .settings-fab { transition: opacity .01s linear; }
}

.header-left { display: flex; align-items: center; gap: 8px; }
.header-points-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  font-size: 0.8rem;
  font-weight: 700;
  line-height: 1;
  color: var(--primary);
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 5px 10px;
  white-space: nowrap;
}
/* One coin, wherever the total is reported - the header badge and the
   settings row. */
.coin-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  color: #eab308;
}
.coin-icon svg { width: 15px; height: 15px; }
/* A couple of px down: digit glyphs sit slightly high within their own
   line box compared to the icon's true visual center, so the two read as
   misaligned even though both are flex-centered on the same row. */
.header-points-badge #header-points-value { line-height: 1; position: relative; top: 2px; }

/* Bottom app-style navigation on phones/tablets: easier to reach with a thumb,
   and it keeps the brand header from competing with four tab pills for width. */
@media (max-width: 820px) {
  .app-header {
    justify-content: flex-start;
    /* max(), not a sum: the status bar is already a gap above the header's
       contents, so adding the header's own padding on top of it pushed
       everything a whole extra row down on an iPhone. Two pixels of clearance
       under the clock is enough; where there is no inset the 10px stands. */
    padding: max(10px, calc(env(safe-area-inset-top, 0px) + 2px)) calc(16px + env(safe-area-inset-right, 0px)) 10px calc(16px + env(safe-area-inset-left, 0px));
    position: relative;
  }
  /* Centred on the row, not on the box: the box now includes the status bar,
     and half of that would drop this cluster below the title beside it. */
  .header-left {
    position: absolute;
    left: calc(14px + env(safe-area-inset-left, 0px));
    top: calc(50% + max(0px, calc(env(safe-area-inset-top, 0px) + 2px - 10px)) / 2);
    transform: translateY(-50%);
  }

  .tabs {
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    z-index: 40;
    background: var(--surface);
    border-top: 1px solid var(--border);
    border-radius: 0;
    padding: 6px 4px calc(6px + env(safe-area-inset-bottom, 0px));
    justify-content: space-around;
    box-shadow: 0 -2px 12px rgba(0,0,0,0.06);
  }
  .tab-btn {
    flex: 1;
    flex-direction: column;
    gap: 3px;
    padding: 5px 2px 6px;
    font-size: 0.72rem;
    border-radius: 12px;
    color: color-mix(in srgb, var(--muted) 70%, var(--text));
  }
  .tab-icon svg { width: 23px; height: 23px; }

  /* The open tab is marked by a pill behind its icon, not by painting the
     whole cell: a solid block a quarter of the screen wide is the loudest
     thing on the page, and it marks the part of it nobody is looking at.
     The label carries the colour instead, mixed toward the theme's text so
     it reads on the bar in every theme - the maroon theme's own maroon is
     invisible on the maroon theme's own surface. */
  .tabs .tab-btn.active,
  :root[data-route="dashboard"] .tabs .tab-btn[data-tab="dashboard"],
  :root[data-route="learn"] .tabs .tab-btn[data-tab="learn"],
  :root[data-route="browse"] .tabs .tab-btn[data-tab="browse"],
  :root[data-route="review"] .tabs .tab-btn[data-tab="review"] {
    background: transparent;
    color: color-mix(in srgb, var(--primary) 55%, var(--text));
    font-weight: 700;
  }
  .tabs .tab-icon {
    padding: 4px 16px;
    border-radius: 999px;
    transition: background-color .18s ease;
  }
  /* The icon box is padded out to make the pill, so the due badge is pulled
     back in by that padding - otherwise it floats at the pill's corner
     instead of sitting on the glyph. */
  .tabs .tab-badge { inset-inline-end: 6px; top: -2px; }

  .tabs .tab-btn.active .tab-icon,
  :root[data-route="dashboard"] .tabs .tab-btn[data-tab="dashboard"] .tab-icon,
  :root[data-route="learn"] .tabs .tab-btn[data-tab="learn"] .tab-icon,
  :root[data-route="browse"] .tabs .tab-btn[data-tab="browse"] .tab-icon,
  :root[data-route="review"] .tabs .tab-btn[data-tab="review"] .tab-icon {
    background: color-mix(in srgb, var(--primary) 55%, var(--surface));
  }

  /* The clearance over the fixed bar is main#app's padding-bottom, set in
     the media block near the end of this file - after the `padding`
     shorthand below, so it is not overridden. */
}

main#app {
  max-width: 880px;
  margin: 0 auto;
  padding: 20px;
  /* The tab on its way out is positioned against this while it slides. */
  position: relative;
}

/* The open tab is stamped on <html> by the inline script in the document
   head so the right panel is in the very first paint, and switchTab keeps
   the .active class in step for the code that reads it. Both are listed:
   before the app's script has run there is only the attribute. */
.tab-panel { display: none; }
.tab-panel.active,
:root[data-route="dashboard"] #tab-dashboard,
:root[data-route="learn"] #tab-learn,
:root[data-route="browse"] #tab-browse,
:root[data-route="review"] #tab-review { display: block; animation: fadeIn .25s ease; }

@keyframes fadeIn {
  from { opacity: 0; transform: translateY(6px); }
  to { opacity: 1; transform: translateY(0); }
}

/* A swiped tab travels: the one arriving comes the whole way in from the
   edge the finger pulled it from, and the one leaving goes out the other
   side at the same time - two pages sliding past each other, not one page
   appearing where the last one was. The leaving page is taken out of the
   flow for those few frames so the two can overlap without the page
   growing. Anyone who has asked for less motion gets neither. */
@keyframes slideInFromLeft {
  from { transform: translateX(-100%); opacity: 0.35; }
  to { transform: translateX(0); opacity: 1; }
}
@keyframes slideInFromRight {
  from { transform: translateX(100%); opacity: 0.35; }
  to { transform: translateX(0); opacity: 1; }
}
@keyframes slideOutToRight {
  from { transform: translateX(0); opacity: 1; }
  to { transform: translateX(100%); opacity: 0.35; }
}
@keyframes slideOutToLeft {
  from { transform: translateX(0); opacity: 1; }
  to { transform: translateX(-100%); opacity: 0.35; }
}
/* !important because the rule that reveals a tab is written against its id
   (:root[data-route="learn"] #tab-learn) so the right panel is painted before
   any script runs - and an id beats any number of classes, so its fadeIn was
   quietly winning over this and the arriving page never moved. */
.tab-panel.active.slide-from-left { animation: slideInFromLeft .28s cubic-bezier(.22,.61,.36,1) !important; }
.tab-panel.active.slide-from-right { animation: slideInFromRight .28s cubic-bezier(.22,.61,.36,1) !important; }
/* Still on screen, no longer in the flow: same box it occupied a frame ago
   (main's padding is 20px, so the inset matches it). */
.tab-panel.leaving {
  display: block;
  position: absolute;
  top: 20px;
  inset-inline: 20px;
  pointer-events: none;
}
.tab-panel.leaving.leave-to-right { animation: slideOutToRight .28s cubic-bezier(.22,.61,.36,1) forwards; }
.tab-panel.leaving.leave-to-left { animation: slideOutToLeft .28s cubic-bezier(.22,.61,.36,1) forwards; }
@media (prefers-reduced-motion: reduce) {
  .tab-panel.active,
  .tab-panel.active.slide-from-left,
  .tab-panel.active.slide-from-right { animation: none !important; }
  .tab-panel.leaving { display: none; }
}

.panel {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 18px 20px;
  margin-bottom: 18px;
  box-shadow: var(--shadow);
}

/* ---------- Ornament that follows the chosen background/theme ----------
   Extends the vocabulary the ayah box already uses (an inset gold hairline
   and gold corner brackets) out to the cards, so picking a decorative
   background or the embroidered theme changes the app's whole frame rather
   than only what shows between the cards. Deliberately escalating: the
   plain choices stay bare, a decorative background adds one hairline, and
   the embroidered theme - the ornate one by definition - also gets the
   corners, like a Mushaf cover. Nothing here is louder than a hairline. */
:root[data-bg="geometric-gold"] .panel,
:root[data-bg="stars-scatter"] .panel,
:root[data-theme="embroidered"] .panel {
  outline: 1px solid color-mix(in srgb, var(--accent) 50%, transparent);
  outline-offset: -6px;
}
:root[data-theme="embroidered"] .panel { position: relative; }
:root[data-theme="embroidered"] .panel::before,
:root[data-theme="embroidered"] .panel::after {
  content: "";
  position: absolute;
  width: 15px;
  height: 15px;
  border: 2px solid var(--accent);
  opacity: 0.5;
  pointer-events: none;
}
:root[data-theme="embroidered"] .panel::before { top: 9px; right: 9px; border-left: none; border-bottom: none; }
:root[data-theme="embroidered"] .panel::after { bottom: 9px; left: 9px; border-right: none; border-top: none; }
:root[data-theme="embroidered"] .app-header {
  border-bottom-color: color-mix(in srgb, var(--accent) 45%, var(--border));
}

.panel h2 { font-size: 1.05rem; margin-bottom: 4px; }
.muted { color: var(--muted); font-size: 0.85rem; }

.ornament {
  text-align: center;
  color: var(--accent);
  font-size: 1.3rem;
  opacity: 0.6;
  margin: 4px 0 18px;
}

/* ---------- Daily challenge card ---------- */
.challenge-card {
  background: linear-gradient(135deg, var(--primary), var(--primary-dark));
  color: #fff;
  border-radius: var(--radius);
  padding: 20px;
  margin-bottom: 18px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
  /* Shadow in the theme's own colour, not the default theme's green - it was
     casting a teal shadow under a maroon card. */
  box-shadow: 0 4px 14px color-mix(in srgb, var(--primary) 35%, transparent);
  position: relative;
  overflow: hidden;
}
.challenge-card::after {
  content: "";
  position: absolute;
  inset: 0;
  background-image: radial-gradient(circle, rgba(255,255,255,0.12) 1px, transparent 1px);
  background-size: 14px 14px;
  pointer-events: none;
}
.challenge-text h2 { font-size: 1.1rem; margin-bottom: 6px; }
.challenge-text p { margin: 0; color: rgba(255,255,255,0.85); font-size: 0.85rem; max-width: 480px; }
.btn.accent {
  background: var(--accent);
  border-color: var(--accent);
  color: #201e17;
  font-weight: 700;
  white-space: nowrap;
}
@media (hover: hover) { .btn.accent:hover { background: var(--accent-light); } }
/* Second action on the challenge card's dark green: readable there without
   competing with the gold primary next to it. */
/* Two equal columns rather than a wrapping row: at the labels' old length
   the second button dropped to its own line and the pair read as one
   primary action with an afterthought under it. */
.challenge-actions { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.challenge-actions .btn { padding-inline: 10px; white-space: nowrap; }
.btn.challenge-secondary {
  background: rgba(255,255,255,0.14);
  border-color: rgba(255,255,255,0.45);
  color: #fff;
  white-space: nowrap;
}
@media (hover: hover) { .btn.challenge-secondary:hover { background: rgba(255,255,255,0.24); } }
/* Finished for today. It was a fixed green, which wore the wrong colour in
   every theme but the default one - and the card already says it is done in
   words. So it keeps the theme's own gradient and turns it round instead:
   the same card, settled. */
.challenge-card.done { background: linear-gradient(135deg, var(--primary-dark), var(--primary)); }

/* First run. Someone who has memorized nothing was being shown the whole
   dashboard - four zeros, an empty plan, an empty progress bar, an empty
   Mushaf map, a 0/5 ring, a commitment form - about seven screens of it,
   with the one button that actually starts anything sitting at the very
   bottom. Until there is a single ayah, every card that only means
   something once there is data is out of the way, and the starting card is
   first instead of last. The moment an ayah exists they all come back. */
:root[data-first-run="true"] .needs-data { display: none !important; }

#onboarding-panel { display: none; }
/* Both routes to the same thing: the attribute so it is there in the first
   frame, the class because renderDashboard is the authority once it runs. */
#onboarding-panel.show,
:root[data-first-run="true"] #onboarding-panel { display: block; text-align: center; }
/* It is the first thing on the screen now, so it carries the weight the
   stats grid used to. */
.onboarding-panel h2 { font-size: 1.35rem; margin-bottom: 6px; }
.onboarding-panel .onboarding-start {
  width: 100%;
  margin-top: 14px;
  padding-block: 13px;
  font-size: 1.05rem;
}
.onboarding-alt { margin-top: 12px; font-size: 0.85rem; }

.stats-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 12px;
  margin-bottom: 18px;
}
@media (max-width: 640px) {
  .stats-grid { grid-template-columns: repeat(2, 1fr); }
}

.stat-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 16px;
  text-align: center;
  box-shadow: var(--shadow);
  transition: transform .15s ease;
}
@media (hover: hover) { .stat-card:hover { transform: translateY(-2px); } }
.stat-value { font-size: 1.8rem; font-weight: 700; color: var(--primary); }
.stat-label { font-size: 0.8rem; color: var(--muted); margin-top: 4px; }
/* Second line on a stat card: the part of the number that isn't being asked
   of today (a deferred review backlog) or the honest weekly count behind a
   forgiven streak. */
.stat-note {
  margin-top: 6px;
  font-size: 0.72rem;
  color: var(--muted);
  background: color-mix(in srgb, var(--text) 6%, transparent);
  border-radius: 999px;
  padding: 2px 8px;
  display: inline-block;
}

.plan-list { display: flex; flex-direction: column; gap: 10px; margin-top: 10px; }

.plan-surah-group {
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--bg);
  overflow: hidden;
}
.plan-surah-summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 12px 14px;
  cursor: pointer;
  list-style: none;
  font-weight: 700;
  user-select: none;
}
.plan-surah-summary::-webkit-details-marker { display: none; }
.plan-surah-summary::before {
  content: "◀";
  font-size: 0.7rem;
  color: var(--accent);
  transition: transform .2s ease;
  order: 2;
  margin-inline-start: auto;
}
.plan-surah-group[open] .plan-surah-summary::before { transform: rotate(-90deg); }
.plan-surah-name { color: var(--primary); }
.plan-surah-meta { font-size: 0.78rem; font-weight: 400; color: var(--muted); display: flex; align-items: center; gap: 6px; }
.plan-surah-items {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 0 12px 12px;
}

.plan-item {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 8px;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--bg);
  transition: transform .15s ease, border-color .15s ease;
}
@media (hover: hover) { .plan-item.due-now:hover { transform: translateY(-1px); } }
.plan-item .ref { font-weight: 700; }
.plan-item.due-now { cursor: pointer; border-color: var(--primary); }
.plan-item .btn-review-now {
  flex-shrink: 0;
  font-size: 0.75rem;
  padding: 7px 12px;
  background: var(--primary);
  color: #fff;
  border-color: var(--primary);
}
/* The ayah on its own line, the ✕ beside it; everything that describes the
   ayah on the line below. */
.plan-item-text {
  display: flex;
  align-items: center;
  gap: 8px;
}
.plan-item-meta {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px 10px;
}
.plan-item-meta .ref { margin-inline-end: auto; }
.plan-item .snippet {
  font-family: var(--ayah-font-family, 'Amiri', serif);
  font-size: 1.1rem;
  line-height: 2;
  color: var(--text);
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.badge {
  font-size: 0.72rem;
  padding: 2px 8px;
  border-radius: 999px;
  white-space: nowrap;
}
/* Tinted from the theme's own surface, never a fixed pale colour: a badge
   painted #f6dede keeps that colour under a dark theme, and the text on it
   does not. */
/* The label keeps its hue but is pulled toward the theme's own text colour,
   which is what makes it land on the right side of the surface it sits on in
   every theme: an amber that reads on cream is invisible on maroon, and the
   theme's maroon primary is invisible on the maroon theme's own surface. */
.badge.due { background: color-mix(in srgb, var(--danger) 16%, var(--surface)); color: color-mix(in srgb, var(--danger) 50%, var(--text)); }
.badge.new { background: color-mix(in srgb, var(--primary) 16%, var(--surface)); color: color-mix(in srgb, var(--primary) 50%, var(--text)); }
.badge.scheduled { background: color-mix(in srgb, var(--good) 16%, var(--surface)); color: color-mix(in srgb, var(--good) 50%, var(--text)); }
.badge.temp { background: var(--border); color: var(--muted); }

.icon-btn {
  background: none;
  border: none;
  cursor: pointer;
  font-size: 1rem;
  color: var(--muted);
}
@media (hover: hover) { .icon-btn:hover { color: var(--danger); } }

.surah-progress-list { display: flex; flex-direction: column; gap: 10px; margin-top: 10px; }
.surah-progress-row { display: flex; align-items: center; gap: 10px; }
.surah-progress-row .name { width: 140px; flex-shrink: 0; font-size: 0.9rem; }
.progress-bar {
  flex: 1;
  height: 8px;
  background: var(--border);
  border-radius: 999px;
  overflow: hidden;
}
.progress-fill {
  height: 100%;
  background: var(--primary);
  width: 0%;
  transition: width .3s ease;
}
.surah-progress-row .pct { width: 48px; text-align: end; font-size: 0.8rem; color: var(--muted); }

.add-form {
  display: flex;
  gap: 14px;
  flex-wrap: wrap;
  align-items: end;
  margin-top: 12px;
}
.add-form label {
  display: flex;
  flex-direction: column;
  gap: 6px;
  font-size: 0.85rem;
  color: var(--muted);
}
.add-form input, .add-form select {
  font-family: inherit;
  padding: 8px 10px;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--bg);
  color: var(--text);
  font-size: 0.95rem;
}
.add-form input[type=number] { width: 90px; }
.add-form label.grow { flex: 1; min-width: 200px; }
.add-form input[type=text] { min-width: 160px; }

/* Searchable surah combobox - replaces a native <select>, which on some
   mobile browsers takes over the whole screen with a plain OS list. */
.combo {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 6px;
  font-size: 0.85rem;
  color: var(--muted);
  min-width: 220px;
}
.combo input[type=text] {
  font-family: inherit;
  padding: 9px 10px;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--bg);
  color: var(--text);
  font-size: 0.95rem;
}
.combo-list {
  position: absolute;
  top: calc(100% + 4px);
  right: 0;
  left: 0;
  max-height: 260px;
  overflow-y: auto;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 10px;
  box-shadow: 0 8px 24px rgba(0,0,0,0.12);
  z-index: 30;
}
.combo-item {
  padding: 9px 12px;
  font-size: 0.9rem;
  cursor: pointer;
  border-bottom: 1px solid var(--border);
}
.combo-item:last-child { border-bottom: none; }
@media (hover: hover) { .combo-item:hover { background: var(--bg); } }
.combo-empty { padding: 12px; text-align: center; }

/* ---------- Browse/add tab: grouped sections instead of two loose rows ---------- */
.browse-add-section {
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 14px 16px;
  margin-top: 14px;
}
.browse-add-section-title {
  font-size: 0.8rem;
  font-weight: 700;
  color: var(--primary);
  margin-bottom: 10px;
}
.browse-add-range-btn { width: 100%; margin-top: 12px; }
#surah-meta-info { margin-top: 8px; }

.range-inputs {
  display: flex;
  align-items: end;
  gap: 10px;
}
.range-inputs label {
  display: flex;
  flex-direction: column;
  gap: 6px;
  font-size: 0.85rem;
  color: var(--muted);
  flex: 1;
}
.range-inputs input[type=number] {
  font-family: inherit;
  padding: 8px 10px;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
  font-size: 0.95rem;
  width: 100%;
}
.range-dash { color: var(--muted); padding-bottom: 9px; }

.browse-add-divider {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 16px 0;
  color: var(--muted);
  font-size: 0.8rem;
}
.browse-add-divider::before, .browse-add-divider::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--border);
}

.search-field {
  position: relative;
  display: block;
}
.search-field-icon {
  position: absolute;
  top: 50%;
  right: 12px;
  transform: translateY(-50%);
  width: 16px;
  height: 16px;
  color: var(--muted);
  pointer-events: none;
}
.search-field-icon svg { width: 100%; height: 100%; }
.search-field input {
  width: 100%;
  font-family: inherit;
  padding: 9px 38px 9px 12px;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
  font-size: 0.95rem;
}

.browse-range-summary { font-weight: 400; color: var(--muted); font-size: 0.85rem; }

/* ---------- Settings panel ---------- */
.settings-overlay {
  position: fixed;
  inset: 0;
  overscroll-behavior: contain;
  background: rgba(20,20,15,0.45);
  /* Above the fullscreen reader (400), since settings is reachable from
     inside it via the floating gear - at 200 it opened behind the reader
     and looked like nothing had happened. Still below the info/voice
     modals (500) so a purchase confirm opens on top of settings, which is
     where it's triggered from. */
  z-index: 450;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
}
.settings-panel {
  background: var(--surface);
  border-radius: var(--radius);
  padding: 20px;
  width: 100%;
  max-width: 380px;
  max-height: 85vh;
  overflow-y: auto;
  overscroll-behavior: contain;
  box-shadow: 0 12px 40px rgba(0,0,0,0.25);
}
.settings-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 16px;
}
.settings-header h2 { font-size: 1.1rem; }
.settings-field {
  display: flex;
  flex-direction: column;
  gap: 6px;
  font-size: 0.85rem;
  color: var(--muted);
  margin-bottom: 14px;
}
/* A settings row whose control is a checkbox rather than a select: label on
   one side, switch on the other. */
/* Four age profiles as picture-cards rather than a select: this is the
   first question a new user is asked, and it should read as a choice about
   a person, not a preference buried in a dropdown. */
/* The points figure, wherever it is reported: last, small and quiet, so the
   headline stays the thing that was actually accomplished. */
/* The update offer: a toast that waits to be answered rather than one that
   fades, so its two buttons can be reached. */
.toast-update { pointer-events: auto; }
.toast-update-actions {
  display: flex;
  gap: 8px;
  margin-top: 10px;
  justify-content: center;
}
.toast-update-actions .btn { padding: 6px 14px; font-size: 0.82rem; }

.toast-note, .reward-note {
  margin-top: 4px;
  font-size: 0.75rem;
  opacity: 0.75;
}
.reward-note { color: var(--muted); }

.settings-section-note {
  margin: 0 0 12px;
  font-size: 0.78rem;
  line-height: 1.7;
  color: var(--muted);
}

/* ---------- Settings, folded ----------
   Laid out flat, the sheet was a long scroll of grids most of which are set
   once and never touched again. Each group is a drawer now: shut by
   default, its heading carrying the choice inside it, so the whole sheet
   fits on one screen and only what is being changed takes up room. */
.settings-group {
  border: 1px solid var(--border);
  border-radius: 14px;
  background: color-mix(in srgb, var(--text) 2.5%, var(--surface));
  margin-bottom: 10px;
  overflow: hidden;
}
.settings-group[open] {
  border-color: color-mix(in srgb, var(--primary) 35%, transparent);
  background: var(--surface);
}
.settings-group-summary {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 14px;
  cursor: pointer;
  list-style: none;
  user-select: none;
}
.settings-group-summary::-webkit-details-marker { display: none; }
.settings-group-summary::before {
  content: "\25C0";
  font-size: 0.7rem;
  line-height: 1;
  flex: none;
  color: var(--accent);
  transition: transform .2s ease;
}
.settings-group[open] > .settings-group-summary::before { transform: rotate(-90deg); }
@media (hover: hover) { .settings-group-summary:hover { background: color-mix(in srgb, var(--primary) 6%, transparent); } }
.settings-group-title {
  font-size: 0.9rem;
  font-weight: 700;
  color: var(--text);
  flex: none;
}
/* What is chosen inside, on the same line - the reason the drawer can stay
   shut. It gives way first when the two don't fit. */
.settings-group-value {
  margin-inline-start: auto;
  min-width: 0;
  font-size: 0.76rem;
  line-height: 1.5;
  color: var(--muted);
  text-align: end;
  /* Wraps rather than clips: a value cut off mid-word ("تنويع تلقائي · 0…")
     defeats the point of showing it at all, and a second line here is still
     shorter than opening the drawer to read it. It breaks between words
     only - "20/يوم" split across two lines would be its own puzzle. */
  overflow-wrap: normal;
}
.settings-group-body {
  padding: 0 14px 4px;
  border-top: 1px solid var(--border);
  padding-top: 12px;
}
.settings-group-body .settings-field:last-child { margin-bottom: 10px; }

/* Today's word: the meaning is the reward on show, so it gets a card of its
   own rather than a line in a list. */
.meaning-day-card { text-align: center; }
.meaning-day-word {
  font-family: var(--ayah-font-family, 'Amiri', serif);
  font-size: 2rem;
  line-height: 1.8;
  margin: 6px 0 2px;
  color: var(--primary);
}
.meaning-day-meaning {
  font-size: 1.05rem;
  margin: 0 0 8px;
  color: var(--text);
}
.meaning-day-card .muted { margin: 0; font-size: 0.78rem; }

/* The حفظ:مراجعة balance - stated, never enforced. */
/* Sat straight on the page, which on a patterned background meant reading
   small grey text through an ornament. It gets a surface of its own, like
   the footer note does. */
.balance-note {
  width: fit-content;
  max-width: 100%;
  margin: -4px auto 18px;
  padding: 8px 16px;
  border-radius: 999px;
  background: var(--surface);
  border: 1px solid var(--border);
  box-shadow: var(--shadow);
  text-align: center;
  font-size: 0.8rem;
  line-height: 1.7;
  color: var(--text);
}
.balance-note.out-of-balance {
  color: color-mix(in srgb, var(--warn) 50%, var(--text));
  border-color: color-mix(in srgb, var(--warn) 45%, transparent);
  background: color-mix(in srgb, var(--warn) 10%, var(--surface));
}

.balance-banner {
  border: 1px solid color-mix(in srgb, var(--warn) 45%, transparent);
  background: color-mix(in srgb, var(--warn) 10%, transparent);
  border-radius: 14px;
  padding: 12px 14px;
  margin-bottom: 14px;
  text-align: center;
}
.balance-banner p { margin: 0 0 10px; font-size: 0.85rem; line-height: 1.7; }
.balance-banner-actions { display: flex; gap: 10px; justify-content: center; flex-wrap: wrap; }

/* The grade the voice test points at: marked, not pressed. */
.grade-suggestion {
  margin: 0 0 10px;
  font-size: 0.8rem;
  color: var(--primary);
}
.grade-buttons button.suggested {
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary) 28%, transparent);
}

/* The due count on the bottom nav, so a queue is noticed without opening
   the tab to look. */
.tab-badge {
  position: absolute;
  top: -6px;
  inset-inline-end: -10px;
  min-width: 18px;
  height: 18px;
  padding: 0 5px;
  border-radius: 999px;
  background: var(--danger);
  color: #fff;
  font-size: 0.62rem;
  font-weight: 700;
  line-height: 18px;
  text-align: center;
}

/* One card per surah with ayahs due: what is waiting, and where. */
.review-surah-list h2 { margin-bottom: 4px; }
.review-surah-list .muted { margin-bottom: 14px; }
#review-surah-cards { display: flex; flex-direction: column; gap: 10px; }
.review-surah-card {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 14px 16px;
  border: 1px solid var(--border);
  border-radius: 14px;
  background: var(--surface);
  box-shadow: var(--shadow);
}
.review-surah-card-text { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.review-surah-card-name {
  font-family: var(--ayah-font-family, 'Amiri', serif);
  font-size: 1.25rem;
  color: var(--text);
}
.review-surah-card-count { font-size: 0.78rem; color: var(--muted); }
.review-surah-card .btn { flex-shrink: 0; }

.review-surah-banner {
  text-align: center;
  font-size: 0.85rem;
  color: var(--primary);
  background: color-mix(in srgb, var(--primary) 10%, transparent);
  border-radius: 999px;
  padding: 8px 14px;
  margin-bottom: 12px;
}

.age-mode-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 8px;
  margin: 8px 0 0;
  align-items: stretch;
}
/* Whatever follows the grid is a separate step ("ابدأ الآن"), and needs to
   read as one - butted straight against the cards it looked like a fifth
   one. */
.age-mode-grid + * { margin-top: 18px; }
.age-mode-btn {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  padding: 10px 4px;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--surface);
  color: var(--text);
  font-family: inherit;
  cursor: pointer;
  transition: border-color .15s ease, background-color .15s ease, transform .1s ease;
}
.age-mode-btn:active { transform: translateY(1px); }
.age-mode-btn.active { border-color: var(--primary); background: color-mix(in srgb, var(--primary) 12%, transparent); }
.age-mode-emoji { font-size: 1.5rem; line-height: 1.2; }
.age-mode-label { font-size: 0.8rem; font-weight: 700; }
.age-mode-hint { font-size: 0.65rem; color: var(--muted); }

.settings-toggle {
  flex-direction: row;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  cursor: pointer;
}
.settings-toggle > span { flex: 1; }
.settings-toggle small { display: block; margin-top: 4px; font-size: 0.75rem; line-height: 1.6; }
.settings-toggle input[type="checkbox"] {
  flex-shrink: 0;
  width: 22px;
  height: 22px;
  accent-color: var(--primary);
}
.settings-field select {
  font-family: inherit;
  padding: 9px 12px;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--bg);
  color: var(--text);
  font-size: 0.95rem;
}

.tafsir-fallback {
  font-size: 0.92rem;
  line-height: 1.9;
  color: var(--text);
  margin: 0;
  text-align: center;
}

.btn {
  font-family: inherit;
  padding: 9px 16px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
  cursor: pointer;
  font-size: 0.9rem;
  transition: transform .1s ease, box-shadow .15s ease;
}
@media (hover: hover) { .btn:hover { transform: translateY(-1px); } }
.btn:active { transform: translateY(0); }
.btn.primary { background: var(--primary); color: white; border-color: var(--primary); }
@media (hover: hover) { .btn.primary:hover { background: var(--primary-dark); } }

.ayah-browse-list { display: flex; flex-direction: column; gap: 4px; margin-top: 12px; }
.ayah-browse-item {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 12px 0;
  border-bottom: 1px solid var(--border);
}
.ayah-browse-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.ayah-browse-head-left { display: flex; align-items: center; gap: 8px; }
.ayah-browse-surah-badge {
  font-size: 0.75rem;
  font-weight: 700;
  color: var(--primary);
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 3px 10px;
}
.ayah-num-chip {
  flex-shrink: 0;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  background: var(--bg);
  border: 1px solid var(--border);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.75rem;
  color: var(--muted);
}
.ayah-browse-text {
  font-family: var(--ayah-font-family, 'Amiri', serif);
  font-size: 1.3rem;
  line-height: 2.2;
  width: 100%;
  margin: 0;
}
.ayah-add-btn {
  flex-shrink: 0;
  font-size: 0.75rem;
}
.ayah-add-btn.jump { border-color: var(--accent); color: var(--accent); }

.empty-state { text-align: center; padding: 40px 10px; }

.review-session { display: flex; flex-direction: column; gap: 16px; }
.review-progress { display: flex; align-items: center; gap: 12px; font-size: 0.85rem; color: var(--muted); }
.review-progress #review-progress-fill { background: var(--accent); }

.challenge-banner {
  background: var(--accent);
  color: #201e17;
  font-weight: 700;
  text-align: center;
  padding: 10px;
  border-radius: 10px;
  margin-bottom: 14px;
}

.ayah-ref, #learn-ref {
  text-align: center;
  font-weight: 700;
  color: var(--primary);
}
/* Ornate parentheses - the same glyphs traditional Mushaf typesetting uses
   around a surah name or ayah marker - for an authentic manuscript feel. */
.ayah-ref::before, #learn-ref::before { content: "\FD3F "; color: var(--accent); }
.ayah-ref::after, #learn-ref::after { content: " \FD3E"; color: var(--accent); }

.ayah-display {
  background: var(--bg);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160' viewBox='0 0 160 160'%3E%3Cg fill='none' stroke='%23b98b2a' stroke-opacity='0.16' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M80 55C68 45 50 42 33 45v66c17-3 35 0 47 10 12-10 30-13 47-10V45c-17-3-35 0-47 10Z'/%3E%3Cpath d='M80 55v66'/%3E%3Cpath d='M45 60h22M45 74h22M45 88h18M93 60h22M93 74h22M93 88h18'/%3E%3Ccircle cx='80' cy='80' r='70'/%3E%3C/g%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: center;
  background-size: 220px 220px;
  border: 2px solid var(--border);
  outline: 1px solid var(--accent);
  outline-offset: -6px;
  border-radius: var(--radius);
  padding: 30px 20px;
  min-height: 140px;
  display: flex;
  align-items: center;
  justify-content: center;
  position: relative;
}
.ayah-display::before, .ayah-display::after {
  content: "";
  position: absolute;
  width: 26px; height: 26px;
  border: 3px solid var(--accent);
  opacity: 0.7;
}
.ayah-display::before { top: 8px; right: 8px; border-left: none; border-bottom: none; }
.ayah-display::after { bottom: 8px; left: 8px; border-right: none; border-top: none; }

.ayah-text {
  font-family: var(--ayah-font-family, 'Amiri', serif);
  font-size: var(--ayah-font-size, 1.8rem);
  line-height: 2.2;
  text-align: center;
  margin: 0;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 10px 10px;
}
.word {
  display: inline-flex;
  align-items: center;
  cursor: pointer;
  transition: opacity .15s ease;
}

/* Fixed height + flex centering keeps every hidden-word tile the same size
   regardless of how tall that word's diacritics make its glyphs - without
   this, masked words render as uneven, jumbled blocks. A tinted card with a
   dotted edge rather than a flat grey block: it still reads as "tap me to
   reveal", but as part of the page instead of a hole punched in it. */
.word.masked {
  height: 1.5em;
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  border-bottom: 2px dotted color-mix(in srgb, var(--accent) 60%, transparent);
  color: transparent;
  border-radius: 7px 7px 3px 3px;
  padding: 0 10px;
  user-select: none;
}
@media (hover: hover) { .word.masked:hover { opacity: 0.7; } }
/* The masked word currently being asked about, so the four options below
   are visibly attached to one place in the ayah and not to the ayah at
   large; and one that was answered wrong, kept marked after it opens. */
.word.masked.asking {
  background: color-mix(in srgb, var(--primary) 22%, transparent);
  border-bottom-color: var(--primary);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--primary) 30%, transparent);
}
/* :not(.masked) matters: a word answered wrong is still covered until the
   right option is picked, and a colour applied while it is covered would
   override the transparent text and hand over the answer. */
.word.missed:not(.masked) { color: color-mix(in srgb, var(--danger) 55%, var(--text)); }

/* The question under the ayah: which word stood there. */
.review-choice {
  background: color-mix(in srgb, var(--primary) 5%, var(--surface));
  border: 1px solid color-mix(in srgb, var(--primary) 18%, transparent);
  border-radius: 14px;
  padding: 10px 12px 12px;
  margin-bottom: 12px;
}
.review-choice-lead {
  margin: 0 0 8px;
  font-size: 0.82rem;
  font-weight: 700;
  color: var(--primary);
  text-align: center;
}

/* A word not reached yet in the sequential modes: no tile, just the ruled
   blank it stands for, so what's left to recall reads as quiet empty space
   and the one word being asked for is the only thing competing for
   attention. */
.word.slot {
  /* Shorter than a real word: a run of blanks should read as quiet ruled
     space, not as tall empty boxes holding the panel open. */
  height: 1.05em;
  /* Inset rule rather than a full-width border: adjacent blanks would
     otherwise run together into one long line and lose the word count. */
  background-image: linear-gradient(currentColor, currentColor);
  background-size: calc(100% - 10px) 2px;
  background-position: 50% calc(100% - 3px);
  background-repeat: no-repeat;
  color: color-mix(in srgb, var(--text) 20%, transparent);
  cursor: default;
  user-select: none;
}

/* The word actually being asked for - the only lit element in the ayah. */
.word.blank {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 1.35em;
  min-width: 2.4em;
  padding: 0 6px;
  border-bottom: 3px solid var(--accent);
  border-radius: 6px 6px 2px 2px;
  background: color-mix(in srgb, var(--accent) 14%, transparent);
  color: transparent;
  animation: blankGlow 2.4s ease-in-out infinite;
}
@keyframes blankGlow {
  0%, 100% { background-color: color-mix(in srgb, var(--accent) 10%, transparent); }
  50% { background-color: color-mix(in srgb, var(--accent) 26%, transparent); }
}
@media (prefers-reduced-motion: reduce) { .word.blank { animation: none; } }

/* How far into the ayah this round has walked - a long ayah otherwise gives
   no sense of progress at all between rounds. */
.ayah-word-progress {
  height: 3px;
  max-width: 60%;
  margin: 18px auto 0;
  border-radius: 2px;
  background: color-mix(in srgb, var(--text) 10%, transparent);
  overflow: hidden;
}
.ayah-word-progress span {
  display: block;
  height: 100%;
  width: 0;
  border-radius: 2px;
  background: var(--accent);
  transition: width .35s ease;
}
.word.correct-flash { animation: correctPulse .5s ease; color: var(--good) !important; }
.word.wrong-flash { animation: shake .4s ease; }

@keyframes correctPulse {
  0% { transform: scale(1); }
  40% { transform: scale(1.25); }
  100% { transform: scale(1); }
}
@keyframes shake {
  0%, 100% { transform: translateX(0); }
  25% { transform: translateX(-6px); }
  75% { transform: translateX(6px); }
}

.mode-controls, .grade-buttons, .partial-round-buttons { display: flex; gap: 10px; flex-wrap: wrap; justify-content: center; }
.grade-controls { text-align: center; }
.grade-again { border-color: var(--danger); color: var(--danger); }
.grade-hard { border-color: var(--warn); color: var(--warn); }
.grade-good { border-color: var(--good); color: var(--good); }
.grade-easy { border-color: var(--primary); color: var(--primary); }

.reveal-controls { text-align: center; }

.hidden { display: none !important; }

/* ---------- Learn tab ---------- */
.learn-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 14px;
}
.round-dots { display: flex; gap: 6px; margin-top: 4px; }
.round-dots .dot {
  width: 12px; height: 12px;
  border-radius: 50%;
  background: var(--border);
  transition: all .2s ease;
}
.round-dots .dot.filled { background: var(--accent); transform: scale(1.15); }

/* Always a single row, even on narrow phones - it scrolls horizontally
   instead of wrapping to a second line, with the scrollbar itself hidden
   (still fully scrollable by touch/trackpad, just without the visible
   track cluttering a small card). */
.mode-switch {
  display: flex;
  flex-wrap: nowrap;
  overflow-x: auto;
  scrollbar-width: none;
  -ms-overflow-style: none;
  justify-content: center;
  gap: 6px;
  background: var(--bg);
  border-radius: 16px;
  padding: 4px;
  width: fit-content;
  max-width: 100%;
  margin: 0 auto 16px;
}
.mode-switch::-webkit-scrollbar { display: none; }
.mode-btn {
  flex-shrink: 0;
  border: none;
  background: transparent;
  padding: 8px 16px;
  border-radius: 12px;
  cursor: pointer;
  font-family: inherit;
  font-size: 0.72rem;
  color: var(--muted);
  white-space: nowrap;
}
.mode-btn .icon-label { flex-direction: column; gap: 4px; }
.mode-btn .icon-label svg { width: 20px; height: 20px; }
.mode-btn.active { background: var(--primary); color: #fff; }

.learn-ayah-display { min-height: 160px; }
.ayah-display { flex-direction: column; }
.ayah-scroll {
  width: 100%;
  /* The panel's own fade already says there is more below; a scrollbar
     column on top of it just puts a grey bar through the frame. */
  scrollbar-width: none;
  -ms-overflow-style: none;
}
.ayah-scroll::-webkit-scrollbar { width: 0; height: 0; display: none; }

.info-modal-body .meaning-chip {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  font-size: 0.85rem;
  color: var(--muted);
  padding: 8px 4px;
  border-bottom: 1px solid var(--border);
}
.info-modal-body .meaning-chip:last-child { border-bottom: none; }
.meaning-chip .mw-ar { font-family: var(--ayah-font-family, 'Amiri', serif); font-size: 1.15rem; color: var(--text); }

.learn-controls { display: flex; gap: 10px; flex-wrap: wrap; justify-content: center; margin: 14px 0; }

.icon-label { display: inline-flex; align-items: center; gap: 6px; }
.icon-label svg { width: 16px; height: 16px; flex-shrink: 0; }

/* One tray, three equal buttons. The play button used to be 54px against
   the other two at 44, which is a reasonable way to mark the primary action
   and a poor way to make a row: three circles of two sizes floating on the
   page read as an accident rather than a control. They are one size now and
   sit in a single rounded tray, so the group is the object and play is
   marked by colour instead of by being bigger than its neighbours. */
.audio-controls {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  margin: 10px auto;
  padding: 6px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--text) 4%, var(--surface));
  border: 1px solid var(--border);
}
/* inline-flex needs the row centred by its parent rather than by itself. */
.ayah-display + .audio-controls, .answer-area + .audio-controls,
.learn-audio-row, #learn-audio-controls, #review-audio-controls { align-self: center; }
#learn-audio-controls, #review-audio-controls { display: flex; width: fit-content; margin-inline: auto; }
.audio-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 46px;
  height: 46px;
  flex: 0 0 auto;
  border-radius: 50%;
  border: 1px solid transparent;
  background: transparent;
  color: var(--primary);
  cursor: pointer;
  transition: transform .1s ease, background-color .15s ease, color .15s ease;
}
.audio-btn svg { width: 20px; height: 20px; }
@media (hover: hover) { .audio-btn:hover { transform: translateY(-1px); } }
/* Same diameter as its neighbours; it is the primary action, so it is
   filled rather than enlarged. */
#learn-audio-controls-playpause, #review-audio-controls-playpause {
  background: var(--primary);
  color: #fff;
  border-color: var(--primary);
}
.audio-btn.active { background: var(--accent); color: #201e17; border-color: var(--accent); }

/* Waiting for the recitation to buffer: the play button itself spins, so
   the wait is shown where the tap happened instead of somewhere else on
   the page. */
.audio-btn.loading { cursor: progress; }
.audio-btn.loading svg { animation: audioSpin .85s linear infinite; }
@keyframes audioSpin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  .audio-btn.loading svg { animation-duration: 2.4s; }
}
@media (hover: hover) { .audio-btn.loading:hover { transform: none; } }

/* The answer sits directly under the ayah, not below the audio and lookup
   rows: the choice and the blank it fills belong to one another, and every
   control between them was distance the eye had to travel mid-recall. */
.answer-area { margin-top: 16px; }
.answer-hint {
  margin: 0 0 10px;
  text-align: center;
  font-size: 0.85rem;
  letter-spacing: .01em;
  color: var(--muted);
}

/* A fixed two-column grid rather than a wrapping row: four options in a
   ragged 3+1 read as "three, then a leftover", and every option lands in a
   predictable place instead of moving with the length of its neighbours. */
.mcq-options {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 10px;
}
.mcq-btn {
  font-family: var(--ayah-font-family, 'Amiri', serif);
  font-size: clamp(1.1rem, 4.4vw, 1.45rem);
  line-height: 1.9;
  min-height: 58px;
  padding: 6px 10px;
  border-radius: 14px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
  box-shadow: 0 1px 2px rgba(0,0,0,.04), 0 6px 16px rgba(0,0,0,.05);
  cursor: pointer;
  transition: transform .12s ease, border-color .15s ease, background-color .15s ease, box-shadow .15s ease;
}
@media (hover: hover) { .mcq-btn:hover { border-color: var(--primary); transform: translateY(-2px); } }
.mcq-btn:active { transform: translateY(1px) scale(.985); box-shadow: 0 1px 2px rgba(0,0,0,.05); }
/* Same again, and this is the one that showed: the answered option was
   painted a fixed near-white while its text stayed the theme's - under the
   embroidered theme, cream on cream. */
.mcq-btn.correct { border-color: var(--good); background: color-mix(in srgb, var(--good) 18%, var(--surface)); box-shadow: 0 0 0 3px color-mix(in srgb, var(--good) 22%, transparent); }
.mcq-btn.wrong { border-color: var(--danger); background: color-mix(in srgb, var(--danger) 18%, var(--surface)); animation: shake .4s ease; }
.mcq-btn:disabled { cursor: default; opacity: 0.75; }

/* A range already in the plan has nothing left to add, so its button says so
   and stops inviting the press. */
.browse-add-range-btn:disabled {
  cursor: default;
  background: var(--surface);
  color: var(--muted);
  border: 1px solid var(--border);
  box-shadow: none;
}

/* The تسميع round's panel: no words to show, so one quiet line about what is
   coming, centred in the frame rather than a wall of empty tiles. */
.voice-round-shape {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-height: 120px;
  font-family: 'Tajawal', sans-serif;
}
/* Where to start from, in the ayah's own script - quiet enough that it reads
   as a cue rather than as the text to recite. */
.voice-round-lead {
  font-family: var(--ayah-font-family, 'Amiri', serif);
  font-size: 1.25rem;
  line-height: 2;
  text-align: center;
  color: color-mix(in srgb, var(--text) 62%, transparent);
}
.voice-round-count {
  font-size: 1.05rem;
  font-weight: 700;
  color: color-mix(in srgb, var(--primary) 60%, var(--text));
  background: color-mix(in srgb, var(--primary) 12%, var(--surface));
  border: 1px solid color-mix(in srgb, var(--primary) 30%, transparent);
  border-radius: 999px;
  padding: 6px 18px;
}
.voice-round-last {
  font-size: 0.82rem;
  color: color-mix(in srgb, var(--warn) 50%, var(--text));
}

/* The way out of a failed recitation, under the result that explains it. */
.voice-score-area .vr-again { display: block; margin: 14px auto 0; }

/* The تسميع round: one button, and a line saying why the ayah is covered. */
.voice-round-actions { display: flex; justify-content: center; }
.voice-round-actions .btn { font-size: 1rem; padding: 12px 26px; }
.voice-round-note { text-align: center; font-size: 0.78rem; margin-top: 10px; }

.type-answer { display: flex; gap: 10px; justify-content: center; flex-wrap: wrap; }
.type-answer input {
  font-family: var(--ayah-font-family, 'Amiri', serif);
  font-size: 1.2rem;
  padding: 10px 14px;
  border-radius: 10px;
  border: 2px solid var(--border);
  background: var(--surface);
  color: var(--text);
  min-width: 220px;
  text-align: center;
}
.type-answer input.correct { border-color: var(--good); }
.type-answer input.wrong { border-color: var(--danger); animation: shake .4s ease; }

/* Pinned above the on-screen keyboard on phones instead of sitting in the
   normal page flow, where the keyboard opening could still carry the ayah
   panel above it off-screen too - but only while the input actually has
   focus (see the focus/blur listeners), not for the whole time "type" mode
   is active: fixed positioning would otherwise sit over the bottom nav
   (same screen region) and block it even when the keyboard isn't open.
   The interactive-widget=resizes-content viewport meta tag is what makes
   position:fixed land right above the keyboard rather than under it. */
@media (max-width: 820px) {
  /* The row itself leaves the flow when it pins above the keyboard, so its
     label has to go with it - left behind it would caption nothing. */
  .answer-area.pinned .answer-hint { display: none; }

  /* Four grades wrap to 3 + 1 on a phone, which reads as one of them being
     an afterthought; a 2x2 grid is the same height and deliberate. */
  .grade-buttons { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); }

  /* The answer docks to the bottom of the screen when - and only when - the
     ayah above it is tall enough to push it off. Sticky leaves a short ayah
     exactly where it is in the flow and pulls a long one up to sit above
     the bottom nav, so what you have to tap to answer is reachable whether
     the ayah is three words or thirty. */
  .answer-area {
    position: sticky;
    bottom: calc(66px + env(safe-area-inset-bottom, 0px));
    z-index: 6;
    background: var(--surface);
    border-radius: 14px;
    padding: 10px 8px 6px;
    margin-inline: -4px;
  }
  /* Reads as a dock only while it actually is one. */
  .answer-area.docked {
    box-shadow: 0 -6px 18px rgba(0,0,0,0.10);
    border: 1px solid var(--border);
  }
  /* Pinned above the keyboard, the row positions itself (see below) and
     must not also try to stick. */
  .answer-area.pinned { position: static; background: none; box-shadow: none; border: none; }
  .type-answer.keyboard-active {
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    z-index: 45;
    background: var(--surface);
    border-top: 1px solid var(--border);
    margin: 0;
    padding: 10px 16px calc(10px + env(safe-area-inset-bottom, 0px));
    box-shadow: 0 -4px 16px rgba(0,0,0,0.08);
    flex-wrap: nowrap;
  }
  .type-answer.keyboard-active input { flex: 1; min-width: 0; }
}

/* ---------- Voice recitation modal ---------- */
.voice-modal-overlay, .info-modal-overlay {
  position: fixed;
  inset: 0;
  overscroll-behavior: contain;
  z-index: 500;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  background: rgba(15,15,10,0.35);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  padding: 0;
}
@media (min-width: 640px) {
  .voice-modal-overlay, .info-modal-overlay { align-items: center; padding: 20px; }
}

.voice-modal, .info-modal {
  width: 100%;
  max-width: 480px;
  max-height: 88vh;
  overflow-y: auto;
  overscroll-behavior: contain;
  background: color-mix(in srgb, var(--surface) 82%, transparent);
  backdrop-filter: blur(18px) saturate(160%);
  -webkit-backdrop-filter: blur(18px) saturate(160%);
  border: 1px solid var(--border);
  border-radius: 22px 22px 0 0;
  box-shadow: 0 -8px 40px rgba(0,0,0,0.25);
  padding: 20px;
  padding-bottom: calc(20px + env(safe-area-inset-bottom, 0px));
}
@media (min-width: 640px) {
  .voice-modal, .info-modal { border-radius: 22px; box-shadow: 0 12px 48px rgba(0,0,0,0.3); }
}

.voice-modal-header, .info-modal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 18px;
}
.voice-modal-header h2, .info-modal-header h2 { font-size: 1.05rem; }

.info-modal-body {
  font-size: 0.95rem;
  line-height: 1.9;
  color: var(--text);
  text-align: right;
}
.info-modal-actions { display: flex; gap: 10px; justify-content: center; margin-top: 18px; }

/* ---------- Modal open/close animation (shared by all 4 overlays) ---------- */
/* A dedicated class instead of the generic .hidden utility, since that one
   uses display:none!important (needed everywhere else it's used) which
   can't be transitioned - opacity/visibility can. */
.settings-overlay, .info-modal-overlay, .voice-modal-overlay,
.mushaf-reader-overlay, .tasmee-overlay {
  opacity: 1;
  visibility: visible;
  transition: opacity .22s ease, visibility .22s ease;
}

/* Reciting to a person: the phone leaves your hands, so this is a screen
   for the listener - full text, no controls to hunt for, and one row of
   three verdicts big enough to hit without looking. */
.tasmee-overlay {
  position: fixed;
  inset: 0;
  overscroll-behavior: contain;
  z-index: 400;
  background: var(--bg);
  display: flex;
  flex-direction: column;
  padding: env(safe-area-inset-top, 0px) env(safe-area-inset-right, 0px) env(safe-area-inset-bottom, 0px) env(safe-area-inset-left, 0px);
}
.tasmee-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 14px 18px 14px calc(58px + env(safe-area-inset-left, 0px));
  border-bottom: 1px solid var(--border);
  flex-shrink: 0;
  color: var(--muted);
  font-size: 0.85rem;
}
.tasmee-body {
  flex: 1;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 16px 18px 8px;
}
.tasmee-listener-note {
  margin: 0 0 12px;
  text-align: center;
  font-size: 0.8rem;
  line-height: 1.7;
  color: var(--muted);
}
.tasmee-display { margin-bottom: 12px; }
/* ---------- خريطة الحفظ ---------- */
.mushaf-map { margin-top: 14px; }
.mushaf-map-summary {
  display: flex;
  align-items: center;
  gap: 8px;
  cursor: pointer;
  list-style: none;
  font-weight: 700;
  font-size: 0.9rem;
  color: var(--primary);
  padding: 6px 0;
  user-select: none;
}
.mushaf-map-summary::-webkit-details-marker { display: none; }
.mushaf-map-summary::before {
  content: "\25C0";
  font-size: 0.7rem;
  line-height: 1;
  color: var(--accent);
  transition: transform .2s ease;
  flex: none;
}
.mushaf-map[open] > .mushaf-map-summary::before { transform: rotate(-90deg); }
.mushaf-map-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(10px, 1fr));
  gap: 3px;
  margin: 10px 0;
  direction: rtl;
}
.map-cell {
  aspect-ratio: 1;
  border-radius: 2px;
  background: color-mix(in srgb, var(--text) 8%, transparent);
  border: 1px solid transparent;
  cursor: pointer;
}
.map-cell.l1 { background: color-mix(in srgb, var(--primary) 28%, transparent); }
.map-cell.l2 { background: color-mix(in srgb, var(--primary) 58%, transparent); }
.map-cell.l3 { background: var(--primary); }
/* The year sheet. One row per weekday, one column per week, RTL so the
   oldest week sits on the right and today at the left end - the direction
   the rest of the app reads in. */
.activity-calendar {
  display: flex;
  gap: 5px;
  margin: 10px 0 8px;
  direction: rtl;
  align-items: stretch;
}
.activity-days {
  display: grid;
  grid-template-rows: repeat(7, 1fr);
  gap: 2px;
  font-size: 0.6rem;
  color: var(--muted);
  width: 12px;
  text-align: center;
}
.activity-days span { line-height: 1; display: flex; align-items: center; justify-content: center; }
.activity-grid {
  flex: 1;
  min-width: 0;
  display: grid;
  /* Column-major: seven cells fill a week before the next week starts. */
  grid-template-rows: repeat(7, 1fr);
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
  gap: 2px;
}
.activity-cell {
  aspect-ratio: 1;
  border-radius: 2px;
  background: color-mix(in srgb, var(--text) 8%, transparent);
  cursor: pointer;
}
.activity-cell.l1 { background: color-mix(in srgb, var(--primary) 25%, transparent); }
.activity-cell.l2 { background: color-mix(in srgb, var(--primary) 48%, transparent); }
.activity-cell.l3 { background: color-mix(in srgb, var(--primary) 74%, transparent); }
.activity-cell.l4 { background: var(--primary); }
/* Days that haven't happened yet are part of the current week's column and
   have to be there for the rows to line up - just not offered as data. */
.activity-cell.future { opacity: 0.35; cursor: default; }
.activity-cell.picked { outline: 2px solid var(--accent, var(--primary)); outline-offset: 1px; }
.activity-caption { font-size: 0.8rem; min-height: 1.4em; }
.activity-legend {
  display: flex;
  align-items: center;
  gap: 4px;
  font-size: 0.72rem;
  color: var(--muted);
}
.activity-legend .activity-cell { width: 11px; height: 11px; aspect-ratio: auto; cursor: default; }

.mushaf-map-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 14px;
  font-size: 0.72rem;
  color: var(--muted);
}
.mushaf-map-legend span { display: inline-flex; align-items: center; gap: 5px; }
.mushaf-map-legend i { width: 10px; height: 10px; display: inline-block; }

.backup-row { display: flex; gap: 10px; flex-wrap: wrap; }
.backup-row .btn { flex: 1; min-width: 130px; }

/* ---------- المتشابهات ---------- */
.btn-similar {
  border-color: color-mix(in srgb, var(--warn) 45%, transparent);
  color: color-mix(in srgb, var(--warn) 50%, var(--text));
}
/* The other way to answer a review: recite the whole ayah instead of picking
   its hidden words. It is an alternative to the drill above, not another
   utility next to التفسير, so it is coloured like one. */
#btn-review-voice {
  border-color: color-mix(in srgb, var(--primary) 45%, transparent);
  color: color-mix(in srgb, var(--primary) 60%, var(--text));
}
.cmp-pair {
  margin-top: 16px;
  padding: 12px 14px;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--bg);
}
.cmp-ref { margin: 0 0 4px; font-size: 0.8rem; font-weight: 700; color: var(--primary); }
.cmp-ref.other { margin-top: 12px; color: color-mix(in srgb, var(--accent) 60%, var(--text)); }
.cmp-text {
  margin: 0;
  font-family: var(--ayah-font-family, 'Amiri', serif);
  font-size: 1.15rem;
  line-height: 2.2;
}
/* The one thing worth looking at: where the two part. */
.cmp-word.differs {
  color: color-mix(in srgb, var(--danger) 50%, var(--text));
  background: color-mix(in srgb, var(--danger) 14%, transparent);
  border-radius: 6px;
  padding: 0 3px;
  font-weight: 700;
}
.cmp-note {
  margin: 10px 0 0;
  font-size: 0.82rem;
  color: color-mix(in srgb, var(--danger) 45%, var(--text));
}

/* ---------- وصل الآيات ---------- */
.seam-overlay .tasmee-body { text-align: center; }
.seam-lead {
  font-family: 'Tajawal', sans-serif;
  font-weight: 700;
  color: var(--primary);
  margin: 0 0 6px;
}
.seam-cue-label { font-size: 0.76rem; color: var(--muted); margin: 0 0 6px; }
.seam-cue { font-size: calc(var(--ayah-font-size, 1.8rem) * 0.8); line-height: 2.1; }
.seam-display { min-height: 0; }
.seam-display .ayah-scroll { padding: 14px 16px; }
.seam-question {
  font-family: 'Tajawal', sans-serif;
  font-weight: 700;
  margin: 16px 0 10px;
}
.seam-options { display: grid; gap: 10px; }
.seam-option {
  font-family: var(--ayah-font-family, 'Amiri', serif);
  font-size: 1.15rem;
  line-height: 2;
  padding: 10px 14px;
  text-align: center;
}
.seam-option.correct {
  border-color: var(--good);
  background: color-mix(in srgb, var(--good) 18%, var(--surface));
}
.seam-option.wrong {
  border-color: var(--danger);
  background: color-mix(in srgb, var(--danger) 18%, var(--surface));
}
.seam-option:disabled { cursor: default; }
.seam-feedback {
  margin: 12px 0 0;
  font-size: 0.85rem;
  color: color-mix(in srgb, var(--primary) 50%, var(--text));
}
.seam-actions { justify-content: center; }
.seam-actions .btn { min-width: 190px; }

.tasmee-actions {
  flex-shrink: 0;
  border-top: 1px solid var(--border);
  padding: 12px 18px calc(14px + env(safe-area-inset-bottom, 0px));
  text-align: center;
  background: var(--surface);
}
.tasmee-actions .muted { margin: 0 0 10px; font-size: 0.8rem; }
.tasmee-buttons {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 10px;
}
.tasmee-buttons .btn { min-height: 52px; font-size: 1rem; }

.tasmee-entry { text-align: center; margin-bottom: 14px; }
.share-row { text-align: center; margin-bottom: 18px; }

.settings-overlay.modal-closed, .info-modal-overlay.modal-closed,
.voice-modal-overlay.modal-closed, .mushaf-reader-overlay.modal-closed,
.tasmee-overlay.modal-closed {
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
}
/* Landing straight on /settings opens the sheet in the first painted frame,
   from the attribute the document head stamps (see index.html) - the class
   below is what the app itself toggles once it has run. */
:root[data-overlay~="settings"] #settings-overlay {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
}
:root[data-overlay~="settings"] #settings-overlay .settings-panel { transform: none; }

.settings-panel, .voice-modal, .info-modal {
  transition: transform .22s ease;
}
.settings-overlay.modal-closed .settings-panel,
.voice-modal-overlay.modal-closed .voice-modal,
.info-modal-overlay.modal-closed .info-modal {
  transform: translateY(16px) scale(0.97);
}

/* ---------- Fullscreen Mushaf reader ---------- */

.mushaf-reader-overlay {
  position: fixed;
  inset: 0;
  overscroll-behavior: contain;
  z-index: 400;
  background: var(--bg);
  display: flex;
  flex-direction: column;
  padding: env(safe-area-inset-top, 0px) env(safe-area-inset-right, 0px) env(safe-area-inset-bottom, 0px) env(safe-area-inset-left, 0px);
}
.mushaf-reader-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  /* Extra room on the left for the floating settings button, which stays
     put over the reader too - so the page label never sits under it. */
  padding: 14px 18px 14px calc(58px + env(safe-area-inset-left, 0px));
  border-bottom: 1px solid var(--border);
  flex-shrink: 0;
}
.mushaf-reader-body {
  flex: 1;
  overflow-y: auto;
  overscroll-behavior: contain;
  display: flex;
  flex-direction: column;
  padding: 24px 10px;
  touch-action: pan-y;
}
/* Centering the overflowing text with align-items:center (rather than an
   auto margin on the item itself) makes the part of it above the visual
   center permanently unreachable by scrolling in most browsers - a well
   known flexbox pitfall. margin:auto still centers short pages but doesn't
   trap the top of a long one. Justified (rather than centered) so the text
   itself spans the full width of the page instead of sitting in a narrower
   centered column; text-align-last keeps the final line from stretching
   into awkward gaps like the rest of a justified paragraph would. */
.mushaf-reader-body .mushaf-text { font-size: 1.7rem; text-align: justify; text-align-last: center; margin: auto 0; }
.mushaf-bismillah {
  display: block;
  font-size: 0.85em;
  color: var(--accent);
  margin: 0 0 20px;
  text-align: center;
}
/* The ayah's number at the end of its ayah. A circle for one or two digits,
   and it lengthens into a lozenge for three rather than letting 100+ spill
   out of the ring - which is what a fixed 1.6em width did from البقرة 100
   onwards. min-width keeps the round shape where the number still fits. */
.ayah-number-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 1.7em;
  height: 1.7em;
  padding: 0 0.32em;
  font-size: 0.5em;
  line-height: 1;
  vertical-align: middle;
  border: 1.5px solid var(--accent);
  border-radius: 999px;
  color: var(--accent);
}
.mushaf-reader-footer {
  display: flex;
  gap: 10px;
  justify-content: center;
  padding: 14px 18px calc(14px + env(safe-area-inset-bottom, 0px));
  border-top: 1px solid var(--border);
  flex-shrink: 0;
}

.voice-mic-area {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  padding: 10px 0 22px;
}
.voice-mic-btn {
  position: relative;
  width: 76px;
  height: 76px;
  border-radius: 50%;
  border: none;
  background: var(--primary);
  color: white;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  box-shadow: 0 6px 18px rgba(31,111,92,0.35);
  transition: transform .15s ease;
}
.voice-mic-btn svg { width: 30px; height: 30px; position: relative; z-index: 2; }
/* While recording the button offers "stop", so it shows a stop square
   rather than still showing a microphone. Both glyphs sit in the button
   and swap on the same .listening class the recording state already
   toggles, so there's no second source of truth to keep in sync. */
.voice-mic-btn .stop-glyph { display: none; }
.voice-mic-btn.listening .mic-glyph { display: none; }
.voice-mic-btn.listening .stop-glyph { display: block; }
@media (hover: hover) { .voice-mic-btn:hover { transform: scale(1.04); } }
/* Green is "go": the microphone is live and the recitation may carry on.
   Red is "stop": it is not hearing you yet. Recording apps use red for the
   opposite, but here the question the colour answers is not "am I recording?"
   - it is "may I recite now?" */
.voice-mic-btn.listening { background: var(--good); animation: micBeat 1s ease-in-out infinite; }

.mic-pulse-ring {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  border: 2px solid var(--primary);
  opacity: 0;
}
.voice-mic-btn.listening .mic-pulse-ring {
  border-color: var(--good);
  animation: micPulse 1.6s ease-out infinite;
}
.voice-mic-btn.listening .mic-pulse-ring.delay { animation-delay: 0.6s; }

@keyframes micPulse {
  0% { transform: scale(1); opacity: 0.55; }
  100% { transform: scale(1.9); opacity: 0; }
}
@keyframes micBeat {
  0%, 100% { transform: scale(1); }
  50% { transform: scale(1.06); }
}

.voice-status-text {
  text-align: center;
  color: var(--muted);
  font-size: 0.9rem;
  min-height: 1.3em;
}

.voice-words-area {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  justify-content: center;
  padding: 14px;
  margin-bottom: 14px;
  background: var(--bg);
  border-radius: 12px;
  font-family: var(--ayah-font-family, 'Amiri', serif);
  font-size: 1.2rem;
}
.voice-word-chip {
  padding: 4px 10px;
  border-radius: 8px;
  background: var(--surface);
  border: 1px solid var(--border);
  cursor: text;
  outline: none;
}
.voice-word-chip:focus { border-color: var(--primary); }

.voice-score-area {
  padding: 14px;
  border-radius: 12px;
  background: var(--bg);
  text-align: center;
  margin-bottom: 14px;
}
.voice-score-area .vr-text { font-family: var(--ayah-font-family, 'Amiri', serif); font-size: 1.3rem; line-height: 2.2; }
.voice-score-area .vr-word.ok { color: color-mix(in srgb, var(--good) 50%, var(--text)); }
.voice-score-area .vr-word.bad { color: color-mix(in srgb, var(--danger) 50%, var(--text)); text-decoration: line-through; }
/* A word that isn't in this ayah at all - said instead of, or on top of,
   what is. Marked differently from a missed word: one is absence, the
   other is something that doesn't belong. */
.voice-score-area .vr-word.extra {
  color: color-mix(in srgb, var(--warn) 50%, var(--text));
  border-bottom: 2px dashed color-mix(in srgb, var(--warn) 60%, transparent);
}
.voice-score-area .vr-score { margin-top: 8px; font-weight: 700; color: var(--primary); }
.voice-score-area .vr-note { margin-top: 4px; font-size: 0.82rem; color: color-mix(in srgb, var(--warn) 50%, var(--text)); }
/* One word off is still fixable, so it is said in the colour of a hint
   rather than of a verdict. */
.voice-score-area .vr-fix {
  margin-top: 10px;
  font-size: 0.82rem;
  line-height: 1.7;
  color: color-mix(in srgb, var(--primary) 55%, var(--text));
  background: color-mix(in srgb, var(--primary) 8%, var(--surface));
  border: 1px solid color-mix(in srgb, var(--primary) 25%, transparent);
  border-radius: 10px;
  padding: 8px 10px;
}

/* Whether the microphone is live, said plainly - and in the one colour the
   answer is worth: red for listening, amber for the gap where nothing is
   being heard. */
.voice-listen-state {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  margin: 10px auto 0;
  padding: 5px 14px;
  border-radius: 999px;
  font-size: 0.82rem;
  font-weight: 700;
  color: color-mix(in srgb, var(--good) 50%, var(--text));
  background: color-mix(in srgb, var(--good) 14%, var(--surface));
  border: 1px solid color-mix(in srgb, var(--good) 35%, transparent);
}
.voice-listen-state::before {
  content: "";
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--good);
  animation: micDot 1.1s ease-in-out infinite;
}
.voice-listen-state.waiting {
  color: color-mix(in srgb, var(--danger) 50%, var(--text));
  background: color-mix(in srgb, var(--danger) 14%, var(--surface));
  border-color: color-mix(in srgb, var(--danger) 35%, transparent);
}
.voice-listen-state.waiting::before { background: var(--danger); animation: none; }
@keyframes micDot { 0%, 100% { opacity: 1; } 50% { opacity: 0.25; } }

.voice-breath-note {
  margin: 8px auto 0;
  max-width: 34ch;
  font-size: 0.76rem;
  line-height: 1.7;
  color: var(--muted);
  text-align: center;
}

/* The pulse rings are the microphone being live; in the gap they stop, so the
   button itself says the same thing the pill does. */
.voice-mic-btn.resuming { background: var(--danger); animation: none; }
.voice-mic-btn.resuming .mic-pulse-ring { animation: none; opacity: 0; }

.voice-modal-actions { display: flex; gap: 10px; justify-content: center; }

/* ---------- Toasts ---------- */
.toast-container {
  position: fixed;
  /* Below the notch and the clock: this is fixed to the viewport, and the
     viewport reaches under the status bar. */
  top: calc(16px + env(safe-area-inset-top, 0px));
  right: calc(16px + env(safe-area-inset-right, 0px));
  left: calc(16px + env(safe-area-inset-left, 0px));
  z-index: 1000;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  pointer-events: none;
}
.toast {
  background: var(--surface);
  color: var(--text);
  border: 1px solid var(--border);
  border-inline-start: 4px solid var(--primary);
  padding: 12px 20px;
  border-radius: 10px;
  box-shadow: 0 6px 20px rgba(0,0,0,0.15);
  font-size: 0.9rem;
  max-width: 420px;
  text-align: center;
  opacity: 0;
  transform: translateY(-14px);
  transition: all .3s ease;
}
.toast.show { opacity: 1; transform: translateY(0); }
.toast.toast-success { border-inline-start-color: var(--good); }
.toast.toast-error { border-inline-start-color: var(--danger); }

#confetti-canvas {
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 999;
  width: 100%;
  height: 100%;
}

.app-footer {
  text-align: center;
  padding: 20px;
  margin-top: 20px;
}
.link-btn {
  border: none;
  background: none;
  padding: 0;
  font-family: inherit;
  font-size: inherit;
  color: var(--primary);
  text-decoration: underline;
  cursor: pointer;
}
@media (hover: hover) { .link-btn:hover { color: var(--primary-dark); } }
.settings-legal { flex-direction: row; gap: 16px; justify-content: center; }
.footer-links { display: block; margin-top: 8px; }
.footer-links > * { margin: 0 4px; }

.info-modal-body ul { margin: 8px 0; padding-inline-start: 20px; }
.info-modal-body li { margin-bottom: 4px; }

/* Its own surface behind the text: this line sits directly on the page
   background, and against a busy pattern (the star/scatter ones) plain
   text on top of it is genuinely hard to read. */
.app-footer .muted {
  display: inline-block;
  max-width: 640px;
  margin: 0;
  padding: 10px 16px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}
/* The fixed bottom nav bar (see the phones/tablets media query above) sits
   over the last ~80px of the page, so the footer needs the same clearance
   main#app gets - otherwise its text ends up hidden behind the nav. */
@media (max-width: 820px) {
  /* The clearance over the fixed bottom bar belongs to the page, not to the
     footer. It used to be the footer's padding, which worked only for as
     long as the footer was on every tab - the moment it became
     dashboard-only, the other three tabs lost their clearance entirely and
     their last card sat under the bar with nothing left to scroll to.
     Declared after the `padding` shorthand on main#app further up, so it
     wins; the footer keeps a normal gap of its own. */
  main#app { padding-bottom: calc(120px + env(safe-area-inset-bottom, 0px)); }
  .app-footer { padding-bottom: 16px; }
}

/* On phones the header isn't sticky (see the tabs/header rules above), so
   pinning the ayah panel itself keeps it visible when the on-screen
   keyboard opens for "type the missing word" and the browser auto-scrolls
   that input into view above the keyboard - without this the scroll also
   carries the ayah text off the top of the screen. */
@media (max-width: 820px) {
  .learn-ayah-display { position: sticky; top: 0; z-index: 5; }

  /* A long ayah (البقرة 25 runs to thirty words) is taller than the phone
     screen on its own, so the answer below it - the options, or the "did I
     recall it?" buttons - ended up off the bottom of the page with no sign
     that it was there. The text keeps to its own height instead of the
     page's: it scrolls inside the panel, and what has to be reachable to
     answer always stays on screen. The frame, its corner ornaments and the
     progress bar stay put - only the text moves. The reserve below is the
     answer area plus the bottom nav; dvh (not vh) so it tracks the phone's
     collapsing URL bar. */
  /* The height itself is measured and set by fitLearnAyahHeight(): what's
     left of the screen once the rows above, the answer below and the bottom
     nav have taken theirs. The calc() here only covers the moment before
     the first measurement. */
  .learn-ayah-display .ayah-scroll {
    max-height: calc(100dvh - 480px);
    min-height: 130px;
    overflow-y: auto;
    overscroll-behavior: contain;
  }
  #review-text-container .ayah-scroll {
    max-height: calc(100dvh - 340px);
    overflow-y: auto;
    overscroll-behavior: contain;
  }
  /* Only while there's more below: a fade at the bottom edge, so a capped
     ayah reads as "continues" rather than as one that just ends. */
  .ayah-scroll.has-more {
    mask-image: linear-gradient(to bottom, #000 calc(100% - 34px), transparent);
    -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 34px), transparent);
  }
}

/* ---------- Points display & theme grid (settings panel) ---------- */

/* The label holds the right edge of the pill and the total the left, with
   the coin travelling with the number it belongs to - one group, not two
   loose items. */
.points-display-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 10px 16px;
  margin-bottom: 14px;
  font-size: 0.95rem;
  line-height: 1;
}
/* The count and the coin on one baseline. The digits are a heavier, taller
   face than the icon, so centring the boxes still left the number riding
   above the coin. */
.points-display-value {
  display: inline-flex;
  align-items: center;
  gap: 8px;
}
.points-display-row .coin-icon svg { width: 18px; height: 18px; }
.points-display-row strong {
  color: var(--primary);
  font-size: 1.1rem;
  line-height: 1;
  position: relative;
  /* The digits are a heavier, taller face than the coin beside them, so
     centring the two boxes still leaves the number riding above it. */
  top: 3px;
}

/* Shared by every cosmetic collectible grid (reciters, themes, fonts,
   backgrounds) - see renderCollectibleGrid in app.js. */
.swatch-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 10px;
  margin-top: 8px;
}
.swatch {
  font-family: inherit;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 4px;
  padding: 10px 14px;
  border-radius: 12px;
  border: 2px solid var(--border);
  background: var(--surface);
  color: var(--text);
  cursor: pointer;
  text-align: right;
  transition: transform .1s ease, border-color .15s ease;
}
@media (hover: hover) { .swatch:hover { transform: translateY(-2px); } }
.swatch.active { border-color: var(--primary); box-shadow: 0 0 0 2px var(--primary) inset; }
.swatch.locked { border-style: dashed; color: var(--muted); }
.swatch-name { font-weight: 600; font-size: 0.9rem; }
.swatch-sub { font-size: 0.75rem; color: var(--muted); }

/* Every swatch carries its own text colour, not the running theme's: these
   two are pale, and under a dark theme their labels were being drawn in that
   theme's light text - a cream card with cream writing on it. */
.swatch.theme-default { background: linear-gradient(135deg, #eef7f2, #d8ecdf); color: #22322b; }
.swatch.theme-default .swatch-sub { color: #4f6b60; }
.swatch.theme-sepia { background: linear-gradient(135deg, #f7ecd8, #ecd8ae); color: #4a3b28; }
.swatch.theme-sepia .swatch-sub { color: #6a5432; }
.swatch.theme-night { background: linear-gradient(135deg, #1a2436, #2a3a52); color: #eaf0fb; }
.swatch.theme-night .swatch-sub { color: #b7c4dd; }
.swatch.theme-forest { background: linear-gradient(135deg, #16281c, #28402e); color: #e6f3ea; }
.swatch.theme-forest .swatch-sub { color: #b9d6c2; }
.swatch.theme-embroidered { background: linear-gradient(135deg, #2b1416, #6e1620); color: #f3e3c8; }
.swatch.theme-embroidered .swatch-sub { color: #d8bd8f; }
/* The swatch has to be the theme in miniature, so it carries its own moon
   and a couple of stars rather than just the two darkest hex values. */
.swatch.theme-lastthird {
  background:
    radial-gradient(circle at 78% 26%, rgba(239,228,189,0.20), transparent 55%),
    radial-gradient(circle at 26% 58%, rgba(255,206,122,0.30), transparent 48%),
    radial-gradient(circle at 22% 26%, rgba(238,234,214,0.75) 1.1px, transparent 1.4px),
    radial-gradient(circle at 45% 78%, rgba(238,234,214,0.45) 1px, transparent 1.3px),
    linear-gradient(160deg, #101430, #05060e);
  color: #d9d2c6;
  position: relative;
}
.swatch.theme-lastthird::after {
  content: "";
  position: absolute;
  top: 9px; left: 12px;
  width: 20px; height: 20px;
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120' viewBox='0 0 120 120'%3E%3Cdefs%3E%3Cmask id='s'%3E%3Crect width='120' height='120' fill='white'/%3E%3Ccircle cx='74' cy='48' r='40' fill='black'/%3E%3C/mask%3E%3C/defs%3E%3Ccircle cx='58' cy='58' r='40' fill='%23efe4bd' mask='url(%23s)'/%3E%3C/svg%3E") center/contain no-repeat;
  pointer-events: none;
}
.swatch.theme-lastthird .swatch-sub { color: #a89f90; }
.swatch.bg-arabesque {
  background:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='80' height='80' viewBox='0 0 80 80'%3E%3Cg fill='none' stroke='%231f6f5c' stroke-opacity='0.55' stroke-width='2' stroke-linejoin='round'%3E%3Cpath d='M40 6 62 18v24L40 54 18 42V18Z'/%3E%3Cpath d='M40 18 51 24v12l-11 6-11-6V24Z'/%3E%3C/g%3E%3C/svg%3E") center/50px 50px repeat,
    var(--surface);
}
.swatch.bg-lanterns {
  background:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='88' height='88' viewBox='0 0 88 88'%3E%3Cg fill='none' stroke='%23b98b2a' stroke-opacity='0.6' stroke-width='2' stroke-linejoin='round' stroke-linecap='round'%3E%3Cpath d='M44 14v6'/%3E%3Cpath d='M38 20h12l-2 4H40Z'/%3E%3Cpath d='M40 24h8l4 14-8 12-8-12Z'/%3E%3Cpath d='M36 38h16'/%3E%3Cpath d='M44 50v6M41 56h6'/%3E%3C/g%3E%3C/svg%3E") center/52px 52px repeat,
    var(--surface);
}

/* Each swatch shows the pattern it is offering, drawn over the theme's own
   surface. The gold one used to be a fixed cream gradient - on a dark theme
   that was a pale block with pale text on it, which is why "زخرفة ذهبية" was
   unreadable in مطرز and أزرق ليلي and أخضر داكن. */
.swatch.bg-default {
  background-color: var(--surface);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='46' height='46' viewBox='0 0 160 160'%3E%3Cg fill='none' stroke='%231f6f5c' stroke-opacity='0.3' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M80 55C68 45 50 42 33 45v66c17-3 35 0 47 10 12-10 30-13 47-10V45c-17-3-35 0-47 10Z'/%3E%3Cpath d='M80 55v66'/%3E%3Cpath d='M45 64h22M45 84h18M93 64h22M93 84h18'/%3E%3Ccircle cx='80' cy='80' r='70'/%3E%3C/g%3E%3C/svg%3E");
  background-size: 46px 46px;
}
.swatch.bg-geometric-gold {
  background-color: var(--surface);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='44' height='44' viewBox='0 0 160 160'%3E%3Cg fill='none' stroke='%23b98b2a' stroke-opacity='0.42' stroke-width='4.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M80 55C68 45 50 42 33 45v66c17-3 35 0 47 10 12-10 30-13 47-10V45c-17-3-35 0-47 10Z'/%3E%3Cpath d='M80 55v66'/%3E%3Cpath d='M45 64h22M45 84h18M93 64h22M93 84h18'/%3E%3Ccircle cx='80' cy='80' r='70'/%3E%3C/g%3E%3C/svg%3E");
  background-size: 44px 44px;
}
.swatch.bg-waves { background: repeating-linear-gradient(135deg, var(--surface) 0, var(--surface) 6px, var(--border) 6px, var(--border) 8px); }
.swatch.bg-stars-scatter { background: radial-gradient(circle at 25% 30%, var(--accent) 1.5px, transparent 1.5px), radial-gradient(circle at 70% 65%, var(--accent) 1.5px, transparent 1.5px), radial-gradient(circle at 50% 90%, var(--accent) 1.5px, transparent 1.5px), var(--surface); background-size: 14px 14px, 18px 18px, 22px 22px, auto; }

.font-grid .swatch-name { font-size: 1.3rem; }

/* ---------- Daily wird card ---------- */

.wird-card { text-align: center; }

/* The when/where commitment. Deliberately quiet and text-first: it's a
   sentence the person wrote about themselves, not another counter. */
.wird-plan-card { text-align: center; }
.wird-plan-sentence {
  font-family: var(--ayah-font-family, 'Amiri', serif);
  /* It was set at ayah scale, and it is not an ayah - it is a sentence
     about an appointment, and at 1.35rem/1.9 it took two tall lines at the
     top of a card that has six other things to say. */
  font-size: 1.12rem;
  line-height: 1.75;
  margin: 4px 0 2px;
  color: var(--primary);
}
.wird-plan-sentence.hidden { display: none; }
.wird-plan-card .muted { margin: 0 0 8px; }
.wird-plan-next {
  margin: 0 0 10px;
  font-size: 0.85rem;
  color: var(--primary);
  background: color-mix(in srgb, var(--primary) 10%, transparent);
  border-radius: 999px;
  padding: 8px 16px;
  display: inline-block;
}
.wird-plan-field {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: 10px;
  margin: 12px 0;
}
.wird-plan-label { font-size: 0.85rem; color: var(--muted); }
.wird-plan-anchors {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  justify-content: center;
}
.wird-anchor-btn {
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
  font-family: inherit;
  font-size: 0.85rem;
  padding: 8px 14px;
  border-radius: 999px;
  cursor: pointer;
  transition: background-color .15s ease, color .15s ease, border-color .15s ease;
}
.wird-anchor-btn.active { background: var(--primary); color: #fff; border-color: var(--primary); }
.wird-plan-field input {
  font-family: inherit;
  font-size: 0.95rem;
  padding: 9px 12px;
  border-radius: 10px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
}
.wird-plan-field input[type="text"] { min-width: 220px; text-align: center; }
/* Where the person actually stands, right under the backup buttons. Given
   its own colour so a warning in it isn't read as more grey small print. */
.storage-status {
  margin-top: 10px;
  line-height: 1.8;
  padding: 10px 12px;
  border-radius: 10px;
  background: color-mix(in srgb, var(--primary) 7%, transparent);
  border: 1px solid var(--border);
}
#btn-protect-storage { margin-top: 10px; width: 100%; }

/* The one visible sign that a startup load failed. Warning-coloured rather
   than muted: the whole problem was that the failure was invisible. */
.connection-banner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 10px;
  margin-bottom: 16px;
  padding: 12px 14px;
  border-radius: var(--radius);
  border: 1px solid color-mix(in srgb, #b45309 45%, var(--border));
  background: color-mix(in srgb, #b45309 12%, var(--surface));
  font-size: 0.9rem;
  line-height: 1.7;
}
.connection-banner.hidden { display: none; }
.connection-banner .btn { flex: 0 0 auto; }
.connection-banner.busy { opacity: 0.6; pointer-events: none; }

/* Reviewing by ear. The ayah stays mounted underneath - masked words,
   fitted height, grade gate and all - and is simply covered, so lifting
   the veil lands on the state that was already there rather than
   rebuilding it. */
/* The veil sizes the box instead of floating over it. Laid over the ayah
   with position:absolute it had no say in how tall the panel was, so on a
   short ayah - where the panel is at its 180px floor - the lead line and
   three buttons simply hung out of the bottom of the frame. In the flow,
   the panel is as tall as whatever is inside it, and the overflow cannot
   happen by construction.
   display:none rather than visibility:hidden on the text underneath, for
   the same reason: hidden still reserves the ayah's full height, so the
   panel would be the ayah plus the veil stacked. Nothing is lost - the
   masked words and the grade gate are DOM, not layout, and the height is
   refitted when the veil lifts. */
.ayah-scroll.veiled { display: none; }
.listen-veil {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 14px;
  padding: 4px 2px;
  width: 100%;
  text-align: center;
}
.listen-veil.hidden { display: none; }
.listen-veil-lead {
  font-size: 1rem;
  line-height: 1.9;
  color: var(--text);
  margin: 0;
  max-width: 34ch;
}
.listen-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  justify-content: center;
}
/* The mode is on, and the button that turns it off should look like it. */
#btn-review-listen.active {
  background: var(--primary);
  color: #fff;
  border-color: var(--primary);
}

/* The commitment form, folded. Same disclosure the settings groups use, so
   the arrow and its rotation are already understood here. */
.wird-plan-details.hidden { display: none; }
.wird-plan-open { margin-top: 4px; }
.wird-plan-details > #wird-plan-form { margin-top: 10px; }

/* The reminder switch. A whole-row <label> so the text is part of the hit
   target - a 16px checkbox alone is a poor thing to aim a thumb at. */
.wird-notify-row {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  margin-top: 10px;
  padding: 8px 14px;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--surface);
  cursor: pointer;
  font-size: 0.95rem;
}
.wird-notify-row input { width: 18px; height: 18px; accent-color: var(--primary); cursor: pointer; }
/* Unavailable, not invisible: the note underneath is what explains why, so
   the row has to stay readable enough to be read next to it. */
.wird-notify-row.disabled { opacity: 0.55; cursor: not-allowed; }
.wird-notify-row.disabled input { cursor: not-allowed; }
.wird-notify-note {
  margin-top: 6px;
  font-size: 0.8rem;
  line-height: 1.6;
  text-align: center;
}
.wird-ics-link {
  display: block;
  margin: 2px auto 0;
  font-size: 0.82rem;
}
.wird-plan-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  justify-content: center;
  margin-top: 10px;
}
/* Three buttons on a 390px card wrap to 2 + 1, which is two rows for what
   is really one row of choices. Slightly tighter and they fit on one. */
.wird-plan-actions .btn { padding-inline: 14px; font-size: 0.88rem; }
.wird-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 10px;
  text-align: right;
}
.wird-target-control {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  font-size: 0.85rem;
  color: var(--muted);
}
.wird-target-control label { display: flex; align-items: center; gap: 6px; }
.wird-target-control input {
  width: 56px;
  font-family: inherit;
  padding: 5px 8px;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--bg);
  color: var(--text);
  text-align: center;
}
.wird-type-toggle {
  display: flex;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 3px;
}
.wird-type-btn {
  font-family: inherit;
  font-size: 0.8rem;
  padding: 5px 12px;
  border: none;
  border-radius: 999px;
  background: transparent;
  color: var(--muted);
  cursor: pointer;
  transition: all .15s ease;
}
.wird-type-btn.active { background: var(--primary); color: #fff; }
.wird-progress {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 16px;
  margin-top: 12px;
}
.wird-ring {
  width: 96px;
  height: 96px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: conic-gradient(var(--primary) 0%, var(--border) 0%);
  transition: background 0.3s ease;
}
.wird-ring span {
  width: 76px;
  height: 76px;
  border-radius: 50%;
  background: var(--surface);
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 700;
  font-size: 0.95rem;
  color: var(--primary);
}
.wird-week {
  display: flex;
  gap: 6px;
  width: 100%;
  overflow-x: auto;
  scroll-behavior: smooth;
  padding: 3px; /* keeps the "today" ring's box-shadow from being clipped at the scroll edges */
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none; /* Firefox */
  -ms-overflow-style: none; /* old Edge */
}
.wird-week::-webkit-scrollbar { display: none; }
.wird-day {
  flex-shrink: 0;
  min-width: 62px;
  height: 38px;
  border-radius: 999px;
  padding: 0 12px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.78rem;
  border: 1px solid var(--border);
  background: var(--bg);
  color: var(--muted);
  transition: background-color .2s ease, box-shadow .2s ease, color .2s ease;
}
.wird-day.met { background: var(--primary); border-color: var(--primary); color: #fff; }
.wird-day.today { box-shadow: 0 0 0 2px var(--accent); }
/* A day that has not arrived yet: present in the week, making no claim
   about itself. */
.wird-day.upcoming { opacity: 0.45; border-style: dashed; }

.overall-progress-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 10px;
  gap: 10px;
}
.overall-progress-header h2 { margin: 0; }
.overall-progress-since { margin: 8px 0 0; }
.wird-header h2 { margin: 0 0 2px; }
#wird-today-date { margin: 0; font-size: 0.8rem; }

/* ---------- Mushaf reading (logs wird independently of memorization) ---------- */

.mushaf-mode-toggle {
  display: flex;
  gap: 6px;
  background: var(--bg);
  border-radius: 999px;
  padding: 4px;
  width: fit-content;
  margin-top: 10px;
}
.mushaf-mode-btn {
  border: none;
  background: transparent;
  padding: 6px 14px;
  border-radius: 999px;
  cursor: pointer;
  font-family: inherit;
  font-size: 0.82rem;
  color: var(--muted);
}
.mushaf-mode-btn.active { background: var(--primary); color: #fff; }

.mushaf-controls { display: flex; flex-direction: column; gap: 12px; margin-top: 10px; }
.mushaf-controls .combo { min-width: 0; }
.mushaf-controls .combo-list { z-index: 25; }
.mushaf-page-field {
  display: flex;
  flex-direction: column;
  gap: 6px;
  font-size: 0.85rem;
  color: var(--muted);
}
.mushaf-page-field input {
  font-family: inherit;
  padding: 9px 10px;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--bg);
  color: var(--text);
  font-size: 0.95rem;
}
.mushaf-load-btn { width: 100%; margin-top: 12px; }
.mushaf-text {
  font-family: var(--ayah-font-family, 'Amiri', serif);
  font-size: 1.5rem;
  line-height: 2.1;
  color: var(--text);
  margin: 0 0 16px;
}

/* ---------- Surah info caption & persistent tafsir panel ---------- */

.surah-info-caption {
  font-size: 0.8rem;
  color: var(--muted);
  margin: 2px 0 0;
}

