/* =========================================
   SADHANA, cross-platform web app styles
   Adapted from the Sadhana UI Kit (V9)
   ========================================= */

/* Every font-size in this file (and every other stylesheet in web/) is
   expressed in rem, converted 1:1 from the original px values on the
   assumption that 1rem = 16px — so at this default root size, nothing
   changes: it renders pixel-identical to the old px design. What changes is
   what happens BELOW that reference width. The design was tuned against an
   iPhone in the 390-393px class; a lot of Android phones sold today
   (Motorola's G-series among them) report a CSS viewport width of 360px or
   less. Every font-size in the app was fixed in px, so on a narrower phone
   the exact same text took up a larger fraction of a smaller screen: lines
   wrapped that weren't supposed to, beats in the lesson player overflowed
   into their own inner scroll far more often than the design intended
   (see lesson-player.css's .slp-beat-scroll), and fixed-height screens like
   the onboarding plan card ran past the bottom edge. Text size was not a
   symptom, it was the mechanism: shrinking the root proportionally on
   narrow viewports gets everything (via rem) to fit the way it does on the
   reference width, without touching any of the 896 individual declarations
   that already carry the exact original design values.
   4.0712vw is calibrated so the clamp reaches the 16px ceiling exactly at
   393px (that reference iPhone width) and holds there for anything wider —
   phones and the tablet cap in the media query below both stay at the
   original, unscaled design. Below 393px it shrinks smoothly; the 13px
   floor (81.25% of reference) is where a ~320px phone lands, which is about
   as narrow as anything still sold gets, and is chosen to stay short of
   where text becomes uncomfortable to read rather than to hit any exact
   device's width. Only font-size was converted, not padding/margin/width —
   so tap targets and layout boxes stay full-size and simply get MORE
   breathing room around smaller text on a narrow phone, which is what
   actually relieves the overflow rather than just shrinking everything
   uniformly and leaving the same proportion problem. */
html { font-size: clamp(13px, 4.0712vw, 16px); }

* { box-sizing: border-box; margin: 0; padding: 0; -webkit-tap-highlight-color: transparent; }


/* Buttons do not inherit font-family. Every interactive element in this app is a
   <button>, and without this they render in the UA's 13.33px Arial rather than
   the app's type. It went unnoticed because most buttons set their own
   background and border, so only the typeface was wrong and it read as "a
   slightly different font" rather than as a bug. .list-row was the exception
   that made it obvious: written for a <div>, it set no background, border or
   font at all, so ten rows in the Library painted raw UA chrome, 2px outset
   black on #EFEFEF buttonface. buttonface is not theme-aware, so those rows
   stayed light grey with black text in dark mode, on a #1A1917 page.

   font-family only, deliberately. `font: inherit` would also flatten size and
   weight, which the button classes below set for themselves. */
button, input, select, textarea, optgroup { font-family: inherit; }

/* NEVER set a text input below 16px. iOS (WKWebView included) zooms the whole
   page in when a field smaller than that takes focus, and it never zooms back
   out — see the same note at app.js:3190, which fixed one field and missed the
   rest. The zoom is global and sticky for the life of the process, so the
   symptom does not look like a font bug at all: every screen in the app is
   suddenly cut off at the right edge and the page can be panned sideways.
   Capacitor disables pinch-zoom on iOS, so the user cannot even undo it.
   15px inputs cost us exactly that, app-wide, until 2026-08-09. */

/* Color/shadow/radius tokens live in theme.css (single source of truth).
   Only typography + motion tokens are defined here. */
:root {
  --font-sans: 'Plus Jakarta Sans', 'Nunito', system-ui, sans-serif;
  --font-title: 'Outfit', 'Plus Jakarta Sans', 'Nunito', sans-serif;
  --font-verse: 'Plus Jakarta Sans', 'Nunito', sans-serif;
  --transition-spring: transform 0.28s cubic-bezier(0.34, 1.56, 0.64, 1), box-shadow 0.25s ease, opacity 0.25s ease, border-color 0.2s ease, background-color 0.2s ease;
}

/* The Karana Plus mark: the same /assets/KaranaPlus_WhiteLogo.png masked shape
   Today's own premium button uses (#td-premium-open), as a small inline badge
   for any row/card that's Plus-only content — prayers, chapters, sadhana
   builder rows. Two colourways, because the mark sits on two different
   backgrounds depending on where it's used: .plus-mark is gold-on-transparent
   for a plain dark/neutral row background; .plus-mark-dark is the same mark
   in the existing gold-badge's own ink colour (#4A2F00), for the spots (the
   roadmap's episode Plus badge) whose badge background is ALREADY that same
   gold gradient — a gold-on-gold mark there would be invisible. */
.plus-mark, .plus-mark-dark {
  display: inline-block;
  flex: 0 0 auto;
  width: 15px;
  height: 15px;
  -webkit-mask: url(/assets/KaranaPlus_WhiteLogo.png) center / contain no-repeat;
  mask: url(/assets/KaranaPlus_WhiteLogo.png) center / contain no-repeat;
}
/* Row badges (audio track list, sadhana builder picker rows) at 2x the base
   size — the base 15px read as too small next to a play/add icon. */
.plus-mark { width: 30px; height: 30px; background: linear-gradient(135deg, #FFEFB0, #D4A852, #B28833); }
.plus-mark-dark { background: #4A2F00; }

/* Android's WebView boosts text size to match the system "Font size" display
   setting unless a page opts out, even for a page that already sets its own
   fixed px sizes everywhere. iOS never does this, so it shipped unnoticed
   until a device with a larger-than-default system font size (Samsung ships
   several models that default above 100%) rendered every screen oversized
   and reflowed. This app supplies its own type scale throughout; it does not
   want the OS re-scaling on top of it. */
html { -webkit-text-size-adjust: 100%; text-size-adjust: 100%; }

html, body { height: 100%; }

body {
  font-family: var(--font-sans);
  background-color: var(--bg-app);
  color: var(--text-main);
  transition: background-color .4s cubic-bezier(0.16, 1, 0.3, 1), color .4s cubic-bezier(0.16, 1, 0.3, 1);
  -webkit-font-smoothing: antialiased;
  /* NO DOUBLE-TAP ZOOM, BUT PINCH-ZOOM SURVIVES. The app is a webview, so a
     double tap anywhere zoomed the page like a website, which is not something
     an app does and made mistaps look like breakage.

     `manipulation` is specifically "panning and pinch zoom only": it drops the
     double-tap gesture and the 300ms click delay that comes with waiting to see
     whether a second tap arrives, and leaves pinch alone.

     DELIBERATELY NOT `user-scalable=no` / `maximum-scale=1` on the viewport meta,
     which is the other way to kill double-tap. That also kills PINCH, and the
     comment on the meta tag in index.html records enabling pinch as a considered
     WCAG 1.4.4 decision: a reader has to be able to enlarge Devanagari. Fixing a
     polish complaint by removing an accessibility affordance would be a bad
     trade, so the narrower tool is the right one. */
  touch-action: manipulation;
}

.material-symbols-rounded {
  font-family: 'Material Symbols Rounded';
  font-weight: normal;
  font-style: normal;
  font-size: 1.5rem;
  line-height: 1;
  letter-spacing: normal;
  text-transform: none;
  display: inline-block;
  white-space: nowrap;
  word-wrap: normal;
  direction: ltr;
  -webkit-font-feature-settings: 'liga';
  -webkit-font-smoothing: antialiased;
  font-variation-settings: 'FILL' 1, 'wght' 500, 'GRAD' 0, 'opsz' 24;
  user-select: none;
}

/* App shell: full viewport on mobile; centered phone column on desktop */
.app {
  position: relative;
  width: 100%;
  max-width: 480px;
  height: 100dvh;
  margin: 0 auto;
  background: var(--bg-app);
  display: flex;
  flex-direction: column;
  /* The status-bar inset lives HERE, on the non-scrolling frame, not on
     .app-content. As padding on the scroll container it scrolled away with the
     content, so every page header ended up drawn over the clock and the
     Dynamic Island. .detail, .player and #auth-overlay are position:absolute
     inset:0 against this element's PADDING box, so they still cover the full
     frame and are unaffected; .bottom-nav's bottom offset is measured from the
     same box, so it does not move either. */
  padding-top: max(48px, env(safe-area-inset-top));
  /* clip (not hidden): focus/scrollIntoView can programmatically scroll an
     overflow:hidden box, dragging the offscreen player/detail into view. */
  overflow: clip;
}
/* The phone frame. This exists so the app looks deliberate in a DESKTOP BROWSER
   rather than stretching a one-column layout across 1400px. It must never apply
   inside the native wrapper: on an iPad it drew a 480x852 fake phone with a
   black bezel floating in the middle of a real device, which is both absurd and
   the kind of thing review reads as an unported phone app. */
@media (min-width: 520px) {
  body { display: flex; align-items: center; justify-content: center; min-height: 100dvh; padding: 24px; }
  .app {
    height: 852px;
    min-height: 0;
    border-radius: 44px;
    border: 12px solid #141414;
    box-shadow: 0 30px 60px -15px rgb(0 0 0 / .35);
    overflow: clip;   /* not `hidden`, see the base .app rule */
  }
  /* Inside the wrapper the device IS the frame, so no bezel/border/shadow —
     see the note above about the fake-phone-in-a-bezel bug on iPad. But every
     screen in this app is still authored as a single 480px mobile column, so
     letting .app run the full width of an iPad/tablet does not "scale" it,
     it just stretches one narrow column across a much wider frame: card
     grids go one-item-wide with huge gaps, text lines run far past a
     comfortable reading width, and full-bleed buttons look absurd at 10+
     inches. Centering a capped column here is the same trick the desktop
     browser frame above already uses, minus the bezel — deliberate
     letterboxing that reads as "designed for this size" rather than a phone
     layout that forgot to check for a tablet. surface-alt (not the page
     background) fills the gutters so they read as part of the app's own
     surface hierarchy rather than as dead/broken space. */
  html.is-native body { display: block; padding: 0; background: var(--surface-alt); }
  html.is-native .app {
    height: 100%; min-height: 100dvh; max-width: 680px;
    margin: 0 auto;
    border: 0; border-radius: 0; box-shadow: none;
  }

  /* IMMERSIVE LAYERS BREAK OUT OF THE READING COLUMN.
     The 680px cap above is right for the tabbed content, but these are the
     full-bleed layers: .slp-overlay is the scroll-lesson (lesson-player.css)
     and .detail is the reader/player. Those are
     authored edge-to-edge, so capping them is exactly what reads as "phone app
     letterboxed onto an iPad" -- a dark illustration in a narrow strip with
     surface-alt bands down both sides. inset:0 resolves against .app, so the
     fix is to resolve against the viewport instead.
     This only works because nothing between here and the viewport sets a
     transform or filter -- see the note on .page's entrance animation, which is
     opacity-only for this same reason. If that ever changes, this silently
     falls back to the narrow column. */
  html.is-native .detail,
  html.is-native .slp-overlay { position: fixed; }
}

.app-content {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overflow-x: hidden;
  /* Top inset moved to .app above so it cannot scroll away. Bottom is the
     floating .bottom-nav's real footprint (~86px) plus clearance — at the old
     24px the last card on every tab sat permanently behind the bar and could
     not be scrolled clear of it. Same value .detail-body already uses. */
  padding: 0 22px calc(120px + env(safe-area-inset-bottom)) 22px;
  scrollbar-width: none;
}
.app-content::-webkit-scrollbar { display: none; }

/* Pages: the five bottom-nav tabs. A `display:none` -> displayed transition
   restarts a running CSS animation from its start per spec, so switching
   tabs replays this with no JS hook needed — same trick as the Audio tab's
   own .audio-cat-grid/.audio-album entrance (audioPanelIn, below). Shorter
   duration and a smaller translate than that one on purpose: a bottom-nav
   tab switch should feel instantaneous, not like opening a new screen.
   Opacity only, deliberately no transform: ANY transform value other than
   literally `none` — including a finished animation's own last keyframe,
   held by fill-mode:both, even one as inert as translateY(0) scale(1) —
   makes the element a new containing block for position:fixed descendants.
   #modal-me-settings/#modal-me-library (.quick-action-modal, position:fixed;
   inset:0) live inside #profile, a .page, so as long as this animation
   touched transform the modal and its dim resolved against #profile's own
   (narrower, shorter, padded) box instead of the real viewport — the "dim
   doesn't reach the edges, the sheet doesn't reach full width" bug. */
.page { display: none; animation: pageIn .18s cubic-bezier(0.16, 1, 0.3, 1) both; }
.page.active { display: block; }
@keyframes pageIn {
  from { opacity: 0; }
  to   { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .page { animation: none; }
}
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }

/* A jump straight to a tab from content on another one (see go()'s `slide`
   option) — a bit more motion than the base .page fade above, so the
   destination visibly arrives rather than just popping in. Same class
   name/selector wins over .page's own `animation` shorthand by simply
   coming later in the cascade (equal specificity, later declaration). */
.page.page-slide-in { animation: pageSlideIn .32s cubic-bezier(0.16, 1, 0.3, 1) both; }
@keyframes pageSlideIn {
  from { opacity: 0; transform: translateX(28px); }
  to   { opacity: 1; transform: translateX(0); }
}
@media (prefers-reduced-motion: reduce) {
  .page.page-slide-in { animation: none; }
}

/* Typography */
h1 { font-family: var(--font-title); font-size: 1.75rem; font-weight: 900; margin-bottom: 6px; letter-spacing: -0.5px; line-height: 1.2; }
h2 { font-family: var(--font-title); font-size: 1.3125rem; font-weight: 800; margin-top: 10px; margin-bottom: 16px; letter-spacing: -0.2px; }
h3 { font-family: var(--font-sans); font-size: 1rem; font-weight: 800; margin-bottom: 4px; }
p  { color: var(--text-muted); font-size: 0.9375rem; line-height: 1.55; font-weight: 600; }

/* Today Header */
.today-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 28px;
  animation: slideDown .5s cubic-bezier(0.16, 1, 0.3, 1);
}
@keyframes slideDown { from { opacity: 0; transform: translateY(-10px); } to { opacity: 1; transform: translateY(0); } }

.today-greeting {
  flex: 1;
}
.welcome-title {
  font-size: 1.5625rem;
  margin-bottom: 4px;
  font-family: var(--font-title);
}
.welcome-title span {
  background: var(--gradient-sunset);
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  font-weight: 900;
}
.welcome-subtitle {
  font-size: 0.875rem;
  color: var(--text-muted);
  font-family: var(--font-sans);
  font-weight: 700;
}

/* ============================================================
   HALLOW-STYLE HOME DASHBOARD & SWIPEABLE HERO CAROUSEL
   ============================================================ */
#today {
  position: relative;
}
.hallow-home-header {
  position: absolute;
  top: 24px;
  left: 22px;
  right: 22px;
  z-index: 20;
  display: flex;
  align-items: center;
  justify-content: space-between;
  pointer-events: none;
}
.hallow-home-header button {
  pointer-events: auto;
}
.hallow-page-title {
  font-family: var(--font-title);
  font-size: 1.875rem;
  font-weight: 900;
  color: #fff;
  margin: 0;
  letter-spacing: -0.6px;
  text-shadow: 0 2px 10px rgba(0, 0, 0, 0.75);
}
.hallow-header-icons {
  display: flex;
  align-items: center;
  gap: 10px;
}
.hallow-top-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 5px;
  height: 40px;
  min-width: 40px;
  padding: 0 12px;
  border-radius: 99px;
  background: rgba(0, 0, 0, 0.38);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  border: 1px solid rgba(255, 255, 255, 0.18);
  color: #fff;
  font-family: var(--font-sans);
  font-weight: 800;
  font-size: 0.875rem;
  cursor: pointer;
  transition: background 0.2s, transform 0.1s;
}
.hallow-top-btn:active {
  transform: scale(0.95);
  background: rgba(255, 255, 255, 0.22);
}
.hallow-top-btn .material-symbols-rounded {
  font-size: 1.25rem;
}
.hallow-top-btn.streak-btn {
  color: #FFD9C4;
  border-color: rgba(255, 217, 196, 0.35);
  background: rgba(212, 93, 53, 0.45);
}
.hallow-top-btn.avatar-btn {
  width: 40px;
  height: 40px;
  padding: 0;
  background: var(--brand-primary);
  font-weight: 850;
  font-size: 0.9375rem;
}

/* Horizontal swipeable hero carousel, flush full bleed to all screen edges */
.hallow-hero-carousel {
  display: flex;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  gap: 0;
  margin: -48px -22px 20px -22px;
  padding: 0;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}
.hallow-hero-carousel::-webkit-scrollbar {
  display: none;
}
.hallow-hero-card {
  flex: 0 0 100%;
  width: 100%;
  scroll-snap-align: center;
  height: 54vh;
  min-height: 430px;
  max-height: 560px;
  border-radius: 0;
  position: relative;
  overflow: hidden;
  cursor: pointer;
  box-shadow: 0 18px 40px rgba(0, 0, 0, 0.45);
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  padding: 82px 24px 28px;
  border: none;
  transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.hallow-hero-card:active {
  transform: scale(0.985);
}
.hallow-card-bg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  z-index: 0;
  transition: transform 6s ease-out;
}
.hallow-hero-card:hover .hallow-card-bg {
  transform: scale(1.04);
}
.hallow-card-overlay {
  position: absolute;
  inset: 0;
  z-index: 1;
  background: linear-gradient(
    180deg,
    rgba(0, 0, 0, 0.18) 0%,
    rgba(0, 0, 0, 0.25) 40%,
    rgba(0, 0, 0, 0.82) 75%,
    var(--bg-app) 100%
  );
  pointer-events: none;
}


/* Center circular play button */
.hallow-center-play {
  position: relative;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: center;
  flex: 1;
}
.hallow-play-circle {
  width: 72px;
  height: 72px;
  border-radius: 50%;
  background: #ffffff;
  color: #111111;
  border: none;
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 12px 30px rgba(0, 0, 0, 0.5);
  cursor: pointer;
  transition: transform 0.15s ease, background 0.15s ease;
}
.hallow-play-circle:hover {
  transform: scale(1.08);
  background: #f8fafc;
}
.hallow-play-circle:active {
  transform: scale(0.94);
}
.hallow-play-circle .material-symbols-rounded {
  font-size: 2.375rem;
  margin-left: 3px;
}

/* Bottom text area inside card */
.hallow-card-bottom {
  position: relative;
  z-index: 2;
  display: flex;
  flex-direction: column;
  gap: 5px;
}
.hallow-card-badge {
  font-size: 0.6875rem;
  font-weight: 850;
  letter-spacing: 1.2px;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.75);
  margin: 0;
}
.hallow-card-title {
  font-family: var(--font-title);
  font-size: 1.6875rem;
  font-weight: 900;
  color: #ffffff;
  line-height: 1.15;
  margin: 0;
  letter-spacing: -0.3px;
  text-shadow: 0 2px 10px rgba(0, 0, 0, 0.8);
}
.hallow-card-subtitle {
  font-size: 0.84375rem;
  font-weight: 650;
  color: rgba(255, 255, 255, 0.85);
  margin: 0;
}

/* Pagination dots indicator below carousel */
.hallow-carousel-dots {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  margin-bottom: 24px;
}
.hallow-dot {
  width: 6px;
  height: 6px;
  border-radius: 99px;
  background: rgba(255, 255, 255, 0.28);
  transition: width 0.25s ease, background 0.25s ease;
}
.hallow-dot.active {
  width: 22px;
  background: #ffffff;
}

/* Hallow Quick Actions Row */
.hallow-quick-actions {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 12px;
  margin-bottom: 28px;
}
.hallow-action-btn {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 14px 6px 12px;
  border-radius: 22px;
  background: var(--surface);
  border: 1px solid var(--border);
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.06);
  cursor: pointer;
  transition: transform 0.18s ease, background 0.18s ease, border-color 0.18s ease;
  user-select: none;
}
.hallow-action-btn:hover {
  transform: translateY(-2px);
  border-color: var(--brand-primary);
}
.hallow-action-btn:active {
  transform: scale(0.96);
}
.hallow-action-icon {
  position: relative;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  background: var(--surface-alt);
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--text-main);
  transition: background 0.2s ease, color 0.2s ease;
}
.calendar-badge-dot {
  position: absolute;
  top: 1px;
  right: 1px;
  width: 11px;
  height: 11px;
  border-radius: 50%;
  background: var(--brand-primary);
  border: 2px solid var(--surface);
  box-shadow: 0 0 8px rgba(248, 94, 0, 0.65);
}
.hallow-action-btn:hover .hallow-action-icon {
  background: var(--brand-primary);
  color: #fff;
}
.hallow-action-icon .material-symbols-rounded {
  font-size: 1.375rem;
}
.hallow-action-label {
  font-size: 0.75rem;
  font-weight: 750;
  color: var(--text-main);
  text-align: center;
  letter-spacing: -0.1px;
}

/* Modals for Quick Actions (Saved, Calendar, Progress, Sadhana). Same dim
   value .sheet-backdrop already uses elsewhere (rgba(0,0,0,.4)) — this used
   to be a near-opaque 0.85 near-black, which read fine on a dark screen but
   on this app's light theme looked like a jarring black box sitting over
   the page rather than a soft dim behind the sheet. */
.quick-action-modal {
  position: fixed;
  inset: 0;
  z-index: 10000;
  background: rgba(0, 0, 0, 0.6);
  display: none;
  align-items: flex-end;
  justify-content: center;
}
.quick-action-modal.open {
  display: flex;
  animation: fadeIn 0.2s ease both;
}
/* Closing used to be nothing but classList.remove('open') — which snaps
   .quick-action-modal straight back to its base display:none, instantly,
   since display can't be transitioned. The setTimeout(..., 250) callers
   already had (to remove the node from the DOM) looked like it was giving
   a close animation time to play, but there never was one: the backdrop and
   sheet both just vanished on frame one and the 250ms was spent staring at
   nothing. .closing plays the reverse of .open's own animations while
   staying visible (display:flex) for exactly as long as they take; the
   caller's remove() now happens after that, not after a guessed delay with
   no animation behind it. */
.quick-action-modal.closing {
  display: flex;
  animation: fadeOut 0.2s ease both;
}
.quick-action-modal.closing .quick-action-sheet {
  animation: sheetSlideDown 0.22s cubic-bezier(0.4, 0, 1, 1) both;
}
@keyframes fadeOut {
  from { opacity: 1; }
  to { opacity: 0; }
}
@keyframes sheetSlideDown {
  from { transform: translateY(0); }
  to { transform: translateY(100%); }
}
@media (prefers-reduced-motion: reduce) {
  .quick-action-modal.open,
  .quick-action-modal.closing,
  .quick-action-modal.closing .quick-action-sheet,
  .quick-action-sheet {
    animation: none;
  }
}
.quick-action-sheet {
  width: 100%;
  max-width: 480px;
  max-height: 85vh;
  background: var(--bg-app);
  border-radius: 32px 32px 0 0;
  padding: 24px 22px 36px;
  overflow-y: auto;
  box-shadow: 0 -12px 40px rgba(0, 0, 0, 0.4);
  display: flex;
  flex-direction: column;
  gap: 16px;
  animation: sheetSlideUp 0.3s cubic-bezier(0.16, 1, 0.3, 1) both;
}
@keyframes sheetSlideUp {
  from { transform: translateY(100%); }
  to { transform: translateY(0); }
}

/* The starter-lesson prompt: one clear hierarchy instead of four stacked,
   similarly-weighted, all-centered blocks (kicker/title/question/body all
   competing at once was the actual "confusing and ugly" complaint). The
   episode itself — art, kicker, title — is now a single compact, LEFT-
   aligned preview row: secondary information, presented like a chip, not
   the headline. The actual question ("Start your first episode?") is the
   one true H1 below it, centered and the largest text on the sheet. */
