:root {
  color-scheme: dark;
  --bg-color: #121212;
  --surface-color: #242424;
  --text-color: #ffffff;
  --accent-green: #4ade80;
  --accent-pink: #f472b6;
  --border-light: #aaaaaa;
  --focus-color: #fdfd96;
  --on-accent: #000000;
  --error-color: #f87171;
}

:root[data-theme="light"] {
  color-scheme: light;
  --bg-color: #f5f5f5;
  --surface-color: #dddddd;
  --text-color: #000000;
  --border-light: #444444;
  --focus-color: #9f9f03;
  --accent-green: #097732;
  --accent-pink: #800d48;
  --on-accent: #ffffff;
  --error-color: #b91c1c;
}

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  background-color: var(--bg-color);
  color: var(--text-color);
  font-family: system-ui, -apple-system, sans-serif;
  line-height: 1.6;
  /* Inherited site-wide so that at very large OS/browser text-size
     settings, a single long word (a category heading like "Productivity",
     a run-together product name, a breadcrumb's current-page label, etc.)
     wraps - or if needed, breaks mid-word - instead of overflowing past
     its container's border. `anywhere` rather than the more common
     `break-word` deliberately: `break-word` only kicks in once layout has
     already happened, so a flex/grid item sized from that word's full
     min-content width (a title in a narrow flex column, a breadcrumb
     crumb wrapped alone onto its own line) still overflows its box even
     though the text visually wraps inside it. `anywhere` additionally
     counts toward min-content sizing, so the box itself is allowed to
     shrink small enough that the wrap has somewhere to go. */
  overflow-wrap: anywhere;
}

img {
  max-width: 100%;
  height: auto;
}

/* Several elements this hides (like .app-card) set their own display
   value directly, which otherwise beats the browser's default
   [hidden] rule in the cascade — without !important here, setting
   .hidden = true in JS silently does nothing visually. */
[hidden] {
  display: none !important;
}

a {
  color: var(--focus-color);
  text-decoration: underline;
}
a:hover,
a:focus-visible {
  color: var(--focus-color);
  text-decoration: underline;
}

/* Visible only when focused, so keyboard users can jump past the header */
.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  background: var(--focus-color);
  color: #000000;
  padding: 12px 20px;
  font-weight: bold;
  z-index: 1000;
  border-radius: 0 0 8px 0;
  text-decoration: none;
}
.skip-link:focus {
  left: 0;
}
/* a:focus-visible below sets color: var(--focus-color) on every link,
   which would otherwise win over this rule's #000000 by specificity and
   render the skip-link's text the same yellow as its own background -
   invisible right when Tab first makes it visible. */
.skip-link:focus-visible {
  color: #000000;
}

*:focus-visible {
  outline: 3px solid var(--focus-color);
  outline-offset: 2px;
}

.site-header {
  position: relative;
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: 12px;
  padding: 20px 24px;
  border-bottom: 2px solid var(--divider-color);
}

.site-title {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  font-size: 28px;
  font-weight: 800;
  margin: 0;
  text-decoration: none;
  color: var(--text-color);
}
.site-logo {
  display: block;
  width: 36px;
  height: 36px;
  border-radius: 8px;
  flex-shrink: 0;
}
.site-title:hover,
.site-title:focus-visible {
  color: var(--text-color);
  text-decoration: underline;
}
.site-title .accent-pink { color: var(--accent-pink); }
.site-title .accent-green { color: var(--accent-green); }

.header-controls {
  display: flex;
  align-items: center;
  gap: 20px;
  flex-wrap: wrap;
}

.site-nav ul {
  list-style: none;
  display: flex;
  gap: 24px;
  margin: 0;
  padding: 0;
}

.site-nav a {
  color: var(--text-color);
  text-decoration: none;
  font-weight: bold;
  font-size: 19px;
  padding: 8px 4px;
  border-bottom: 2px solid transparent;
}
.site-nav a:hover,
.site-nav a:focus-visible {
  border-bottom-color: var(--accent-pink);
}
.site-nav a[aria-current="page"] {
  border-bottom-color: var(--accent-green);
}
.site-nav a[aria-current="page"]:hover,
.site-nav a[aria-current="page"]:focus-visible {
  border-bottom-color: var(--accent-green);
}

/* Home/Apps/Games hub pages only (same bare-.hero marker used elsewhere
   in this file to scope to just these 3 pages) - nav underline doubled
   from 2px to 4px for both the current-page and hover states. Widening
   the transparent idle-state border by the same amount (not just the
   colored states) keeps the link's box the same size in every state, so
   nothing shifts when a link gains/loses the underline. */
body:has(main > .hero:not(.product-hero-card)) .site-nav a {
  border-bottom-width: 4px;
}

/* gap + padding (not overflow: hidden) - overflow: hidden would clip a
   focus ring on the last button, since it needs to render outside the
   button's own box but was being clipped by the group's rounded corner. */
