:root {
  --bg: #14151a;
  --surface: #1c1e26;
  --surface-hover: #262933;
  --border: #2c2f3a;
  --text: #f2f2f5;
  --subtext: #9497a6;
  /* Matches the Tailwind "accent"/"accentDim" pair in
     Preview/Preview/NakiContentShare's _Layout.cshtml exactly (not sampled
     from naki-logo.png, and not the site's own earlier blue either) - the
     preview iframe embedded in this site uses those colors, and this should
     read as one continuous shade rather than two slightly different blues
     next to each other. */
  --accent: #3b6ef6;
  --accent-dim: #1f3566;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  /* dvh (dynamic viewport height) tracks the actually-visible area on mobile
     as the browser's address bar shows/hides - plain vh is computed against
     a fixed viewport that doesn't account for that, so on a phone the body
     can end up shorter than the real screen, leaving the page (and anything
     sized relative to it) looking like it doesn't fill the screen. Same fix
     as the WordPress iframe embed snippet in README.md, for the same reason.
     Older browsers that don't know dvh just keep using the vh line above. */
  min-height: 100vh;
  min-height: 100dvh;
  background: var(--bg);
  color: var(--text);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  /* #coverflow/#cf-background-stage are deliberately widened beyond the
     viewport while receding/advancing (see cfSetRecedeWidth in app.js), so
     there's a scale() applied to bring them back to the same on-screen
     footprint - without this, that temporary extra width would otherwise
     show up as a horizontal scrollbar. */
  overflow-x: hidden;
}

.topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  /* Wraps to two lines only if the content genuinely doesn't fit on one -
     harmless at any width where it does fit, since flex-wrap only ever
     kicks in on actual overflow. */
  flex-wrap: wrap;
  gap: 16px;
  row-gap: 12px;
  padding: 16px 24px;
  border-bottom: 1px solid var(--border);
  position: sticky;
  top: 0;
  background: var(--bg);
  z-index: 10;
}

.topbar-actions {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 14px;
  flex-shrink: 0;
}
.brand {
  justify-content: center;
}
/* Below this, .brand (logo alone) and .topbar-actions (Sign up + badges)
   genuinely no longer fit on one line (verified empirically - .topbar wraps
   on its own down to ~425-444px already via flex-wrap above) and centering
   here reads as "logo on top, one deliberate row of buttons below" instead
   of the buttons trailing off wherever they happened to wrap to. Set at or
   below that measured natural-wrap point, never above it - width: 100% here
   would otherwise force .topbar-actions onto its own line even at widths
   where it would've comfortably fit next to .brand, which is exactly the
   premature-wrap bug this same pattern caused once already. */
@media (max-width: 430px) {
  .brand {
    width: 100%;
  }
  .topbar-actions {
    justify-content: center;
    width: 100%;
  }
}

/* Store badges: like preview.naki.audio's header, each store is actually two
   separate pre-made images (a full badge, and a small square icon-only
   badge) with only one shown at a time via the media query below - not one
   image resized or clipped, so the icon-only version stays crisp and reads
   as an actual app icon rather than a shrunk-down badge. */
.store-badge-link {
  display: block;
  opacity: 0.9;
  transition: opacity 0.15s ease;
}
.store-badge-link:hover { opacity: 1; }
.store-badge-full img {
  display: block;
  height: 40px;
  width: auto;
}
.store-badge-small {
  display: none;
}
.store-badge-small img {
  display: block;
  height: 40px;
  width: 40px;
}
@media (max-width: 720px) {
  .store-badge-full { display: none; }
  .store-badge-small { display: block; }
}