.starter-lesson-sheet {
  align-items: center;
  text-align: center;
  padding-top: 28px;
}
.starter-lesson-preview {
  display: flex; align-items: center; gap: 14px;
  width: 100%; box-sizing: border-box;
  padding: 12px; border-radius: 18px;
  background: var(--surface-alt);
  text-align: left;
}
.starter-lesson-art {
  position: relative;
  flex-shrink: 0;
  width: 56px; height: 56px;
  border-radius: 14px;
  display: flex; align-items: center; justify-content: center;
  overflow: hidden;
  /* --sl-accent set inline per episode (its own palette color, same as
     roadmap.js's --krm-accent) — a hardcoded brand color here would win
     for episode 1's actual gold but drift from whatever episode ever ends
     up first. Same ring treatment roadmap.css gives an unlocked/current
     episode node (.krm-unlocked .krm-circle), just at this row's scale. */
  box-shadow:
    0 0 0 2px var(--sl-accent, #EAC145),
    0 0 12px 1px color-mix(in srgb, var(--sl-accent, #EAC145) 45%, transparent);
}
.starter-lesson-art-emoji { font-size: 1.5rem; line-height: 1; }
.starter-lesson-art img {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
}
.starter-lesson-preview-copy { min-width: 0; }
.starter-lesson-kicker {
  font-size: 0.6875rem; font-weight: 800;
  color: var(--brand-primary);
  text-transform: uppercase; letter-spacing: 0.04em;
}
.starter-lesson-ep-title {
  margin-top: 2px;
  font-size: 0.9375rem; font-weight: 700;
  color: var(--text-main);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.starter-lesson-question {
  /* .quick-action-sheet already gaps every child 16px; a few px more here
     (not stacked on top of a second full margin) is what actually sets the
     question apart as the primary headline instead of just another item in
     the stack. */
  margin: 6px 0 0;
  font-size: 1.375rem;
}
.starter-lesson-body {
  color: var(--text-muted);
  font-size: 0.9375rem; line-height: 1.5;
  margin: 0;
}
.quick-action-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.quick-action-header h2 {
  font-family: var(--font-title);
  font-size: 1.375rem;
  font-weight: 900;
  color: var(--text-main);
  margin: 0;
}
.saved-materials-card {
  padding: 10px;
}
.saved-materials-list {
  display: flex;
  flex-direction: column;
}
.saved-material-row {
  align-items: flex-start;
  gap: 12px;
  padding: 12px 6px;
  border-bottom: 1px solid var(--border);
  cursor: pointer;
}
.saved-material-row:last-child {
  border-bottom: none;
}
.saved-material-copy {
  flex: 1;
  min-width: 0;
}
.saved-material-copy h4 {
  font-size: 0.9375rem;
  line-height: 1.25;
  margin: 0;
}
.saved-material-copy p {
  font-size: 0.78125rem;
  color: var(--text-muted);
  margin: 3px 0 0;
}
.saved-material-preview {
  margin-top: 8px;
  font-size: 0.78125rem;
  line-height: 1.45;
  color: var(--text-main);
  opacity: .82;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.saved-empty {
  text-align: center;
  padding: 30px 16px 34px;
}
.saved-empty-icon {
  width: 58px;
  height: 58px;
  border-radius: 50%;
  background: var(--surface-alt);
  color: var(--text-muted);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  margin-bottom: 14px;
}
.saved-empty h3 {
  font-size: 1.0625rem;
  margin: 0 0 6px;
  color: var(--text-main);
}
.saved-empty p {
  font-size: 0.8125rem;
  line-height: 1.45;
  margin: 0;
  color: var(--text-muted);
}

/* ============================================================
   HYPER-STYLIZED FLAT VECTOR DIYA & GAMIFIED FLAME LEVELS
   ============================================================ */
.diya-widget-card {
  position: relative;
  border-radius: 28px;
  background: var(--card-marigold);
  border: 1px solid var(--border);
  padding: 24px 18px 20px;
  display: flex;
  flex-direction: column;
  align-items: center;
  overflow: hidden;
  box-shadow: var(--shadow-soft);
  text-align: center;
}
.diya-widget-card.is-lit {
  background: color-mix(in srgb, var(--brand-primary) 90%, black);
  border-color: transparent;
}
.diya-widget-card.is-lit .diya-level-badge {
  background: rgba(255, 255, 255, 0.2);
  color: #fff;
  border-color: transparent;
}
.diya-widget-card.is-lit .diya-status-title {
  color: #fff;
}
.diya-widget-card.is-lit .diya-status-desc {
  color: rgba(255, 255, 255, 0.8);
}
body.dark-mode .diya-widget-card {
  background: var(--card-marigold);
  border-color: var(--border);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
}
/* The same deepam illustration lesson-player.css draws at lesson completion
   (.slp-diya and friends) — same colors, same animation curves, reused
   here via diyaFlameSvg (app.js) so this is visibly the same lamp, not a
   second drawing. This wrap just sizes/centers the plain inline SVG; no
   object-fit/mask-image tricks needed since it scales to its box on its
   own via viewBox. */
.diya-art-wrap {
  position: relative;
  z-index: 1; /* explicit, not left to DOM-order tiebreaking — must sit above .light-ray-burst */
  width: 100%;
  max-width: 260px;
  height: 230px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.diya-flame-svg {
  width: 100%;
  height: 100%;
  display: block;
  overflow: visible;
}
.diya-halo { fill: var(--brand-primary); transform-origin: 160px 115px; transition: opacity 0.4s ease; }
/* diya-bowl/flame-outer/flame-inner fills are inline in the markup now
   (matching the hand-supplied reference exactly, colors and all) — no
   class-based fill rules needed. */

/* transform-box:fill-box makes transform-origin resolve against each
   group's own bounding box instead of the outer SVG's viewBox (the modern
   default for nested SVG transforms) — needed so every animation below
   scales/rotates/pulses from ITS OWN center, not the widget's. Three
   separately-scoped transform targets (.inner-flame, .flame-animated,
   .diya-ignite-wrap, plus diyaFlameSvg's own static per-stage .diya-
   stage-scale wrapper) because they animate independently and
   simultaneously — the inner core pulses on its own faster beat, the
   outer silhouette flickers on a slower one, and the ignite wrap only
   ever plays its one-shot bounce — stacking any of these on a single
   element would have their transforms fight each other. */
.inner-flame, .flame-animated, .diya-ignite-wrap {
  transform-box: fill-box;
  transform-origin: center;
}
/* A perfectly static flame reads as dead art — continuous flicker always
   running once lit, standing in for what used to be an actual filmed
   flame. alternate (not a straight loop) so it eases back the way it
   came instead of snapping from the last keyframe to the first. */
@keyframes flameFlicker {
  0% { transform: rotate(-1.5deg) scale(0.98, 1); }
  25% { transform: rotate(1deg) scale(1.01, 1.02); }
  50% { transform: rotate(-1deg) scale(0.99, 0.98); }
  75% { transform: rotate(1.5deg) scale(1.01, 1.01); }
  100% { transform: rotate(-0.5deg) scale(1, 1.02); }
}
.flame-animated { animation: flameFlicker 1.8s ease-in-out infinite alternate; }
/* The inner core pulses on its own, faster, independent beat — a plain
   static flame core (even with the outer silhouette flickering) still
   reads as inert; this is what actually sells "burning". */
@keyframes corePulse {
  0% { transform: scale(0.95); }
  100% { transform: scale(1.05); }
}
.inner-flame { animation: corePulse 1.2s ease-in-out infinite alternate; }
@keyframes diyaFlameLight {
  0% { transform: scale(0); }
  60% { transform: scale(1.12); }
  100% { transform: scale(1); }
}
.diya-ignite-wrap.diya-ignite-anim { animation: diyaFlameLight 0.9s cubic-bezier(0.34, 1.56, 0.64, 1); }

/* The spinning light-ray burst behind the lamp, activated only for the
   level-up celebration (wireDiyaWidget) — otherwise fully transparent and
   inert, so it costs nothing on an ordinary render. z-index below
   .diya-art-wrap's so it stays behind the lamp, not on top of it. */
.light-ray-burst {
  position: absolute;
  z-index: 0;
  top: 46%;
  left: 50%;
  width: 250vmax;
  height: 250vmax;
  transform: translate(-50%, -50%);
  background: conic-gradient(from 0deg,
    transparent 0deg 15deg, #FFE082 15deg 30deg, transparent 30deg 45deg,
    #FFE082 45deg 60deg, transparent 60deg 75deg, #FFE082 75deg 90deg,
    transparent 90deg 105deg, #FFE082 105deg 120deg, transparent 120deg 135deg,
    #FFE082 135deg 150deg, transparent 150deg 165deg, #FFE082 165deg 180deg,
    transparent 180deg 195deg, #FFE082 195deg 210deg, transparent 210deg 225deg,
    #FFE082 225deg 240deg, transparent 240deg 255deg, #FFE082 255deg 270deg,
    transparent 270deg 285deg, #FFE082 285deg 300deg, transparent 300deg 315deg,
    #FFE082 315deg 330deg, transparent 330deg 345deg, #FFE082 345deg 360deg);
  animation: diyaRaySpin 15s linear infinite;
  /* Paused until it is actually visible. This layer is 250vmax square, blended
     with mix-blend-mode:screen, and spins forever. At opacity 0 none of that is
     on screen, but the compositor still rotates and re-blends a layer several
     times the viewport on every frame for as long as the tab holding the diya
     is mounted, which is most of the app's life. Pausing costs nothing visually
     (the element is invisible) and takes the idle app from "never stops
     compositing" to genuinely idle between interactions.
     The play-state rules below mirror the opacity rules exactly, so it runs in
     precisely the cases it is seen in and no others. */
  animation-play-state: paused;
  opacity: 0;
  mix-blend-mode: screen;
  pointer-events: none;
  transition: opacity 0.6s ease;
}
.light-ray-burst.active,
#streak-detail-flame .light-ray-burst { opacity: 0.35; animation-play-state: running; }
@keyframes diyaRaySpin { 100% { transform: translate(-50%, -50%) rotate(360deg); } }

/* Level-up pop: a quick overshoot scale on the whole card, not just the
   video, so the badge/title update lands with the same beat. */
@keyframes diyaPopIn {
  0% { transform: scale(1); }
  40% { transform: scale(1.06); }
  70% { transform: scale(0.98); }
  100% { transform: scale(1); }
}
.diya-widget-card.diya-pop-in { animation: diyaPopIn 0.5s cubic-bezier(0.34, 1.56, 0.64, 1); }

.diya-level-badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 14px;
  border-radius: 99px;
  background: rgba(251, 191, 36, 0.25);
  border: 1px solid rgba(180, 83, 9, 0.35);
  color: var(--card-marigold-text);
  font-weight: 800;
  font-size: 0.8125rem;
  margin-top: 6px;
}
body.dark-mode .diya-level-badge {
  background: rgba(248, 94, 0, 0.18);
  border-color: rgba(248, 94, 0, 0.4);
  color: #FFB380;
}
.diya-status-title {
  font-family: var(--font-title);
  font-size: 1.25rem;
  font-weight: 850;
  color: var(--text-main);
  margin: 10px 0 4px;
}
.diya-status-desc {
  font-size: 0.84375rem;
  color: var(--text-muted);
  line-height: 1.45;
  max-width: 320px;
}


/* Universal Home Search Modal */
.home-search-modal {
  position: fixed;
  inset: 0;
  z-index: 9999;
  background: rgba(14, 14, 16, 0.96);
  backdrop-filter: blur(20px);
  -webkit-backdrop-filter: blur(20px);
  display: none;
  flex-direction: column;
  padding: 24px 20px;
}
.home-search-modal.open {
  display: flex;
  animation: fadeIn 0.2s ease;
}
.home-search-top {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 20px;
}
.home-search-input-wrap {
  flex: 1;
  display: flex;
  align-items: center;
  gap: 10px;
  background: rgba(255, 255, 255, 0.12);
  border: 1px solid rgba(255, 255, 255, 0.22);
  border-radius: 99px;
  padding: 0 16px;
  height: 48px;
}
.home-search-input-wrap input {
  flex: 1;
  background: transparent;
  border: none;
  color: #fff;
  font-size: 1rem;
  font-family: var(--font-sans);
  outline: none;
}
.home-search-close {
  width: 44px;
  height: 44px;
  border-radius: 50%;
  border: none;
  background: rgba(255, 255, 255, 0.12);
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}
.home-search-results {
  flex: 1;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 10px;
}


.today-header-accessory {
  flex: 0 0 auto;
  margin-left: 12px;
  display: flex; align-items: center; gap: 10px;
}
.streak-chip {
  display: flex; align-items: center; gap: 4px;
  background: var(--brand-primary-light); color: var(--brand-primary);
  border: 2px solid var(--border); border-radius: 99px;
  padding: 6px 12px 6px 8px; font-weight: 900; font-size: 1rem;
  font-family: var(--font-title);
}
.streak-chip .material-symbols-rounded { font-size: 1.25rem; }
.streak-chip.zero { color: var(--text-muted); background: var(--surface-alt); }

/* Festival reminder banner (7 days or less away) */
.fest-banner {
  display: flex; align-items: center; gap: 12px;
  background: var(--brand-primary-light); border: 1px solid var(--brand-primary);
  color: var(--text-main);
  border-radius: 16px; padding: 14px; margin-bottom: 20px;
  font-size: 0.875rem; cursor: pointer;
}
.fest-banner > .material-symbols-rounded { color: var(--brand-primary); }
.fest-banner.urgent {
  background: var(--brand-primary); border-color: var(--brand-primary); color: #fff;
}
.fest-banner.urgent > .material-symbols-rounded { color: #FFD9C4; }
.today-header-accessory .avatar {
  width: 44px;
  height: 44px;
  border: 2px solid var(--border);
  box-shadow: var(--shadow-soft);
  font-family: var(--font-title);
  font-size: 1.25rem;
  background: var(--brand-primary-light);
  color: var(--brand-primary);
}

/* Install-as-app banner (browser only) */
.install-banner {
  display: flex; align-items: center; gap: 12px;
  background: var(--brand-primary-light); border: 1px solid var(--brand-primary);
  border-radius: 16px; padding: 12px 14px; margin-bottom: 20px;
}
.install-banner[hidden] { display: none; }
.install-banner-icon { color: var(--brand-primary); font-size: 1.625rem; flex: 0 0 auto; }
.install-banner-text { flex: 1; display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.install-banner-text strong { font-size: 0.875rem; color: var(--text-main); }
.install-banner-text span { font-size: 0.75rem; color: var(--text-muted); font-weight: 700; }
.install-banner-btn { flex: 0 0 auto; background: var(--brand-primary); color: #fff; border: none; border-radius: 99px; padding: 8px 16px; font-weight: 800; font-family: inherit; font-size: 0.8125rem; cursor: pointer; }
.install-banner-btn[hidden] { display: none; }
.install-banner-x { flex: 0 0 auto; background: transparent; border: none; color: var(--text-muted); cursor: pointer; display: flex; padding: 2px; }
.install-banner-x .material-symbols-rounded { font-size: 1.25rem; }

/* Profile tab */
.profile-head { display: flex; align-items: center; gap: 14px; margin-bottom: 24px; }
.profile-avatar { width: 60px; height: 60px; border-radius: 50%; background: var(--brand-primary-light); color: var(--brand-primary); display: flex; align-items: center; justify-content: center; font-weight: 900; font-size: 1.625rem; flex: 0 0 auto; }
.profile-section-label { font-size: 0.75rem; font-weight: 800; color: var(--text-muted); text-transform: uppercase; letter-spacing: .5px; margin: 22px 0 10px; }
.avatar {
  width: 40px; height: 40px; border-radius: 50%;
  background: var(--brand-primary-light); color: var(--brand-primary);
  display: flex; align-items: center; justify-content: center; font-weight: 900;
}
.icon-btn {
  width: 44px; height: 44px; border-radius: 50%;
  background: var(--surface); border: 2px solid var(--border);
  display: flex; align-items: center; justify-content: center;
  color: var(--text-main); cursor: pointer; transition: all .2s;
}
.icon-btn:active { transform: scale(.95); }

/* Horizontal scroll + pills */
.h-scroll {
  display: flex; gap: 12px; overflow-x: auto;
  margin: 0 -22px 24px; padding: 0 22px 8px; scrollbar-width: none;
}
.h-scroll::-webkit-scrollbar { display: none; }
.pill {
  padding: 10px 20px; background: var(--surface); border: 2px solid var(--border);
  border-radius: 99px; font-weight: 800; font-size: 0.9375rem; white-space: nowrap;
  color: var(--text-main); box-shadow: 0 2px 0 var(--border); cursor: pointer;
}
.pill.active { background: var(--brand-primary); color: #fff; border-color: var(--brand-primary); box-shadow: 0 2px 0 var(--brand-primary-shadow); }

/* Cards */
.card { background: var(--surface); border-radius: 24px; padding: 24px; margin-bottom: 20px; box-shadow: var(--shadow-soft); border: 1px solid var(--border); }
.feature-card {
  border-radius: 28px; padding: 24px; margin-bottom: 20px; display: flex;
  align-items: center; justify-content: space-between; position: relative;
  overflow: hidden; box-shadow: var(--shadow-soft); min-height: 140px; cursor: pointer;
}
.feature-blob {
  position: absolute; right: -20px; bottom: -20px; width: 140px; height: 140px;
  border-radius: 50%; background: rgba(255,255,255,.15); z-index: 1;
  display: flex; align-items: center; justify-content: center;
}
/* Today's Chant (Audio tab's featured card): a real gold play button, not
   just the translucent watermark every other feature-card uses inside
   .feature-blob — this card's whole point is "tap to play", so it gets an
   actual affordance instead of a decorative icon. */
.audio-chant-play {
  position: absolute; right: 16px; bottom: 16px; z-index: 2;
  width: 44px; height: 44px; border-radius: 50%;
  background: var(--gold);
  color: #4A2F00;
  display: flex; align-items: center; justify-content: center;
  box-shadow: 0 2px 8px rgba(0,0,0,.3);
}
.audio-chant-play .material-symbols-rounded { font-size: 1.375rem; font-variation-settings: 'FILL' 1; }

/* Dashboard tiles */
.dash-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-bottom: 24px; }
.dash-tile {
  background: var(--surface);
  border-radius: var(--radius-lg);
  padding: 20px;
  box-shadow: var(--shadow-soft);
  display: flex;
  flex-direction: column;
  border: 1px solid var(--border);
  cursor: pointer;
  transition: var(--transition-spring);
}
.dash-tile:hover {
  transform: translateY(-4px) scale(1.02);
  box-shadow: var(--shadow-premium);
}
.dash-tile:active {
  transform: translateY(2px) scale(0.96);
  box-shadow: var(--shadow-soft);
}
.tile-label { font-size: 0.6875rem; text-transform: uppercase; letter-spacing: 0.8px; color: var(--text-muted); font-weight: 800; margin-bottom: 6px; }
.ring-container {
  width: 64px;
  height: 64px;
  border-radius: 50%;
  margin-top: 16px;
  align-self: center;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
  box-shadow: 0 4px 12px rgba(0,0,0,0.04);
}
.dash-tile:hover .ring-container {
  transform: scale(1.08);
}
.ring-tulsi { background: var(--gradient-tulsi); }
.ring-marigold { background: var(--gradient-gold); }
.ring-inner {
  width: 48px;
  height: 48px;
  background: var(--surface);
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--brand-primary);
  transition: background-color 0.3s ease;
}
.ring-tulsi .ring-inner { color: var(--success-ink); }
.ring-marigold .ring-inner { color: var(--brand-primary); }

/* Festival Hero Card */
.festival-hero-card {
  border-radius: var(--radius-lg);
  padding: 24px;
  border: none;
  margin-bottom: 24px;
  position: relative;
  overflow: hidden;
  cursor: pointer;
  transition: var(--transition-spring);
}
.festival-hero-card.color-marigold {
  background: var(--gradient-gold);
  color: #fff;
  box-shadow: 0 12px 28px rgba(217, 119, 6, 0.15);
}
.festival-hero-card.color-tulsi {
  background: var(--gradient-tulsi);
  color: #fff;
  box-shadow: 0 12px 28px rgba(5, 150, 105, 0.15);
}
.festival-hero-card:hover {
  transform: translateY(-4px) scale(1.02);
}
.festival-hero-card.color-marigold:hover {
  box-shadow: 0 16px 36px rgba(217, 119, 6, 0.3);
}
.festival-hero-card.color-tulsi:hover {
  box-shadow: 0 16px 36px rgba(5, 150, 105, 0.3);
}
.festival-hero-card:active {
  transform: translateY(2px) scale(0.96);
}
.festival-hero-card::after {
  content: '';
  position: absolute;
  right: -24px;
  bottom: -24px;
  width: 130px;
  height: 130px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.12);
  z-index: 1;
}
.festival-hero-content {
  position: relative;
  z-index: 2;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 16px;
}
.festival-label {
  color: rgba(255, 255, 255, 0.8) !important;
  font-weight: 800;
}
.festival-hero-title {
  font-size: 1.375rem;
  font-weight: 900;
  color: #fff;
  margin: 4px 0 2px;
  line-height: 1.25;
}
.festival-hero-when {
  font-size: 0.875rem;
  color: rgba(255,255,255,0.9);
  font-weight: 700;
  margin-bottom: 12px;
}
.festival-hero-link {
  font-size: 0.8125rem;
  font-weight: 800;
  color: #fff;
  background: rgba(255,255,255,0.18);
  padding: 6px 14px;
  border-radius: 99px;
  display: inline-block;
  backdrop-filter: blur(4px);
  transition: background-color 0.2s ease;
}
.festival-hero-card:hover .festival-hero-link {
  background: rgba(255,255,255,0.28);
}
.festival-hero-icon {
  width: 56px;
  height: 56px;
  border-radius: 50%;
  background: rgba(255,255,255,0.15);
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}
.festival-hero-icon .material-symbols-rounded {
  font-size: 2rem;
  color: #fff;
}

/* Search */
.search {
  display: flex;
  align-items: center;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 18px;
  padding: 0 18px;
  height: 54px;
  margin-bottom: 24px;
  box-shadow: var(--shadow-soft);
  transition: border-color 0.25s ease, box-shadow 0.25s ease;
}
.search:focus-within {
  border-color: var(--brand-primary);
  box-shadow: var(--shadow-glow);
}
.search input {
  border: none;
  background: transparent;
  flex: 1;
  height: 100%;
  margin-left: 12px;
  /* 16px, not 15: see the note under the form-control reset at the top of this
     file. This class is on the onboarding name field, the sign-in email and
     password, the reset field and the Profile library search, so 15px here
     meant every single user zoomed the app on their way in. */
  font-size: 1rem;
  font-family: var(--font-sans);
  font-weight: 700;
  color: var(--text-main);
  outline: none;
}
.search input::placeholder {
  color: var(--text-muted);
  opacity: 0.8;
}

/* Lists */
.list-row {
  /* appearance + the explicit background/border/color: this is a <button> at
     every call site, so without them it inherits UA chrome. See the reset above. */
  -webkit-appearance: none;
          appearance: none;
  width: 100%;
  text-align: left;
  background: var(--surface);
  border: 1px solid var(--border);
  color: var(--text-main);
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 14px 12px;
  cursor: pointer;
  border-radius: 16px;
  transition: var(--transition-spring);
  margin: 4px 0;
}
.list-row:hover {
  background: var(--surface-alt);
  transform: translateX(4px) scale(1.01);
}
.list-row:active {
  transform: scale(0.97);
}
.list-thumb {
  width: 48px;
  height: 48px;
  border-radius: 14px;
  background: var(--brand-primary-light);
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--brand-primary);
  flex-shrink: 0;
  box-shadow: 0 4px 8px rgba(0,0,0,0.02);
}
body.dark-mode .list-thumb {
  background: var(--surface-alt);
}
.play-btn {
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background: var(--gradient-sunset);
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: var(--shadow-glow);
  flex-shrink: 0;
  cursor: pointer;
  transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.play-btn:hover {
  transform: scale(1.08);
}
.play-btn:active {
  transform: translateY(2px) scale(0.95);
  box-shadow: none;
}

/* Product grid */
.product-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }

/* Quiz options */
.option-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-bottom: 24px; }
.option-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 20px;
  padding: 24px 12px;
  text-align: center;
  cursor: pointer;
  box-shadow: 0 4px 0 var(--border), var(--shadow-soft);
  transition: transform 0.15s cubic-bezier(0.34, 1.56, 0.64, 1), box-shadow 0.15s ease, border-color 0.15s ease, background-color 0.15s ease;
  color: var(--text-main);
  font-weight: 750;
}
.option-card:hover {
  transform: translateY(-2px);
  box-shadow: 0 6px 0 var(--border), var(--shadow-premium);
  border-color: var(--text-muted);
}
.option-card:active {
  transform: translateY(4px) scale(0.96);
  box-shadow: 0 0 0 transparent;
}
.option-card.selected {
  border-color: var(--brand-primary);
  background: var(--brand-primary-light);
  box-shadow: 0 4px 0 var(--brand-primary-shadow);
  color: var(--brand-primary);
}

/* Primary button */
.btn-primary {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  width: 100%;
  height: 56px;
  border-radius: var(--radius-round);
  border: none;
  font-weight: 800;
  font-size: 1.03125rem;
  font-family: var(--font-sans);
  background: var(--gradient-sunset);
  color: #fff;
  box-shadow: var(--shadow-btn);
  cursor: pointer;
  transition: var(--transition-spring);
}
.btn-primary:active {
  transform: scale(0.96) translateY(2px);
  box-shadow: 0 1px 0 var(--brand-primary-shadow);
}

/* Section helpers */
.section-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; }
.link { color: var(--brand-primary); font-weight: 800; font-size: 0.875rem; cursor: pointer; }

/* Bottom nav */
/* Floating overlay, not a layout-consuming flex sibling: .app-content
   always fills .app's full height, and the bar floats above it at the
   bottom (each page already pads its own bottom edge to clear the bar's
   footprint where needed, e.g. .krm-wrap's bottom padding). Was a flex
   sibling once — that meant hiding it via transform alone (see
   .nav-hidden) still reserved its layout space, so every page rendered
   short by the bar's height instead of using the full screen; absolute
   positioning is what actually frees that space up. */
/* Floating rounded bar; the ACTIVE tab is marked by .nav-pill sliding
   behind it (see below) rather than its icon rising out of the bar. */
.bottom-nav {
  position: absolute;
  left: 12px;
  right: 12px;
  bottom: calc(8px + env(safe-area-inset-bottom));
  background: var(--surface);
  padding: 10px 8px;
  border: 1px solid var(--border);
  /* Full stadium shape, same degree of roundness as .nav-pill inside it
     (both var(--radius-round)) — a smaller fixed radius here read as
     "less rounded than its own selection highlight". */
  border-radius: var(--radius-round, 99px);
  /* Clips .nav-pill to the bar's own rounded shape — nothing in this bar
     is ever meant to render outside its box anymore (the old rising-icon
     treatment was the one thing that needed overflow:visible here). */
  overflow: hidden;
  /* Sits above the content overlays (detail 160 / player 200) whenever it IS
     showing, so it's never clipped by them mid-transition — but it's only
     actually visible on a tab's topmost/root screen; see .nav-hidden below
     (toggled from updateNavVisibility() in app.js) for anything deeper
     (a course's chapter list, a lesson, the detail/player/lesson-player
     overlays). .app's own overflow:clip is what actually hides it once
     translated past the bottom edge. */
  z-index: 250;
  /* Composed from two independent custom properties rather than two
     classes fighting over one `transform` — .nav-hidden (any overlay/
     deeper screen) and .nav-compact (scrolling down, Instagram-style)
     can each toggle their own axis without clobbering the other, and
     mid-scroll-compact really can coincide with e.g. an overlay opening. */
  --nav-hide: 0;
  --nav-scale: 1;
  transform-origin: center bottom;
  /* Hiding must travel its own height PLUS however far its resting `bottom`
     already sits above the true edge (8px + the home-indicator inset) —
     the flat +20px here used to be the entire cushion, which only fully
     clears the bar on a device with a small/zero safe-area-inset-bottom
     (a home-button iPhone, or any desktop browser, which always reports 0).
     On a real Face-ID iPhone that inset runs ~34px, so the old fixed
     cushion undershot by about that much and left the bar visibly peeking
     over the bottom edge whenever it was supposed to be fully hidden —
     exactly the class of bug this app's safe-area handling keeps running
     into: correct-looking in any environment that reports 0, wrong on the
     device that doesn't. +20px is now pure extra buffer on top of the
     real resting offset, not a stand-in for it. */
  transform: translateY(calc(var(--nav-hide) * (100% + 8px + env(safe-area-inset-bottom) + 20px))) scale(var(--nav-scale));
  transition: transform 0.32s cubic-bezier(0.22, 1, 0.36, 1);
  box-shadow: 0 8px 28px rgba(0, 0, 0, 0.10);
}
/* Withdraws below the screen — a course's chapter list, a lesson, or any
   full overlay (detail/player/lesson player) — instead of floating over
   content that isn't a tab's own root anymore. pointer-events:none so it
   can't be tapped mid-slide-out or while parked off-screen. */
.bottom-nav.nav-hidden {
  --nav-hide: 1;
  pointer-events: none;
}
/* Instagram-style: shrinks while scrolling down (more content, less
   chrome), back to full size scrolling up — see the scroll listener in
   app.js. Anchored to its own bottom edge (transform-origin above) so it
   reads as sinking toward the edge it's already pinned to, not shrinking
   symmetrically in from all sides. */
.bottom-nav.nav-compact {
  --nav-scale: 0.8;
}
/* .nav-tabs exists purely so .nav-pill has a padding-free positioning
   context: percentage left/width on an absolutely-positioned child resolve
   against its containing block's PADDING box, but flex children lay out
   within the CONTENT box — with .bottom-nav's own 8px horizontal padding,
   those two boxes differ by 16px, so a pill positioned directly against
   .bottom-nav was computing its percentages against a box 16px wider than
   the one the tabs actually occupy (worse toward the edges — this was the
   "still slightly off center on the edge buttons" bug). .nav-tabs has no
   padding of its own, so its content box and padding box are identical,
   and both .nav-pill and the .nav-item flex row now agree on the exact
   same reference width. .bottom-nav's own padding still supplies the
   outer breathing room from the bar's rounded corner to the first/last
   tab, unchanged. */
.nav-tabs {
  position: relative;
  display: flex;
  justify-content: space-around;
  align-items: center;
  width: 100%;
  height: 100%;
}
/* Sliding pill: sized/moved to sit exactly under whichever tab is active.
   updateNavPill() in app.js sets `left`/`width` as percentages of the tab
   count (e.g. 25% per tab, 4 tabs) rather than measuring pixel rects — a
   percentage naturally stays correct across any viewport/tab-count change
   with no resize listener needed. Full height of .nav-tabs (which is
   already exactly the bar's own padded content area — see above — so no
   extra vertical inset is needed here) with a small horizontal inset on
   each side so it always reads as a pill floating inside its slot, never
   touching a tab boundary — the 25%/0% here are only a same-paint
   fallback for the default (Today, item 1 of 4) before that first
   placement runs. Replaces the old rising-bubble-plus-notch treatment,
   which depended on the bar's overflow:visible to let the bubble poke up
   past the top edge — that overflow is exactly what let the bubble still
   peek through mid-slide when the whole bar hides (see .nav-hidden
   above); a same-height pill that never leaves the bar's own box needs no
   such escape hatch. */
.nav-pill {
  position: absolute;
  top: 0;
  bottom: 0;
  left: calc(0% + 4px);
  width: calc(25% - 8px);
  z-index: 0;
  border-radius: var(--radius-round, 99px);
  background: color-mix(in srgb, var(--brand-primary) 14%, transparent);
  transition: left 0.35s cubic-bezier(0.22, 1, 0.36, 1), width 0.35s cubic-bezier(0.22, 1, 0.36, 1);
  pointer-events: none;
}

/* Icon-only, Instagram-style — no label under the glyph, so the whole
   .nav-item is just a centered tap target the same width as its slot. */
.nav-item {
  position: relative;
  z-index: 1; /* above .nav-pill */
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--text-muted);
  cursor: pointer;
  transition: color 0.3s cubic-bezier(0.22, 1, 0.36, 1), transform 0.15s ease;
  flex: 1;
  min-height: 48px; /* 44px+ tap target on the primary navigation */
  -webkit-tap-highlight-color: transparent;
}
.nav-item:not(.active):active {
  transform: scale(0.94);
}
.nav-item.active {
  color: var(--brand-primary);
}
.nav-item::after {
  display: none !important;
}
.nav-item.active::after {
  display: none !important;
}
.nav-item .material-symbols-rounded {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  font-size: 1.5625rem;
  font-variation-settings: 'FILL' 0, 'wght' 500, 'GRAD' 0, 'opsz' 24;
}
/* Snap, not animate — see the app-wide note on why variable-font axes
   never get a transition (re-rasterizes every frame, reads as a shimmer).
   The color transition above still carries the visible "selecting" motion. */
.nav-item.active .material-symbols-rounded {
  font-variation-settings: 'FILL' 1, 'wght' 600, 'GRAD' 0, 'opsz' 24;
}

/* Prayer Player overlay, a full-screen immersive view; it stays ABOVE the bottom
   nav (unlike detail/reader overlays) so its transport controls aren't covered.
   Exit is the down-chevron at the top.
   Follows the app's own light/dark theme (var(--surface)/var(--text-main),
   same tokens every card uses) rather than a permanently-dark background —
   it used to hardcode --indigo/#fff regardless of theme, which is exactly
   --indigo's own value in dark mode (so dark mode looks unchanged) but read
   as "stuck in dark mode" in light mode. Every rgba(255,255,255,X) below,
   throughout the player's sub-components, is the same fix applied the same
   way: color-mix(in srgb, var(--text-main) X%, transparent) — X% carried
   over unchanged from the old alpha, just measured against the theme's own
   ink color instead of a hardcoded white. */
.player {
  position: absolute;
  inset: 0;
  z-index: 300;
  background: var(--surface);
  color: var(--text-main);
  display: flex;
  flex-direction: column;
  padding: 24px;
  box-sizing: border-box;
  width: 100%;
  overflow-x: hidden;
  transform: translateY(100%);
  transition: transform 0.45s cubic-bezier(0.16, 1, 0.3, 1);
}
.player.open { transform: translateY(0); }
.player-top { display: flex; align-items: center; justify-content: space-between; }
.player-top .icon-btn {
  background: color-mix(in srgb, var(--text-main) 14%, transparent);
  border-color: transparent;
  color: var(--text-main);
  backdrop-filter: blur(8px);
}
.player-top .icon-btn:hover {
  background: color-mix(in srgb, var(--text-main) 24%, transparent);
}

/* Spotify-style mini bar: docked above the bottom nav (250) but below the
   full player (300), shown while the full player is minimized rather than
   closed. Fixed offset rather than measuring .bottom-nav's height — same
   "somewhere above the nav" approach #toast already uses (bottom: 110px). */
.mini-player {
  position: absolute;
  left: 12px;
  right: 12px;
  bottom: calc(88px + env(safe-area-inset-bottom));
  z-index: 260;
  display: flex;
  align-items: center;
  gap: 8px;
  background: var(--surface);
  color: var(--text-main);
  border: 1px solid var(--border);
  border-radius: 16px;
  padding: 9px 8px 9px 12px;
  box-shadow: 0 8px 28px rgba(0, 0, 0, 0.18);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  overflow: hidden;
  /* Matches .bottom-nav's own transform transition (same duration/easing)
     so the two follow-along rules below (nav-compact/nav-hidden) move in
     lockstep with the nav's own shrink/hide animation instead of snapping. */
  transition: transform 0.32s cubic-bezier(0.22, 1, 0.36, 1), bottom 0.32s cubic-bezier(0.22, 1, 0.36, 1);
}
.mini-player[hidden] { display: none; }
/* Scroll-compact (Instagram-style shrink, see .bottom-nav.nav-compact)
   scales the nav down from ITS OWN bottom edge, so its top edge sinks as
   it shrinks — left alone, the mini player (fixed height, fixed position)
   stayed put while the nav sank away beneath it, opening a gap that grew
   the longer you scrolled and read as "floating," not docked. Sinking the
   mini player by the same amount the nav's top edge drops (its resting
   ~70px height * the 20% scale-down = 14px) keeps the two reading as one
   dock instead of drifting apart. Pure CSS via the general sibling
   combinator — no JS needed, it reacts the instant navScrollShrink()
   (app.js) toggles nav-compact on .bottom-nav itself. */
.bottom-nav.nav-compact ~ #mini-player {
  transform: translateY(14px);
}
/* A deeper screen (a course chapter, a lesson) hides the nav entirely — no
   bar left for the mini player to sit above, so it drops to the nav's own
   resting spot instead of floating over the empty space the nav left. */
.bottom-nav.nav-hidden ~ #mini-player {
  bottom: calc(8px + env(safe-area-inset-bottom));
  /* Cancels the nav-compact rule's translateY above for the (real: scroll
     down, then navigate into a lesson — .app-content's scroll position,
     and so nav-compact, persists across that) case where both classes are
     on .bottom-nav at once. Without this the two shifts stacked, pushing
     the mini player about 14px past the bottom of the viewport. */
  transform: none;
}
.mini-player-scrubber {
  position: absolute; left: 0; right: 0; top: 0; height: 2.5px;
  background: color-mix(in srgb, var(--text-main) 18%, transparent);
}
#mini-player-scrubber-fill { height: 100%; width: 0%; background: var(--brand-primary); }
.mini-player-info { display: flex; align-items: center; gap: 10px; flex: 1; min-width: 0; }
.mini-player-icon { flex: 0 0 auto; font-size: 1.25rem; opacity: .85; }
.mini-player-text { min-width: 0; }
.mini-player-title {
  font-size: 0.84375rem; font-weight: 800;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.mini-player-sub {
  font-size: 0.6875rem; color: color-mix(in srgb, var(--text-main) 70%, transparent); margin-top: 1px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.mini-player-btn {
  flex: 0 0 auto; width: 34px; height: 34px; border-radius: 50%;
  background: color-mix(in srgb, var(--text-main) 14%, transparent); border: none; color: var(--text-main);
  display: flex; align-items: center; justify-content: center; cursor: pointer;
}
.mini-player-btn:active { background: color-mix(in srgb, var(--text-main) 24%, transparent); }
.mini-player-btn .material-symbols-rounded { font-size: 1.25rem; }

.lyrics {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 16px;
  text-align: center;
  overflow-y: auto;
  overflow-x: hidden;
  scrollbar-width: none;
  width: 100%;
  max-width: 100%;
  box-sizing: border-box;
  padding: 16px;
}
.lyrics::before,
.lyrics::after {
  content: '';
  flex-grow: 1;
  flex-shrink: 1;
}
.lyrics::-webkit-scrollbar { display: none; }
.lyric {
  color: color-mix(in srgb, var(--text-main) 45%, transparent);
  font-weight: 800;
  font-size: 1.0625rem;
  font-family: var(--font-verse);
  transition: all 0.35s ease;
  line-height: 1.45;
  width: 100%;
  max-width: 100%;
  box-sizing: border-box;
  overflow-wrap: break-word;
  word-wrap: break-word;
  word-break: break-word;
}
.lyric small {
  display: block;
  font-size: 0.75rem;
  opacity: .75;
  font-weight: 600;
  font-family: var(--font-sans);
  margin-top: 4px;
  overflow-wrap: break-word;
  word-break: break-word;
}
.lyric.active {
  color: var(--brand-primary);
  font-size: 1.3125rem;
  text-shadow: none;
  transform: scale(1.04);
}
/* The word-level highlight used to carry dark mode's only glow treatment
   (it only ever worked because the surface behind it is dark) — moved
   here onto the whole line now that highlighting is line-only. */
body.dark-mode .lyric.active {
  color: #ffffff;
  text-shadow: 0 0 14px rgba(255, 235, 170, 0.85), 0 0 26px rgba(255, 190, 85, 0.5);
}
.lyric-meaning {
  font-size: 0.84375rem;
  color: color-mix(in srgb, var(--text-main) 85%, transparent);
  margin-top: 8px;
  max-width: 85%;
  font-family: var(--font-sans);
  font-weight: 600;
  line-height: 1.5;
  overflow-wrap: break-word;
  word-break: break-word;
}
.scrubber {
  height: 6px;
  background: color-mix(in srgb, var(--text-main) 20%, transparent);
  border-radius: 99px;
  margin: 16px 0 8px;
  cursor: pointer;
  position: relative;
  transition: height 0.1s ease;
}
.scrubber:hover {
  height: 8px;
}
.scrubber > div {
  width: 0%;
  height: 100%;
  background: var(--brand-primary);
  border-radius: 99px;
  box-shadow: none;
  transition: width 0.1s linear;
}
.ptime { display: flex; justify-content: space-between; font-size: 0.6875rem; font-weight: 800; color: color-mix(in srgb, var(--text-main) 60%, transparent); margin-bottom: 12px; font-family: var(--font-sans); }
.transport { display: flex; align-items: center; justify-content: center; gap: 36px; margin-bottom: 16px; }
.transport .big {
  /* flex-shrink defaults to 1 even with an explicit width/height — on a
     narrow screen, .transport (a row) had nowhere to take that space from
     but this button's own width, while its height (not constrained by the
     row's main axis) stayed at 68px. That's the squished-oval bug: only the
     width was ever shrinking. flex-shrink:0 makes the circle a hard
     minimum instead, same as the ctrl buttons on either side of it. */
  flex-shrink: 0;
  width: 68px;
  height: 68px;
  border-radius: 50%;
  background: var(--brand-primary);
  color: #FFFFFF;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  box-shadow: 0 4px 14px rgba(255, 206, 99, 0.35);
  transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.transport .big:hover {
  transform: scale(1.08);
}
.transport .big:active {
  transform: scale(0.95);
}
.transport .ctrl {
  color: color-mix(in srgb, var(--text-main) 90%, transparent);
  cursor: pointer;
  transition: transform 0.2s, color 0.2s;
}
.transport .ctrl:hover {
  color: var(--text-main);
  transform: scale(1.15);
}
.transport .ctrl:active {
  transform: scale(0.9);
}
.transport .ctrl.active {
  color: var(--brand-primary);
}
.player.no-audio .scrubber,
.player.no-audio .ptime,
.player.no-audio #player-loop,
.player.no-audio #player-back10,
.player.no-audio #player-fwd10 {
  display: none;
}
.player.no-play .scrubber,
.player.no-play .ptime,
.player.no-play .transport {
  display: none;
}

.player-nudge {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  margin-top: 14px;
  font-size: 0.8125rem;
  font-weight: 700;
  color: color-mix(in srgb, var(--text-main) 90%, transparent);
}
.player-nudge[hidden] { display: none; }
.player-nudge button {
  /* var(--text-main): white in dark mode (matches the original white pill
     exactly, --text-main there is #EFF1F0), dark ink in light mode — an
     equally high-contrast "pop off the player" pill either way. */
  background: var(--text-main);
  color: var(--brand-primary);
  border: none;
  border-radius: 99px;
  padding: 6px 14px;
  font-weight: 800;
  font-family: var(--font-sans);
  cursor: pointer;
  box-shadow: 0 4px 10px rgba(0,0,0,0.1);
  transition: transform 0.2s;
}
.player-nudge button:active {
  transform: scale(0.95);
}
.player-noaudio {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  margin: 0 0 16px;
  padding: 12px 16px;
  border-radius: 14px;
  background: color-mix(in srgb, var(--text-main) 8%, transparent);
  border: 1px solid color-mix(in srgb, var(--text-main) 12%, transparent);
  color: color-mix(in srgb, var(--text-main) 90%, transparent);
  font-size: 0.8125rem;
  font-weight: 700;
  text-align: center;
  backdrop-filter: blur(8px);
}
.player-noaudio[hidden] { display: none; }
.player-noaudio .material-symbols-rounded { font-size: 1.125rem; }

/* Misc */
.streak-bars {
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  height: 80px;
  margin-bottom: 16px;
  padding: 0 12px;
}
.streak-bars > div {
  width: 12px;
  height: 100%;
  border-radius: 99px;
  background: var(--surface-alt);
  transform: scaleY(var(--h, 0.08));
  transform-origin: bottom;
  transition: transform 0.4s cubic-bezier(0.16, 1, 0.3, 1);
  position: relative;
}
.streak-bars > div.completed {
  background: rgba(212, 93, 53, 0.35);
}
body.dark-mode .streak-bars > div.completed {
  background: rgba(228, 117, 80, 0.35);
}
.streak-bars > div.active-today {
  background: var(--gradient-sunset);
  box-shadow: var(--shadow-glow);
}
.streak-bars > div.active-today::before {
  content: '🔥';
  font-size: 0.875rem;
  position: absolute;
  top: -22px;
  left: 50%;
  transform: translateX(-50%);
  animation: flamePulse 1.2s infinite ease-in-out;
}
@keyframes flamePulse {
  0%, 100% { transform: translateX(-50%) scale(1); filter: drop-shadow(0 0 2px rgba(212, 93, 53, 0.5)); }
  50% { transform: translateX(-50%) scale(1.2); filter: drop-shadow(0 0 6px rgba(212, 93, 53, 0.8)); }
}
.streak-days {
  display: flex;
  justify-content: space-between;
  padding: 0 12px;
  color: var(--text-muted);
  font-size: 0.6875rem;
  font-weight: 800;
  letter-spacing: 0.5px;
}
.streak-days .active-day {
  color: var(--brand-primary);
  font-weight: 950;
}
.muted-chat { background: var(--surface-alt); color: var(--text-muted); padding: 12px 16px; border-radius: 20px; border-bottom-left-radius: 4px; font-weight: 800; }
.seller-banner { display:flex; align-items:center; gap:12px; background: var(--brand-primary-light); color: var(--brand-primary); border-radius:20px; padding:16px; font-weight:800; cursor:pointer; margin-bottom: 20px; }

/* Language bottom sheet */
.sheet-backdrop { position:absolute; inset:0; z-index:150; background:rgba(0,0,0,.4); display:flex; align-items:flex-end; overflow:hidden; }
.sheet-backdrop[hidden] { display:none; }
/* Flex column: handle + header stay fixed, only .sheet-body scrolls. */
.sheet { width:100%; max-height:88%; min-height:0; display:flex; flex-direction:column; background:var(--surface); border-radius:28px 28px 0 0; padding:16px 22px calc(28px + env(safe-area-inset-bottom)); animation: sheetUp .3s cubic-bezier(.2,.8,.2,1); }
@keyframes sheetUp { from { transform: translateY(100%); } to { transform: translateY(0); } }
.sheet-handle { flex:0 0 auto; width:42px; height:5px; border-radius:99px; background:var(--border); margin:0 auto 16px; }
.sheet-header { flex:0 0 auto; margin-bottom:12px; display:flex; align-items:center; justify-content:space-between; }
.sheet-body { flex:1 1 auto; min-height:0; overflow-y:auto; overscroll-behavior:contain; -webkit-overflow-scrolling:touch; margin:0 -22px; padding:0 22px; }
.lang-row { display:flex; align-items:center; justify-content:space-between; padding:14px 8px; font-weight:800; color:var(--text-main); cursor:pointer; border-radius:12px; }
.lang-row:hover { background:var(--surface-alt); }
.lang-row.active { color:var(--brand-primary); }

/* Settings toggle */
.setting-row { display:flex; align-items:center; justify-content:space-between; padding:6px 0; }
.switch { position:relative; display:inline-block; width:48px; height:28px; }
.switch input { display:none; }
.slider { position:absolute; inset:0; background:var(--border); border-radius:99px; transition:.2s; }
.slider:before { content:''; position:absolute; width:22px; height:22px; left:3px; top:3px; background:#fff; border-radius:50%; transition:.2s; }
.switch input:checked + .slider { background:var(--brand-primary); }
.switch input:checked + .slider:before { transform:translateX(20px); }

/* Detail overlay */
.detail {
  position: absolute;
  inset: 0;
  z-index: 160;
  background: var(--bg-app);
  display: flex;
  flex-direction: column;
  transform: translateX(100%);
  transition: transform 0.28s cubic-bezier(0.16, 1, 0.3, 1);
  box-shadow: -8px 0 32px rgba(0,0,0,0.06);
}
.detail.open { transform: translateX(0); }

/* Loading skeleton: shown the instant the overlay opens, replaced when data lands. */
.skeleton-hero {
  height: 220px;
  border-radius: 0 0 24px 24px;
  background: linear-gradient(135deg, var(--surface-alt), var(--border));
}
.skeleton-bar {
  height: 14px;
  border-radius: 7px;
  background: var(--surface-alt);
  margin: 14px 0;
}
.shimmer {
  position: relative;
  overflow: hidden;
}
.shimmer::after {
  content: '';
  position: absolute;
  inset: 0;
  transform: translateX(-100%);
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.28), transparent);
  animation: shimmer 1.2s infinite;
}
@keyframes shimmer { to { transform: translateX(100%); } }
.detail-top {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  z-index: 200;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: max(16px, env(safe-area-inset-top)) 18px 12px;
  pointer-events: none;
}
.detail-top button, .detail-top #detail-close {
  pointer-events: auto;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background: rgba(18, 18, 22, 0.65);
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
  border: 1px solid rgba(255, 255, 255, 0.15);
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.4);
}
/* Bottom padding clears the bottom nav (now z-250, ~69px + safe-area) so the last
   interactive element, a game's Continue button, a lesson's Start Quiz, is never
   hidden behind the tab bar. */