.theme-toggle-group {
  display: flex;
  gap: 4px;
  border: 2px solid var(--border-light);
  border-radius: 8px;
  padding: 3px;
}
.theme-toggle-btn {
  background-color: transparent;
  color: var(--text-color);
  border: none;
  border-radius: 6px;
  padding: 6px 14px;
  font-size: 14px;
  font-weight: bold;
  cursor: pointer;
  text-decoration: none;
}
.theme-toggle-btn:hover,
.theme-toggle-btn:focus-visible {
  background-color: var(--border-light);
  color: var(--bg-color);
}
.theme-toggle-btn[aria-checked="true"] {
  background-color: var(--accent-pink);
  color: var(--bg-color);
}

/* shared/coa-auth-modal.css writes .coa-auth-account-section for a narrow
   vertical settings-panel context (block-stacked Log In/Sign Up buttons,
   a bottom border, bottom margin). Inline it into this horizontal header
   row instead, without touching the shared file itself. */
.site-header .coa-auth-account-section {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 0;
  padding-bottom: 0;
  border-bottom: none;
}
.site-header .coa-auth-login-btn,
.site-header .coa-auth-signup-btn {
  display: inline-block;
  width: auto;
  margin-bottom: 0;
  padding: 8px 14px;
  font-size: 14px;
}
.site-header .coa-auth-account-status:not([hidden]) {
  gap: 8px;
}
.site-header .coa-auth-logout-btn {
  padding: 6px 14px;
  font-size: 14px;
}

/* Hamburger toggle for .site-nav, injected by script.js. Hidden by
   default; only shown (via the media query below) once JS has actually
   added the button and the .has-nav-toggle class, so a page with no JS
   just keeps showing the full nav inline instead of losing it. */
.nav-toggle {
  display: none;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  padding: 0;
  background-color: transparent;
  border: 2px solid var(--accent-pink);
  border-radius: 8px;
  cursor: pointer;
}
.nav-toggle:hover,
.nav-toggle:focus-visible {
  background-color: color-mix(in srgb, var(--accent-pink) 15%, transparent);
}
.nav-toggle-icon,
.nav-toggle-icon::before,
.nav-toggle-icon::after {
  display: block;
  width: 22px;
  height: 3px;
  border-radius: 2px;
  background-color: var(--text-color);
  transition: transform 0.15s ease, opacity 0.15s ease, top 0.15s ease;
}
.nav-toggle-icon {
  position: relative;
}
.nav-toggle-icon::before,
.nav-toggle-icon::after {
  content: "";
  position: absolute;
  left: 0;
}
.nav-toggle-icon::before { top: -7px; }
.nav-toggle-icon::after { top: 7px; }
.nav-toggle[aria-expanded="true"] .nav-toggle-icon {
  background-color: transparent;
}
.nav-toggle[aria-expanded="true"] .nav-toggle-icon::before {
  top: 0;
  transform: rotate(45deg);
}
.nav-toggle[aria-expanded="true"] .nav-toggle-icon::after {
  top: 0;
  transform: rotate(-45deg);
}

@media (max-width: 960px) {
  .site-header.has-nav-toggle .nav-toggle {
    display: inline-flex;
  }
  /* The originals are replaced by the nav-modal clones below on narrow
     viewports - this selector only reaches the ones still inline in the
     header, never the clones, since those live outside .site-header. */
  .site-header.has-nav-toggle .site-nav,
  .site-header.has-nav-toggle .theme-toggle-group,
  .site-header.has-nav-toggle #auth-account-section {
    display: none;
  }
}

/* Mobile nav modal - a native <dialog> holding clones of the theme toggle
   and nav links, injected by script.js and styled to match the
   Access-a-Lyrics Settings modal (sticky header, circular close button). */