.brand {
  display: flex;
  align-items: center;
  gap: 8px;
  font-weight: 700;
  font-size: 18px;
}
.brand-logo {
  height: 36px;
  width: auto;
  display: block;
  /* The image's own artwork is centered within its file, but flexbox
     centers it against the text's full line-box (which reserves descender
     space most fonts don't visually use), so it reads as sitting a touch
     high without this nudge. */
  margin-top: 3px;
}
.section-tabs {
  display: flex;
  align-items: center;
  justify-content: space-between;
  /* Lets the search box drop to its own line on a narrow screen instead of
     being squeezed or overflowing - row-gap plus a real bottom padding
     (not just top) means that second line always ends with genuine
     breathing room before #content starts, however tall this row ends up
     being. That matters because #cf-back-btn floats *up* out of #content
     (see .cf-back-btn) - without a guaranteed gap here, it could end up
     overlapping whatever this row wrapped to, however the search box
     was previously) - which is exactly what the search box collided with
     before this row was resized. */
  flex-wrap: wrap;
  gap: 16px;
  row-gap: 10px;
  padding: 14px 24px;
}
.section-tab-group {
  display: flex;
  gap: 4px;
}
.section-tab {
  background: none;
  border: none;
  color: var(--subtext);
  font-size: 14px;
  font-weight: 600;
  padding: 6px 14px;
  border-radius: 16px;
  cursor: pointer;
}
.section-tab:hover { background: var(--surface); color: var(--text); }
.section-tab.active { background: var(--accent-dim); color: var(--text); }

/* Music/Stories are the primary catalog; Podcasts/Shiurim are secondary -
   emphasized via opacity alone (not size, so all four tabs stay the same
   scale) so the hierarchy reads even before either is clicked into. All four
   share the same bold weight - the contrast is opacity-only. */
#tab-music, #tab-tales, #tab-podcasts, #tab-shiurim {
  font-weight: 800;
}
#tab-podcasts, #tab-shiurim {
  opacity: 0.6;
}
#tab-podcasts:hover, #tab-shiurim:hover,
#tab-podcasts.active, #tab-shiurim.active {
  opacity: 1;
}

.search-wrap input {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 20px;
  padding: 8px 16px;
  color: var(--text);
  font-size: 14px;
  width: 240px;
  max-width: 100%;
  outline: none;
}
.search-wrap input:focus { border-color: var(--accent); }

.content {
  /* Extra top clearance (vs. the 20px elsewhere) - #cf-back-btn floats up
     out of #content (see .cf-back-btn), and on a narrow screen .section-tabs
     wraps to two lines (tabs + search), which can otherwise leave too little
     real gap between the wrapped search box and that floated button. */
  padding: 40px 24px 48px;
}

.cf-hint {
  text-align: center;
  font-size: 15px;
  font-weight: 500;
  color: #c7c9d4;
  margin-bottom: 4px;
  /* Always exactly one line - the different hint strings between views
     (Artists vs Albums/Stories) must never wrap differently from each
     other, or cfComputeLayout()'s height budget (and so the whole
     coverflow's size) would shift between views. */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.cf-hint-row {
  /* The back button is positioned absolutely (see .cf-back-btn) so it never
     takes up flow height - .cf-hint-row's own height is always exactly
     .cf-hint's, meaning the hint/coverflow/artwork below never shift when
     the button is shown or hidden. */
  position: relative;
}
.cf-back-btn {
  position: absolute;
  bottom: 100%;
  left: 50%;
  transform: translateX(-50%);
  margin-bottom: 6px;
  background: none;
  border: 1px solid var(--border);
  color: var(--subtext);
  font-size: 13px;
  font-weight: 600;
  padding: 6px 14px;
  border-radius: 16px;
  cursor: pointer;
  white-space: nowrap;
}
.cf-back-btn:hover {
  background: var(--surface);
  color: var(--text);
}

.grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
  gap: 16px;
}

.cf-viewport {
  position: relative;
  /* Break out of .content's horizontal padding so the row of covers reaches
     the actual edges of the viewport, rather than stopping short of them. */
  margin: 0 -24px;
  width: calc(100% + 48px);
}