.detail-body { flex:1; overflow-y:auto; overflow-x:hidden; padding:0 22px calc(120px + env(safe-area-inset-bottom)) 22px; scrollbar-width:none; }
/* .detail's own background is the flat --bg-app (near-black, but neutral —
   not literally blue). Sitting flush against the diya card's saturated
   warm-orange rays with nothing between them, that neutral reads cold by
   simultaneous contrast — the classic "grey looks blue next to orange"
   illusion. A soft warm glow bleeding down from the top (where the card
   sits) into the flat background removes the hard neutral/orange edge
   that was causing it, instead of trying to re-tint --bg-app globally. */
.detail-body.streak-layout {
  display:flex; flex-direction:column; overflow:hidden; padding-bottom:22px;
  background:
    radial-gradient(ellipse 130% 55% at 50% 0%, color-mix(in srgb, var(--brand-primary) 12%, transparent), transparent 70%),
    var(--bg-app);
}
.detail-body::-webkit-scrollbar { display:none; }
/* The floating back button already shows where you are; the duplicate title in
   the top bar overlapped the hero title, so hide it. */
#detail-title { display:none; }
/* Pad the hero's top so the floating (safe-area-offset) back button never
   overlaps the hero's own label/title. */
.detail-hero { margin: 0 -22px 28px -22px; border-radius: 0 0 28px 28px; padding: calc(env(safe-area-inset-top, 0px) + 64px) 26px 34px; color:#fff; position:relative; overflow:hidden; }
.detail-section { margin-bottom:24px; }
.detail-section h3 { font-size:0.8125rem; text-transform:uppercase; letter-spacing:.5px; color:var(--brand-primary); margin-bottom:8px; }

/* Sadhana builder content: progress dots, preset cards, accordions, tally —
   reused as-is inside the full-screen swb- panels (see practice.css for the
   panel/scroller/overlay shell itself, and app.js's renderSwb). */
.sw-progress { position: relative; display: flex; align-items: flex-start; justify-content: center; gap: 4px; margin-bottom: 26px; }
.sw-dot { display: flex; flex-direction: column; align-items: center; gap: 6px; flex: 1; max-width: 90px; }
.sw-dot-mark {
  width: 28px; height: 28px; border-radius: 50%; display: flex; align-items: center; justify-content: center;
  font-size: 0.8125rem; font-weight: 800; color: var(--text-muted); background: var(--surface-alt, rgba(0,0,0,.04));
  border: 1.5px solid var(--border); transition: background-color 0.3s ease, border-color 0.3s ease, color 0.3s ease, transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.sw-dot-mark .material-symbols-rounded { font-size: 1rem; }
.sw-dot-label { font-size: 0.65625rem; font-weight: 700; color: var(--text-muted); text-transform: uppercase; letter-spacing: .3px; transition: color 0.3s ease; }
.sw-dot.is-active .sw-dot-mark { background: var(--brand-primary); border-color: var(--brand-primary); color: #fff; transform: scale(1.1); }
.sw-dot.is-active .sw-dot-label { color: var(--text-main); }
.sw-dot.is-done .sw-dot-mark { background: var(--brand-primary-light); border-color: var(--brand-primary); color: var(--brand-primary); }
/* Connecting rail between dots, drawn as a pseudo-element on each dot (except
   the first) so it sits behind the circles without a separate wrapper div. */
.sw-dot:not(:first-child) { position: relative; }
.sw-dot:not(:first-child)::after {
  content: ''; position: absolute; top: 14px; right: 50%; width: 100%; height: 1.5px;
  background: var(--border); z-index: -1; transition: background-color 0.3s ease;
}
.sw-dot.is-done::after, .sw-dot.is-active::after { background: var(--brand-primary); }

.sw-tally { display: flex; align-items: center; gap: 14px; padding: 12px 16px; border-radius: 12px; background: var(--surface-alt, rgba(0,0,0,.035)); font-size: 0.84375rem; font-weight: 700; color: var(--text-main); }

/* Step 1's choice cards: 3 presets + Custom, 2x2 — the only content on the
   step now that the intention field is gone, so sized up (taller cards,
   bigger icon/type) to read as the deliberate focus of the screen rather
   than a small control floating in a mostly-empty one (see the panel-0
   centering in practice.css). A preset tap here is a starting point, not a
   confirm — it loads that preset into step 2 rather than saving it as-is
   (see wireSwb); Custom does the same with a blank draft and stays
   Plus-gated. */
.sw-preset-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-top: 14px; }
.sw-preset-card {
  display: flex; flex-direction: column; align-items: flex-start; justify-content: center; gap: 8px;
  aspect-ratio: 1; padding: 20px; border-radius: 20px; border: 1px solid var(--border);
  background: var(--surface); cursor: pointer; text-align: left;
  transition: transform 0.15s cubic-bezier(0.34, 1.56, 0.64, 1), border-color 0.15s ease, box-shadow 0.15s ease;
}
.sw-preset-card:active { transform: scale(0.96); }
.sw-preset-card:hover { border-color: var(--brand-primary); box-shadow: 0 2px 10px rgba(0,0,0,.06); }
.sw-preset-card .material-symbols-rounded { font-size: 1.875rem; color: var(--brand-primary); transition: transform 0.15s ease; }
.sw-preset-card:active .material-symbols-rounded { transform: scale(1.15); }
.sw-preset-card b { font-size: 1.0625rem; color: var(--text-main); }
.sw-preset-card span:not(.material-symbols-rounded) { font-size: 0.78125rem; color: var(--text-muted); font-weight: 600; }
.sw-preset-custom { border-style: dashed; background: transparent; }
.sw-preset-custom .material-symbols-rounded { color: var(--text-muted); }

/* Step 2's category accordions — one card per category, header always
   visible, body (the toggle-row list) expands/collapses smoothly via a
   grid-template-rows 0fr/1fr transition (the modern any-height accordion
   trick) rather than an instant `hidden` toggle. */
.sw-accordion-list { display: flex; flex-direction: column; gap: 10px; margin-top: 18px; }
/* No overflow:hidden here (only .sw-accordion-body needs it, for the grid
   trick below) — an overflow:hidden ancestor would clip the open header's
   position:sticky right at this box's own edge instead of letting it stick
   against the real scroll container (.detail-body), defeating the point. */
.sw-accordion { border: 1px solid var(--border); border-radius: 14px; background: var(--surface); }
.sw-accordion-head {
  width: 100%; display: flex; align-items: center; gap: 10px; padding: 14px 16px;
  background: var(--surface); border: none; cursor: pointer; text-align: left;
  border-radius: 14px;
}
/* Sticky while open: a category like Prayers & Mantras can run long enough
   that scrolling through it loses the header entirely — pinning it means
   you can always tap straight back to collapse without scrolling up first.
   Only one section is ever open at a time (see sadhanaOpenSection), so
   there's never a second sticky header to collide with this one. */
.sw-accordion.is-open .sw-accordion-head {
  position: sticky;
  /* Clears the swb-overlay's floating close button (.swb-top) — see
     practice.css — so the stuck header sits just below it, not under it. */
  top: calc(env(safe-area-inset-top, 0px) + 64px);
  z-index: 5;
  border-radius: 14px 14px 0 0;
  box-shadow: 0 4px 10px -6px rgba(0, 0, 0, 0.25);
}
.sw-accordion-label { flex: 1; min-width: 0; font-size: 0.90625rem; font-weight: 700; color: var(--text-main); }
.sw-accordion-count {
  flex: 0 0 auto; min-width: 20px; height: 20px; padding: 0 6px; border-radius: 99px;
  background: var(--brand-primary); color: #fff; font-size: 0.6875rem; font-weight: 800;
  display: flex; align-items: center; justify-content: center;
  transition: transform 0.2s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.sw-accordion-chevron { font-size: 1.25rem; color: var(--text-muted); transition: transform 0.25s ease; }
.sw-accordion.is-open .sw-accordion-chevron { transform: rotate(180deg); }
.sw-accordion-panel {
  display: grid; grid-template-rows: 0fr;
  transition: grid-template-rows 0.28s cubic-bezier(0.16, 1, 0.3, 1);
}
.sw-accordion.is-open .sw-accordion-panel { grid-template-rows: 1fr; }
.sw-accordion-body { min-height: 0; overflow: hidden; padding: 0 14px; border-top: 0 solid var(--border); border-radius: 0 0 14px 14px; }
.sw-accordion.is-open .sw-accordion-body { padding: 0 14px 6px; border-top-width: 1px; }
.sw-accordion-body .list-row { padding: 10px 4px; }
@media (prefers-reduced-motion: reduce) {
  .sw-accordion-panel { transition: none; }
}

/* Sadhana builder badges: a flat, solid-color rounded-square icon (accordion
   headers, "add X" rows, the review list, the Today's Sadhana checklist) —
   see sadhanaBadgeHtml() in app.js. The background comes in per-call as an
   inline style, one 10-color palette shared with the Practice tab's games
   shelf (GAME_SHELF_ICONS) so a game step wears the same badge everywhere. */
.sbi-badge {
  flex: 0 0 auto;
  border-radius: 27.5%;
  display: flex; align-items: center; justify-content: center;
}
.sbi-icon { width: 55%; height: 55%; }

/* Step 3's swipe-up-to-complete button: an up arrow + label instead of the
   plain filled pill every other btn-primary uses, tall enough to grab and
   drag. wireSwipeToComplete() drives its transform/opacity while dragging;
   the arrow itself idly bounces (a different element/property than the
   button's own drag transform, so the two never fight) as a "swipe up"
   hint, same idea as the lesson player's scroll-hint chevron. */
.sw-complete { display: flex; align-items: center; justify-content: center; gap: 8px; touch-action: none; }
.sw-complete .material-symbols-rounded { font-size: 1.25rem; animation: swArrowHint 1.6s ease-in-out infinite; }
@keyframes swArrowHint {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(-4px); }
}
@media (prefers-reduced-motion: reduce) {
  .sw-complete .material-symbols-rounded { animation: none; }
}

/* Lesson reader: give the teaching/text/reflection sections room to breathe, 
   the same content with tight margins read as a compressed wall of text. */
.reader-section { margin-bottom: 36px; }
.reader-section h3 { letter-spacing: 1.2px; margin-bottom: 14px; }
.reader-prose { color: var(--text-main); font-size: 1.03125rem; line-height: 1.8; max-width: 38em; }
.reader-hint { display:flex; align-items:center; gap:6px; font-size:0.78125rem; color:var(--text-muted); font-weight:700; margin-bottom:16px; }
.reader-hint .material-symbols-rounded { font-size:1.0625rem; }
.section-teaching-collapsible { margin: 0 0 20px 0; background: var(--surface); border: 1px solid var(--border); border-radius: 16px; overflow: hidden; transition: all 0.25s ease; box-shadow: var(--shadow-soft); }
.section-teaching-toggle { width: 100%; display: flex; align-items: center; justify-content: space-between; padding: 14px 18px; background: transparent; border: none; cursor: pointer; color: var(--text-main); text-align: left; transition: background 0.2s ease; }
.section-teaching-toggle:hover { background: rgba(255, 255, 255, 0.03); }
.teaching-chevron { font-size: 1.375rem; color: var(--text-muted); transition: transform 0.25s ease; }
.section-teaching-collapsible.open .teaching-chevron { transform: rotate(180deg); }
.section-teaching-body { padding: 0 18px 16px; }
.verse-card { background: var(--surface-alt, rgba(0,0,0,.035)); border: 1px solid var(--border, rgba(0,0,0,.06)); border-radius: 18px; padding: 18px 18px 16px; margin-bottom: 14px; }
.verse-card .verse-sanskrit { font-weight:700; font-size:1.0625rem; line-height:2; margin:10px 0 0; color:var(--text-main); }
.verse-card .verse-translit { font-size:0.8125rem; color:var(--text-muted); font-style:italic; line-height:1.7; margin:8px 0 0; }
.verse-card .verse-translation { font-size:0.96875rem; line-height:1.75; margin:12px 0 0; color:var(--text-main); }
.verse-card .read-exp-inner { margin-top:12px; padding-top:12px; border-top:1px dashed var(--border, rgba(0,0,0,.1)); }
.reflection-card { background: var(--surface-alt, rgba(0,0,0,.03)); border-radius: 18px; padding: 20px 20px 16px; }
.check-item { display:flex; align-items:center; gap:12px; padding:10px 0; font-weight:700; color:var(--text-main); }
.check-item .material-symbols-rounded { color:var(--brand-primary); }

/* Search results */
.search-results { margin-bottom:20px; }
.search-res { display:flex; align-items:center; gap:12px; padding:12px 0; border-bottom:1px solid var(--border); cursor:pointer; font-weight:800; }
.search-res .tag { font-size:0.6875rem; text-transform:uppercase; color:var(--text-muted); margin-left:auto; font-weight:800; }

/* Quiz */
.quiz-feedback { text-align:center; font-weight:800; margin-top:14px; min-height:22px; font-family:var(--font-sans); }
.option-card.correct {
  border-color: var(--success-ink);
  background: var(--card-tulsi);
  color: var(--card-tulsi-text);
  box-shadow: 0 4px 0 var(--success), 0 8px 24px rgba(6, 214, 160, 0.15);
}
.option-card.wrong {
  border-color: #dc2626;
  background: #fee2e2;
  color: #b91c1c;
  box-shadow: 0 4px 0 #dc2626, 0 8px 24px rgba(220, 38, 38, 0.15);
}

/* Auth + onboarding overlay */
.auth-overlay { position:absolute; inset:0; z-index:210; background:var(--bg-app); overflow-y:auto; }
.auth-overlay[hidden] { display:none; }
.auth-view { min-height:100%; box-sizing:border-box; display:flex; flex-direction:column; justify-content:center; padding:max(56px,env(safe-area-inset-top)) 28px calc(40px + env(safe-area-inset-bottom)); position:relative; }
.auth-view[hidden] { display:none; }
.auth-back { position:absolute; top:max(48px,env(safe-area-inset-top)); left:22px; }
.auth-brand { width:88px; height:88px; border-radius:28px; background:var(--brand-primary-light); color:var(--brand-primary); display:flex; align-items:center; justify-content:center; margin:0 auto 24px; }
.auth-title { font-size:1.875rem; font-weight:900; color:var(--text-main); margin:0 0 8px; text-align:center; }
.auth-sub { font-size:0.9375rem; color:var(--text-muted); margin:0 0 28px; text-align:center; line-height:1.5; }
.onboard-payoff {
  font-size: 1.5rem !important;
  font-weight: 800 !important;
  background: linear-gradient(110deg, #d97706 0%, #f59e0b 40%, #fef3c7 50%, #f59e0b 60%, #d97706 100%);
  background-size: 200% auto;
  color: transparent;
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  animation: shineText 3s linear infinite;
}
.auth-actions, .auth-form { display:flex; flex-direction:column; gap:12px; }
.auth-form .search { margin:0; }

/* OAuth buttons */
.btn-oauth { display:flex; align-items:center; justify-content:center; gap:10px; width:100%; height:52px; border-radius:14px; font-weight:800; font-size:0.9375rem; font-family:inherit; cursor:pointer; border:2px solid var(--border); transition:transform .1s, background .2s; }
/* An author `display` beats the user-agent rule that makes [hidden] disappear,
   so setting el.hidden on one of these did nothing at all and the button stayed
   on screen. Anything that sets display must say this too. */
.btn-oauth[hidden], .auth-divider[hidden] { display:none; }
.btn-oauth:active { transform:scale(.98); }
.btn-oauth svg { flex:0 0 auto; }
.btn-oauth-google { background:var(--surface); color:var(--text-main); }
/* Apple's own guidelines require their button to be black on white or white on
   black, with no other colour, so this deliberately does NOT use a theme token
   the way everything else does. It has to look the same in dark mode too. */
.btn-oauth-apple { background:#000; color:#fff; border-color:#000; }
.btn-oauth-apple svg { color:#fff; }
.auth-actions { display:flex; flex-direction:column; gap:10px; }
.auth-divider { display:flex; align-items:center; gap:12px; color:var(--text-muted); font-size:0.75rem; font-weight:800; text-transform:uppercase; letter-spacing:.5px; margin:4px 0; }
.auth-divider::before, .auth-divider::after { content:''; flex:1; height:1px; background:var(--border); }
.auth-msg { font-size:0.8125rem; font-weight:700; min-height:18px; text-align:center; color:#b91c1c; }
.auth-msg.ok { color:var(--brand-primary); }
.auth-forgot { padding-top:2px; font-size:0.8125rem; }
.auth-forgot[hidden] { display:none; }

/* Share button on the Learn tab's daily-wisdom card */
.wisdom-share-btn {
  position: absolute; top: 14px; right: 14px;
  width: 38px; height: 38px;
  background: rgba(255,255,255,.16); border: none; color: #fff;
}
.wisdom-share-btn .material-symbols-rounded { font-size: 1.25rem; }

.btn-ghost { padding:13px; border:none; border-radius:14px; background:transparent; color:var(--text-muted); font-weight:800; font-size:0.9375rem; font-family:inherit; cursor:pointer; }
.btn-ghost:active { color:var(--text-main); }
/* Onboarding */
/* One continuous bar for the whole onboarding flow (see showOnboardStep's
   width update) — not a dot per step. At 11 steps a dot-per-step row either
   wrapped to a second line or shrank into an unreadable row of specks on a
   real phone width; a single bar reads as "how far along" regardless of how
   many steps that number happens to be. Same element/markup for every
   screen, including the ones after the diya + account steps, so the look
   never changes partway through. */
.onboard-progress { margin-bottom:32px; padding:0 4px; }
.onboard-progress-track { height:6px; border-radius:99px; background:var(--border); overflow:hidden; }
.onboard-progress-fill { height:100%; width:0; border-radius:99px; background:var(--brand-primary); transition:width .35s cubic-bezier(0.16,1,0.3,1); }
/* .onboard-step's own layout (flex:0 0 100% inside the sliding track) and
   the [inert]-driven hide are declared in index.html's own <style> block,
   next to the track markup they belong to — kept together since editing
   one without the other breaks the slide. This file only owns what's
   generic across steps. */
.onboard-step .search { margin:0; }

/* Single-tap option cards — goal / experience-level / reminder-time steps.
   Built by buildOnboardOptionList() in app.js.
   Sized with clamp()s against dvh, not just fixed px, because this list's
   height problem is a SHORT-VIEWPORT problem, not (only) a narrow-viewport
   one: the fluid root font-size elsewhere in this file answers "the phone
   is narrower than the ~390px reference," but 5 stacked cards (the goal
   step) overflowing a short screen is about available HEIGHT, which vw-only
   scaling can't see at all. A 260x569 viewport is far outside any real
   phone, but the same failure — a fixed-height list of cards taller than a
   fixed viewport, no shrink, straight to scroll/clip — happens on real
   short devices too (an iPhone SE-class 568px-tall screen, or any device
   once the keyboard/nav chrome eats into it), just less dramatically. The
   clamp floors below are tuned so a normal phone at its normal height
   renders identically to before (the floor is rarely if ever reached there)
   and only kicks in once height is actually scarce. */
.onboard-options { display:flex; flex-direction:column; gap:clamp(6px, 1.6dvh, 12px); width:100%; }
.onboard-option-card {
  display:flex; align-items:center; gap:14px; width:100%;
  padding:clamp(8px, 2.2dvh, 16px) 18px; border-radius:18px;
  background:var(--surface); border:1.5px solid var(--border);
  font-family:inherit; font-size:1rem; font-weight:700; color:var(--text-main);
  text-align:left; cursor:pointer; box-shadow:var(--shadow-soft);
  transition:transform 0.12s ease, border-color 0.15s ease, background 0.15s ease;
}
.onboard-option-card:active { transform:scale(0.97); }
.onboard-option-card.is-selected { border-color:var(--brand-primary); background:var(--brand-primary-light, rgba(248,94,0,0.08)); }
/* Icon badge is sadhanaBadgeHtml's own flat colorful squircle now (see
   buildOnboardOptionList) — same component the sadhana builder uses, no
   separate icon-wrap rule needed here. Its 40px size is set inline by
   sadhanaBadgeHtml (JS-computed, not this stylesheet), so overriding it for
   a short viewport needs !important — the one legitimate use of it here,
   since there is no other way to win against an inline style short of
   changing the shared badge helper's own call signature for this one
   caller. */
.onboard-option-card .sadhana-badge { width:clamp(28px, 8dvh, 40px) !important; height:clamp(28px, 8dvh, 40px) !important; }
.onboard-option-label { flex:1; min-width:0; }
/* The subtitle above each of these three steps carries its own fixed
   28-32px margin-bottom, set inline in index.html per step (not shared
   here) — on a short viewport that's real height being spent before the
   card list — the thing actually short on room — even gets a chance to
   shrink. !important because an inline style otherwise always wins over an
   external stylesheet regardless of selector weight; scoped to just these
   three steps (not every .auth-sub, most of which have nothing height-
   sensitive stacked under them) so nothing else's spacing changes. */
#onboard-goal .auth-sub, #onboard-experience .auth-sub, #onboard-reminder .auth-sub {
  margin-bottom: clamp(10px, 3dvh, 28px) !important;
}
.onboard-option-check { flex-shrink:0; color:var(--brand-primary); display:flex; }
.onboard-option-check .material-symbols-rounded { font-size:1.5rem; font-variation-settings:'FILL' 1; }

/* Plan reveal — two real onboarding steps now (onboard-plan-intro,
   onboard-plan-list — see renderOnboardPlanIntro/renderOnboardPlanList in
   app.js), not a separate overlay; they get their layout for free from the
   same .onboard-step/.onboard-panel-track machinery every other step uses. */

/* Intro: up to 4 real sadhanaBadgeHtml badges (same flat colored squircle
   the builder itself uses), each sliding in diagonally from its own corner.
   .animate is added fresh every time this step is entered (see
   renderOnboardPlanIntro) so the reveal replays instead of only playing
   once per onboarding session. */
.onboard-plan-badges { display:grid; grid-template-columns:repeat(2, 1fr); gap:22px; justify-content:center; width:fit-content; margin:0 auto; }
/* Slowed down on purpose — each badge landing one at a time, deliberately,
   is what sells "this is being built for you" rather than a quick flourish
   that reads as decoration. */
.onboard-plan-badge { opacity:0; animation-duration:2.4s; animation-timing-function:cubic-bezier(0.16,1,0.3,1); animation-fill-mode:forwards; }
.onboard-plan-badges.animate .onboard-plan-badge-tl { animation-name:onboardPlanSlideTL; animation-delay:0.2s; }
.onboard-plan-badges.animate .onboard-plan-badge-tr { animation-name:onboardPlanSlideTR; animation-delay:0.9s; }
.onboard-plan-badges.animate .onboard-plan-badge-bl { animation-name:onboardPlanSlideBL; animation-delay:1.6s; }
.onboard-plan-badges.animate .onboard-plan-badge-br { animation-name:onboardPlanSlideBR; animation-delay:2.3s; }
@keyframes onboardPlanSlideTL { 0%{opacity:0; transform:translate(-40px,-40px) scale(0.9);} 100%{opacity:1; transform:translate(0,0) scale(1);} }
@keyframes onboardPlanSlideTR { 0%{opacity:0; transform:translate(40px,-40px) scale(0.9);} 100%{opacity:1; transform:translate(0,0) scale(1);} }
@keyframes onboardPlanSlideBL { 0%{opacity:0; transform:translate(-40px,40px) scale(0.9);} 100%{opacity:1; transform:translate(0,0) scale(1);} }
@keyframes onboardPlanSlideBR { 0%{opacity:0; transform:translate(40px,40px) scale(0.9);} 100%{opacity:1; transform:translate(0,0) scale(1);} }
@media (prefers-reduced-motion: reduce) {
  .onboard-plan-badge { animation: none !important; opacity: 1; transform: none; }
}

/* The list: the real generated steps, same row language the builder itself
   uses for a routine's steps.
   dvh-clamped for the same reason as the goal/experience option cards above
   (see that block's comment): this is a fixed-height stack of N rows plus a
   title, a stat line and a CTA, and none of it could shrink for a short
   viewport before — it either fit or it silently ran past the bottom of
   .onboard-panels. The list's own #onboard-plan-list-rows margin-top (set
   inline in index.html) and .onboard-plan-stat's margin-top get the same
   treatment below. This still keeps #onboard-plan-list .onboard-step-content
   scrollable as a fallback (see that rule above) for a genuinely long
   generated plan on a genuinely short device — the clamps make scrolling
   rare in the realistic 3-4-row case, not impossible in every case. */
.onboard-plan-list-rows { display:flex; flex-direction:column; gap:clamp(6px, 1.4dvh, 12px); margin-top:clamp(8px, 2dvh, 20px) !important; }
.onboard-plan-row {
  display:flex; align-items:center; gap:14px;
  padding:clamp(8px, 1.8dvh, 14px) 16px; border-radius:16px;
  background:var(--surface); border:1px solid var(--border);
}
.onboard-plan-row .sadhana-badge { width:clamp(30px, 7dvh, 44px) !important; height:clamp(30px, 7dvh, 44px) !important; }
.onboard-plan-row-copy { flex:1; min-width:0; display:flex; flex-direction:column; }
.onboard-plan-row-copy b { font-size:0.9375rem; color:var(--text-main); }
.onboard-plan-row-copy span { font-size:0.8125rem; color:var(--text-muted); margin-top:2px; display:-webkit-box; -webkit-line-clamp:2; line-clamp:2; -webkit-box-orient:vertical; overflow:hidden; }

/* Reuses .td-premium-shine/.td-premium-line's own glint sweep (see the
   Premium plate card) rather than a second shimmer implementation — same
   "light catching a shiny surface" read, just on a button instead of a
   plate. Gold gradient matches the paywall's own .pr-cta-gold (pricing.css),
   duplicated here in the numbers rather than shared: pricing.css loads lazily
   only once the paywall opens, and this button needs to look right from the
   very first frame of an always-loaded onboarding step. */
.onboard-plan-unlock {
  position: relative; overflow: hidden;
  padding: 14px; border-radius: 16px; border: none; cursor: pointer;
  font-size: 1.0625rem; font-weight: 800; font-family: inherit; text-align: center;
  background: linear-gradient(180deg, #FFF0B3 0%, #D4A852 100%);
  color: #5A4A20;
  box-shadow: 0 6px 0 #9E7321, 0 10px 20px rgba(212,168,82,0.3), inset 0 2px 0 rgba(255,255,255,0.5);
  transition: transform 0.1s ease;
}
.onboard-plan-unlock:active { transform: translateY(4px); box-shadow: 0 2px 0 #9E7321, 0 4px 8px rgba(212,168,82,0.2), inset 0 2px 0 rgba(255,255,255,0.5); }
.onboard-plan-unlock-label { position: relative; z-index: 1; }
.onboard-plan-row-min { flex-shrink:0; font-size:0.8125rem; font-weight:800; color:var(--text-muted); }
/* Fills the gap between the step list and the CTA on the plan-list step
   with a reason to tap it, rather than leaving that space empty. The two
   emphasized phrases reuse .premium-text-shine (see td-greet/me-user-name)
   verbatim — the app's one existing "this text is gold and precious"
   treatment — rather than inventing a second one. */
.onboard-plan-stat {
  text-align: center;
  font-size: 1.1875rem;
  line-height: 1.5;
  font-weight: 700;
  color: var(--text-main);
  max-width: 320px;
  margin: clamp(10px, 2.5dvh, 30px) auto 0;
}
/* Scoped to plan-list only — the shared .onboard-step-actions rule (used by
   every other step too) keeps its normal 24px; this is the one step whose
   content above it (the row list) can be tall enough that the fixed 24px
   gap is worth reclaiming on a short viewport. */
#onboard-plan-list .onboard-step-actions { padding-top: clamp(8px, 2dvh, 24px); }
.onboard-plan-stat .premium-text-shine { font-weight: 900; }

/* Notification-permission screen: a bell ringing, two rings expanding out
   and fading — reads as "this is actively notifying you" rather than a
   static icon, which is the one beat where showing (not just telling) what
   the ask is for actually matters. */
.onboard-notif-bell {
  position: relative; width: 108px; height: 108px; margin: 0 auto;
  display: flex; align-items: center; justify-content: center;
  border-radius: 50%; background: var(--brand-primary-light);
}
.onboard-notif-bell-icon {
  position: relative; z-index: 1;
  font-size: 3rem; color: var(--brand-primary);
  font-variation-settings: 'FILL' 1;
}
.onboard-notif-wave {
  position: absolute; inset: 0; border-radius: 50%;
  border: 2px solid var(--brand-primary);
  opacity: 0;
  animation: onboardNotifWave 2.4s ease-out infinite;
}
.onboard-notif-wave-2 { animation-delay: 1.2s; }
@keyframes onboardNotifWave {
  0% { transform: scale(0.85); opacity: 0.5; }
  100% { transform: scale(1.55); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .onboard-notif-wave { animation: none; opacity: 0; }
}

/* Gita course path (Duolingo-style) */
.course { display:flex; flex-direction:column; gap:24px; margin-bottom:28px; }
.unit {
  border-radius: var(--radius-lg);
  overflow: hidden;
  border: 1px solid var(--border);
  box-shadow: var(--shadow-soft);
}
.unit-head {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 20px;
  border-bottom: 1px solid var(--border);
}
.unit-icon {
  width: 46px;
  height: 46px;
  border-radius: 16px;
  background: rgba(255,255,255,0.7);
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--text-main);
  flex: 0 0 auto;
  box-shadow: 0 2px 6px rgba(0,0,0,0.03);
}
body.dark-mode .unit-icon {
  background: rgba(0,0,0,0.25);
  color: #fff;
}
.unit-ch {
  font-size: 0.6875rem;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.8px;
  color: var(--text-muted);
}
.lesson-path { background: var(--surface); }
.lesson-node {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 16px 20px;
  cursor: pointer;
  border-top: 1px solid var(--border);
  transition: background-color 0.2s ease, transform 0.2s ease;
}
.lesson-node:not(.locked):hover {
  background: var(--surface-alt);
  transform: translateX(4px);
}
.lesson-dot {
  width: 38px;
  height: 38px;
  border-radius: 50%;
  background: var(--surface-alt);
  color: var(--text-muted);
  display: flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  box-shadow: inset 0 2px 4px rgba(0,0,0,0.02);
  transition: background-color 0.2s, color 0.2s;
}
.lesson-node.done .lesson-dot {
  background: var(--card-tulsi);
  color: var(--card-tulsi-text);
  box-shadow: none;
}
.lesson-meta { flex: 1; min-width: 0; }
.lesson-title { font-weight: 850; font-size: 0.9375rem; color: var(--text-main); }
.lesson-theme { font-size: 0.75rem; color: var(--text-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; margin-top: 2px; }
.lesson-node .material-symbols-rounded { color: var(--text-muted); }

/* Lesson practice questions */
.lq { margin-bottom: 12px; }
.lq-opt {
  display: block;
  width: 100%;
  text-align: left;
  margin: 8px 0;
  padding: 14px 16px;
  border: 1px solid var(--border);
  border-radius: 14px;
  background: var(--surface);
  color: var(--text-main);
  font-weight: 750;
  font-family: inherit;
  font-size: 0.875rem;
  cursor: pointer;
  box-shadow: 0 3px 0 var(--border);
  transition: transform 0.1s ease, box-shadow 0.1s ease, border-color 0.2s ease;
}
.lq-opt:hover:not(:disabled) {
  border-color: var(--brand-primary);
}
.lq-opt:active:not(:disabled) {
  transform: translateY(2px);
  box-shadow: 0 1px 0 var(--border);
}
.lq-opt.correct {
  border-color: var(--success-ink);
  background: var(--card-tulsi);
  color: var(--card-tulsi-text);
  box-shadow: 0 3px 0 var(--success);
}
.lq-opt.wrong {
  border-color: #dc2626;
  background: #fee2e2;
  color: #b91c1c;
  box-shadow: 0 3px 0 #dc2626;
}
.lq-opt:disabled { cursor: default; }
.lq-exp {
  margin: 10px 0 6px;
  padding: 12px 16px;
  background: var(--surface-alt);
  border-radius: 12px;
  font-size: 0.8125rem;
  color: var(--text-main);
  line-height: 1.55;
}
.lq-exp[hidden] { display: none; }

/* ---- toast ----
   position:absolute (not fixed) and appended inside #app (see showToast
   in app.js), not document.body: .app fills the viewport on mobile but
   becomes a fixed-size centered phone frame at >=520px (see .app's own
   media query), same as .bottom-nav — a fixed-to-viewport toast had no
   way to agree with that frame's actual bottom edge on desktop, which is
   what let it clip into the nav bar instead of sitting above it. */
#toast { position: absolute; left: 50%; bottom: 110px; transform: translateX(-50%) translateY(16px); background: var(--text-main); color: var(--surface); padding: 11px 20px; border-radius: 99px; font-weight: 800; font-size: 0.8125rem; opacity: 0; pointer-events: none; transition: opacity .25s, transform .25s; z-index: 200; max-width: 85%; text-align: center; }
#toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }

/* ---- course: locked lessons + review ---- */
.lesson-node.locked { opacity: 0.45; }
.lesson-node.locked .lesson-dot { background: var(--surface-alt); color: var(--text-muted); }
.lesson-node.locked:hover { background: var(--surface); transform: none; }
.course-note { display: flex; align-items: center; gap: 6px; font-size: 0.75rem; font-weight: 700; color: var(--text-muted); margin: -6px 0 12px; }
.course-note .material-symbols-rounded { font-size: 1rem; }
.review-card {
  border: 1.5px dashed var(--brand-primary);
  background: var(--surface);
  border-radius: var(--radius-lg);
  padding: 20px;
}
.review-card > h3 { display: flex; align-items: center; gap: 6px; font-family: var(--font-title); }
.review-card > h3 .material-symbols-rounded { font-size: 1.125rem; color: var(--brand-primary); }
.review-chip {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 14px;
  margin: 8px 0;
  background: var(--surface-alt);
  border-radius: 14px;
  font-weight: 800;
  font-size: 0.8125rem;
  cursor: pointer;
  border: 1px solid var(--border);
  transition: transform 0.2s ease, background-color 0.2s ease;
}
.review-chip:hover {
  transform: translateX(4px);
  background-color: var(--border);
}
.review-chip .material-symbols-rounded { font-size: 1.125rem; color: var(--brand-primary); }
.review-chip .tag { margin-left: auto; font-size: 0.6875rem; color: var(--text-muted); }

/* ---- lesson completion ---- */
.lesson-congrats { display: flex; flex-direction: column; align-items: center; text-align: center; padding: 56px 24px 32px; }
.congrats-ring { width: 84px; height: 84px; border-radius: 50%; background: var(--card-tulsi); color: var(--success-ink); display: flex; align-items: center; justify-content: center; }
.congrats-ring .material-symbols-rounded { font-size: 2.75rem; }
.congrats-ring.perfect { background: var(--brand-primary-light); color: var(--brand-primary); border: 1px solid var(--brand-primary); }

/* ---- Duolingo-style lesson drill ---- */
/* Mini-game header: clears the detail overlay's floating back button. */
.game-head { padding-top: calc(env(safe-area-inset-top, 0px) + 60px); }
.game-head-title { font-size: 1.25rem; font-weight: 900; font-family: var(--font-title); color: var(--text-main); margin-bottom: 14px; padding-left: 52px; min-height: 40px; display: flex; align-items: center; }
.drill-top { display: flex; align-items: center; gap: 12px; padding: 8px 0 20px; }
.drill-progress { flex: 1; height: 12px; background: var(--surface-alt); border-radius: 99px; overflow: hidden; }
.drill-progress-fill { height: 100%; background: var(--brand-primary); border-radius: 99px; transition: width .3s cubic-bezier(.2,.8,.2,1); }
.drill-count { font-size: 0.8125rem; font-weight: 800; color: var(--text-muted); min-width: 34px; text-align: right; }
.drill-q { margin-bottom: 16px; }
.drill-question { font-size: 1.25rem; font-weight: 800; line-height: 1.4; margin-bottom: 20px; color: var(--text-main); }
.drill-opts { display: flex; flex-direction: column; gap: 12px; }
.drill-opt { text-align: left; padding: 16px; border: 2px solid var(--border); border-radius: 16px; background: var(--surface); color: var(--text-main); font-weight: 700; font-size: 0.9375rem; font-family: inherit; cursor: pointer; transition: transform .1s, border-color .15s, background .15s; }
.drill-opt:active:not(:disabled) { transform: scale(.99); }
.drill-opt.selected { border-color: var(--brand-primary); background: var(--brand-primary-light); }
.drill-opt.correct { border-color: var(--success); background: var(--success-light); color: var(--success-ink); }
.drill-opt.wrong { border-color: #dc2626; background: #fee2e2; color: #b91c1c; }
.drill-opt:disabled { cursor: default; }
body.dark-mode .drill-opt.correct { background: var(--success-light); color: var(--success-ink); }
body.dark-mode .drill-opt.wrong { background: #3a1620; color: #fca5a5; }
.drill-feedback { border-radius: 16px; padding: 14px 16px; margin: 16px 0; }
.drill-feedback[hidden] { display: none; }
.drill-feedback.ok { background: var(--success-light); color: var(--success-ink); }
.drill-feedback.no { background: #fee2e2; color: #b91c1c; }
body.dark-mode .drill-feedback.ok { background: var(--success-light); color: var(--success-ink); }
body.dark-mode .drill-feedback.no { background: #3a1620; color: #fca5a5; }
.drill-fb-head { display: flex; align-items: center; gap: 8px; font-weight: 900; font-size: 0.9375rem; }
.drill-fb-head .material-symbols-rounded { font-size: 1.25rem; }
.drill-fb-exp { margin-top: 6px; font-size: 0.8125rem; font-weight: 600; line-height: 1.5; opacity: .9; }
.drill-check { width: 100%; margin-bottom: 24px; }
.drill-check:disabled { opacity: .45; cursor: default; }

/* ---- Reward row on the lesson-complete screen ---- */
.reward-row { display: flex; justify-content: center; gap: 14px; margin-bottom: 22px; }
.reward-stat { background: var(--surface-alt); border-radius: 16px; padding: 12px 18px; min-width: 78px; }
.reward-num { font-size: 1.375rem; font-weight: 900; color: var(--brand-primary); display: flex; align-items: center; justify-content: center; gap: 3px; }
.reward-num .material-symbols-rounded { font-size: 1.25rem; }
.reward-lbl { font-size: 0.6875rem; font-weight: 800; text-transform: uppercase; letter-spacing: .4px; color: var(--text-muted); margin-top: 2px; }

.lesson-nudge { text-align: center; margin: 8px 0 32px; }
.lesson-nudge .btn-primary, .lesson-nudge .btn-ghost { height: auto; }

/* ---- Today's Sadhana ritual ---- */
.ritual-card {
  padding: 24px;
  background: var(--surface);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-premium);
  border: 1px solid var(--border);
  position: relative;
  overflow: hidden;
}
.ritual-card::before {
  content: '';
  position: absolute;
  top: 0; left: 0; width: 4px; height: 100%;
  background: var(--gradient-sunset);
}
.ritual-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 20px;
}
.ritual-ring {
  position: relative;
  width: 56px;
  height: 56px;
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  transition: box-shadow 0.3s ease;
}
.ritual-ring.complete-glow {
  box-shadow: 0 0 16px rgba(212, 93, 53, 0.4);
  animation: completionPulse 2s infinite cubic-bezier(0.25, 0, 0, 1);
}
@keyframes completionPulse {
  0% { box-shadow: 0 0 0 0 rgba(212, 93, 53, 0.6); }
  70% { box-shadow: 0 0 0 18px rgba(212, 93, 53, 0); }
  100% { box-shadow: 0 0 0 0 rgba(212, 93, 53, 0); }
}
.ritual-ring svg {
  transform: rotate(-90deg);
  width: 56px;
  height: 56px;
}
.ritual-ring circle {
  fill: none;
  stroke-width: 4px;
}
.ritual-ring .progress-bg {
  stroke: var(--surface-alt);
}
.ritual-ring .progress-bar {
  stroke: url(#ritualGradient);
  stroke-linecap: round;
  transition: stroke-dashoffset 0.5s cubic-bezier(0.4, 0, 0.2, 1);
}
/* Re-usable gradient definition via inline SVG reference or fallback to brand color */
.ritual-ring .progress-bar {
  stroke: var(--brand-primary);
}
body.dark-mode .ritual-ring .progress-bar {
  stroke: var(--brand-primary);
}
.ritual-ring-text {
  position: absolute;
  font-family: var(--font-sans);
  font-weight: 800;
  font-size: 0.8125rem;
  color: var(--text-main);
}
.ritual-step {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 14px 0;
  border-top: 1px solid var(--border);
  cursor: pointer;
  transition: transform 0.2s ease, opacity 0.2s ease;
}
.ritual-step:hover {
  transform: translateX(4px);
}
.ritual-step .material-symbols-rounded:last-child {
  color: var(--text-muted);
  transition: color 0.2s ease, transform 0.2s ease;
}
.ritual-step:active .material-symbols-rounded:last-child {
  transform: scale(1.1);
}
.ritual-step.done .material-symbols-rounded:last-child {
  color: var(--success-ink);
}
body.dark-mode .ritual-step.done .material-symbols-rounded:last-child {
  color: var(--success);
}
.ritual-step.done {
  opacity: .7;
}
.ritual-step.done > div:nth-child(2) {
  text-decoration: line-through;
  color: var(--text-muted);
}
.ritual-dot {
  width: 40px;
  height: 40px;
  border-radius: 14px;
  background: var(--brand-primary-light);
  color: var(--brand-primary);
  display: flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  box-shadow: 0 4px 10px rgba(212, 93, 53, 0.08);
  transition: background-color 0.3s ease, color 0.3s ease;
}
.ritual-step.done .ritual-dot {
  background: var(--card-tulsi);
  color: var(--card-tulsi-text);
  box-shadow: none;
}
.ritual-complete {
  margin-top: 16px;
  padding: 14px;
  border-radius: 16px;
  background: var(--card-tulsi);
  color: var(--card-tulsi-text);
  text-align: center;
  font-family: var(--font-sans);
  animation: popIn 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
  box-shadow: 0 4px 12px rgba(6, 95, 70, 0.05);
}
@keyframes popIn {
  from { transform: scale(0.95); opacity: 0; }
  to { transform: scale(1); opacity: 1; }
}

/* ---- Sadhana Plus (premium gate) ---- */
.plus-card { border: 1.5px solid var(--brand-primary); }
.plus-locked .unit { opacity: .65; cursor: pointer; }
.plus-locked .unit-icon .material-symbols-rounded { color: var(--text-muted); }

.profile-stats { display: flex; gap: 12px; margin-bottom: 8px; }
.profile-stats .reward-stat { flex: 1; text-align: center; }

/* ---- Sacred Texts & Story albums ---- */
.album-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 14px;
  margin-bottom: 16px;
}
.home-shelf-section {
  margin: 26px 0 20px;
}

/* Audio tab: Spotify-style genre grid -> album detail. .audio-cat-tile and
   .audio-album-hero deliberately share the same color + icon per genre (set
   inline from RECITE_GROUPS) so the detail screen reads as "the tile, opened"
   rather than a different place. */

/* Open/close both just replace one panel's innerHTML and toggle `hidden` on
   the other (see renderAudio in app.js) — no JS-driven transition, so the
   entrance has to be a plain CSS animation on each panel that replays every
   time it goes from `[hidden]` to shown (a `display:none` -> displayed
   transition restarts a running `animation` from its start, per spec, so
   this needs no JS hook at all). Only the incoming panel animates; the
   outgoing one just disappears instantly under it, which reads as clean
   rather than janky since it's covered by the incoming panel's motion. */
@keyframes audioPanelIn {
  from { opacity: 0; transform: translateY(10px) scale(0.98); }
  to   { opacity: 1; transform: translateY(0) scale(1); }
}
.audio-cat-grid,
.audio-album {
  animation: audioPanelIn 0.32s cubic-bezier(0.16, 1, 0.3, 1) both;
}
@media (prefers-reduced-motion: reduce) {
  .audio-cat-grid, .audio-album { animation: none; }
}
/* .audio-album is reused inside the full-text reader (openText/openTextBook
   in app.js), which — unlike Audio's own tab page — sits inside the .detail
   overlay under a floating circular back button (.detail-top, position:
   absolute over the content). Without this the hero's rounded top corners
   render clipped by the viewport edge and the back button sits on top of
   its own title. Scoped to #detail-body's copy only, or Audio's own
   instance (a plain tab page that already starts below the normal header)
   would gain a pointless gap it never needed. */
#detail-body .audio-album { padding-top: calc(env(safe-area-inset-top, 0px) + 64px); }

.audio-cat-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
  margin-top: 6px;
}
.audio-cat-grid[hidden] { display: none; }
.audio-cat-tile {
  position: relative;
  overflow: hidden;
  border: none;
  border-radius: 14px;
  min-height: 108px;
  padding: 14px;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  align-items: flex-start;
  text-align: left;
  color: #fff;
  cursor: pointer;
  font-family: inherit;
}
.audio-cat-tile:active { transform: scale(.98); }
/* The lone tile of an odd-count grid (Meditative, usually) — spans both
   columns instead of leaving a bare gap next to it. */
.audio-cat-tile-wide { grid-column: span 2; }
.audio-cat-watermark {
  position: absolute;
  right: -14px;
  bottom: -20px;
  font-size: 5.75rem;
  line-height: 1;
  opacity: .24;
  transform: rotate(18deg);
  pointer-events: none;
}
.audio-cat-watermark .material-symbols-rounded { font-size: 5.75rem; }
.audio-cat-title {
  font-family: var(--font-title);
  font-size: 1.03125rem;
  font-weight: 850;
  letter-spacing: -0.2px;
  position: relative;
  z-index: 1;
}
.audio-cat-count {
  font-size: 0.71875rem;
  font-weight: 800;
  color: rgba(255,255,255,.95);
  position: relative;
  z-index: 1;
}

/* Album detail (one genre's track list). */
.audio-back {
  display: inline-flex; align-items: center; gap: 4px;
  border: none; background: transparent; padding: 0 0 14px;
  font-family: inherit; font-weight: 800; font-size: 0.90625rem;
  color: var(--text-muted); cursor: pointer;
}
.audio-album-hero {
  position: relative;
  overflow: hidden;
  border-radius: 20px;
  padding: 28px 22px 22px;
  margin-bottom: 18px;
  color: #fff;
}
.audio-album-hero-icon {
  position: absolute;
  right: -16px;
  top: -18px;
  font-size: 6.875rem;
  line-height: 1;
  opacity: .22;
  transform: rotate(14deg);
}
.audio-album-hero-icon .material-symbols-rounded { font-size: 6.875rem; }
/* Full-text chapter-select heroes (openText/openTextBook in app.js) use
   this in place of .audio-album-hero-icon — the same falling-Devanagari
   rain the book covers carry (roadmap.js's rainLayerSvg via
   window.KaranaBookCover), not a static rotated icon. Absolutely
   positioned + the hero's own overflow:hidden/border-radius clips it
   without needing its own SVG clipPath, unlike the book cover's
   asymmetric right-only-rounded shape. */
.text-hero-rain { position: absolute; inset: 0; width: 100%; height: 100%; display: block; pointer-events: none; }
.audio-album-hero h1 {
  font-family: var(--font-title);
  font-size: 1.625rem;
  font-weight: 900;
  letter-spacing: -0.4px;
  margin: 0 0 6px;
  position: relative; z-index: 1;
}
.audio-album-hero p {
  margin: 0;
  font-size: 0.84375rem;
  font-weight: 600;
  color: rgba(255,255,255,.88);
  position: relative; z-index: 1;
}
.audio-track-list {
  display: flex; flex-direction: column; gap: 2px;
}
.audio-track-row {
  display: flex; align-items: center; gap: 14px;
  width: 100%; border: none; background: transparent;
  padding: 11px 4px; border-radius: 12px;
  font-family: inherit; text-align: left; cursor: pointer;
}
.audio-track-row:active { background: var(--surface-alt); }
.audio-track-num {
  flex: 0 0 auto; width: 22px; text-align: center;
  font-size: 0.84375rem; font-weight: 700; color: var(--text-muted);
}
.audio-track-copy { flex: 1; min-width: 0; }
.audio-track-copy b { font-size: 0.90625rem; color: var(--text-main); }
.audio-track-sub { color: var(--text-muted); font-size: 0.78125rem; margin-top: 1px; }
.audio-track-row > .material-symbols-rounded:last-child { color: var(--text-muted); flex: 0 0 auto; }

/* Library's Saved / Recent segmented control. */
.lib-tabs {
  display: flex; gap: 4px; padding: 4px; margin: 4px 0 6px;
  background: var(--surface-alt); border-radius: 14px;
}
/* 44px minimum. These measured 38px tall, which is under the tap target every
   platform guideline asks for and is felt as a miss rather than seen as one. */
.lib-tab {
  flex: 1; text-align: center; padding: 13px 0; min-height: 44px; border: none; background: transparent;
  border-radius: 10px; font-family: inherit; font-weight: 800; font-size: 0.90625rem;
  color: var(--text-muted); cursor: pointer; transition: background .15s, color .15s;
}
.lib-tab.active {
  background: var(--surface); color: var(--brand-primary); box-shadow: var(--shadow-soft);
}
.home-shelf-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 14px;
}
.home-shelf-header h3 {
  font-family: var(--font-title);
  font-size: 1.1875rem;
  font-weight: 850;
  color: var(--text-main);
  margin: 0;
  letter-spacing: -0.3px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  flex-shrink: 0;
}
.home-shelf-header span,
.recite-shelf-desc {
  font-size: 0.78125rem;
  font-weight: 600;
  color: var(--text-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  text-align: right;
  min-width: 0;
}
.home-shelf-scroll {
  display: flex;
  gap: 16px;
  overflow-x: auto;
  padding-bottom: 6px;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;
}
.home-shelf-scroll::-webkit-scrollbar {
  display: none;
}

/* Hallow Clean Card Layout (Thumbnail + Text Below) */
.hallow-clean-item {
  width: 164px;
  min-width: 164px;
  flex-shrink: 0;
  scroll-snap-align: start;
  cursor: pointer;
  display: flex;
  flex-direction: column;
  user-select: none;
}
.hallow-clean-item:active {
  transform: scale(0.97);
  transition: transform 0.1s ease;
}
.hallow-clean-thumb {
  position: relative;
  width: 100%;
  height: 108px;
  border-radius: 16px;
  overflow: hidden;
  background: var(--surface-alt);
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.28);
  transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.hallow-clean-item:hover .hallow-clean-thumb {
  transform: translateY(-2px);
  box-shadow: 0 10px 24px rgba(0, 0, 0, 0.4);
}
.hallow-clean-thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.hallow-clean-title {
  font-family: var(--font-title);
  font-size: 0.9375rem;
  font-weight: 800;
  color: var(--text-main);
  margin: 10px 0 2px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.hallow-clean-sub {
  font-size: 0.8125rem;
  font-weight: 500;
  color: var(--text-muted);
  margin: 0 0 2px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.hallow-clean-meta {
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--text-muted);
  margin: 0;
}

/* ---- Me Page Styling ---- */
.me-top-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin: 10px 0 16px;
}
/* Plain heading, not a button — there is no Edit Profile entry point
   anymore, so the name itself has nothing to open. */
.me-user-name-plain {
  font-family: var(--font-title);
  font-size: 1.625rem;
  font-weight: 900;
  color: var(--text-main);
  margin: 0;
  line-height: 1.2;
}
.me-settings-btn {
  width: 42px;
  height: 42px;
  border-radius: 50%;
  background: var(--surface-alt);
  color: var(--text-main);
  border: none;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  box-shadow: 0 4px 12px rgba(0,0,0,0.2);
}
/* Profile's streak card: the exact .td-streak-card markup/CSS Today's
   compact flame tile uses (see styles.css's "streak card" block near
   .td-mast), reused wholesale rather than rebuilt so the hand-tuned glow
   position stays in sync in one place. .me-streak-hero only trims the
   card's own bottom corners/margin so the stats footer reads as one
   fused unit, not two stacked cards. */
.me-streak-group {
  margin-bottom: 20px;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.me-streak-group .me-streak-hero {
  margin-bottom: 0;
  border-radius: 24px 24px 0 0;
}
.me-streak-stats {
  display: flex;
  background: var(--surface);
  border: 1px solid var(--border);
  border-top: none;
  border-radius: 0 0 24px 24px;
  padding: 14px 8px;
  box-shadow: var(--shadow-soft);
}
/* Fused to an unlit hero above (see .td-streak-card:not(.is-lit) in the
   "streak card" block), which is always dark now regardless of theme — the
   footer has to follow it there or the two halves of one visual card split
   into a dark top and a light-mode-white bottom. */
.me-streak-group:has(.me-streak-hero:not(.is-lit)) .me-streak-stats {
  background: #252422;
  border-color: #403D39;
}
.me-streak-group:has(.me-streak-hero:not(.is-lit)) .me-streak-stat b { color: #EFF1F0; }
.me-streak-group:has(.me-streak-hero:not(.is-lit)) .me-streak-stat span { color: #A6A199; }
.me-streak-stat { flex: 1; text-align: center; }
.me-streak-stat b {
  display: block;
  font-family: var(--font-title);
  font-size: 1.125rem;
  font-weight: 800;
  color: var(--text-main);
}
.me-streak-stat span {
  display: block;
  font-size: 0.65625rem;
  color: var(--text-muted);
  margin-top: 1px;
}

/* My Sadhana: the same .feature-card/.feature-blob/.td-feature-* family
   Today's "Be Mindful" banner uses (see styles.css's Be Mindful block) —
   one big colorful tap target into the builder. Lives on the Practice tab
   (#pr-sadhana-card, see renderExplore). */
#pr-sadhana-card { margin-bottom: 20px; }
#pr-sadhana-card .material-symbols-rounded:last-child {
  position: relative; z-index: 2; color: rgba(255,255,255,.85); flex: 0 0 auto;
}
.album-card {
  position: relative;
  aspect-ratio: 4 / 3;
  border-radius: 18px;
  overflow: hidden;
  cursor: pointer;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  padding: 14px;
  box-shadow: var(--shadow-soft);
  transition: transform 0.15s ease, box-shadow 0.15s ease;
  border: none;
}
.album-card:active {
  transform: scale(0.98);
}
/* Option 1: Split Card Layout for effortless legibility and full cover art visibility */
.album-card.split-card {
  padding: 0;
  aspect-ratio: auto;
  background: var(--surface);
  border: 1px solid var(--border);
  justify-content: flex-start;
}
.album-card.split-card .album-card-img-wrap {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 10.5;
  overflow: hidden;
}
.album-card.split-card .album-card-img-wrap .cover-fg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.3s ease;
}
.album-card.split-card:hover .cover-fg {
  transform: scale(1.04);
}
.album-card.split-card .album-card-content {
  position: relative;
  padding: 12px 14px 14px;
  background: var(--surface);
  display: flex;
  flex-direction: column;
  gap: 3px;
  border-top: 1px solid var(--border);
}
.album-card.split-card .album-title {
  color: var(--text-main);
  font-size: 0.90625rem;
  font-weight: 850;
  text-shadow: none;
  line-height: 1.25;
}
.album-card.split-card .album-count {
  color: var(--text-muted);
  font-size: 0.71875rem;
  font-weight: 650;
  text-shadow: none;
  line-height: 1.3;
}
/* The currently-read course: a full-width banner above the 2-col grid. */
.album-card-lg {
  grid-column: 1 / -1;
  aspect-ratio: 16 / 9;
}
.album-card-lg .album-title { font-size: 1.375rem; line-height: 1.2; }
.album-card-lg .album-count { font-size: 0.8125rem; }
.album-card-badge {
  align-self: flex-start;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: 0.65625rem;
  font-weight: 800;
  letter-spacing: 0.4px;
  text-transform: uppercase;
  color: #fff;
  background: rgba(255, 255, 255, 0.22);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  padding: 4px 10px 4px 7px;
  border-radius: 99px;
  margin-bottom: 8px;
}
.album-card-badge .material-symbols-rounded { font-size: 0.875rem; }
/* Narrative-course markers: a corner chip on the shelf card, a badge above the
   hero title, and an info note under it, so users know it's a retelling, not
   per-verse scripture. */
.album-card-chip {
  position: absolute; top: 10px; right: 10px; z-index: 3;
  font-size: 0.59375rem; font-weight: 800; letter-spacing: 0.5px; text-transform: uppercase;
  color: #fff; background: rgba(0, 0, 0, 0.42); backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
  padding: 4px 9px; border-radius: 99px; border: 1px solid rgba(255,255,255,0.25);
}
.narrative-badge {
  align-self: flex-start; display: inline-flex; align-items: center; gap: 5px;
  font-size: 0.65625rem; font-weight: 800; letter-spacing: 0.4px; text-transform: uppercase;
  color: #fff; background: rgba(255, 255, 255, 0.2); backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
  padding: 4px 11px 4px 8px; border-radius: 99px; margin-bottom: 10px;
}
.narrative-badge .material-symbols-rounded { font-size: 0.875rem; }
.narrative-note {
  display: flex; align-items: flex-start; gap: 8px; margin: 16px 4px 0;
  font-size: 0.78125rem; line-height: 1.5; color: var(--text-muted);
  background: var(--surface-alt); border-radius: 12px; padding: 12px 14px;
}
.narrative-note .material-symbols-rounded { font-size: 1.125rem; color: var(--brand-primary); flex: none; }
.album-card .cover-fg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  object-fit: cover;
  transition: transform 0.3s ease;
}
.album-card:hover .cover-fg {
  transform: scale(1.04);
}
.album-card-overlay {
  position: absolute;
  inset: 0;
  z-index: 1;
  background: linear-gradient(180deg, rgba(0, 0, 0, 0) 45%, rgba(0, 0, 0, 0.40) 75%, rgba(0, 0, 0, 0.82) 100%);
  pointer-events: none;
}
.album-card-content {
  position: relative;
  z-index: 2;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.album-card .album-title {
  font-weight: 800;
  font-size: 0.84375rem;
  line-height: 1.2;
  color: #ffffff;
  margin: 0;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.6);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.album-card .album-count {
  font-size: 0.6875rem;
  font-weight: 600;
  line-height: 1.2;
  color: rgba(255, 255, 255, 0.85);
  margin: 0;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.album-card-icon {
  position: absolute;
  top: 14px;
  left: 14px;
  color: rgba(255, 255, 255, 0.9);
  z-index: 0;
}
.album-card-icon .material-symbols-rounded {
  font-size: 2.125rem;
}
/* Full-width card (Gita) spanning both grid columns */
.album-card-wide {
  grid-column: 1 / -1;
  aspect-ratio: 16 / 7;
  max-height: 200px;
}

/* ---- Continue Reading banner ---- */
.continue-card {
  position: relative;
  display: flex;
  align-items: center;
  gap: 14px;
  border-radius: 18px;
  overflow: hidden;
  padding: 18px 16px;
  cursor: pointer;
  margin-bottom: 8px;
  min-height: 90px;
  transition: opacity 0.15s;
}
.continue-card:active { opacity: 0.75; }
.continue-card-bg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  z-index: 0;
}
.continue-card-overlay {
  position: absolute;
  inset: 0;
  z-index: 1;
  background: linear-gradient(90deg, rgba(0,0,0,0.88) 0%, rgba(0,0,0,0.55) 60%, rgba(0,0,0,0.2) 100%);
  pointer-events: none;
}
.continue-card-body {
  position: relative;
  z-index: 2;
  flex: 1;
  min-width: 0;
}
.continue-card-label {
  font-size: 0.6875rem;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.6px;
  color: rgba(255,255,255,0.65);
  margin-bottom: 3px;
}
.continue-card-title {
  font-weight: 800;
  font-size: 1rem;
  color: #fff;
  line-height: 1.3;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.continue-card-sub {
  font-size: 0.75rem;
  color: rgba(255,255,255,0.6);
  font-weight: 600;
  margin-top: 2px;
}
.continue-card-btn {
  position: relative;
  z-index: 2;
  flex-shrink: 0;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  background: rgba(255,255,255,0.2);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  border: 1.5px solid rgba(255,255,255,0.4);
  display: flex;
  align-items: center;
  justify-content: center;
  color: #fff;
}
.continue-card-btn .material-symbols-rounded { font-size: 1.5rem; }

/* Sacred Texts cards (inside detail overlay) */
.scripture-card { margin: 0 20px 14px; }
.scripture-card:first-of-type { margin-top: 4px; }
.scripture-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 10px; }
.scripture-sanskrit { font-family: var(--font-title); color: var(--brand-primary); font-size: 0.875rem; margin-top: 2px; }
.scripture-card .tag { flex-shrink: 0; font-size: 0.6875rem; font-weight: 800; color: var(--text-muted); background: var(--surface-alt); padding: 4px 10px; border-radius: 99px; white-space: nowrap; }

/* Album track list (inside detail overlay) */
.album-hero { text-align: center; padding: 40px 24px 32px; }
.album-hero-art { width: 96px; height: 96px; margin: 0 auto; border-radius: 20px; background: rgba(255,255,255,.18); display: flex; align-items: center; justify-content: center; }
.album-hero-art .material-symbols-rounded { font-size: 3.25rem; color: #fff; }
.album-hero-art.has-img { position: relative; overflow: hidden; box-shadow: 0 8px 24px rgba(0,0,0,.22); }
.album-hero-art.has-img img { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }
.album-hero-art.has-img .cover-bg { display: none; }
/* ============================================================
   Netflix-style Mobile Hero (Course / Album Detail)
   ============================================================ */
.netflix-hero {
  position: relative;
  /* Full-bleed: break out of .detail-body's 22px side padding and pull up to
     the very top edge so the image touches all edges; only the bottom gradient
     fades into the page. */
  width: calc(100% + 44px);
  margin: 0 -22px 0 -22px;
  /* taller – fills roughly 60 % of typical mobile viewport */
  aspect-ratio: 4 / 3;
  min-height: 300px;
  max-height: 460px;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  padding: 0 22px 28px;
  overflow: hidden;
  background: var(--indigo-deep);
  border-radius: 0;
}
.netflix-hero.has-img .netflix-hero-bg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  object-fit: cover;
  object-position: center 42%;
  z-index: 0;
  /* subtle zoom so the image feels alive */
  transform: scale(1.04);
  transition: transform 6s ease-out;
}
.netflix-hero.has-img:hover .netflix-hero-bg { transform: scale(1.0); }
.netflix-hero-overlay {
  position: absolute;
  inset: 0;
  z-index: 1;
  /* deep 4-stop gradient: dark top (for back btn) → transparent mid → dark bottom */
  background: linear-gradient(
    180deg,
    rgba(0, 0, 0, 0.62) 0%,
    rgba(0, 0, 0, 0) 28%,
    rgba(0, 0, 0, 0) 38%,
    rgba(26, 25, 23, 0.72) 62%,
    rgba(26, 25, 23, 0.95) 84%,
    #1A1917 100%
  );
  pointer-events: none;
}
.netflix-hero-content {
  position: relative;
  z-index: 2;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.netflix-hero-title {
  font-family: var(--font-title);
  font-weight: 900;
  font-size: 2rem;
  line-height: 1.1;
  color: #ffffff;
  margin: 0;
  letter-spacing: -0.5px;
  text-shadow: 0 2px 16px rgba(0,0,0,0.9);
}
.netflix-hero-sub {
  font-size: 0.8125rem;
  font-weight: 700;
  line-height: 1.4;
  color: rgba(255,255,255,0.75);
  margin: 0;
  letter-spacing: 0.3px;
  text-transform: uppercase;
  text-shadow: 0 1px 6px rgba(0,0,0,0.9);
}
/* Row of action buttons rendered below title */
.netflix-hero-actions {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 4px;
}
.netflix-btn-play {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  height: 40px;
  border-radius: 8px;
  background: #ffffff;
  color: #111;
  font-weight: 800;
  font-size: 0.875rem;
  font-family: var(--font-sans);
  border: none;
  cursor: pointer;
  letter-spacing: 0.2px;
  transition: background 0.2s;
}
.netflix-btn-play:active { background: #ddd; }
.netflix-btn-play .material-symbols-rounded { font-size: 1.25rem; }
.netflix-btn-list {
  width: 40px; height: 40px;
  border-radius: 8px;
  background: rgba(255,255,255,0.18);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  border: 1.5px solid rgba(255,255,255,0.35);
  display: flex; align-items: center; justify-content: center;
  cursor: pointer;
  transition: background 0.2s;
}
.netflix-btn-list:active { background: rgba(255,255,255,0.3); }
.netflix-btn-list .material-symbols-rounded { font-size: 1.25rem; color: #fff; }

/* ---- Featured Continue Reading Hero ---- */
.cr-hero-badge {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-size: 0.71875rem;
  font-weight: 800;
  letter-spacing: 1.2px;
  color: #fde68a;
  text-transform: uppercase;
  margin-bottom: 4px;
  text-shadow: 0 1px 6px rgba(0,0,0,0.95);
}
.cr-hero-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--brand-primary);
  box-shadow: 0 0 8px rgba(248, 94, 0, 0.75);
}
.cr-hero-btn-secondary {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  height: 40px;
  border-radius: 10px;
  background: rgba(255,255,255,0.15);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  border: 1px solid rgba(255,255,255,0.25);
  color: #fff;
  font-weight: 800;
  font-size: 0.875rem;
  font-family: var(--font-sans);
  cursor: pointer;
  letter-spacing: 0.2px;
  transition: background 0.2s, border-color 0.2s;
}
.cr-hero-btn-secondary:active { background: rgba(255,255,255,0.3); }
.cr-hero-btn-secondary .material-symbols-rounded { font-size: 1.25rem; color: #fff; }

/* ---- Symmetrical Integrated Continue Reading Card ---- */
.cr-horizontal-card {
  display: flex;
  align-items: center;
  gap: 16px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 18px;
  padding: 16px;
  margin-bottom: 24px;
  box-shadow: var(--shadow-soft);
}
.cr-horiz-thumb {
  flex: 0 0 115px;
  width: 115px;
  height: 125px;
  border-radius: 14px;
  overflow: hidden;
  position: relative;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.cr-horiz-body {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
}
.cr-horiz-label {
  font-size: 0.65625rem;
  font-weight: 850;
  letter-spacing: 0.8px;
  color: var(--brand-primary);
  text-transform: uppercase;
  margin-bottom: 4px;
}
.cr-horiz-title {
  font-size: 1.125rem;
  font-weight: 850;
  color: var(--text-main);
  line-height: 1.25;
  margin: 0 0 3px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.cr-horiz-sub {
  font-size: 0.78125rem;
  font-weight: 650;
  color: var(--text-muted);
  margin: 0 0 14px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.cr-horiz-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
}
.cr-horiz-btn-primary {
  flex: 1;
  height: 38px;
  border-radius: 10px;
  background: var(--brand-primary);
  color: #fff;
  font-weight: 800;
  font-size: 0.8125rem;
  border: none;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 5px;
  transition: filter 0.2s, transform 0.15s;
}
.cr-horiz-btn-primary:hover {
  filter: brightness(1.08);
}
.cr-horiz-btn-primary:active {
  transform: scale(0.97);
}
.cr-horiz-btn-secondary {
  height: 38px;
  width: 42px;
  border-radius: 10px;
  background: var(--surface-alt);
  color: var(--text-main);
  border: 1px solid var(--border);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background 0.2s, transform 0.15s;
}
.cr-horiz-btn-secondary:hover {
  background: var(--surface-hover);
}
.cr-horiz-btn-secondary:active {
  transform: scale(0.97);
}

/* synopsis below hero */
.netflix-synopsis {

  padding: 16px 20px 4px;
  font-size: 0.84375rem;
  line-height: 1.65;
  color: var(--text-muted);
  border-bottom: 1px solid var(--border);
  margin-bottom: 4px;
}
/* Section heading row */
.netflix-section-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  padding: 16px 20px 10px;
}
.netflix-section-head h2 {
  margin: 0;
  font-size: 1.1875rem;
  font-weight: 900;
  color: var(--text-main);
  letter-spacing: -0.2px;
}
.netflix-section-head .pill-count {
  font-size: 0.75rem;
  font-weight: 700;
  color: var(--text-muted);
  background: var(--surface-alt);
  padding: 4px 10px;
  border-radius: 99px;
}
/* "See all chapters" expand button in the featured course block */
.feat-expand-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  width: calc(100% - 40px);
  margin: 8px 20px 20px;
  padding: 10px 16px;
  border-radius: 10px;
  background: var(--surface-alt);
  border: none;
  color: var(--text-muted);
  font-weight: 700;
  font-size: 0.84375rem;
  font-family: var(--font-sans);
  cursor: pointer;
  transition: background 0.2s, color 0.2s;
}
.feat-expand-btn:hover { background: var(--surface); color: var(--text-main); }
.feat-expand-btn:active { opacity: 0.7; }
.feat-expand-btn .material-symbols-rounded { font-size: 1.125rem; }
/* ---- Episode / Chapter cards (Netflix style) ---- */
.episode-list {
  display: flex;
  flex-direction: column;
  padding: 0 20px 60px;
  gap: 0;
}
.episode-card {
  display: flex;
  align-items: flex-start;
  gap: 14px;
  padding: 14px 0;
  border-bottom: 1px solid rgba(255,255,255,0.06);
  cursor: pointer;
  transition: opacity 0.15s;
}
.episode-card:last-child { border-bottom: none; }
.episode-card:active { opacity: 0.65; }
.episode-card.locked { opacity: 0.38; cursor: default; }
.episode-thumb {
  flex-shrink: 0;
  width: 100px;
  height: 60px;
  border-radius: 10px;
  background: var(--surface-alt);
  display: flex;
  align-items: center;
  justify-content: center;
  position: relative;
  overflow: hidden;
}
.episode-thumb-num {
  font-size: 1.375rem;
  font-weight: 900;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}
.episode-thumb-done {
  position: absolute;
  inset: 0;
  background: rgba(22, 163, 74, 0.18);
  display: flex;
  align-items: center;
  justify-content: center;
}
.episode-thumb-done .material-symbols-rounded {
  font-size: 1.75rem;
  color: var(--success);
}
.episode-thumb-lock {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
}
.episode-thumb-lock .material-symbols-rounded {
  font-size: 1.375rem;
  color: var(--text-muted);
}
.episode-bar {
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  height: 3px;
  background: rgba(255,255,255,0.12);
  border-radius: 0 0 10px 10px;
}
.episode-bar-fill {
  height: 100%;
  background: var(--brand-primary);
  border-radius: inherit;
  transition: width 0.3s;
}
.episode-meta {
  flex: 1;
  min-width: 0;
  padding-top: 2px;
}
.episode-label {
  font-size: 0.6875rem;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  color: var(--text-muted);
  margin-bottom: 3px;
}
.episode-title {
  font-weight: 800;
  font-size: 0.90625rem;
  color: var(--text-main);
  line-height: 1.35;
  margin-bottom: 4px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.episode-sub {
  font-size: 0.75rem;
  color: var(--text-muted);
  font-weight: 600;
}
.episode-chevron {
  flex-shrink: 0;
  margin-top: 18px;
  color: var(--text-muted);
}
/* ---- Track list (story albums) ---- */
.track-list { padding: 4px 20px 60px; }
.track-row {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 13px 0;
  cursor: pointer;
  border-bottom: 1px solid rgba(255,255,255,0.06);
  transition: opacity 0.15s;
}
.track-row:last-child { border-bottom: none; }
.track-row:active { opacity: 0.6; }
.track-num {
  width: 28px;
  text-align: center;
  font-weight: 900;
  color: var(--text-muted);
  font-size: 0.9375rem;
  font-variant-numeric: tabular-nums;
  flex-shrink: 0;
}
.track-meta { flex: 1; min-width: 0; }
.track-title { font-weight: 800; font-size: 0.9375rem; color: var(--text-main); margin-bottom: 2px; }
.track-sub { font-size: 0.75rem; color: var(--text-muted); font-weight: 600; }

/* ---- Gita course: chapter cards (Learn) ---- */
/* Kept for Gita which uses chapter-card flow; courses use episode-card above */
.chapter-list { display: flex; flex-direction: column; gap: 10px; margin-bottom: 8px; }
.chapter-card { display: flex; align-items: center; gap: 14px; background: var(--surface); border: none; border-radius: 16px; padding: 14px; box-shadow: none; cursor: pointer; transition: opacity .15s; }
.chapter-card:active { opacity: 0.6; }
.chapter-card.locked { opacity: .38; cursor: default; }
.chapter-icon { width: 52px; height: 52px; border-radius: 10px; flex-shrink: 0; display: flex; align-items: center; justify-content: center; color: var(--text-main); }
.chapter-icon .material-symbols-rounded { font-size: 1.75rem; }
.chapter-meta { flex: 1; min-width: 0; }
.chapter-num { font-size: 0.6875rem; font-weight: 800; text-transform: uppercase; letter-spacing: .5px; color: var(--text-muted); margin-bottom: 2px; }
.chapter-title { font-weight: 800; font-size: 0.9375rem; color: var(--text-main); margin: 0 0 6px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.chapter-progress { display: flex; align-items: center; gap: 8px; }
.chapter-bar { flex: 1; height: 4px; background: rgba(255,255,255,0.1); border-radius: 99px; overflow: hidden; }
.chapter-bar-fill { height: 100%; background: var(--brand-primary); border-radius: 99px; transition: width .3s; }
.chapter-progress span { font-size: 0.6875rem; font-weight: 800; color: var(--text-muted); }
.chapter-card > .material-symbols-rounded { color: var(--text-muted); flex-shrink: 0; font-size: 1.125rem; }
.chapter-check { color: var(--success) !important; }

/* ---- Japa repeat: see the single definition further down this file ----
   There used to be a second, older block here. Two definitions of the same
   component both applied, and the result was a hybrid nobody designed: the row
   layout came from this block and the chip styling from the other, so the label
   sat beside chips that had been sized for a label above them. Deleted rather
   than reconciled, because the fix for two sources of truth is one. */
.japa-row[hidden] { display: none; }
.japa-count[hidden] { display: none; }

/* ---- Scripture reading flow (verse explanations + stop marker) ---- */
/* A full chapter can be 243 verses / ~2,700 nodes in one innerHTML, which cost a
   measured 30ms forced layout on a Mac (150-250ms on mid-range Android) and made
   the open button feel sluggish. content-visibility skips layout+paint for
   offscreen paragraphs; contain-intrinsic-size keeps the scrollbar honest. */
.read-para { border-radius: 12px; transition: background .15s; content-visibility: auto; contain-intrinsic-size: 0 160px; }
.read-para:active { background: var(--surface-alt); }
.read-exp { overflow: hidden; }
.read-exp-inner { background: var(--brand-primary-light); border-radius: 10px; padding: 10px 14px; margin: 10px 0 2px; font-size: 0.875rem; line-height: 1.6; color: var(--text-main); font-weight: 600; }
.read-stop-btn { display: none; align-items: center; gap: 5px; margin: 10px 0 2px; padding: 6px 12px; border: 1.5px solid var(--border); border-radius: 99px; background: transparent; color: var(--text-muted); font-weight: 800; font-size: 0.75rem; font-family: inherit; cursor: pointer; }
.read-stop-btn .material-symbols-rounded { font-size: 1rem; }
.read-para:hover .read-stop-btn, .read-exp:not([hidden]) ~ .read-stop-btn { display: inline-flex; }
.reading-goal-band { background: var(--card-marigold); border: 1.5px solid var(--brand-primary); border-radius: 14px; padding: 12px 14px; margin: 16px 0; display: flex; gap: 12px; align-items: center; }
.quiz-unlock-card { border: 1.5px solid var(--brand-primary); }
.quiz-unlock-card .btn-primary:disabled { opacity: .5; cursor: default; }

/* Full-text chapter reader's sticky header (openTextChapter in app.js):
   title, the Full Reader/Focus Mode switch and the save button all pinned
   to the top of .detail-body's scroll, replacing the old tall cover-image
   hero that used to push the actual verses below the fold. Full-bleed
   (negative margin cancels .detail-body's 22px side padding, same trick
   .netflix-hero used) so the pinned bar reads as a solid toolbar rather
   than a card floating with scrollable gutters on either side. Top padding
   clears .detail-top's floating back button, which is position:absolute
   against .detail (not .detail-body) and so never scrolls out of the way. */
.text-reader-stickybar {
  position: sticky;
  top: 0;
  z-index: 5;
  width: calc(100% + 44px);
  margin: 0 -22px;
  padding: calc(max(16px, env(safe-area-inset-top)) + 52px) 22px 10px;
  background: var(--bg-app);
  border-bottom: 1px solid var(--border);
}
.text-reader-stickybar-row {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
}
.text-reader-stickybar-title { min-width: 0; }
.text-reader-stickybar-title b {
  display: block;
  font-size: 1.0625rem;
  font-weight: 800;
  color: var(--text-main);
  overflow-wrap: anywhere;
}
.text-reader-stickybar-title span {
  display: block;
  font-size: 0.75rem;
  color: var(--text-muted);
  margin-top: 2px;
}
.text-reader-save-btn {
  flex: 0 0 auto;
  width: 38px;
  height: 38px;
  border-radius: 50%;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text-muted);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  font-family: inherit;
}
.text-reader-save-btn.saved {
  border-color: var(--brand-primary);
  background: var(--brand-primary-light);
  color: var(--brand-primary);
}
.text-reader-save-btn .material-symbols-rounded { font-size: 1.25rem; }

/* ---- Option 1: Ultra-Clean Minimalist Editorial Focus Reader ---- */
.reader-view-switcher {
  display: flex;
  background: var(--surface-alt);
  border: 1px solid var(--border);
  border-radius: 99px;
  padding: 4px;
  margin: 12px 0 16px;
  gap: 4px;
}
.reader-view-btn {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 8px 14px;
  border-radius: 99px;
  border: none;
  background: transparent;
  color: var(--text-muted);
  font-family: var(--font-sans);
  font-weight: 800;
  font-size: 0.8125rem;
  cursor: pointer;
  transition: all .2s cubic-bezier(0.16, 1, 0.3, 1);
}
.reader-view-btn.active {
  background: var(--surface);
  color: var(--text-main);
  box-shadow: var(--shadow-soft);
}
.reader-view-btn:hover:not(.active) {
  color: var(--text-main);
}
/* Focus Mode used to be a bordered, shadowed "card" with its own capped
   max-height and internal overflow-y:auto — a small box floating in a lot
   of empty page, and a scrollbar nested inside .detail-body's own scrollbar
   for anything longer than a couple of lines. Neither reads as mobile-
   native. This is plain content flow now: no border, no card background, no
   second scroll surface — .detail-body is the only thing that ever scrolls.
   min-height keeps a short verse from leaving a bare gap under it: the
   reader fills whatever room .text-reader-stickybar isn't using instead of
   shrink-wrapping to its content and stranding the nav row halfway down an
   otherwise empty screen. */
.focus-carousel-wrap {
  margin: 0;
  background: transparent;
  display: flex;
  flex-direction: column;
  min-height: calc(100dvh - var(--text-reader-header-h, 210px) - 90px);
}
.focus-verse-body {
  padding: 18px 0 20px;
  flex: 1;
  display: flex;
  flex-direction: column;
}
.focus-card-topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 18px;
  font-size: 0.8125rem;
  font-weight: 700;
  color: var(--text-muted);
  gap: 12px;
}
.focus-card-topbar > span {
  min-width: 0;
  overflow-wrap: anywhere;
}
.focus-card-actions {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  flex-shrink: 0;
}
.focus-save-btn {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  background: transparent;
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 4px 10px;
  font-size: 0.75rem;
  font-weight: 700;
  color: var(--text-muted);
  cursor: pointer;
  transition: all .15s ease;
}
.focus-save-btn:hover {
  background: var(--surface-alt);
  color: var(--text-main);
}
.focus-save-btn.saved {
  border-color: var(--brand-primary);
  background: var(--brand-primary-light);
  color: var(--brand-primary);
}
.focus-save-btn .material-symbols-rounded {
  font-size: 1.125rem;
}
@media (max-width: 420px) {
  .focus-card-topbar {
    align-items: flex-start;
    flex-direction: column;
  }
  .focus-card-actions {
    width: 100%;
  }
  .focus-save-btn {
    flex: 1;
    justify-content: center;
  }
}
.focus-sanskrit {
  font-family: var(--font-title);
  font-size: 1.3125rem;
  line-height: 1.75;
  color: var(--text-main);
  font-weight: 750;
  margin: 4px 0 8px;
}
.focus-translit {
  font-size: 0.84375rem;
  line-height: 1.6;
  color: var(--text-muted);
  margin: 0 0 16px;
}
.focus-translation {
  font-size: 1.03125rem;
  line-height: 1.68;
  color: var(--text-main);
  font-weight: 600;
}
.focus-tab-toggle {
  margin-top: 20px;
  padding-top: 16px;
  border-top: 1px solid var(--border);
  display: flex;
  align-items: center;
  justify-content: space-between;
  font-size: 0.84375rem;
  font-weight: 700;
  color: var(--brand-primary);
  cursor: pointer;
  user-select: none;
}
.focus-tab-toggle:hover {
  opacity: 0.85;
}
.focus-meaning-prose {
  animation: focusFadeIn .2s ease-out;
  margin-top: 12px;
  padding: 14px 16px;
  background: var(--surface-alt);
  border-radius: 12px;
  font-size: 0.90625rem;
  line-height: 1.68;
  color: var(--text-main);
}
.focus-meaning-takeaway {
  font-weight: 750;
  color: var(--brand-primary);
  margin-bottom: 8px;
  display: flex;
  align-items: center;
  gap: 6px;
}
@keyframes focusFadeIn {
  from { opacity: 0; transform: translateY(4px); }
  to { opacity: 1; transform: translateY(0); }
}
.focus-nav-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 16px 0 4px;
  margin-top: auto;
  border-top: 1px solid var(--border);
}
.focus-nav-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 10px 18px;
  border-radius: 12px;
  font-family: var(--font-sans);
  font-weight: 750;
  font-size: 0.84375rem;
  cursor: pointer;
  transition: all .15s ease;
  border: none;
}
.focus-nav-btn.prev {
  background: transparent;
  border: 1px solid var(--border);
  color: var(--text-main);
}
.focus-nav-btn.prev:hover:not(:disabled) {
  background: var(--surface-alt);
}
.focus-nav-btn.prev:disabled {
  opacity: .35;
  cursor: default;
}
.focus-nav-btn.next {
  background: var(--text-main);
  color: var(--surface);
}
.focus-nav-btn.next:hover {
  opacity: 0.9;
}
.focus-nav-btn.finish {
  background: var(--brand-primary);
  color: #fff;
}
.focus-nav-btn.finish:hover {
  opacity: 0.9;
}
.focus-nav-counter {
  font-size: 0.8125rem;
  font-weight: 700;
  color: var(--text-muted);
}
/* Full-text Focus Mode only: pinned to the true bottom of the SCREEN, not
   just .focus-carousel-wrap or .detail-body's scroll box. position:sticky
   only sticks within its own containing block, and .detail-body reserves
   120px of bottom padding for a bottom-nav bar that's actually hidden on
   this screen (see .bottom-nav's own comment) — so a sticky bar settles
   into that dead padding zone, short of the real edge. .detail carries a
   `transform` (its open/close slide), which makes it the containing block
   for any position:fixed descendant, and .detail is itself inset:0, so
   this still lands flush with the physical screen edge without needing a
   portal out of .detail-body. .focus-carousel-wrap-chapter reserves the
   matching space below so scrolled verse text never runs under it. */
.focus-nav-row-sticky {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 205;
  padding: 12px 22px calc(12px + env(safe-area-inset-bottom, 0px));
  background: var(--surface);
  border-top: 1px solid var(--border);
}
.focus-carousel-wrap-chapter {
  padding-bottom: 76px;
}

/* ---- Social Video Embed Carousel (@getkarana) ---- */
.social-video-scroll {
  display: flex;
  gap: 14px;
  overflow-x: auto;
  padding-bottom: 8px;
  scroll-snap-type: x mandatory;
}
.social-video-scroll::-webkit-scrollbar {
  display: none;
}
.social-video-card {
  flex: 0 0 280px;
  scroll-snap-align: start;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 18px;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  box-shadow: var(--shadow-soft);
}
.social-video-header {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 14px;
  border-bottom: 1px solid var(--border);
  background: var(--surface-alt);
}
.social-video-avatar {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: var(--brand-primary);
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 800;
  font-size: 0.8125rem;
}
.social-video-handle {
  font-weight: 800;
  font-size: 0.84375rem;
  color: var(--text-main);
}
.social-video-platform {
  font-size: 0.71875rem;
  color: var(--text-muted);
  font-weight: 700;
}
.social-video-embed-wrap {
  width: 100%;
  height: 380px;
  position: relative;
  background: #000;
  display: flex;
  align-items: center;
  justify-content: center;
}
.social-video-embed-wrap iframe {
  width: 100%;
  height: 100%;
  border: none;
}
.social-video-footer {
  padding: 12px 14px;
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.social-follow-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 0.78125rem;
  font-weight: 800;
  color: var(--brand-primary);
  text-decoration: none;
}
.social-follow-btn:hover {
  text-decoration: underline;
}

/* ---- Chapter subsections (read-passage + quiz units) ---- */
.sub-list { display: flex; flex-direction: column; gap: 10px; }
.sub-card { display: flex; align-items: center; gap: 14px; background: var(--surface); border: 1px solid var(--border); border-radius: 16px; padding: 14px; box-shadow: var(--shadow-soft); cursor: pointer; transition: transform .1s; }
.sub-card:active { transform: scale(.99); }
.sub-card.locked { opacity: .55; cursor: default; }
.sub-card.done { background: var(--card-tulsi); }
.sub-num { width: 38px; height: 38px; border-radius: 12px; flex-shrink: 0; display: flex; align-items: center; justify-content: center; font-weight: 900; font-size: 0.9375rem; background: var(--brand-primary-light); color: var(--brand-primary); }
.sub-card.done .sub-num { background: var(--success-light); color: var(--success-ink); }
.sub-card.locked .sub-num { background: var(--surface-alt); color: var(--text-muted); }
.sub-meta { flex: 1; min-width: 0; }
.sub-title { font-weight: 800; font-size: 0.9375rem; color: var(--text-main); }
.sub-sub { font-size: 0.75rem; color: var(--text-muted); font-weight: 700; margin-top: 1px; }
.sub-card > .material-symbols-rounded { color: var(--text-muted); flex-shrink: 0; }

/* ---- Inline lyric meaning inside player ---- */
.lyric-meaning-inline {
  display: none;
  font-size: 0.84375rem;
  color: var(--brand-primary);
  margin-top: 8px;
  max-width: 85%;
  font-family: var(--font-sans);
  font-weight: 600;
  line-height: 1.5;
  overflow-wrap: break-word;
  word-break: break-word;
  margin-left: auto;
  margin-right: auto;
  text-shadow: none;
}
body.dark-mode .lyric-meaning-inline {
  color: #FFD9C4;
  text-shadow: 0 0 10px rgba(255, 230, 168, 0.3);
}
.lyric.active .lyric-meaning-inline {
  display: block;
  animation: fadeInInline 0.35s ease-out;
}
@keyframes fadeInInline {
  from { opacity: 0; transform: translateY(4px); }
  to { opacity: 1; transform: translateY(0); }
}

/* ---- Speed Dropdown Menu ---- */
.speed-dropdown {
  position: absolute;
  bottom: 42px;
  right: -10px;
  background: rgba(40, 20, 15, 0.96);
  border: 1.5px solid rgba(255, 255, 255, 0.25);
  border-radius: 12px;
  padding: 6px 0;
  display: flex;
  flex-direction: column;
  min-width: 90px;
  z-index: 300;
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
  backdrop-filter: blur(10px);
}
.speed-dropdown[hidden] {
  display: none !important;
}
.speed-option {
  padding: 8px 16px;
  font-size: 0.8125rem;
  font-weight: 800;
  color: rgba(255, 255, 255, 0.85);
  cursor: pointer;
  text-align: center;
  transition: background 0.2s, color 0.2s;
  font-family: var(--font-sans);
}
.speed-option:hover {
  background: rgba(255, 217, 196, 0.15);
  color: #fff;
}
.speed-option.active {
  background: var(--brand-primary);
  color: #FFFFFF;
}

/* Recite Page Shelf Cards & Hero */
.recite-daily-hero {
  border-radius: 20px;
  padding: 20px;
  margin-bottom: 28px;
  cursor: pointer;
  position: relative;
  overflow: hidden;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.25);
  display: flex;
  align-items: center;
  justify-content: space-between;
  transition: transform 0.25s ease, box-shadow 0.25s ease;
}
.recite-daily-hero:hover {
  transform: translateY(-2px);
  box-shadow: 0 12px 30px rgba(0, 0, 0, 0.35);
}
.recite-shelf-card {
  position: relative;
  flex: 0 0 230px;
  width: 230px;
  height: 148px;
  border-radius: 18px;
  padding: 15px;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  cursor: pointer;
  overflow: hidden;
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.22);
  transition: transform 0.25s ease, box-shadow 0.25s ease;
}
.recite-shelf-card:hover {
  transform: translateY(-3px);
  box-shadow: 0 10px 24px rgba(0, 0, 0, 0.32);
}
.recite-shelf-card:active {
  transform: scale(0.98);
}
.recite-card-watermark {
  position: absolute;
  top: 6px;
  right: 10px;
  font-size: 2.375rem;
  font-weight: 700;
  color: rgba(255, 255, 255, 0.16);
  pointer-events: none;
  user-select: none;
  white-space: nowrap;
}
.recite-card-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  position: relative;
  z-index: 2;
}
.recite-card-tag {
  background: rgba(255, 255, 255, 0.2);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  color: #fff;
  font-size: 0.6875rem;
  font-weight: 700;
  padding: 3px 10px;
  border-radius: 99px;
  letter-spacing: 0.3px;
}
.recite-card-meta {
  color: rgba(255, 255, 255, 0.92);
  font-size: 0.71875rem;
  font-weight: 700;
}
.recite-card-bottom {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  position: relative;
  z-index: 2;
  gap: 8px;
}
.recite-card-info {
  flex: 1;
  min-width: 0;
}
.recite-card-title {
  color: #fff;
  font-size: 0.9375rem;
  font-weight: 800;
  line-height: 1.25;
  margin: 0;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.5);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.recite-card-sub {
  color: rgba(255, 255, 255, 0.88);
  font-size: 0.71875rem;
  font-weight: 600;
  margin: 3px 0 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.recite-card-play {
  width: 34px;
  height: 34px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.24);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  display: flex;
  align-items: center;
  justify-content: center;
  color: #fff;
  transition: background 0.2s ease, transform 0.2s ease;
  flex-shrink: 0;
}
.recite-shelf-card:hover .recite-card-play {
  background: #ffffff;
  color: var(--navy);
  transform: scale(1.08);
}

/* ---------- global polish pass ---------- */
/* Visible keyboard focus everywhere (buttons, rows, nav) without affecting
   touch/mouse users; :focus-visible only fires on keyboard navigation. */
button:focus-visible,
[role="button"]:focus-visible,
.nav-item:focus-visible,
.list-row:focus-visible,
.search-res:focus-visible,
a:focus-visible,
input:focus-visible,
textarea:focus-visible {
  outline: 2px solid var(--brand-primary, #F85E00);
  outline-offset: 2px;
  border-radius: 6px;
}

/* Player buffering: the play control pulses while audio is fetching so a slow
   network reads as loading, not broken. */
@keyframes k-loading-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.35; }
}
#player-toggle.loading span {
  animation: k-loading-pulse 0.9s ease-in-out infinite;
}

/* ---------- daily verse (guided chooser + contemplative unit) ---------- */
.daily-wisdom-chooser {
  display: flex;
  flex-direction: column;
  gap: 18px;
}
.daily-wisdom-progress {
  position: sticky;
  top: 0;
  z-index: 2;
  display: flex;
  justify-content: center;
  gap: 8px;
  padding: 4px 0 2px;
  background: var(--bg-app);
}
.daily-wisdom-progress-dot {
  width: 8px;
  height: 8px;
  border-radius: 99px;
  background: var(--border);
  transition: width .16s ease, background .16s ease;
}
.daily-wisdom-progress-dot.active {
  width: 24px;
  background: var(--brand-primary);
}
.daily-wisdom-progress-dot.complete {
  background: var(--brand-primary);
}
.daily-wisdom-question-stack {
  display: flex;
  flex-direction: column;
  gap: 18px;
}
.daily-wisdom-question-card {
  scroll-margin-top: 18px;
  border: 1.5px solid var(--border);
  border-radius: 18px;
  background: var(--surface);
  padding: 18px;
  box-shadow: var(--shadow-soft);
}
.daily-wisdom-question-kicker {
  color: var(--brand-primary);
  font-size: 0.75rem;
  font-weight: 900;
  letter-spacing: .06em;
  text-transform: uppercase;
}
.daily-wisdom-question-card h2 {
  margin: 8px 0 6px;
  color: var(--text-main);
  font-size: 1.3125rem;
  line-height: 1.2;
  letter-spacing: 0;
}
.daily-wisdom-question-card p {
  margin: 0 0 16px;
  color: var(--text-muted);
  font-size: 0.875rem;
  line-height: 1.45;
  font-weight: 700;
}
.daily-wisdom-option-list {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.daily-wisdom-choice {
  width: 100%;
  display: grid;
  grid-template-columns: auto 1fr;
  align-items: center;
  gap: 14px;
  border: 1.5px solid var(--border);
  border-radius: 16px;
  background: var(--surface-alt);
  color: var(--text-main);
  padding: 14px;
  text-align: left;
  font-family: var(--font-sans);
  cursor: pointer;
  transition: border-color .16s ease, background .16s ease, transform .12s ease;
}
.daily-wisdom-choice:active {
  transform: scale(.98);
}
.daily-wisdom-choice.selected {
  border-color: var(--brand-primary);
  background: var(--brand-primary-light);
}
.daily-wisdom-choice-icon {
  width: 46px;
  height: 46px;
  border-radius: 14px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--brand-primary);
  background: var(--surface);
  flex: 0 0 auto;
}
.daily-wisdom-choice.selected .daily-wisdom-choice-icon {
  background: var(--brand-primary);
  color: #fff;
}
.daily-wisdom-choice-title {
  display: block;
  font-size: 1.0625rem;
  line-height: 1.2;
  font-weight: 900;
  color: var(--text-main);
}
.daily-wisdom-choice-sub {
  display: block;
  margin-top: 7px;
  font-size: 0.8125rem;
  line-height: 1.45;
  color: var(--text-muted);
  font-weight: 650;
}
.daily-wisdom-show {
  width: 100%;
  margin: 2px 0 0;
}
.daily-wisdom-show:disabled {
  opacity: .45;
  cursor: not-allowed;
  box-shadow: none;
}
.daily-wisdom-loading {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  min-height: 180px;
  color: var(--text-muted);
  font-weight: 800;
}
.daily-wisdom-focus {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 8px 12px;
  align-items: center;
  padding: 14px 16px;
  border: 1.5px solid var(--border);
  border-radius: 16px;
  background: var(--surface-alt);
}
.daily-wisdom-focus span {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-size: 0.75rem;
  font-weight: 850;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--brand-primary);
}
.daily-wisdom-focus b {
  grid-column: 1;
  font-size: 1rem;
  color: var(--text-main);
}
.daily-wisdom-change {
  grid-row: 1 / span 2;
  grid-column: 2;
  border: 1px solid var(--border);
  border-radius: 99px;
  background: var(--surface);
  color: var(--text-main);
  padding: 8px 12px;
  font: inherit;
  font-size: 0.75rem;
  font-weight: 850;
  cursor: pointer;
}
@media (max-width: 380px) {
  .daily-wisdom-focus {
    grid-template-columns: 1fr;
  }
  .daily-wisdom-change {
    grid-column: 1;
    grid-row: auto;
    width: 100%;
  }
}
.daily-verse-meaning {
  font-size: 0.9375rem;
  line-height: 1.75;
  color: var(--text-muted);
  padding: 14px 16px;
  background: var(--surface-alt);
  border-radius: 14px;
}
.daily-verse-sit {
  border: 1px solid var(--border);
  border-radius: 14px;
  padding: 14px 16px;
  background: transparent;
}
.daily-verse-sit-label {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 0.75rem;
  font-weight: 800;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--brand-primary);
  margin-bottom: 8px;
}
.daily-verse-sit-label .material-symbols-rounded { font-size: 1.125rem; }
.daily-verse-sit p {
  margin: 0;
  font-size: 1.03125rem;
  line-height: 1.7;
  color: var(--text-main);
}

/* ---------- Home: the guided path ----------
   The Continue card is the answer to "what should I learn next?", so it is the
   first and heaviest thing on Home. Built from existing tokens (brand gradient,
   card radius, page type scale) so it reads as an evolution of the surface
   rather than a new design language. */
.home-continue {
  margin: 4px 0 22px;
  padding: 20px 20px 18px;
  border-radius: 20px;
  background: linear-gradient(150deg, var(--brand-primary) 0%, var(--brand-primary-shadow) 100%);
  color: #fff;
  box-shadow: 0 10px 26px rgba(0, 0, 0, .16);
}
.home-continue-eyebrow {
  font-size: 0.71875rem; font-weight: 800; letter-spacing: .12em; text-transform: uppercase;
  opacity: .82; margin-bottom: 8px;
}
.home-continue-course { font-size: 1.5625rem; font-weight: 900; line-height: 1.15; margin: 0 0 6px; color: #fff; }
/* Explicit colour: these are <p>, and the global paragraph rule sets
   --text-muted, which renders grey-blue on the orange field (fails contrast). */
.home-continue-lesson { font-size: 0.9375rem; font-weight: 700; margin: 0; color: #fff; opacity: .96; }
.home-continue-tagline {
  font-size: 0.84375rem; margin: 6px 0 0; color: #fff; opacity: .84; line-height: 1.45;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.home-continue-progress { display: flex; align-items: center; gap: 10px; margin: 16px 0 16px; }
.home-continue-bar {
  flex: 1; height: 6px; border-radius: 999px; background: rgba(255, 255, 255, .28); overflow: hidden;
}
.home-continue-bar > span {
  display: block; height: 100%; border-radius: 999px; background: #fff;
  transition: width .5s cubic-bezier(.4, 0, .2, 1);
}
.home-continue-count { font-size: 0.75rem; font-weight: 700; opacity: .9; white-space: nowrap; }
/* The CTA inverts the primary button: white on the brand field it sits in. */
.home-continue .btn-primary,
.home-continue-cta {
  width: 100%; background: #fff; color: var(--brand-primary);
  font-weight: 800; border: none;
}
.home-continue .btn-primary:active, .home-continue-cta:active { transform: scale(.98); }
@media (prefers-reduced-motion: reduce) {
  .home-continue-bar > span { transition: none; }
}

/* Home header now sits in normal flow.
   It was absolutely positioned with white text so it could overlay the dark
   hero carousel that used to open the page. The guided Continue card leads now,
   so the header sits above it on the page background and takes page-coloured
   text and buttons instead of the over-image treatment. */
#today .hallow-home-header {
  position: relative;
  top: auto; left: auto; right: auto;
  margin: 2px 0 14px;
  pointer-events: auto;
}
#today .hallow-home-header .hallow-page-title {
  color: var(--text-main);
  text-shadow: none;
}
#today .hallow-home-header .hallow-top-btn {
  background: var(--surface-alt, rgba(0, 0, 0, .06));
  border: 1px solid var(--line, rgba(0, 0, 0, .08));
  color: var(--text-main);
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}
#today .hallow-header-icons { display: flex; gap: 8px; }

/* ---------- classes that app.js renders but nothing defined ----------
   Kept from the design pass that was otherwise reverted, because these are not
   an aesthetic choice: app.js emits .page-head, .page-sub and .chip with no
   matching rule anywhere, so without these they fall through to browser
   defaults (bare h1/p, and "chips" that are just inline text). Removing them
   would not restore a previous look, it would leave the markup unstyled. */
.page-head { margin: 2px 0 18px; }
.page-head h1 {
  font-family: var(--font-title);
  font-size: 1.6875rem;
  font-weight: 900;
  letter-spacing: -0.5px;
  color: var(--text-main);
  margin: 0;
  text-wrap: balance;
}
.page-sub {
  margin: 5px 0 0;
  font-size: 0.875rem;
  line-height: 1.5;
  color: var(--text-muted);
  max-width: 46ch;
}
.chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 8px 14px;
  border-radius: 99px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text-main);
  font-family: var(--font-sans);
  font-weight: 700;
  font-size: 0.8125rem;
  cursor: pointer;
  transition: border-color .2s ease, background-color .2s ease;
}
.chip:hover { border-color: var(--text-muted); }
.chip[aria-pressed="true"], .chip.is-active {
  border-color: var(--brand-primary);
  background: var(--brand-primary-light);
  color: var(--brand-primary);
}

/* ============================================================
   TODAY — card-based layout: a greeting, then five full-width
   cards (streak, sadhana, verse, meditation, achievements) in
   the app's standard .card language (24px radius, shadow-soft).
   ============================================================ */

/* ---- masthead: greeting + search action ---- */
.td-mast {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin: 2px 0 20px;
}

/* Fluid, because the masthead gained a second 44px button. Two buttons plus
   their gaps take 108px of the 331px of content width at 375px, leaving about
   223px, and "Namaste, <name>" at a flat 34px overflowed that and wrapped to two
   lines. min-width:0 is what actually lets the h1 shrink inside the flex row;
   without it the clamp cannot help. Long names still wrap, which is fine. */
.td-greet {
  font-size: clamp(24px, 6.6vw, 34px);
  line-height: 1.12;
  margin: 0;
  min-width: 0;
}
/* Same gold gradient as the K+ mark itself (#td-premium-open, .plus-mark) —
   a Plus member's own name carries the same color the membership does. */
.td-greet-name-plus {
  background: linear-gradient(135deg, #FFEFB0, #D4A852, #B28833);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
}

.td-mast-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: 0 0 auto;
}

.td-mast-action {
  display: flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 44px;
  height: 44px;
  border-radius: 14px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text-main);
  box-shadow: var(--shadow-soft);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.td-mast-action:active { transform: scale(0.94); }
.td-mast-action .material-symbols-rounded { font-size: 1.3125rem; }

/* ---- streak card: flame right, streak info + week strip left ---- */
.td-streak-card {
  position: relative;
  display: flex;
  align-items: center;
  width: 100%;
  min-height: 10.75rem;
  text-align: left;
  font-family: inherit;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  /* Clips .td-flame-mini's deliberate bleed (see below) to the card's own
     rounded corner instead of spilling over neighboring cards. */
  overflow: hidden;
}
.td-streak-card:active { opacity: 0.9; }

/* padding-right reserves the zone .td-flame-mini bleeds into (it's
   position:absolute, out of flow, so nothing does this automatically).
   Has to cover the mini flame's full bounding box, not just how much of
   it "looks" occupied by the bowl art — .td-flame-mini sits at
   right:-2.125rem with width:12.25rem, so its box's left edge is
   12.25rem-2.125rem = 10.125rem in from the card's right edge. This used
   to reserve only 96px, which happened to clear the bowl itself on most
   screens (thanks to transparent margin inside the flame's own SVG box)
   but not the ignition ring above the wick, which sits further left
   inside that same box — on some phones that ring landed right on top of
   the weekday circles. Reserving the flame's whole 10.125rem box (+ a
   small buffer) makes this correct by construction instead of by how much
   empty space a given render happens to leave inside the flame's bounding
   box.
   In rem, not px, and so are .td-flame-mini's own box and .td-flame-glow's
   gradient below — all three describe the same physical geometry (the
   glow is centered on the flame, the padding reserves exactly the flame's
   box), so they have to shrink together or a narrow phone desyncs them:
   the glow drifts off the flame, or the reserved zone stops matching the
   art it was sized for. This was fixed geometry tuned against a ~390px
   reference phone; on a narrower Android viewport (see styles.css's root
   font-size clamp) it was reserving the same 168px regardless of how much
   card width was actually available, which on a narrow phone left too
   little room for the headline/tagline/day-circles and squeezed them —
   the same class of bug as the rest of this app's px-only sizing, just in
   a spot font-size alone couldn't reach since none of this is text. */
.td-streak-info { position: relative; z-index: 1; min-width: 0; flex: 1; padding-right: 10.5rem; }

/* Flat "banded gradient" glow behind the flame — genuine circles (an
   ellipse read as visibly squashed/oval, not what was asked for). Four
   bands, not two: the outer ones (95-140px) are big enough that their own
   curvature barely reads inside a ~172px-tall card, so they're colored
   close to the card's own base tone — a soft, nearly-invisible-seamed
   fill rather than a hard edge — while the inner two (0-30, 30-60px) stay
   comfortably small enough to show real curvature and carry the actual
   "these are circles" read. That combination is what makes the glow
   genuinely reach most of the card's width (the ask: "cover the entire
   card") without the big bands flattening into the rectangle-looking bug
   smaller cards kept producing. Each color stop repeats at the SAME
   percentage (hard jumps, no interpolation) — flat rings, not a blur.
   Goes pale (soft cream, as if overexposed by the flame's own light)
   near the flame, down through mid-orange, to the card's own deep base
   tone at the outer edge — brightest at the light source, richest color
   furthest from it, the reverse of a plain vignette. */
.td-flame-glow {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  /* Centered on the flame's ACTUAL rendered position — measured directly
     via getBoundingClientRect() against a live card, not hand-computed
     from the SVG's viewBox math (recompute the same way if the flame path
     or .td-flame-mini's own size/offset ever change, rather than
     re-deriving it by hand — that was off by ~25px on both axes once
     already: the transform chain here is bottom-anchored
     preserveAspectRatio inside an absolutely-positioned box inside a flex
     row, easy to get wrong on paper). */
  background: radial-gradient(circle 17.5rem at right 3.625rem bottom 4.3125rem,
    color-mix(in srgb, var(--brand-primary) 30%, white) 0%,
    color-mix(in srgb, var(--brand-primary) 30%, white) 15%,
    color-mix(in srgb, var(--brand-primary) 65%, white) 15%,
    color-mix(in srgb, var(--brand-primary) 65%, white) 30%,
    color-mix(in srgb, var(--gold) 60%, var(--brand-primary)) 30%,
    color-mix(in srgb, var(--gold) 60%, var(--brand-primary)) 50%,
    var(--brand-primary) 50%,
    var(--brand-primary) 75%,
    color-mix(in srgb, var(--brand-primary) 90%, black) 75%,
    color-mix(in srgb, var(--brand-primary) 90%, black) 100%);
}

.td-streak-kicker {
  font-size: 0.71875rem;
  font-weight: 800;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--brand-primary);
  margin-bottom: 4px;
}