.nav-modal {
  padding: 0;
  border: 2px solid var(--divider-color);
  border-radius: 12px;
  background-color: var(--bg-color);
  color: var(--text-color);
  width: min(320px, calc(100% - 48px));
  max-height: 85vh;
  /* Flex column (header + separately-scrolling body) rather than making
     the whole dialog scroll with a sticky header inside it - a real,
     confirmed Chromium rendering bug lets scrolled content paint above a
     `position: sticky` header when the header's own scroll ancestor is
     itself `position: fixed` (as every one of these dialogs is), even
     though hit-testing and layout are both otherwise correct. Splitting
     the non-scrolling header out of the scrolling box entirely sidesteps
     the bug rather than working around it. */
  overflow: hidden;
}
.nav-modal[open] {
  display: flex;
  flex-direction: column;
}
.nav-modal::backdrop {
  background: rgba(0, 0, 0, 0.6);
}
.nav-modal-header {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 18px 20px;
  border-bottom: 2px solid var(--divider-color);
  background-color: var(--bg-color);
}
.nav-modal-header h2 {
  margin: 0;
  font-size: 19px;
}
.nav-modal-close-btn {
  flex-shrink: 0;
  width: 2.3em;
  height: 2.3em;
  background: transparent;
  border: 2px solid var(--border-light);
  border-radius: 50%;
  color: var(--text-color);
  font-size: 1.25rem;
  line-height: 1;
  cursor: pointer;
}
.nav-modal-close-btn:hover,
.nav-modal-close-btn:focus-visible {
  background-color: var(--border-light);
  color: var(--bg-color);
}
.nav-modal-body {
  display: flex;
  flex-direction: column;
  gap: 22px;
  padding: 20px;
  overflow-y: auto;
  flex: 1 1 auto;
  min-height: 0;
}
.nav-modal-body .theme-toggle-group {
  align-self: flex-start;
}
.nav-modal-body .site-nav ul {
  flex-direction: column;
  gap: 2px;
}
.nav-modal-body .site-nav a {
  display: block;
  padding: 12px 14px;
  font-size: 19px;
}

main {
  max-width: 900px;
  margin: 0 auto;
  padding: 48px 24px 80px;
}

.hero {
  text-align: center;
  margin-bottom: 56px;
}
.hero h1 {
  font-size: 36px;
  margin-bottom: 12px;
}
.hero p {
  font-size: 18px;
  color: var(--border-light);
  max-width: 640px;
  margin: 0 auto;
}

/* Top-level category headings, e.g. "Apps" / "Games" on the homepage,
   "Educational" / "Fun" on the Games page - not the product/game titles
   inside individual .app-card entries. */
main > h2 {
  color: var(--accent-pink);
  font-size: 26px;
  margin: 48px 0 20px;
}
main > h2:first-child {
  margin-top: 0;
}
main > h2 a {
  color: inherit;
}

/* Home/Apps/Games hub pages only (identified by a bare .hero - product
   pages use .hero.product-hero-card) get noticeably tighter space above
   the H1 and more breathing room above H2s than product/policy pages. */
main:has(> .hero:not(.product-hero-card)) {
  padding-top: 20px;
}
main:has(> .hero:not(.product-hero-card)) > h2,
main:has(> .hero:not(.product-hero-card)) .catalog-category > h2 {
  margin-top: 56px;
}

/* Catalog filter bar (search + category buttons) used on the Apps and
   Games hub pages. Search row is a 2-column grid (label | input) so the
   result count below can sit in column 2 and land exactly under the
   input, not the shorter label. Filter row is plain flex so the label
   and buttons share a line the same way the search row does. */
.catalog-filters {
  display: flex;
  flex-direction: column;
  gap: 12px;
  margin-bottom: 8px;
}

.catalog-label {
  font-weight: bold;
  color: var(--text-color);
  font-size: 15px;
}

.catalog-search-row {
  display: grid;
  grid-template-columns: auto 1fr;
  align-items: center;
  column-gap: 12px;
  row-gap: 2px;
}
/* Zeroes the row-gap too while the status line is empty, so it takes up
   no visible space at all (matches height:0 below) instead of leaving a
   stray 2px sliver under the search box. */
.catalog-search-row:has(.catalog-filter-status:empty) {
  row-gap: 0;
}
.catalog-search-row .catalog-label {
  grid-column: 1;
  grid-row: 1;
}

.catalog-search-input {
  grid-column: 2;
  grid-row: 1;
  width: 100%;
  background-color: var(--surface-color);
  color: var(--text-color);
  border: 2px solid var(--border-light);
  border-radius: 8px;
  padding: 10px 14px;
  font-size: 15px;
}
.catalog-search-input:focus-visible {
  outline: 3px solid var(--focus-color);
  outline-offset: 2px;
}

.catalog-filter-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px;
}