.coverflow {
  position: relative;
  /* height and the custom properties below are set by cfComputeLayout() in
     app.js so exactly CF_VISIBLE_COUNT covers fill the real screen width. */
  height: 400px;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  perspective: var(--cf-perspective, 1200px);
  user-select: none;
  -webkit-user-select: none;
  /* Suppresses two separate mobile Safari/Chrome behaviors that plain
     user-select: none doesn't cover: the gray "callout" bubble (Save Image
     etc.) that can pop up on a touch-and-hold over an <img>, and the brief
     tap-highlight flash - both can trigger on an ordinary drag-to-scroll
     gesture over the artwork, reading as the covers getting "selected". */
  -webkit-touch-callout: none;
  -webkit-tap-highlight-color: transparent;
  touch-action: pan-y;
  overflow: hidden;
  transition: transform 0.45s cubic-bezier(0.22, 0.68, 0, 1), opacity 0.45s ease;
}

.cf-stage {
  position: relative;
  width: 100%;
  height: 100%;
  transform-style: preserve-3d;
}

.cf-stage.dragging .cf-item { transition: none; }

/* Drilling from Artists into an artist's Albums (and back): rather than an
   instant cut, the outgoing view recedes/flies off and the incoming one
   animates in from the same spot - see cfDrillIn()/cfDrillOut() in app.js.
   Applied to #coverflow itself (which is what declares `perspective` for
   its 3D contents), not to .cf-stage inside it - by the time .coverflow's
   own transform is resolved, its 3D children are already fully rendered/
   composited into one flat picture (perspective only shapes how a
   *parent's* 3D children are drawn, not how the element with `transform`
   is itself placed in its own parent's - ordinary 2D - space). So this
   really is just resizing/fading an already-finished picture, like scaling
   a photo: every cover keeps its exact angle and position relative to the
   others, with zero reprocessing, reflattening, or repositioning. */
.coverflow.cf-receded {
  transform: scale(0.55);
  opacity: 0.4;
}
.coverflow.cf-flying-off {
  transform: scale(1.8);
  opacity: 0;
}

.cf-background-stage {
  position: absolute;
  inset: 0;
  pointer-events: none;
  overflow: hidden;
  /* Static (no transition) - this is a frozen backdrop that just sits at
     the same "receded" look the whole time it's shown, matching
     .coverflow.cf-receded above exactly. */
  transform: scale(0.55);
  opacity: 0.4;
}

.cf-item {
  /* No margin-left/top here - the JS transform's translate(-50%, -50%)
     already centers on left/top; adding a margin offset too would double it
     (that was a real bug once already - see updateCfItemTransform). */
  position: absolute;
  left: 50%;
  top: 40%;
  width: var(--cf-item-size, 168px);
  height: var(--cf-item-size, 168px);
  cursor: pointer;
  transition: transform 0.4s cubic-bezier(0.22, 0.68, 0, 1.01);
  /* Hints the browser to keep these on their own GPU-composited layer ahead
     of time, rather than promoting them on demand - without this, a whole
     batch of covers freshly mounted at once (e.g. switching views) can
     render one flat/unrotated frame before the 3D transform is composited,
     which looks like a brief "rectangles, then they angle" pop. */
  will-change: transform;
}

.cf-face {
  width: 100%;
  height: 100%;
  border-radius: 4px;
  overflow: hidden;
  background: var(--surface);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: calc(var(--cf-item-size, 168px) * 0.262);
  font-weight: 700;
  color: #14151a;
  box-shadow: 0 14px 34px rgba(0, 0, 0, 0.55);
}
.cf-face img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  pointer-events: none;
  -webkit-user-drag: none;
}
.cf-item.focused .cf-face {
  box-shadow: 0 22px 50px rgba(59, 110, 246, 0.35), 0 14px 34px rgba(0, 0, 0, 0.65);
}

.cf-arrow {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  z-index: 50;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  border: 1px solid var(--border);
  background: rgba(28, 30, 38, 0.85);
  color: var(--text);
  font-size: 20px;
  line-height: 1;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
}
.cf-arrow:hover { background: var(--surface-hover); }
.cf-arrow:disabled { opacity: 0.25; cursor: default; pointer-events: none; }
.cf-arrow-prev { left: 12px; }
.cf-arrow-next { right: 12px; }