.td-streak-headline {
  font-family: var(--font-title);
  font-size: 2rem;
  font-weight: 800;
  line-height: 1.1;
  color: var(--text-main);
}

.td-streak-tagline {
  font-size: 0.8125rem;
  color: var(--text-muted);
  margin: 3px 0 12px;
  line-height: 1.4;
}

/* Fixed-size (26px) circles used to overflow .td-streak-info's shrunk
   content width on narrower phones — .td-flame-mini's bleed offsets are
   fixed px too, so on those widths the overflowing circles ran straight
   under the reserved-but-not-enforced flame zone and got covered by it.
   flex:1 + max-width lets the whole row shrink together to whatever width
   .td-streak-info actually has at any resolution, instead of overflowing. */
.td-streak-days {
  display: flex;
  align-items: center; /* not the default stretch — that overrides each
    circle's aspect-ratio:1 with the row's own cross-size, squashing them
    into ellipses instead of letting width and aspect-ratio set the height */
  gap: 6px;
  width: 100%;
}
.td-streak-day {
  display: flex;
  align-items: center;
  justify-content: center;
  flex: 1 1 0;
  min-width: 0;
  min-height: 0;
  /* Flex items get an automatic min cross-size from their own content
     (here, the letter's line-height, ~15px) that wins over aspect-ratio
     whenever it's taller than the shrunk width would otherwise call for —
     that's what turned these into short, wide pills instead of shrinking
     circles. overflow:hidden opts out of that automatic minimum so
     aspect-ratio actually governs the height at every width. */
  overflow: hidden;
  max-width: 1.625rem;
  aspect-ratio: 1;
  border-radius: 50%;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text-muted);
  font-size: 0.65625rem;
  font-weight: 700;
}
.td-streak-day.is-done {
  border-color: var(--brand-primary);
  background: var(--brand-primary);
  color: #fff;
}
.td-streak-day.is-today {
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--brand-primary) 45%, transparent);
}