.catalog-filter-categories {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

/* Live result count, shown only while a search/filter is actually
   narrowing the list - empty (and collapsed via :empty) once you're back
   to "All" with no search text, since the count isn't interesting when
   showing everything. Same element serves the visible line and the
   aria-live announcement, so sighted and screen reader users get
   identical wording instead of two separately-maintained copies. Grid
   column 2 puts it directly under the search input above it. */
.catalog-filter-status {
  grid-column: 2;
  grid-row: 2;
  font-weight: bold;
  color: var(--focus-color);
  margin: 0;
}
/* height:0/overflow (not display:none) so the element - and its
   aria-live="polite" region - stays in the accessibility tree even while
   empty. Screen readers only announce updates to a live region they've
   already registered as present; a display:none swap happening in the
   same update as the first search keystroke reads as a brand-new element
   appearing, not a live-region change, so it goes unannounced (the
   second keystroke then works, because by then the region was already
   visible/tracked). Zero size keeps the same "takes up no space while
   idle" look without that gap. */
.catalog-filter-status:empty {
  height: 0;
  overflow: hidden;
}

/* Stands in for the category H2s while a search is narrowing the list
   (see script.js) - one heading instead of several, styled to match the
   category headings it temporarily replaces. */
.catalog-search-results-heading {
  color: var(--accent-pink);
  font-size: 26px;
  margin: 56px 0 20px;
}

.catalog-filter-btn {
  background-color: transparent;
  color: var(--text-color);
  border: 2px solid var(--border-light);
  border-radius: 20px;
  padding: 8px 16px;
  font-weight: bold;
  font-size: 14px;
  cursor: pointer;
}
.catalog-filter-btn:hover,
.catalog-filter-btn:focus-visible {
  border-color: var(--accent-pink);
}
.catalog-filter-btn[aria-pressed="true"] {
  background-color: var(--accent-pink);
  color: var(--on-accent);
  border-color: var(--accent-pink);
}

.catalog-category {
  margin-bottom: 8px;
}

.app-card {
  background-color: var(--surface-color);
  border: 2px solid var(--divider-color);
  border-radius: 12px;
  padding: 32px;
  margin-bottom: 24px;
  display: flex;
  flex-wrap: wrap;
  gap: 32px;
  align-items: center;
}

.app-card-media-link {
  display: block;
  max-width: 320px;
  width: 100%;
  border-radius: 10px;
}

.app-card img {
  display: block;
  max-width: 320px;
  width: 100%;
  border-radius: 10px;
  border: 2px solid var(--divider-color);
}

/* Shown in place of a screenshot until a real one is added - swap the
   .placeholder-media block for an <img class="app-card img"> when ready. */
.placeholder-media {
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  max-width: 320px;
  width: 100%;
  aspect-ratio: 4 / 3;
  border: 2px dashed var(--divider-color);
  border-radius: 10px;
  color: var(--border-light);
  font-weight: bold;
  font-size: 15px;
  padding: 16px;
}

/* Sized to match .product-hero-icon (a small square icon) instead of the
   larger 4:3 screenshot placeholder above. */
.placeholder-media.product-hero-icon-placeholder {
  width: 96px;
  height: 96px;
  max-width: none;
  aspect-ratio: auto;
  flex-shrink: 0;
  border-radius: 16px;
  font-size: 11px;
  padding: 4px;
}

.app-card-content {
  flex: 1;
  min-width: 260px;
}

/* favorites.html: placeholder shown in an unfavorited card's own slot
   instead of just removing it, so the grid doesn't reflow/jump on every
   click - the Undo link is the only way back, so it needs to stand out. */
.app-card-removed {
  border-color: var(--focus-color);
}
.undo-favorite-btn {
  background: none;
  border: none;
  padding: 0;
  margin: 0;
  font: inherit;
  color: var(--focus-color);
  font-weight: bold;
  text-decoration: underline;
  cursor: pointer;
}
.undo-favorite-btn:hover, .undo-favorite-btn:focus-visible {
  text-decoration: none;
}

.card-tag {
  display: inline-block;
  background-color: var(--bg-color);
  border: 2px solid var(--focus-color);
  color: var(--focus-color);
  font-size: 13px;
  font-weight: bold;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  padding: 4px 10px;
  border-radius: 999px;
  margin-top: 10px;
  margin-right: 10px;
  margin-bottom: 10px;
}

/* "New" badge - white on dark red, sits after a card/hero title. Deliberately
   not a .card-tag (that answers "what kind of thing"); this answers "just
   added", and comes off again once a product has settled in. */
.card-new-badge {
  display: inline-block;
  vertical-align: middle;
  background-color: #8b1a1a;
  color: #ffffff;
  font-size: 12px;
  font-weight: bold;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  line-height: 1;
  padding: 5px 9px;
  border-radius: 999px;
  margin-left: 8px;
}

/* "Recommended for Low Vision" flag - deliberately NOT shaped like
   .card-tag above. A genre tag answers "what kind of thing is this";
   this answers "which of our usual three promises (screen reader +
   keyboard + low vision) doesn't fully apply here" - so it gets its own
   shape (a bordered note, not a pill) and opens a modal explaining why.
   The modal itself is injected once per page by script.js, not hardcoded
   here, so the explanation text only ever needs editing in one place. */
.lowvision-flag {
  display: flex;
  width: fit-content;
  align-items: center;
  gap: 8px;
  background-color: color-mix(in srgb, var(--accent-green) 14%, var(--bg-color));
  border-left: 3px solid var(--accent-green);
  border-radius: 6px;
  padding: 7px 14px 7px 11px;
  margin: 0;
  line-height: 1;
}
/* The gap above/below the flag is controlled entirely by its neighbors
   (the title's margin-bottom, the next paragraph's margin-top) rather than
   the flag's own margin - .lowvision-flag was an inline-flex box, so its
   own vertical margins didn't collapse predictably with the block-level
   title/paragraph around it. Scoped to "only when a .lowvision-flag
   follows/precedes" so every other card/hero without the flag keeps its
   normal spacing untouched. */
.product-page .hero h1:has(+ .lowvision-flag),
.app-card-content h3:has(+ .lowvision-flag) {
  margin-bottom: 10px;
}
.product-page .product-hero-content .lowvision-flag + p,
.app-card-content .lowvision-flag + p {
  margin-top: 10px;
}
.lowvision-flag-link {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  background: none;
  border: none;
  padding: 0;
  font-size: 14.5px;
  font-weight: bold;
  font-family: inherit;
  line-height: 1;
  color: var(--text-color);
  cursor: pointer;
  text-decoration: underline;
  text-decoration-color: var(--accent-green);
  text-underline-offset: 2px;
}
.lowvision-flag-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 17px;
  height: 17px;
  flex-shrink: 0;
  border-radius: 50%;
  border: 2px solid var(--accent-green);
  color: var(--accent-green);
  font-size: 11px;
  font-weight: 800;
  font-style: normal;
}