.cf-caption {
  position: relative;
  text-align: center;
  /* margin-top is set in JS (cfComputeLayout) - it pulls the caption up to
     sit just below the focused cover's shadow instead of the coverflow
     container's own much-taller bottom edge. */
  padding: 0 56px;
}
.cf-title {
  font-size: 16px;
  font-weight: 700;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.cf-subtitle {
  font-size: 13px;
  line-height: 16px;
  min-height: 16px;
  color: var(--subtext);
  margin-top: 4px;
}

.preview-modal {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.6);
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 40px 20px;
}
.preview-modal-inner {
  width: 100%;
  max-width: 480px;
  height: 100%;
  max-height: 780px;
  background: var(--bg);
  border-radius: 12px;
  overflow: hidden;
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
  display: flex;
  flex-direction: column;
}
.preview-modal-header {
  flex-shrink: 0;
  display: flex;
  justify-content: flex-end;
  padding: 8px;
}
.preview-modal-close {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  border: none;
  background: transparent;
  color: var(--text);
  font-size: 20px;
  line-height: 1;
  cursor: pointer;
}
.preview-modal-close:hover { background: var(--surface-hover); }
.preview-modal-frame {
  flex: 1;
  width: 100%;
  border: none;
}

.hidden { display: none !important; }

.empty-state {
  color: var(--subtext);
  font-size: 14px;
  padding: 40px 0;
  text-align: center;
}

/* Same rounded-square shape/size as the badge chips (see .store-badge-small)
   so it reads as part of the same button group, but with a muted-blue fill
   (var(--accent-dim), same tone used elsewhere on the site) so the primary
   CTA still stands out a bit from the neutral store badges next to it. */
.cta-button {
  display: inline-block;
  flex-shrink: 0;
  background: var(--accent-dim);
  border: 1px solid var(--accent);
  color: #fff;
  font-size: 15px;
  font-weight: 700;
  text-decoration: none;
  white-space: nowrap;
  padding: 11px 22px;
  border-radius: 8px;
  transition: background 0.15s ease;
}
.cta-button:hover {
  background: var(--accent);
}

/* --- Pricing popup: shown once on initial load (see app.js), a promo for
   subscribing across multiple Naki devices. Placeholder device glyphs below
   stand in for real product photography until that's ready - swap each
   .pricing-glyph-* for a real <img> later without touching the layout. --- */
.pricing-scrim {
  position: fixed;
  inset: 0;
  background: rgba(8, 8, 11, 0.72);
  backdrop-filter: blur(3px);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  z-index: 200;
}
.pricing-popup {
  position: relative;
  width: 100%;
  max-width: 640px;
  max-height: 100%;
  overflow-y: auto;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 20px;
  padding: 40px 44px 36px;
  box-shadow: 0 40px 100px rgba(0, 0, 0, 0.55);
  text-align: center;
  animation: pricing-rise 0.38s cubic-bezier(0.22, 0.68, 0, 1);
}
@keyframes pricing-rise {
  from { opacity: 0; transform: translateY(14px) scale(0.98); }
  to { opacity: 1; transform: translateY(0) scale(1); }
}
.pricing-popup-close {
  position: absolute;
  top: 14px;
  right: 14px;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  border: none;
  background: transparent;
  color: var(--subtext);
  font-size: 20px;
  line-height: 1;
  cursor: pointer;
}
.pricing-popup-close:hover { background: var(--surface-hover); color: var(--text); }

.pricing-eyebrow {
  text-transform: uppercase;
  letter-spacing: 0.12em;
  font-size: 11.5px;
  font-weight: 700;
  color: var(--accent);
  margin: 0 0 10px;
}
.pricing-heading {
  font-size: 24px;
  font-weight: 800;
  margin: 0 0 26px;
  line-height: 1.3;
  text-wrap: balance;
}