/* ---- lit state: the card itself becomes the glow — a rich orange base
   (deepest right where the text sits) with a pale halo washing out around
   the flame (see .td-flame-glow), white text throughout since nothing
   here is on a neutral surface anymore. Unlit stays the plain neutral
   card (streakStage 0 never adds this class — see renderToday). ---- */
.td-streak-card.is-lit {
  background: color-mix(in srgb, var(--brand-primary) 90%, black);
  border-color: transparent;
}
.td-streak-card.is-lit .td-streak-kicker { color: rgba(255, 255, 255, 0.85); }
.td-streak-card.is-lit .td-streak-headline { color: #fff; }
.td-streak-card.is-lit .td-streak-tagline { color: rgba(255, 255, 255, 0.8); }
.td-streak-card.is-lit .td-streak-day {
  border-color: rgba(255, 255, 255, 0.35);
  background: rgba(255, 255, 255, 0.16);
  color: rgba(255, 255, 255, 0.85);
}
.td-streak-card.is-lit .td-streak-day.is-done {
  border-color: #fff;
  background: #fff;
  color: var(--brand-primary);
}
.td-streak-card.is-lit .td-streak-day.is-today {
  box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.55);
}

/* ---- unlit state: always the dark treatment, in either app theme ----
   An unlit diya on the plain light-mode card background (white box, brown
   bowl, gray text) read as a broken/empty placeholder rather than a lamp
   waiting to be lit. The dark-mode look already solved this by accident —
   forcing it regardless of body.dark-mode makes "waiting to be lit" a
   deliberate, moody state instead of something that only looks right in
   one theme. Lit stays exactly as before (its own rules above already
   replace the card's background outright). */