.lowvision-modal {
  max-width: 480px;
  width: calc(100% - 48px);
  background-color: var(--surface-color);
  color: var(--text-color);
  border: 2px solid var(--divider-color);
  border-radius: 12px;
  padding: 0;
}
.lowvision-modal::backdrop {
  background: rgba(0, 0, 0, 0.55);
}
.lowvision-modal-body {
  padding: 26px 28px;
}
.lowvision-modal-body h2 {
  color: var(--accent-pink);
  font-size: 19px;
  margin: 0 0 12px;
}
.lowvision-modal-body p {
  font-size: 15.5px;
  margin: 0 0 20px;
}
.lowvision-modal-body .btn {
  margin: 0;
}

/* "Desktop Recommended" flag - same shape/behavior as .lowvision-flag
   above (a bordered note that opens an explanatory modal, injected once
   per page by script.js), just pink instead of green so the two flags
   read as visually distinct at a glance. Kept as its own separate set of
   classes rather than reusing .lowvision-flag with a color override, so
   a product can carry both flags at once without one's CSS fighting the
   other's. */
.desktop-flag {
  display: flex;
  width: fit-content;
  align-items: center;
  gap: 8px;
  background-color: color-mix(in srgb, var(--accent-pink) 14%, var(--bg-color));
  border-left: 3px solid var(--accent-pink);
  border-radius: 6px;
  padding: 7px 14px 7px 11px;
  margin: 0;
  line-height: 1;
}
.product-page .hero h1:has(+ .desktop-flag),
.app-card-content h3:has(+ .desktop-flag) {
  margin-bottom: 10px;
}
.product-page .product-hero-content .desktop-flag + p,
.app-card-content .desktop-flag + p {
  margin-top: 10px;
}
/* A product carrying both flags stacked - gap between them instead of
   the taller margin meant for "flag directly followed by the
   description paragraph". */
.lowvision-flag + .desktop-flag,
.desktop-flag + .lowvision-flag {
  margin-top: 8px;
}
.desktop-flag-link {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  background: none;
  border: none;
  padding: 0;
  font-size: 14.5px;
  font-weight: bold;
  font-family: inherit;
  line-height: 1;
  color: var(--text-color);
  cursor: pointer;
  text-decoration: underline;
  text-decoration-color: var(--accent-pink);
  text-underline-offset: 2px;
}
.desktop-flag-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 17px;
  height: 17px;
  flex-shrink: 0;
  border-radius: 50%;
  border: 2px solid var(--accent-pink);
  color: var(--accent-pink);
  font-size: 11px;
  font-weight: 800;
  font-style: normal;
}

.desktop-modal {
  max-width: 480px;
  width: calc(100% - 48px);
  background-color: var(--surface-color);
  color: var(--text-color);
  border: 2px solid var(--divider-color);
  border-radius: 12px;
  padding: 0;
}
.desktop-modal::backdrop {
  background: rgba(0, 0, 0, 0.55);
}
.desktop-modal-body {
  padding: 26px 28px;
}
.desktop-modal-body h2 {
  color: var(--accent-pink);
  font-size: 19px;
  margin: 0 0 12px;
}
.desktop-modal-body p {
  font-size: 15.5px;
  margin: 0 0 20px;
}
.desktop-modal-body .btn {
  margin: 0;
}

.app-card h2,
.app-card h3 {
  margin-top: 0;
  font-size: 26px;
}

.app-card-title-link {
  color: var(--accent-pink);
}

.app-card p {
  font-size: 16px;
}