.pricing-line {
  font-size: 19px;
  font-weight: 700;
  margin: 0;
}
.pricing-amount { color: var(--text); }
.pricing-detail { color: var(--subtext); font-weight: 600; }
.pricing-line-top { margin-bottom: 22px; }
.pricing-line-bottom { margin-top: 22px; }

.pricing-device-row {
  display: flex;
  /* Columns top-align now - each one handles its own bottom-alignment
     internally via .pricing-device-photo-slot below, so a label that wraps
     to a second line just grows the column downward instead of (with the
     old flex-end-on-the-whole-column approach) shifting its photo, and every
     other column's photo, upward to stay bottom-anchored to the taller
     neighbor. */
  align-items: flex-start;
  justify-content: center;
  /* Wider than the gap within a group (see .pricing-device-group) - reads as
     two clusters, hardware vs. software, rather than one undifferentiated
     row of five. */
  gap: 36px;
  padding: 4px 0;
  /* The name labels are position:absolute now (see .pricing-device-name), so
     they no longer push this row's own flow height down like a normal
     child would - without reserving that space back here explicitly, the
     next line ($12.99 on 5 devices) would sit right on top of them. */
  margin-bottom: 43px;
}
.pricing-device-group {
  display: flex;
  align-items: flex-start;
  gap: 22px;
}
/* Position: relative, with the name label pulled out via absolute
   positioning below (see .pricing-device-name) - so this column's own box
   width is driven purely by its photo, never by the label. A label that
   happens to render wider than its photo (e.g. "Naki App"/"Bolt" are
   narrower than the word "Bolt"/"Naki App" itself at this font size) would
   otherwise widen the whole column and visibly push its neighbors apart,
   which is exactly the unevenly-wide gap this fixes. */
.pricing-device {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
}
.pricing-device-name {
  position: absolute;
  top: 100%;
  left: 50%;
  transform: translateX(-50%);
  margin-top: 10px;
  width: max-content;
  max-width: 100px;
  text-align: center;
  font-size: 12px;
  font-weight: 600;
  color: var(--subtext);
  line-height: 1.25;
  /* Shifts to the accent color on hover (see the sibling selector below),
     like a hyperlink would - shadow/glow effects on the photo itself (tried
     twice: box-shadow, then drop-shadow) kept looking unstable one way or
     another, and it turned out what was actually needed was simpler: just
     signal that these are clickable, not recreate the coverflow's glow. */
  transition: color 0.15s ease;
}
.pricing-device-photo-slot:hover ~ .pricing-device-name,
.pricing-device-photo-slot:focus-visible ~ .pricing-device-name {
  color: var(--accent);
}

/* Fixed-height slot (matches Pulse/Naki Desktop, the tallest photos) that
   each photo bottom-aligns within - this is what keeps every photo on one
   shared "shelf" line (real product photos, sized off each device's true
   height in mm - Pulse 193mm, Pulsemax 176mm, Bolt 167mm, relative to Pulse
   as the tallest) while every label still starts at the same fixed y
   position below it, regardless of which photo is actually shorter. */
.pricing-device-photo-slot {
  height: 80px;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  /* Reset - this is an <a>/<button> now (each device photo links out or, for
     Naki App, opens the store-choice popup), not a plain div. */
  border: none;
  background: none;
  padding: 0;
  font: inherit;
  cursor: pointer;
}
.pricing-device-photo {
  display: block;
  width: auto;
  border-radius: 6px;
  /* Lifts slightly on hover (see the rule below) - transform-origin is
     pinned to the bottom so it grows upward off its "shelf" line instead of
     also dipping below it, which would look like it's sinking rather than
     lifting. */
  transform-origin: center bottom;
  transition: transform 0.15s ease;
}
.pricing-device-photo-slot:hover .pricing-device-photo,
.pricing-device-photo-slot:focus-visible .pricing-device-photo {
  transform: scale(1.06);
}
.pricing-photo-pulse { height: 80px; }
.pricing-photo-pulsemax { height: 73px; } /* 80 * 176/193 */
.pricing-photo-bolt { height: 69px; } /* 80 * 167/193 */
.pricing-photo-app { height: 61px; } /* 80 * 146.7/193 - iPhone 12 height */
/* Same height as Pulse (not scaled by real size like the others) - per an
   explicit request that the laptop's top edge line up with Pulse's top
   edge, rather than being scaled by its own real-world size. */