.td-streak-card:not(.is-lit) {
  background: #252422;
  border-color: #403D39;
}
.td-streak-card:not(.is-lit) .td-streak-headline { color: #EFF1F0; }
.td-streak-card:not(.is-lit) .td-streak-tagline { color: #A6A199; }
.td-streak-card:not(.is-lit) .td-streak-day {
  border-color: #403D39;
  background: #322F2C;
  color: #A6A199;
}

/* Compact flame: the same .diya-widget-card/.diya-art-wrap markup the full
   Profile widget uses, but pulled out of the flex flow and blown up past
   the card's own edge — bigger than its box, anchored bottom-right, tip
   reaching up toward the card's top padding, right edge bleeding past the
   card and getting cropped by .td-streak-card's own overflow:hidden +
   rounded corner. .td-streak-info reserves the width this used to occupy
   in flow (padding-right below) so the streak text never sits under it. */
.td-flame-mini.diya-widget-card {
  position: absolute;
  z-index: 2; /* above .td-flame-glow (0) and .td-streak-info (1) */
  /* Bled off the right/bottom edge (cropped by .td-streak-card's own
     overflow:hidden) for presence, and square to match diyaFlameSvg's 320x320
     viewBox so preserveAspectRatio "meet" fills it with no letterboxing.
     Pulled in from -70px/-70px and down from 252px: at the old size the bowl
     ran across the weekday strip and cut the last two day pills in half, which
     looked like a rendering fault rather than a deliberate crop. The lamp still
     bleeds, it just no longer collides with content. */
  right: -2.125rem;
  bottom: -2.75rem;
  width: 12.25rem;
  height: 12.25rem;
  padding: 0;
  border: none;
  background: none;
  box-shadow: none;
  border-radius: 0;
  pointer-events: none;
}
/* body.dark-mode .diya-widget-card (above) sets a solid dark background —
   same class-count as .td-flame-mini.diya-widget-card (2) but WITH an
   element selector (body) the plain rule doesn't have, so it wins the
   specificity tiebreak in dark mode specifically and reasserts a solid
   marigold-brown box here, hiding the card's own .is-lit gradient behind
   a flat rectangle. Needs matching body.dark-mode weight to actually lose. */
body.dark-mode .td-flame-mini.diya-widget-card {
  background: none;
  border-color: transparent;
  box-shadow: none;
}
/* The layered .td-flame-glow rings above replace this SVG's own single
   hard-edged halo circle here — two overlapping glows read as a harder,
   more visible edge, the opposite of what either is for. */
.td-flame-mini .diya-halo { display: none; }
.td-flame-mini .diya-art-wrap {
  width: 100%;
  max-width: none;
  height: 100%;
}

.td-begin {
  width: 100%;
  border-radius: 99px;
  font-family: var(--font-sans);
  font-size: 1rem;
}

/* On the page's own background, not boxed in .card like every other Today
   tile — the checklist rows underneath already read as their own list
   (see .td-check-row's border-bottom dividers), so a surrounding card was
   just one more box around a box. */
.td-sadhana-card { cursor: pointer; -webkit-tap-highlight-color: transparent; margin-bottom: 20px; padding: 0 6px; }

.td-sadhana-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 4px;
}
.td-sadhana-card .td-sec-title { margin: 0; }
.td-sadhana-head-actions { display: flex; align-items: center; gap: 10px; flex-shrink: 0; }
/* The one deliberate way into the full-screen builder from Today now — the
   card itself goes to Practice on tap (see the JS handler), so this needs
   to read clearly as its own tappable control, not part of the ring. */
.td-sadhana-edit {
  display: flex; align-items: center; justify-content: center;
  width: 32px; height: 32px; border-radius: 50%; border: none; cursor: pointer;
  background: var(--surface-alt); color: var(--text-muted);
  -webkit-tap-highlight-color: transparent;
}
.td-sadhana-edit:active { transform: scale(0.92); }
.td-sadhana-edit .material-symbols-rounded { font-size: 1.0625rem; }

.td-sec-title {
  font-size: 1.125rem;
  margin: 0 0 6px;
  color: var(--text-main);
}

.td-ring {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  flex: 0 0 auto;
}
.td-ring svg { width: 100%; height: 100%; transform: rotate(-90deg); }
.td-ring-track {
  fill: none;
  stroke: var(--surface-alt);
  stroke-width: 3;
}
.td-ring-fill {
  fill: none;
  stroke: var(--brand-primary);
  stroke-width: 3;
  stroke-linecap: round;
  transition: stroke-dashoffset 0.5s cubic-bezier(0.22, 1, 0.36, 1);
}
/* Complete: the ring fills solid green with a white check instead of
   showing "N/N" over the thin progress stroke — the svg (now redundant,
   there's nothing left to show partial progress toward) fades out under
   the flat fill. */
.td-ring.is-complete {
  background: var(--success-ink, var(--brand-primary));
  border-radius: 50%;
}
.td-ring.is-complete svg { opacity: 0; }
.td-ring-text {
  position: absolute;
  font-size: 0.65625rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--text-main);
}
.td-ring-check {
  position: absolute;
  display: flex;
  color: #fff;
}
.td-ring-check .material-symbols-rounded { font-size: 1.25rem; font-variation-settings: 'FILL' 1; }