.btn {
  display: inline-block;
  background-color: transparent;
  color: var(--text-color);
  border: 2px solid var(--accent-green);
  padding: 10px 20px;
  border-radius: 8px;
  font-weight: bold;
  text-decoration: none;
  font-size: 15px;
  margin-top: 12px;
  margin-right: 10px;
  transition: all 150ms ease;
  /* Same reference point as .coa-favorite-btn's own vertical-align:middle
     (shared/coa-auth-modal.css) - identical-height .btn siblings look the
     same either way, but this is what actually lines the icon-only
     favorite button up with the text buttons next to it. */
  vertical-align: middle;
}
.btn:hover,
.btn:focus-visible {
  background-color: var(--accent-green);
  color: var(--on-accent);
  text-decoration: none;
}

/* "Learn More" links use this instead of the default green .btn outline. */
.btn.btn-outline {
  border-color: var(--accent-pink);
}
.btn.btn-outline:hover,
.btn.btn-outline:focus-visible {
  background-color: var(--accent-pink);
  color: var(--on-accent);
}

.more-coming {
  margin-top: 40px;
  text-align: center;
  color: var(--border-light);
  font-style: italic;
}

/* Homepage caps each category to a few cards (see .card-group[data-limit]
   in script.js); this links out to the full hub page for the rest. */
.show-more-link {
  text-align: left;
  margin: 0 0 40px;
}
.show-more-link a {
  color: var(--focus-color);
  font-weight: bold;
  text-decoration: none;
}
.show-more-link a:hover,
.show-more-link a:focus-visible {
  text-decoration: underline;
}

.site-footer {
  border-top: 2px solid var(--divider-color);
  padding: 24px;
  text-align: center;
  font-size: 14px;
}
.site-footer p {
  margin: 6px 0;
}

/* --- Breadcrumb trail, sits between the header and <main> on every page
   that used to have a "back" link. Replaces history.back()-based
   navigation entirely - every segment is a fixed, predictable link (or,
   for the current page, plain text), so there's no dependence on actual
   browser history and no way to loop back into a page you left. Sibling
   of <main>, not a child, so it gets its own max-width/centering (does
   not inherit main's own padding/centering). */
.breadcrumb {
  display: block;
  max-width: 900px;
  margin: 24px auto 0;
  padding: 0 24px;
}
.breadcrumb ol {
  display: flex;
  flex-wrap: wrap;
  list-style: none;
  margin: 0;
  padding: 0;
  font-weight: bold;
}
.breadcrumb li:not(:last-child)::after {
  content: "\203A";
  margin: 0 8px;
  color: var(--border-light);
}
.breadcrumb-link {
  color: var(--accent-green);
  text-decoration: none;
}
.breadcrumb-link:hover,
.breadcrumb-link:focus-visible {
  text-decoration: underline;
}
.breadcrumb-current {
  color: var(--text-color);
}

.policy-content h1 {
  color: var(--text-color);
  font-size: 32px;
  margin-top: 0;
  margin-bottom: 4px;
}
.policy-content h2 {
  color: var(--accent-pink);
  font-size: 20px;
  margin-top: 32px;
}
.policy-content p,
.policy-content li {
  font-size: 16px;
}
.policy-content ul {
  padding-left: 24px;
}

/* --- profile.html --- */
.profile-section {
  margin-top: 40px;
  padding-top: 32px;
  border-top: 2px solid var(--divider-color);
}
.profile-section:first-of-type {
  margin-top: 32px;
  padding-top: 0;
  border-top: none;
}
.profile-field {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 12px;
  max-width: 480px;
  margin-top: 16px;
}
.profile-field-grow {
  flex: 1 1 240px;
}
.profile-field label {
  display: block;
  font-weight: bold;
  margin-bottom: 6px;
}
.profile-field input[type="text"],
.profile-field input[type="email"] {
  width: 100%;
  background-color: var(--bg-color);
  color: var(--text-color);
  border: 2px solid var(--text-color);
  border-radius: 8px;
  padding: 10px 12px;
  font-size: 1rem;
  font-family: inherit;
}
.profile-field input[readonly] {
  border-color: var(--border-light);
  color: var(--border-light);
}
.profile-status {
  margin-top: 10px;
  font-weight: bold;
  min-height: 1.4em;
}
.profile-status.is-success {
  color: var(--accent-green);
}
.profile-status.is-error {
  color: var(--error-color);
}

/* Deliberately the loudest section on the page - red border, distinct
   from every other .profile-section - so "this one is different, be
   careful" is obvious even before reading the text. */
.danger-zone {
  margin-top: 40px;
  padding: 24px;
  border: 2px solid var(--error-color);
  border-radius: 12px;
}
.danger-zone h2 {
  color: var(--error-color);
  margin-top: 0;
}
.btn-danger {
  display: inline-block;
  background-color: transparent;
  color: var(--text-color);
  border: 2px solid var(--error-color);
  padding: 10px 20px;
  border-radius: 8px;
  font-weight: bold;
  font-size: 15px;
  font-family: inherit;
  cursor: pointer;
  margin-top: 12px;
}
.btn-danger:hover,
.btn-danger:focus-visible {
  background-color: color-mix(in srgb, var(--error-color) 20%, transparent);
}