.pricing-photo-desktop { height: 80px; }

.pricing-cta {
  display: inline-block;
  margin-top: 34px;
  background: var(--accent);
  color: #fff;
  font-size: 15px;
  font-weight: 700;
  text-decoration: none;
  border: none;
  padding: 13px 34px;
  border-radius: 22px;
  cursor: pointer;
  transition: box-shadow 0.15s ease;
}
.pricing-cta:hover { box-shadow: 0 10px 26px rgba(59, 110, 246, 0.45); }
/* White, like the heading, rather than the muted fineprint gray below it -
   this is an incentive worth noticing, not a disclaimer. */
.pricing-promo {
  margin: 22px 0 0;
  font-size: 13px;
  font-weight: 700;
  color: var(--text);
}
.pricing-fineprint {
  margin: 12px 0 0;
  font-size: 12px;
  color: var(--subtext);
}

/* --- App store chooser: opens on top of the pricing popup (same .pricing-scrim
   backdrop, later in the DOM so it paints above it at the same z-index) when
   Naki App is clicked, since a store link depends on the visitor's phone. --- */
.appstore-popup {
  max-width: 340px;
  padding: 36px 32px 32px;
}
.appstore-heading {
  font-size: 19px;
  font-weight: 800;
  margin: 0 0 22px;
}
.appstore-links {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
}
/* Official store badges - already carry their own look (rounded rect,
   border, wordmark), so the link is just a transparent, unstyled wrapper. */
.appstore-link {
  display: block;
  border-radius: 8px;
  transition: box-shadow 0.15s ease;
}
.appstore-link:hover { box-shadow: 0 10px 26px rgba(59, 110, 246, 0.45); }
.appstore-badge {
  display: block;
  height: 44px;
  width: auto;
  border-radius: 8px;
}

/* --- Podcasts/Shiurim notice: shown on every click into either tab, since
   the default-inclusion behavior differs by device vs. app and is easy to
   assume wrong either way. Same .pricing-scrim/.pricing-popup pattern as the
   other popups above. --- */
.podcast-notice-popup {
  max-width: 460px;
  padding: 40px 36px 32px;
}
.podcast-notice-text {
  margin: 0;
  text-align: left;
  font-size: 14px;
  line-height: 1.55;
  color: var(--subtext);
}
.podcast-notice-text + .podcast-notice-text {
  margin-top: 12px;
}

/* --- Testimonial ticker (experiment): a continuously-scrolling strip of
   customer quotes pinned to the bottom of the viewport, like a news ticker.
   Fixed position/height (rather than the bubbles' dynamic safe-zone
   approach) since a thin strip glued to the bottom edge doesn't need to
   avoid the coverflow the way a floating element would. pointer-events:none
   throughout so it can never intercept a click or drag meant for the
   coverflow - only a visual layer, never an interactive one. */