.td-check-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  width: 100%;
  padding: 14px 0;
  border: none;
  border-bottom: 1px solid var(--border);
  background: none;
  font-family: inherit;
  text-align: left;
  cursor: pointer;
  color: var(--text-main);
  -webkit-tap-highlight-color: transparent;
}
.td-sadhana-card .td-check-row:last-of-type { border-bottom: none; }
.td-check-row:active { opacity: 0.6; }
.td-check-row .td-row-main {
  font-size: 1.03125rem;
  font-weight: 500;
  transition: color 0.15s ease;
}
.td-row-main {
  font-size: 1.03125rem;
  font-weight: 500;
  min-width: 0;
}
.td-check-row .td-row-main.is-done {
  color: var(--text-muted);
  text-decoration: line-through;
  text-decoration-color: var(--border);
}

.td-check-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  flex: 1;
}
.td-check-sub {
  font-size: 0.78125rem;
  font-weight: 500;
  color: var(--text-muted);
}

.td-sadhana-empty {
  font-size: 0.90625rem;
  line-height: 1.55;
  color: var(--text-muted);
  margin: 4px 0 14px;
}

/* Empty-state fast path: the 5/15/30 presets right on the card, styled off
   the wizard's own .sw-preset-card so the two never drift apart visually.
   Presets are always free (see quickBuildSadhana), so there's no lock mark
   here — only Customize, below, carries one. */
.td-sadhana-presets { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
.td-sadhana-preset-btn {
  display: flex; flex-direction: column; align-items: flex-start; gap: 5px;
  padding: 13px 11px; border-radius: 14px; border: 1px solid var(--border);
  background: var(--surface); cursor: pointer; text-align: left;
  transition: transform 0.15s cubic-bezier(0.34, 1.56, 0.64, 1), border-color 0.15s ease;
}
.td-sadhana-preset-btn:active { transform: scale(0.95); }
.td-sadhana-preset-btn .material-symbols-rounded { font-size: 1.3125rem; color: var(--brand-primary); }
.td-sadhana-preset-btn b { font-size: 0.8125rem; color: var(--text-main); line-height: 1.25; }
.td-sadhana-preset-btn span:not(.material-symbols-rounded) { font-size: 0.6875rem; color: var(--text-muted); font-weight: 600; }

.td-sadhana-customize {
  width: 100%; margin-top: 10px; padding: 12px;
  display: flex; align-items: center; justify-content: center; gap: 6px;
  font-size: 0.875rem; font-weight: 700;
}
.td-sadhana-customize .plus-mark { width: 16px; height: 16px; }

.td-check-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 38px;
  height: 38px;
  flex: 0 0 auto;
  border-radius: 12px;
  background: var(--brand-primary-light);
  color: var(--brand-primary);
}
.td-check-icon.is-done {
  background: var(--brand-primary);
  color: #fff;
}
.td-check-icon .material-symbols-rounded { font-size: 1.1875rem; }

.td-check-chevron {
  display: flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  color: var(--text-muted);
}
.td-check-chevron .material-symbols-rounded { font-size: 1.25rem; }

/* ---- Gita verse of the day card ---- */
.td-verse-card { cursor: pointer; -webkit-tap-highlight-color: transparent; }
.td-verse-card:active { opacity: 0.9; }

.td-verse-kicker {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 0.71875rem;
  font-weight: 800;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--brand-primary);
  margin-bottom: 8px;
}
.td-verse-kicker .material-symbols-rounded { font-size: 0.9375rem; }

.td-verse-title {
  font-family: var(--font-title);
  font-size: 1.1875rem;
  font-weight: 800;
  margin: 0 0 6px;
  color: var(--text-main);
}
.td-verse-sub {
  font-size: 0.84375rem;
  color: var(--text-muted);
  line-height: 1.5;
  margin: 0;
}

.td-verse-preview {
  font-family: var(--font-serif);
  font-size: 1.0625rem;
  font-style: italic;
  line-height: 1.55;
  color: var(--text-main);
  margin: 0 0 12px;
}

.td-verse-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.td-verse-ref {
  font-size: 0.78125rem;
  font-weight: 700;
  color: var(--text-muted);
}
/* The preview cuts the translation at 130 chars and the whole card has always
   been tappable (openDailyVerse), but nothing said so — the verse just stopped
   mid-sentence on an ellipsis and read as broken content rather than a preview.
   Shown only when the text was actually truncated. */