/* --- Individual app product pages (template for all apps) --- */
.product-page .hero {
  text-align: left;
  margin-bottom: 24px;
}
.product-page .hero h1 {
  color: var(--text-color);
  font-size: 34px;
  margin-top: 0;
}
.product-page .hero p {
  color: var(--text-color);
  font-size: 18px;
  max-width: none;
  margin: 0 0 16px;
}

.product-hero-card {
  background-color: var(--surface-color);
  border: 2px solid var(--divider-color);
  border-radius: 12px;
  padding: 32px;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 28px;
}

.product-hero-icon {
  display: block;
  width: 96px;
  height: 96px;
  flex-shrink: 0;
  border-radius: 16px;
  border: 2px solid var(--divider-color);
}

/* Placeholder logos (icon-128-dark.png / icon-128-light.png) come as a
   matched pair so the icon's own colors stay legible against the site's
   dark or light theme - only one of the pair is ever shown. */
.product-hero-icon.icon-for-dark { display: block; }
.product-hero-icon.icon-for-light { display: none; }
:root[data-theme="light"] .product-hero-icon.icon-for-dark { display: none; }
:root[data-theme="light"] .product-hero-icon.icon-for-light { display: block; }

.product-hero-content {
  flex: 1;
  min-width: 240px;
}
.product-hero-content > *:first-child {
  margin-top: 0;
}
.product-hero-content > *:last-child {
  margin-bottom: 0;
}

.carousel-card {
  /* --carousel-tint is set per-slide by script.js; falls back to plain surface-color */
  background-color: color-mix(in srgb, var(--carousel-tint, transparent) 5%, var(--surface-color));
  border: 2px solid var(--divider-color);
  border-radius: 12px;
  padding: 32px;
  margin: 32px 0 40px;
}

.carousel-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px;
}

.carousel-media {
  flex: 1 1 320px;
  max-width: 360px;
}

.carousel-image-link {
  display: block;
  border-radius: 10px;
}

.carousel-slide img {
  display: block;
  width: 100%;
  border-radius: 10px;
  border: 2px solid var(--divider-color);
}

/* Enlarged screenshot view - the native <dialog> script.js injects for
   the carousel screenshots to open into (instead of a new tab). Sized to
   fit the picture rather than to a fixed width, so a screenshot that ends
   up height-limited on a short laptop screen doesn't sit in a sea of
   empty dialog; capped at roughly three quarters of the viewport either
   way. Header and circular close button mirror .nav-modal's. */
.screenshot-modal {
  padding: 0;
  /* Yellow (the site's focus colour, so it holds up in both themes) rather
     than the usual divider grey - it outlines the enlarged picture against
     the dimmed page. */
  border: 2px solid var(--focus-color);
  border-radius: 12px;
  background-color: var(--bg-color);
  color: var(--text-color);
  width: fit-content;
  max-width: min(75vw, 1200px);
  max-height: 85vh;
  overflow: hidden;
}
.screenshot-modal[open] {
  display: flex;
  flex-direction: column;
}
.screenshot-modal::backdrop {
  background: rgba(0, 0, 0, 0.7);
}
.screenshot-modal-header {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 14px 18px;
  border-bottom: 2px solid var(--divider-color);
}
.screenshot-modal-header h2 {
  margin: 0;
  font-size: 19px;
  color: var(--accent-pink);
}
.screenshot-modal-close-btn {
  flex-shrink: 0;
  width: 2.3em;
  height: 2.3em;
  background: transparent;
  border: 2px solid var(--border-light);
  border-radius: 50%;
  color: var(--text-color);
  font-size: 1.25rem;
  line-height: 1;
  cursor: pointer;
}
.screenshot-modal-close-btn:hover,
.screenshot-modal-close-btn:focus-visible {
  background-color: var(--border-light);
  color: var(--bg-color);
}
.screenshot-modal-body {
  padding: 16px;
  min-height: 0;
}
.screenshot-modal-body img {
  display: block;
  width: auto;
  height: auto;
  max-width: 100%;
  /* The dialog's 85vh cap minus the header (~76px with its border), this
     body's padding and the dialog border - so the whole picture is always
     in view with nothing to scroll. */
  max-height: calc(85vh - 116px);
  border-radius: 10px;
  border: 2px solid var(--divider-color);
}
@media (max-width: 700px) {
  .screenshot-modal {
    max-width: calc(100% - 24px);
  }
}

.carousel-controls {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 16px;
  margin-top: 20px;
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  border: 0;
  white-space: nowrap;
}