.testimonial-ticker {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  height: 52px;
  background: var(--surface);
  border-top: 1px solid var(--border);
  overflow: hidden;
  /* Above .preview-modal (100) and .pricing-scrim (200) so their full-screen
     dimming overlay doesn't darken the ticker too - pointer-events: none
     means it still never blocks clicks on whatever popup is open. */
  z-index: 250;
  pointer-events: none;
  display: flex;
  align-items: center;
  box-shadow: 0 -4px 16px rgba(0, 0, 0, 0.3);
}
.testimonial-ticker-track {
  display: inline-flex;
  white-space: nowrap;
  /* Duration fallback only - app.js measures the actual track width and sets
     a precise animationDuration so scroll speed (px/sec) stays constant no
     matter how many testimonials are in data/testimonials.json. This 180s
     is just a safe slow default in case that JS never runs. */
  animation: testimonial-ticker-scroll 180s linear infinite;
  will-change: transform;
}
.testimonial-ticker-item {
  padding: 0 32px;
  /* var(--subtext), not var(--accent) - the blue only cleared 3.76:1 contrast
     against the ticker's dark background (below the 4.5:1 minimum for
     normal-size text); this gray clears 5.73:1 and matches how the rest of
     the site already treats secondary/quiet text. */
  color: var(--subtext);
  font-size: 15px;
  font-weight: 600;
}
@keyframes testimonial-ticker-scroll {
  from { transform: translateX(0); }
  /* The track is built from the quote list duplicated back-to-back (see
     app.js) purely so -50% lines up exactly with the start of the second
     copy - the loop point where this snaps back to 0% is invisible. */
  to { transform: translateX(-50%); }
}
@media (prefers-reduced-motion: reduce) {
  .testimonial-ticker-track { animation: none; }
}

@media (max-width: 680px) {
  /* Four tabs (Music/Stories/Podcasts/Shiurim) plus the search box no longer
     fit on one line at this width - .section-tabs already wraps them (see
     that rule's flex-wrap), but left-aligned that reads as everything
     crowding toward the left edge. Centering both the tab row and the
     wrapped search box reads as a deliberate stacked layout instead. */
  .section-tabs {
    flex-direction: column;
    align-items: center;
    justify-content: center;
    row-gap: 20px;
    padding-top: 22px;
    padding-bottom: 22px;
  }
  .topbar {
    padding-top: 20px;
    padding-bottom: 20px;
  }
}

/* Portrait-phone hint: the coverflow itself now adapts to narrow portrait
   widths too (see cfBaseTotalWidth() in app.js), so this is just a nudge
   toward an even bigger view, not something the layout depends on - hidden
   everywhere else, and dismissible per-visit via app.js. */
.rotate-hint {
  position: relative;
  display: none;
  align-items: center;
  justify-content: center;
  margin: 0 24px 16px;
  padding: 10px 16px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 12px;
  color: var(--subtext);
  font-size: 13px;
  font-weight: 600;
  text-align: center;
}
.rotate-hint-close {
  position: absolute;
  top: -8px;
  right: -8px;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  /* A persistent (not hover-only) circular chip - this banner's primary
     audience is touch/portrait phones, which have no hover state to reveal
     "this is a button" the way the site's other close buttons rely on.
     Corner-badge position (rather than inline with the text) reads more like
     a dismiss control on the card itself, less like part of the sentence. */
  background: var(--surface-hover);
  border: 1px solid var(--border);
  border-radius: 50%;
  color: var(--text);
  font-size: 14px;
  line-height: 1;
  cursor: pointer;
  padding: 0;
}
.rotate-hint-close:hover { background: var(--border); }

@media (max-width: 680px) and (orientation: portrait) {
  .rotate-hint {
    display: flex;
  }
}

@media (max-width: 560px) {
  .pricing-popup { padding: 32px 20px 28px; }
  /* row-gap wider than column-gap: when this wraps to two lines (hardware
     group, then Naki App/Desktop group below it), each device's name label
     is position:absolute below its photo (see .pricing-device-name) and
     doesn't count toward the row's own wrap height - a plain 20px gap left
     just barely enough clearance for a one-line label and none at all for a
     name that wraps to two lines, so the first line's labels could overlap
     the second line's photos. 40px covers even a two-line label with room
     to spare. */
  .pricing-device-row { gap: 40px 20px; flex-wrap: wrap; }
  .pricing-device-group { gap: 12px; }
  .pricing-device-photo-slot { height: 64px; }
  .pricing-photo-pulse { height: 64px; }
  .pricing-photo-pulsemax { height: 58px; }
  .pricing-photo-bolt { height: 55px; }
  .pricing-photo-app { height: 49px; }
  .pricing-photo-desktop { height: 64px; }
  .pricing-heading { font-size: 20px; }
}