.td-verse-more {
  margin-left: 8px;
  font-size: 0.78125rem;
  font-weight: 800;
  color: var(--accent, #E8590C);
}
.td-verse-actions {
  display: flex;
  align-items: center;
  gap: 8px;
}
.td-verse-save,
.td-verse-share {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: var(--surface-alt);
  color: var(--text-muted);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.td-verse-save.is-saved { color: var(--brand-primary); background: var(--brand-primary-light); }
.td-verse-save .material-symbols-rounded,
.td-verse-share .material-symbols-rounded { font-size: 1.125rem; }

/* ---- Be Mindful: featured meditation banner, .feature-card + .feature-blob
   shell (defined near .card, previously unused) filled in with real content ---- */
.td-feature-card { align-items: center; }
.td-feature-text { position: relative; z-index: 2; max-width: 78%; }
.td-feature-badge {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 4px 11px;
  border-radius: 99px;
  background: rgba(255, 255, 255, 0.16);
  color: #fff;
  font-size: 0.65625rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  margin-bottom: 10px;
}
/* The "Edit Sadhana" state's pencil rides inside the pill (see renderExplore)
   so a routine already built reads unmistakably as "tap to edit" rather
   than a plain forward chevron, which said "go here" but not "and change
   it" — the default 24px glyph would blow the pill's height out, hence
   the override. */
.td-feature-badge .material-symbols-rounded { font-size: 0.75rem; }
.td-feature-title {
  font-family: var(--font-title);
  font-size: 1.375rem;
  font-weight: 800;
  color: #fff;
  margin: 0 0 5px;
}
.td-feature-sub {
  font-size: 0.84375rem;
  color: rgba(255, 255, 255, 0.75);
  line-height: 1.4;
  margin: 0;
}

/* ---- Practice tab's sadhana card: presets embedded on the card itself ----
   Was a single big .feature-card <button> (tap anywhere -> the builder),
   which made it too easy to land in the editor by accident. Now a column:
   an inert header (title/subtitle only, no tap target, no chevron) plus a
   row of 5/15/30 preset squares that build and save immediately, and a 4th,
   deliberately locked square that is the ONLY way into the Plus-only editor. */
.pr-sadhana-shell { flex-direction: column; align-items: stretch; gap: 16px; cursor: default; }
.pr-sadhana-head { position: relative; z-index: 2; }
.pr-sadhana-presets { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; position: relative; z-index: 2; }
.pr-sadhana-sq {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  aspect-ratio: 1; border-radius: 16px; border: none; cursor: pointer;
  background: rgba(255, 255, 255, 0.16); color: #fff;
  -webkit-tap-highlight-color: transparent;
  transition: transform 0.15s cubic-bezier(0.34, 1.56, 0.64, 1), background 0.15s ease;
}
.pr-sadhana-sq:active { transform: scale(0.92); background: rgba(255, 255, 255, 0.26); }
.pr-sadhana-sq b { font-size: 1.1875rem; font-weight: 800; line-height: 1.1; }
/* Its own class, not a generic span:not() exclusion — that previously also
   caught icon('edit')'s <span class="material-symbols-rounded">edit</span>
   on the 4th square. Material Symbols renders via an OpenType ligature keyed
   to the exact lowercase text "edit"; text-transform:uppercase changes what
   the browser shapes BEFORE that lookup, so the ligature never matched and
   the font fell back to drawing the literal (capitalized) letters instead
   of the pencil glyph — the "EDIT" text and oversized scribble in the bug
   report. Scoping this to the actual minute label avoids the icon span
   entirely, and the icon's own font-size below is what should be governing
   its size regardless. */
.pr-sadhana-sq-label { font-size: 0.625rem; font-weight: 700; color: rgba(255, 255, 255, 0.75); text-transform: uppercase; letter-spacing: 0.04em; }
/* The 4th square: same box, same two-line layout (icon + uppercase label)
   as the 3 free presets, so it reads as a fourth option rather than an
   unrelated locked slab. Used to cover the whole square in a black scrim
   with a 40px K+ mark dead center — at that size the mark WAS the square,
   and the pencil underneath (still 20px, unlit) was invisible under both
   the scrim and the mark sitting on top of it. The lock now reads as a
   small coin badge on the corner instead (matching .game-shelf-lock's
   pattern), so the actual content of the square — pencil + "Edit" — stays
   as visible as the number + "min" on the other three. */
.pr-sadhana-sq-edit { position: relative; }
.pr-sadhana-sq-edit .material-symbols-rounded { font-size: 1.875rem; color: #fff; line-height: 1; }
.pr-sadhana-sq-premium {
  background: linear-gradient(135deg, #FFEFB0, #D4A852, #B28833);
  box-shadow: 0 4px 12px rgba(212, 168, 82, 0.3);
}
.pr-sadhana-sq-premium:active { background: linear-gradient(135deg, #F0DF9D, #C29541, #A17622); }
.pr-sadhana-sq-premium .material-symbols-rounded,
.pr-sadhana-sq-premium .pr-sadhana-sq-label {
  color: #634305;
  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.4), 0 -1px 0 rgba(0, 0, 0, 0.1);
}

/* ---- Karana Premium: a gold plate, deliberately not another orange
   feature-card — this app keeps to one accent color everywhere else (see
   theme.css), so a different material (metallic gold, not brand orange)
   is what marks this out as a different product, not just another feature.
   Text reads as engraved (pressed into the plate) via a light shadow below
   + a dark shadow above, rather than sitting on top of it like every other
   card's white text-on-color. */
.td-premium-card {
  position: relative;
  overflow: hidden;
  width: 100%;
  margin-bottom: 20px;
  padding: 26px 20px;
  border: none;
  border-radius: 24px;
  text-align: center;
  cursor: pointer;
  background: linear-gradient(135deg, #FFF3D2 0%, #F7D774 22%, #E8B646 48%, #C98F1F 72%, #F6DA84 100%);
  box-shadow: 0 10px 26px rgba(180, 128, 15, 0.35), inset 0 1px 0 rgba(255, 255, 255, 0.7);
}
.td-premium-card:active { transform: scale(0.99); }
/* Faint repeating Devanagari, pressed into the plate rather than sitting on
   it — a fill this close to the background color (opacity .09) carries
   almost no shape on its own; the engraved read comes almost entirely from
   the small light-below/dark-above shadow pair, the way an actual shallow
   press into metal only shows as a sliver of highlight/shadow, not a filled
   glyph. Small size + tight line-height so it tiles as a texture behind the
   title/sub (z-index 1), not as legible words competing with them. */
.td-premium-engraving {
  position: absolute;
  inset: 0;
  z-index: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  padding: 8px;
  font-family: var(--font-title);
  /* Smaller and fainter than it was (14px / .09 alpha). At that weight the
     Devanagari stopped reading as a pressed texture and started reading as
     WORDS, and because this block is centred it landed exactly on the title
     and subtitle — two competing strings of text occupying the same pixels.
     On a gold plate that is the worst case for contrast. */
  font-size: 0.6875rem;
  font-weight: 700;
  line-height: 1.9;
  letter-spacing: 1px;
  color: rgba(107, 75, 18, 0.055);
  text-shadow: 0 0.5px 0 rgba(255, 255, 255, 0.22), 0 -0.5px 0.5px rgba(80, 50, 10, 0.1);
  user-select: none;
  pointer-events: none;
}
/* Three parallel diagonal streaks, upper right, animated as one group so
   they periodically sweep across the plate like light catching an actual
   shiny surface — a "glint," not a constant loading-style shimmer, hence
   the animation holding off-card for most of each cycle (see the keyframe:
   the sweep itself is only the first ~30% of the duration). */
.td-premium-shine {
  position: absolute; inset: 0; z-index: 0; overflow: hidden; pointer-events: none;
  animation: premiumGlint 3.6s ease-in-out infinite;
}
.td-premium-line {
  position: absolute; top: -50%; height: 200%;
  background: rgba(255, 255, 255, 0.5);
  transform: rotate(22deg);
  filter: blur(1.5px);
}
.td-premium-line-1 { right: 6%; width: 15%; }
.td-premium-line-2 { right: 22%; width: 8%; background: rgba(255, 255, 255, 0.32); }
.td-premium-line-3 { right: 33%; width: 5%; background: rgba(255, 255, 255, 0.22); }
@keyframes premiumGlint {
  0% { transform: translateX(-60%); }
  32% { transform: translateX(60%); }
  100% { transform: translateX(60%); }
}
@media (prefers-reduced-motion: reduce) {
  .td-premium-shine { animation: none; }
}
.td-premium-title {
  position: relative; z-index: 1;
  font-family: var(--font-title);
  font-size: 1.0625rem;
  font-weight: 900;
  letter-spacing: -0.3px;
  /* Darkened from #6B4B12. Against the plate's mid-tone (#E8B646) that was
     about 3.4:1 — under AA for the 21px weight it is set at, and it had the
     engraving running through it as well. */
  color: #4A3208;
  margin: 0;
  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.7), 0 -1px 1px rgba(90, 60, 10, 0.35);
}
.td-premium-sub {
  position: relative; z-index: 1;
  font-size: 0.84375rem;
  font-weight: 700;
  /* Darkened from #7A5716 for the same reason; the subtitle is the smaller of
     the two and was the harder of the two to read. */
  color: #5A3E0C;
  margin: 0;
  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.6);
}

/* ---- Achievements: a trophy shelf — bordered tiles (badge + name) in a
   horizontally-scrolling row — of real, tracked achievements (see
   ACHIEVEMENTS_DATA's own "tracked" comment in app.js for the selection
   process), using the exact same tier-colored metal badge art
   (.badge-box/.badge-surface/.badge-icon, defined near .ref-card below)
   openAchievementsDetail's full list uses — just smaller. ---- */
.td-ach-card { cursor: pointer; -webkit-tap-highlight-color: transparent; }
.td-ach-card:active { opacity: 0.9; }
.td-ach-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 16px;
}
.td-ach-see-all {
  display: flex;
  align-items: center;
  gap: 1px;
  font-size: 0.78125rem;
  font-weight: 800;
  color: var(--text-muted);
}
.td-ach-see-all .material-symbols-rounded { font-size: 1.0625rem; }
.td-ach-row {
  display: flex;
  gap: 12px;
  overflow-x: auto;
  scrollbar-width: none;
  /* No scroll-snap here on purpose: scroll-snap-type on a padded scroll
     container is a known quirk (WebKit and Chromium both do this) — the
     browser "corrects" the initial scroll position to align the first
     snap-align:start child with the scrollport's start edge, which eats
     exactly this row's own left padding on load (measured scrollLeft was
     24px, i.e. the padding amount, at rest) and pulled every tile flush
     against the card's edge instead of lining up with the header above.
     A shelf this short doesn't need snapping to feel right anyway. */
  margin: 0 -24px;
  padding: 0 24px 4px;
}
.td-ach-row::-webkit-scrollbar { display: none; }
.td-ach-tile {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  flex: 0 0 auto;
  width: 96px;
  padding: 14px 8px;
  border: 1px solid var(--border);
  border-radius: 18px;
  background: var(--surface-alt);
}
.td-ach-tile-name {
  font-size: 0.75rem;
  font-weight: 700;
  color: var(--text-main);
  text-align: center;
  line-height: 1.25;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
/* .ach-mini-icon-wrap (the scaled-down .badge-box for this strip) is
   defined alongside .ach-list-icon-wrap/.ach-modal-icon-wrap, near
   .badge-box itself — same-specificity override, so it has to come after
   .badge-box's own rule in the cascade to actually win. */

/* ---- Journal shelf on Profile (.td-journal-card, above Achievements) ----
   Reuses .td-ach-head/.td-ach-row wholesale (title + "See all" + the
   horizontal-scroll row shell) — only the tile itself is its own class,
   since a journal card shows a text snippet rather than Achievements'
   tier-colored badge art. */
.td-journal-tile {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 8px;
  flex: 0 0 auto;
  width: 148px;
  min-height: 128px;
  padding: 14px;
  border: 1px solid var(--border);
  border-radius: 18px;
  background: var(--surface-alt);
  text-align: left;
  cursor: pointer;
  font-family: inherit;
  -webkit-tap-highlight-color: transparent;
}
.td-journal-tile:active { opacity: 0.85; }
.td-journal-tile .material-symbols-rounded { font-size: 1.125rem; color: var(--brand-primary); }
.td-journal-tile-text {
  flex: 1;
  font-size: 0.78125rem;
  font-weight: 600;
  line-height: 1.4;
  color: var(--text-main);
  display: -webkit-box;
  -webkit-line-clamp: 4;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.td-journal-tile-date {
  font-size: 0.65625rem;
  font-weight: 700;
  color: var(--text-muted);
}

/* ---- Streak detail (full-screen .detail overlay opened from the streak card) ---- */
.streak-detail-week {
  display: flex;
  justify-content: space-between;
  gap: 6px;
}
.streak-detail-day {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  flex: 1;
}
.streak-detail-day-dot {
  width: 30px;
  height: 30px;
  border-radius: 50%;
  border: 1px solid var(--border);
  background: var(--surface);
}
.streak-detail-day.is-done .streak-detail-day-dot {
  border-color: var(--brand-primary);
  background: var(--brand-primary);
}
.streak-detail-day.is-today .streak-detail-day-dot {
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--brand-primary) 45%, transparent);
}
.streak-detail-day-letter {
  font-size: 0.6875rem;
  font-weight: 700;
  color: var(--text-muted);
}

/* Streak detail's own Streak / Flame Tiers segmented control — same
   pattern as Library's Saved/Recent tabs (.lib-tabs/.lib-tab), reused
   rather than a new tab style for one more screen. */
.streak-detail-tabs {
  /* Back to 84px — the actual complaint was never the gap above the tabs
     (that reverted twice now, first from 64 to 84 to 140, chasing the wrong
     target), it was the gap BELOW them, between the tab bar and the list
     content. Fixed there instead — see .streak-detail-panel's margin-top. */
  display: flex; gap: 4px; padding: 4px; margin: calc(env(safe-area-inset-top, 0px) + 84px) 0 26px;
  background: var(--surface-alt); border-radius: 14px;
}
.streak-detail-tab {
  flex: 1; text-align: center; padding: 10px 0; border: none; background: transparent;
  border-radius: 10px; font-family: inherit; font-weight: 800; font-size: 0.90625rem;
  color: var(--text-muted); cursor: pointer; transition: background .15s, color .15s;
}
.streak-detail-tab.active {
  background: var(--surface); color: var(--brand-primary); box-shadow: var(--shadow-soft);
}

.streak-tier-intro {
  margin: -4px 0 14px;
  font-size: 0.8125rem;
  line-height: 1.5;
  color: var(--text-muted);
}

/* Flame milestone timeline: one row per actual tier (7), strung along a
   vertical progress line. Replaces the old 4-card-per-color gallery, which
   relied on this whole chain (.streak-detail-panel, #streak-tier-panel-body)
   having NO scroll of its own — harmless while 4 short cards fit one
   screen, but content taller than the viewport was silently clipped by the
   overflow:hidden below with nothing to scroll it into view. The tiers
   panel now gets its own scroll (#streak-tier-panel-body), so a taller,
   one-row-per-tier list is safe to show in full instead of collapsed down
   to 4. Each row's flame is the SAME diyaFlameSvg component the hero widget
   above uses, so the flicker/pulse animation comes for free. */
.streak-detail-panel {
  flex: 1;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.streak-detail-panel[hidden] {
  display: none !important;
}
#streak-tier-panel-body {
  flex: 1;
  min-height: 0; /* required for a flex child to actually scroll instead of growing to fit content */
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  /* This, not the gap above .streak-detail-tabs, was the actual complaint:
     the list started right at the tab bar's bottom edge with nothing
     between them. */
  margin-top: 18px;
  margin-bottom: 0;
  padding-bottom: 8px;
  /* Scrolls on its own (overflow-y:auto above) but had no padding-right at
     all, so the scrollbar/edge sat flush against the milestone rows with
     nothing between them — tighter still as the viewport narrows and
     everything else compresses toward that same edge. */
  padding-right: 10px;
}
.diya-milestone-track {
  position: relative;
  padding-left: 76px;
}
.diya-milestone-line {
  position: absolute;
  /* left shifted from 29px to keep the (now thicker) line centered under a
     60px node: node center sits at 30px in from the track's left edge, so
     left = 30 - width/2. */
  left: 26px; top: 30px; bottom: 30px;
  width: 8px; /* 3px * 2.5 = 7.5, rounded to a clean value */
  border-radius: 99px;
  background: var(--border);
  overflow: hidden;
}
.diya-milestone-line-fill {
  position: absolute; left: 0; top: 0; width: 100%; height: 0%;
  background: var(--brand-primary);
  transition: height 0.6s cubic-bezier(0.16, 1, 0.3, 1);
}
.diya-milestone-list {
  display: flex;
  flex-direction: column;
  gap: 22px;
}
.diya-milestone {
  position: relative;
  display: flex;
  align-items: center;
  gap: 16px;
}
.diya-milestone-node {
  position: absolute;
  left: -76px;
  width: 60px; height: 60px;
  border-radius: 50%;
  background: var(--surface);
  border: 5px solid var(--border); /* 2px * 2.5 = 5px */
  display: flex; align-items: center; justify-content: center;
  flex-shrink: 0;
  z-index: 1;
}
.diya-milestone.is-reached .diya-milestone-node {
  border-color: var(--brand-primary);
  background: var(--brand-primary-light);
}
.diya-milestone.is-current .diya-milestone-node {
  box-shadow: 0 0 0 4px var(--brand-primary-light);
}
.diya-milestone-flame { width: 38px; height: 34px; }
.diya-milestone-flame .diya-flame-svg { width: 100%; height: 100%; }
/* No .light-ray-burst mounted alongside these small flames, but the shared
   diyaFlameSvg output has no halo-suppression of its own — clip it here so
   a 60px node doesn't show a soft glow sized for the 260px hero. */
.diya-milestone-flame .diya-halo { opacity: 0 !important; }
.diya-milestone-copy {
  /* No margin-left here: the node is position:absolute (pulled out of flex
     flow, see .diya-milestone-node's left:-76px), so .diya-milestone-copy is
     already the first real flex child — .diya-milestone-track's own
     padding-left:76px is what reserves the node/line gutter it sits after. */
  flex: 1;
  min-width: 0;
}
.diya-milestone-head {
  display: flex;
  align-items: center;
  gap: 8px;
}
.diya-milestone-head b {
  font-size: 0.9375rem;
  font-weight: 800;
  color: var(--text-main);
}
.diya-tier-current-tag {
  padding: 3px 9px;
  border-radius: 99px;
  background: var(--brand-primary);
  color: #fff;
  font-size: 0.625rem;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  white-space: nowrap;
}
.diya-milestone-req {
  font-size: 0.6875rem;
  font-weight: 700;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.03em;
  margin-top: 2px;
}
.diya-milestone-desc {
  font-size: 0.8125rem;
  line-height: 1.45;
  color: var(--text-muted);
  margin: 4px 0 0;
}
.diya-milestone-status {
  flex-shrink: 0;
  display: flex;
  color: var(--text-muted);
  opacity: 0.5;
}
.diya-milestone-status .material-symbols-rounded { font-size: 1.25rem; }
.diya-milestone.is-reached .diya-milestone-status {
  color: var(--brand-primary);
  opacity: 1;
}
/* Not-yet-reached rows stay legible (this is "what's ahead", not hidden)
   but visibly recede behind what's already lit. */
.diya-milestone:not(.is-reached) .diya-milestone-head b,
.diya-milestone:not(.is-reached) .diya-milestone-desc {
  opacity: 0.6;
}

/* ---------- panchang ----------
   A quiet line under the greeting, not a card. The panchang is orientation
   rather than a task, so it sits at the weight of a dateline. */
.td-panchang {
  display: flex;
  align-items: center;
  gap: 7px;
  width: 100%;
  margin: -4px 0 18px;
  padding: 6px 0;
  background: none;
  border: none;
  cursor: pointer;
  font-family: var(--font-sans);
  text-align: left;
}
.td-panchang-main { font-size: 0.84375rem; font-weight: 800; color: var(--brand-primary); letter-spacing: .2px; }
.td-panchang-dot  { color: var(--text-muted); opacity: .6; }
.td-panchang-sub  { font-size: 0.84375rem; font-weight: 600; color: var(--text-muted); }
.td-panchang .material-symbols-rounded { font-size: 1.0625rem; color: var(--text-muted); opacity: .7; margin-left: -2px; }

.pnc-wrap { padding: 8px 2px 28px; }
.pnc-head { text-align: center; margin-bottom: 22px; }
.pnc-date { font-size: 0.8125rem; font-weight: 700; color: var(--text-muted); text-transform: uppercase; letter-spacing: 1.1px; }
.pnc-headline {
  font-family: var(--font-title);
  font-size: 1.8125rem; font-weight: 900; letter-spacing: -.5px;
  color: var(--text-main); margin: 6px 0 3px;
}
.pnc-sub { font-size: 0.90625rem; color: var(--text-muted); }

.pnc-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 4px 16px;
}
.pnc-row {
  display: flex; align-items: baseline; justify-content: space-between; gap: 14px;
  padding: 13px 0;
  border-bottom: 1px solid var(--border);
}
.pnc-row:last-child { border-bottom: none; }
.pnc-label { font-size: 0.8125rem; font-weight: 800; color: var(--text-muted); text-transform: uppercase; letter-spacing: .8px; }
.pnc-value { font-size: 1rem; font-weight: 700; color: var(--text-main); text-align: right; }
/* The transition time is the load-bearing detail: a tithi does not own a whole
   day, so the value alone would be a small lie. */
.pnc-note { display: block; font-size: 0.78125rem; font-weight: 600; color: var(--text-muted); margin-top: 2px; }

.pnc-explain { margin-top: 26px; }
.pnc-explain h3 { font-family: var(--font-title); font-size: 1.125rem; font-weight: 900; margin: 0 0 10px; color: var(--text-main); }
.pnc-explain p { font-size: 0.9375rem; line-height: 1.62; color: var(--text-muted); margin: 0 0 12px; }
.pnc-explain b { color: var(--text-main); font-weight: 800; }
.pnc-caveat {
  margin-top: 18px; padding-top: 14px;
  border-top: 1px solid var(--border);
  font-size: 0.78125rem; line-height: 1.55; color: var(--text-muted); opacity: .85;
}

/* ---------- festival calendar ----------
   The Today row sits at the same weight as the panchang line above it: both are
   facts about the day rather than things to do, so neither gets a card. */
.td-fest-row {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  margin: -6px 0 20px;
  padding: 10px 12px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 14px;
  cursor: pointer;
  font-family: var(--font-sans);
  text-align: left;
}
.td-fest-icon { display: flex; color: var(--brand-primary); }
.td-fest-icon .material-symbols-rounded { font-size: 1.3125rem; }
.td-fest-text { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.td-fest-name { font-size: 0.90625rem; font-weight: 800; color: var(--text-main); }
.td-fest-when { font-size: 0.78125rem; font-weight: 600; color: var(--text-muted); }
.td-fest-more { font-size: 0.78125rem; font-weight: 700; color: var(--brand-primary); white-space: nowrap; }
.td-fest-row .material-symbols-rounded:last-child { font-size: 1.125rem; color: var(--text-muted); opacity: .7; }
/* The same row carries a personal observance, in the olive the calendar grid
   uses for it, so "your Ekadashi" and "everyone's Diwali" are not the same
   colour in the one place both can appear. */
.td-fest-row.is-personal .td-fest-icon,
.td-fest-row.is-personal .td-fest-more { color: var(--success-ink); }


/* Birth-place picker. The list is bundled, so there is no geocoding request
   and nothing about a birth ever leaves the device. */
.jyo-suggest {
  position: relative; margin-top: -6px; margin-bottom: 4px;
  border: 1px solid var(--border); border-radius: 12px;
  background: var(--surface); overflow: hidden;
}
.jyo-sugg {
  display: flex; align-items: baseline; gap: 8px; width: 100%;
  padding: 11px 14px; text-align: left; border: 0; background: none;
  color: var(--text-main); font-family: var(--font-sans); font-size: 0.9375rem; cursor: pointer;
}
.jyo-sugg + .jyo-sugg { border-top: 1px solid var(--border); }
.jyo-sugg-n { font-weight: 700; }
.jyo-sugg-a { font-size: 0.78125rem; color: var(--text-muted); }
.jyo-sec-quiet { opacity: .92; }

.game-loading { padding: 120px 20px; text-align: center; color: var(--text-muted); }
/* .ghub-section styled the "Traditional boards" heading in the games hub.
   Those six games were deleted in 53c32b8 and the markup with them, so this had
   nothing left to style. */

/* ===========================================================================
   Coach: the ring and the mimed gesture
   ===========================================================================
   Sits above everything, ignores the pointer entirely so it can never swallow
   the very tap it is asking for. */
.coach { position: fixed; inset: 0; z-index: 9000; pointer-events: none; }
.coach-ring {
  position: fixed; border-radius: 50%;
  box-shadow: 0 0 0 3px var(--brand-primary), 0 0 0 9999px rgba(0,0,0,.42);
  animation: coachRing 1.6s ease-out infinite;
}
@keyframes coachRing {
  0%   { transform: scale(1);    opacity: 1; }
  70%  { transform: scale(1.14); opacity: .75; }
  100% { transform: scale(1);    opacity: 1; }
}
.coach-hand {
  position: fixed; width: 26px; height: 26px; margin: -13px 0 0 -13px;
  border-radius: 50%; background: rgba(255,255,255,.95);
  box-shadow: 0 2px 10px rgba(0,0,0,.4);
}
/* tap: a finger arriving and lifting */
@keyframes coachTap { 0%,100% { transform: scale(1); opacity: .95; } 45% { transform: scale(.62); opacity: 1; } }
.coach-tap { animation: coachTap 1.15s ease-in-out infinite; }
/* hold: the finger stays down and fills */
@keyframes coachHold { 0% { transform: scale(1); } 20% { transform: scale(.66); } 85% { transform: scale(.66); background: var(--brand-primary); } 100% { transform: scale(1); } }
.coach-hold { animation: coachHold 1.8s ease-in-out infinite; }
/* swipe and drag: the finger travels in the named direction */
@keyframes coachSwipeUp   { 0% { transform: translateY(26px); opacity: 0; } 25% { opacity: 1; } 100% { transform: translateY(-30px); opacity: 0; } }
@keyframes coachSwipeDown { 0% { transform: translateY(-26px); opacity: 0; } 25% { opacity: 1; } 100% { transform: translateY(30px); opacity: 0; } }
@keyframes coachSwipeLeft { 0% { transform: translateX(26px); opacity: 0; } 25% { opacity: 1; } 100% { transform: translateX(-30px); opacity: 0; } }
@keyframes coachSwipeRight{ 0% { transform: translateX(-26px); opacity: 0; } 25% { opacity: 1; } 100% { transform: translateX(30px); opacity: 0; } }
.coach-swipe.coach-dir-up    { animation: coachSwipeUp 1.5s ease-in-out infinite; }
.coach-swipe.coach-dir-down  { animation: coachSwipeDown 1.5s ease-in-out infinite; }
.coach-swipe.coach-dir-left  { animation: coachSwipeLeft 1.5s ease-in-out infinite; }
.coach-swipe.coach-dir-right { animation: coachSwipeRight 1.5s ease-in-out infinite; }
.coach-drag.coach-dir-up     { animation: coachSwipeUp 1.9s ease-in-out infinite; }
.coach-drag.coach-dir-right  { animation: coachSwipeRight 1.9s ease-in-out infinite; }

.coach-text {
  position: fixed; left: 0; right: 0; text-align: center;
  font-family: var(--font-sans); font-size: 0.9375rem; font-weight: 700;
  color: #fff; text-shadow: 0 1px 6px rgba(0,0,0,.6); padding: 0 24px;
}
@media (prefers-reduced-motion: reduce) {
  .coach-ring, .coach-hand { animation: none; }
}

/* --- ACHIEVEMENTS CSS --- */
:root {
  /* Flat Bronze Palette */
  --bronze-main: #d9934e;
  --bronze-shadow: #8c4c16;
  --bronze-engrave: #f5c798;
  --bronze-engrave-shadow: rgba(140, 76, 22, 0.4);
  --bronze-inset-border: rgba(255, 255, 255, 0.22);

  /* Flat Silver Palette */
  --silver-main: #a3b8cc;
  --silver-shadow: #5c7287;
  --silver-engrave: #e6f0fa;
  --silver-engrave-shadow: rgba(60, 80, 100, 0.4);
  --silver-inset-border: rgba(255, 255, 255, 0.3);

  /* Flat Gold Palette */
  --gold-main: #f5c400;
  --gold-shadow: #a87900;
  --gold-engrave: #fff3a3;
  --gold-engrave-shadow: rgba(140, 100, 0, 0.45);
  --gold-inset-border: rgba(255, 255, 255, 0.35);

  /* Flat Locked Palette */
  --locked-main: #43545e;
  --locked-shadow: #212c32;
  --locked-engrave: #2f3b42;
  --locked-inset-border: rgba(255, 255, 255, 0.08);
}

.reference-sheet {
  display: flex;
  flex-direction: column;
  gap: 20px;
}

.ref-card {
  background-color: var(--surface);
  border: 2px solid var(--surface-alt);
  border-radius: 24px;
  padding: 20px;
  display: grid;
  grid-template-columns: 88px 1fr;
  gap: 20px;
  align-items: center;
  box-shadow: 0 4px 0 var(--surface-alt);
}

.badge-container {
  width: 88px;
  height: 88px;
  display: flex;
  align-items: center;
  justify-content: center;
}

.badge-box {
  width: 80px;
  height: 80px;
  position: relative;
  border-radius: 22px;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: transform 0.2s ease;
}

.ref-card:hover .badge-box {
  transform: translateY(-2px);
}

.badge-surface {
  position: absolute;
  inset: 0 0 6px 0;
  border-radius: 22px;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}

.badge-surface::after {
  content: '';
  position: absolute;
  inset: 4px;
  border-radius: 17px;
  border: 2px solid transparent;
  pointer-events: none;
  z-index: 3;
}

.badge-surface::before {
  content: '';
  position: absolute;
  top: 6px;
  left: 8px;
  width: 12px;
  height: 6px;
  background: rgba(255, 255, 255, 0.65);
  border-radius: 10px;
  transform: rotate(-25deg);
  filter: blur(0.3px);
  z-index: 4;
  pointer-events: none;
}

.badge-icon {
  position: relative;
  z-index: 2;
  width: 42px;
  height: 42px;
  display: flex;
  align-items: center;
  justify-content: center;
}

.badge-icon svg {
  width: 100%;
  height: 100%;
}

.tier-bronze .badge-box { background: var(--bronze-shadow); }
.tier-bronze .badge-surface { background-color: var(--bronze-main); }
.tier-bronze .badge-surface::after { border-color: var(--bronze-inset-border); }
.tier-bronze .badge-icon svg {
  fill: var(--bronze-engrave);
  filter: drop-shadow(0px -1.5px 0px var(--bronze-engrave-shadow));
}

.tier-silver .badge-box { background: var(--silver-shadow); }
.tier-silver .badge-surface { background-color: var(--silver-main); }
.tier-silver .badge-surface::after { border-color: var(--silver-inset-border); }
.tier-silver .badge-icon svg {
  fill: var(--silver-engrave);
  filter: drop-shadow(0px -1.5px 0px var(--silver-engrave-shadow));
}

.tier-gold .badge-box { background: var(--gold-shadow); }
.tier-gold .badge-surface { background-color: var(--gold-main); }
.tier-gold .badge-surface::after { border-color: var(--gold-inset-border); }
.tier-gold .badge-icon svg {
  fill: var(--gold-engrave);
  filter: drop-shadow(0px -1.5px 0px var(--gold-engrave-shadow));
}

.tier-locked .badge-box { background: var(--locked-shadow); }
.tier-locked .badge-surface { background-color: var(--locked-main); }
.tier-locked .badge-surface::after { border-color: var(--locked-inset-border); }
.tier-locked .badge-surface::before { opacity: 0.2; }
.tier-locked .badge-icon svg {
  fill: var(--locked-engrave);
  filter: drop-shadow(0px 1px 0px rgba(255, 255, 255, 0.08));
}

.ref-details {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.ref-header {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
}

.ref-title {
  font-family: var(--font-brand);
  font-size: 1.25rem;
  color: var(--text);
}

.ref-id {
  font-family: monospace;
  font-size: 0.8rem;
  color: var(--text-muted);
  background: var(--surface-alt);
  padding: 2px 8px;
  border-radius: 6px;
}

.tier-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.tier-item {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 0.88rem;
  font-weight: 700;
}

.tier-pill {
  font-size: 0.7rem;
  font-weight: 900;
  text-transform: uppercase;
  letter-spacing: 0.6px;
  padding: 2px 8px;
  border-radius: 6px;
  min-width: 62px;
  text-align: center;
}

.pill-bronze { color: var(--bronze-main); background: rgba(217, 147, 78, 0.15); }
.pill-silver { color: var(--silver-main); background: rgba(163, 184, 204, 0.15); }
.pill-gold { color: var(--gold-main); background: rgba(245, 196, 0, 0.15); }
.pill-locked { color: var(--locked-main); background: rgba(67, 84, 94, 0.15); }

.tier-desc {
  color: var(--text-muted);
}

.achievements-list {
  display: flex;
  flex-direction: column;
  gap: 16px;
  padding: 16px 0;
}

.ach-list-item {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 24px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 24px;
  box-shadow: var(--shadow-soft);
}

.ach-list-icon-wrap {
  width: 72px;
  height: 72px;
  flex: 0 0 auto;
}

.ach-list-content {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
}

.ach-list-title {
  font-family: var(--font-brand);
  font-size: 1.125rem;
  font-weight: 800;
  color: var(--text-main);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.ach-list-desc {
  font-size: 0.85rem;
  color: var(--text-muted);
  line-height: 1.3;
}

.ach-list-progress {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 6px;
}

.ach-list-progress-bar {
  flex: 1;
  height: 6px;
  background: var(--surface-alt);
  border-radius: 4px;
  overflow: hidden;
}

.ach-list-progress-fill {
  height: 100%;
  background: var(--brand-primary);
  border-radius: 4px;
}

.ach-list-progress-text {
  font-size: 0.75rem;
  font-weight: 800;
  color: var(--text-muted);
}

.ach-modal-content {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 16px;
}
.ach-modal-icon-wrap {
  width: 120px;
  height: 120px;
  margin: 0 auto 8px;
}
/* Achievements trophy shelf on Profile (.td-ach-row/.td-ach-tile) — the
   same badge scaled down to fit a 96px-wide tile alongside its name, so
   the art stays in proportion instead of a full-size glyph crowding it. */
.ach-mini-icon-wrap { width: 64px; height: 64px; }
.ach-mini-icon-wrap .badge-icon { width: 34px; height: 34px; }
.ach-modal-title {
  font-family: var(--font-brand);
  font-size: 1.5rem;
  color: var(--text);
}
.ach-modal-desc {
  font-size: 1rem;
  color: var(--text-muted);
  line-height: 1.5;
  padding: 0 16px;
}
.ach-modal-btn {
  margin-top: 12px;
  width: 100%;
}


/* Global Celebration Overlay */
.celebration-overlay {
  position: fixed;
  top: 0; left: 0; width: 100%; height: 100%;
  /* --bg is not a token this app defines (the palette uses --bg-app/--surface;
     see theme.css), so this only ever resolved to its fallback, which used to
     be a dark translucent scrim. That was the wrong shape for this overlay: a
     celebration is a solid field of the thing being celebrated, not a dimmed
     view of the screen behind it, and the lesson-complete celebration (the one
     showCelebration caller that passed no themeColor) was showing exactly that
     scrim. _playCelebration now defaults the inline colour, so this line is
     belt-and-braces only — but it agrees with it rather than contradicting it,
     and it stays opaque, which the #fff title and white rays require. */
  background: var(--brand-primary);

  z-index: 9999;
  display: flex; align-items: center; justify-content: center;
  opacity: 0; pointer-events: none;
  transition: opacity 0.4s ease;
  overflow: hidden;
}

.celebration-overlay.is-active {
  opacity: 1; pointer-events: auto;
}

/* Light rays radiating from the center */
.celebration-rays {
  position: absolute;
  top: 50%; left: 50%;
  width: 250vmax; height: 250vmax;
  margin-top: -125vmax; margin-left: -125vmax;
  background: repeating-conic-gradient(from 0deg, transparent 0deg 10deg, rgba(255, 255, 255, 0.03) 10deg 20deg);
  opacity: 0;
  transform: scale(0.1);
  transition: opacity 0.5s ease, transform 0.8s cubic-bezier(0.1, 0.8, 0.2, 1);
  animation: spinRays 40s linear infinite;
  /* Same reasoning as .light-ray-burst: the celebration overlay is in the DOM on
     every screen at opacity 0, and this 250vmax conic-gradient span was spinning
     behind every tab, all the time, for the few seconds a year it is seen. The
     .is-active rule below resumes it, and .is-active is exactly when the overlay
     is visible. */
  animation-play-state: paused;
  z-index: -2;
}

@keyframes spinRays { 100% { transform: scale(1) rotate(360deg); } }

.celebration-overlay.is-active .celebration-rays {
  opacity: 1; transform: scale(1); animation-play-state: running;
}

/* Content subtle slide up */
.celebration-content {
  display: flex; flex-direction: column; align-items: center; text-align: center;
  padding: 0 24px;
  text-shadow: 0 0 20px rgba(255, 255, 255, 0.25), 0 0 10px rgba(255, 255, 255, 0.1);
  transform: translateY(60px);
  opacity: 0;
  transition: transform 0.6s cubic-bezier(0.2, 0.9, 0.2, 1.1), opacity 0.5s ease;
}
.celebration-overlay.is-active .celebration-content {
  transform: translateY(0); opacity: 1;
}

/* Subtle icon pulse */
.celebration-icon {
  width: 140px; height: 140px; margin-bottom: 24px;
  position: relative; top: 25px; /* Moves just the icon down to center it on the rays */
  animation: pulseIcon 3s infinite ease-in-out;
  /* Third of the same kind: pulseIcon also carries a drop-shadow filter at its
     midpoint, so an invisible element was asking for a filtered repaint every
     frame, forever. Resumed with the overlay below. */
  animation-play-state: paused;
  display: flex; align-items: center; justify-content: center;
}
.celebration-overlay.is-active .celebration-icon { animation-play-state: running; }
.celebration-icon svg { width: 100%; height: 100%; }

.celebration-title {
  font-family: var(--font-brand);
  font-size: clamp(24px, 8vw, 32px); font-weight: 800; color: #fff;
  line-height: 1.2;
  margin: 0 0 12px 0; text-transform: uppercase; letter-spacing: 2px;
  text-shadow: 0 2px 12px rgba(0,0,0,0.5);
}

.celebration-subtitle {
  font-size: 1rem; color: rgba(255, 255, 255, 0.95); margin: 0; font-weight: 600;
  padding: 8px 20px;
  background: rgba(0, 0, 0, 0.15);
  border: 1.5px solid rgba(255, 255, 255, 0.4);
  border-radius: 100px;
}

@keyframes pulseIcon {
  0% { transform: scale(1); }
  50% { transform: scale(1.05); filter: drop-shadow(0 0 20px rgba(255,165,0,0.3)); }
  100% { transform: scale(1); }
}

/* The install banner's dismiss measured 24x24. A dismiss that is hard to hit is
   worse than most small targets, because the user is already trying to make
   something go away. The icon stays 24; the hit area grows around it. */
#install-banner-close {
  min-width: 44px; min-height: 44px;
  display: flex; align-items: center; justify-content: center;
}

/* ===========================================================================
   Japa: how many times to chant it
   ===========================================================================
   The logic for this shipped with the player and the markup never did, so every
   lookup returned null and the control could not be reached. It matters more
   than it looks: 43 of the 58 recitations are under 30 seconds because they are
   single recitations, so without a repeat control the app plays the Gayatri
   Mantra once, for eleven seconds, and stops.
   =========================================================================== */
/* Label ABOVE the chips, not beside them.
   Beside was tried and measured: at 375px the label and the live count take
   about 120px, which left each of the six chips 20.6px. "108" does not fit in
   20.6px at 13.5px type, so the row was equal-width and unreadable at the same
   time. Stacking gives each chip about 50px, which fits the widest label with
   room to spare and keeps every one of them identical. */
.japa-row {
  padding: 10px 20px 2px;
}

.japa-label {
  margin-bottom: 8px;
  display: flex;
  align-items: baseline;
  gap: 8px;
  font-size: 0.6875rem;
  font-weight: 800;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--text-muted);
}
.japa-count {
  font-variant-numeric: tabular-nums;   /* the count must not jitter as it counts */
  letter-spacing: 0;
  text-transform: none;
  font-size: 0.78125rem;
  color: var(--brand-primary);
}

/* GRID, not flex, and that is the whole fix.
   With flex the chips were sized by their content, so "108" was wider than "1"
   and the row read as ragged. `repeat(6, 1fr)` makes six columns of identical
   width whatever is written in them, so the row stays aligned even if the counts
   are ever changed. minmax(0, 1fr) rather than 1fr because a grid track's default
   minimum is its content, which would let "108" push its column wider again. */
.japa-chips {
  display: grid;
  grid-template-columns: repeat(6, minmax(0, 1fr));
  gap: 7px;
}

.japa-chip {
  -webkit-appearance: none;
          appearance: none;
  /* 44px is the accessible minimum and these sit directly above the scrubber,
     where a near miss would scrub the audio instead of setting a count. */
  min-height: 44px;
  padding: 0 4px;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--surface);
  color: var(--text-main);
  font-family: var(--font-sans);
  font-size: 0.90625rem;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  line-height: 1;
  /* Centres the glyph in the cell. The infinity sign has a different optical
     centre from the digits, and without this it sat visibly low. */
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: background-color .16s ease, border-color .16s ease, color .16s ease;
}
.japa-chip.active {
  background: var(--brand-primary);
  border-color: var(--brand-primary);
  color: #fff;
}

.japa-est {
  min-height: 16px;      /* reserved, so choosing a count never nudges the scrubber */
  margin: 8px 20px 0;
  font-size: 0.78125rem;
  color: var(--text-muted);
}

@media (prefers-reduced-motion: reduce) { .japa-chip { transition: none; } }

.premium-text-shine {
  background: linear-gradient(110deg, #d97706 0%, #f59e0b 40%, #fef3c7 50%, #f59e0b 60%, #d97706 100%);
  background-size: 200% auto;
  color: transparent;
  -webkit-background-clip: text;
  background-clip: text;
  animation: shineText 3s linear infinite;
}
@keyframes shineText {
  to {
    background-position: 200% center;
  }
}