.carousel-btn {
  background-color: transparent;
  color: var(--text-color);
  border: 2px solid var(--border-light);
  border-radius: 8px;
  padding: 8px 16px;
  font-weight: bold;
  font-size: 14px;
  cursor: pointer;
  white-space: nowrap;
}
.carousel-btn:hover,
.carousel-btn:focus-visible {
  background-color: var(--border-light);
  color: var(--bg-color);
}

/* Visible "N of Total" position readout, replacing the old row of dot
   buttons - a screenshot count is easier to read at a glance (especially
   for low vision users) than a row of tiny circles, and the existing
   .carousel-status live region below already covers the screen-reader
   announcement, so this is aria-hidden to avoid announcing it twice. */
.carousel-position {
  font-style: italic;
  font-weight: bold;
  font-size: 15px;
  color: var(--border-light);
  margin: 0;
  white-space: nowrap;
}

@media (max-width: 960px) {
  .carousel-controls {
    flex-wrap: wrap;
  }
  /* Visual-only reorder (count, then Previous, then Next) via flex `order`
     rather than moving it in the markup - .carousel-position is
     aria-hidden (see the comment above its rule), so this has no effect
     on the screen-reader announcement or the two buttons' own tab order,
     which stay exactly as they were. */
  .carousel-position {
    order: -1;
    width: 100%;
    text-align: center;
  }
}

.carousel-captions {
  flex: 1;
  min-width: 220px;
}
.carousel-caption h3 {
  color: var(--accent-pink);
  font-size: 20px;
  margin: 0 0 8px;
}
.carousel-caption p {
  font-size: 16px;
  margin: 0;
}

/* Below ~480px (narrow phones, or wider phones zoomed way in via OS/browser
   text-size settings), the fixed min-width floors above (260px/240px/220px)
   can end up wider than the card's own interior once its 32px padding is
   subtracted. Just zeroing the floor isn't enough on its own though - these
   containers are flex rows with wrapping, and a zeroed min-width lets the
   text column keep sharing a line with the image/icon at an unusably thin
   sliver width instead of actually wrapping onto its own line, which lets
   any fit-content child inside it (the low-vision/desktop flags) overflow
   past that sliver. Forcing a column layout here removes the ambiguity:
   image/icon on top, text given the card's full width underneath. Body's
   site-wide overflow-wrap (see top of file) still handles any single word
   too long to fit even that full width. */
@media (max-width: 480px) {
  .app-card,
  .product-hero-card {
    flex-direction: column;
    align-items: stretch;
    padding: 20px;
  }
  .carousel-card {
    padding: 20px;
  }
  .carousel-row {
    flex-direction: column;
    align-items: stretch;
  }
  .app-card-content,
  .product-hero-content,
  .carousel-captions {
    min-width: 0;
  }
}

.feature-section {
  background-color: var(--surface-color);
  border: 2px solid var(--divider-color);
  border-radius: 10px;
  margin-bottom: 20px;
  padding: 4px 24px;
}
.feature-section summary {
  cursor: pointer;
  font-size: 20px;
  line-height: 1;
  font-weight: bold;
  padding: 18px 0;
  color: var(--accent-pink);
  list-style: none;
  display: flex;
  align-items: center;
  gap: 8px;
}
.feature-section summary:hover,
.feature-section summary:focus-visible {
  color: var(--focus-color);
}
.feature-section summary::-webkit-details-marker {
  display: none;
}
.feature-section summary::before {
  content: "+";
  display: inline-block;
  color: var(--accent-green);
  position: relative;
  top: -3px;
  transform: scale(1.7);
}
.feature-section[open] summary::before {
  content: "\2212";
}
.feature-section[open] summary {
  border-bottom: 2px solid var(--divider-color);
  margin-bottom: 8px;
}
.feature-section ul {
  padding-left: 24px;
  padding-bottom: 20px;
}
.feature-section li {
  margin-bottom: 10px;
  font-size: 16px;
}
.feature-section p {
  font-size: 16px;
  margin: 10px 0;
}
.feature-section > p:last-child {
  padding-bottom: 20px;
}
.feature-section h3 {
  color: var(--accent-pink);
  font-size: 18px;
  margin-top: 20px;
  margin-bottom: 6px;
}
.feature-section h3:first-child {
  margin-top: 0;
}

.product-page code,
.policy-content code {
  background-color: var(--surface-color);
  border: 2px solid var(--focus-color);
  color: var(--text-color);
  padding: 2px 6px;
  border-radius: 4px;
  font-family: monospace;
  font-size: 0.9em;
}

/* Bare "Privacy Policy" button at the bottom of each product's own
   index.html, linking to that product's local privacy.html - distinct
   from the sitewide Privacy Policy link in the footer (root privacy.html). */
.privacy-link {
  margin: 40px 0 0;
  padding-top: 32px;
  border-top: 2px solid var(--divider-color);
}
