/* App Directory - loaded only by AppsLayout.astro, on top of style.css.
   Kept in its own file (not merged into style.css) so this PR cannot touch
   the CSS the six existing marketing pages depend on. */

.breadcrumbs {
  width: 100%;
  max-width: 1000px;
  margin: 0 auto 32px;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  font-size: 0.85rem;
  color: var(--text-muted);
}

.breadcrumbs a {
  color: var(--text-muted);
  text-decoration: none;
}

.breadcrumbs a:hover {
  color: var(--text-primary);
  text-decoration: underline;
}

.breadcrumbs .breadcrumb-current {
  color: var(--text-secondary);
}

.apps-hero {
  max-width: 760px;
  margin: 0 auto 64px;
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 16px;
}

.apps-hero h1 {
  font-size: 2.75rem;
  font-weight: 500;
  line-height: 1.15;
  letter-spacing: -0.02em;
}

.apps-hero p {
  font-size: 1.1rem;
  color: var(--text-secondary);
  line-height: 1.7;
}

/* Compact SimLauncher download nudge under the breadcrumbs on /apps/* (#76):
   a single line of value + one button, aligned to the content column. */
.apps-cta {
  width: 100%;
  max-width: 1000px;
  margin: 0 auto 32px;
  padding: 14px 20px;
  border-radius: var(--radius-card);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 12px 20px;
}

.apps-cta-text {
  margin: 0;
  font-size: 0.95rem;
  line-height: 1.5;
  color: var(--text-secondary);
}

.apps-cta-text strong {
  color: var(--text-primary);
  font-weight: 600;
}

.apps-cta-btn {
  flex-shrink: 0;
}

/* Category chip - reuses the pill color language without the sim brand gradients */
.category-chip {
  display: inline-flex;
  align-items: center;
  padding: 4px 12px;
  border-radius: var(--radius-pill);
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--text-secondary);
  background: var(--glass-bg-elevated);
  text-decoration: none;
  white-space: nowrap;
}

/* On the hub, a card can appear in several category sections (cross-listed).
   The chip matching the current section is emphasised with the brand accent so
   it is clear why the app is in this group; the others read as "also in". */
.category-chip-active,
.filter-chip.is-active {
  color: var(--accent-text, var(--text-primary));
  background: color-mix(in srgb, var(--accent) 18%, var(--glass-bg-elevated));
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent) 45%, transparent);
}

.status-badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 12px;
  border-radius: var(--radius-pill);
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  white-space: nowrap;
}

.status-badge::before {
  content: "";
  width: 7px;
  height: 7px;
  border-radius: 50%;
  flex-shrink: 0;
}

.status-badge-active {
  color: var(--success-text);
  background: var(--success-surface);
  box-shadow: inset 0 0 0 1px var(--success-border);
}

.status-badge-active::before {
  background: #4ade80;
}

.status-badge-maintenance {
  color: var(--warning-text);
  background: var(--warning-surface);
  box-shadow: inset 0 0 0 1px var(--warning-border);
}

.status-badge-maintenance::before {
  background: #fbbf24;
}

.status-badge-abandoned {
  color: var(--danger-text);
  background: var(--danger-surface);
  box-shadow: inset 0 0 0 1px var(--danger-border);
}

.status-badge-abandoned::before {
  background: #fb7185;
}

/* Pricing badge - same pill language as .status-badge but without the
   status dot: pricing is a fact, not a health signal. Free borrows the
   success pair (a free tool is the directory's headline value prop),
   freemium the warning pair (partly paid), paid stays neutral glass. */
.pricing-badge {
  display: inline-flex;
  align-items: center;
  padding: 4px 12px;
  border-radius: var(--radius-pill);
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  white-space: nowrap;
}

.pricing-badge-free {
  color: var(--success-text);
  background: var(--success-surface);
  box-shadow: inset 0 0 0 1px var(--success-border);
}

.pricing-badge-freemium {
  color: var(--warning-text);
  background: var(--warning-surface);
  box-shadow: inset 0 0 0 1px var(--warning-border);
}

.pricing-badge-paid {
  /* Danger pair on purpose: free/freemium/paid reads as a traffic-light cost
     scale at a glance (a neutral glass pill disappeared next to the other two). */
  color: var(--danger-text);
  background: var(--danger-surface);
  box-shadow: inset 0 0 0 1px var(--danger-border);
}

/* Hub grid */
.apps-grid {
  width: 100%;
  max-width: 1000px;
  margin: 0 auto 96px;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 20px;
}

.app-card {
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding: 28px;
  border-radius: var(--radius-card);
  text-decoration: none;
  color: inherit;
  transition: transform 0.3s ease, background 0.3s ease;
}

.app-card:hover {
  transform: translateY(-4px);
  background: var(--glass-bg-elevated);
}

/* Top-aligned, not centred. With the name allowed to wrap to two lines,
   centring floated the icon to the middle of the block, so a one-line card and
   a two-line card sat their logos at different heights down the row. The icon
   box (24px) is almost exactly one line of the name (1.15rem x 1.3 = 23.9px),
   so aligning the tops lines the logo up with the first line. */
.app-card-title-group {
  display: flex;
  align-items: flex-start;
  gap: 10px;
}

/* Tile as above: Popometer and the other white-line marks vanished against the
   light theme without one. */
.app-card-icon {
  width: 24px;
  height: 24px;
  object-fit: contain;
  border-radius: 6px;
  flex-shrink: 0;
  padding: 1px;
  background: var(--icon-tile-bg);
  border: 1px solid var(--icon-tile-border);
}

/* The card slot draws no tile of its own, so the monogram brings one. Without
   it the fallback would float as bare text next to the name. (.app-icon-fallback
   itself lives in style.css, next to the pill variant it shares.) */
.app-card-icon.app-icon-fallback {
  font-size: 10px;
  border: 1px solid var(--glass-border);
  background: var(--glass-bg-elevated);
}

.app-card-name {
  font-size: 1.15rem;
  font-weight: 600;
  color: var(--text-primary);
  line-height: 1.3;
  /* Two lines, same as the tagline is clamped. The dev-status badge used to sit
     opposite the name and its width varies with the word (ACTIVE vs
     MAINTENANCE), so every card gave its title a different amount of room and
     a long name like "VRS (Virtual Racing School)" ran to four lines. The badge
     now lives in the footer, which gives the name the full card width; this
     caps what is left. */
  display: -webkit-box;
  display: box;
  -webkit-box-orient: vertical;
  box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  overflow: hidden;
  /* Always reserve both lines, even for a name that fits on one. Otherwise the
     tagline starts at a different height on every card and nothing below it
     lines up across the row. */
  min-height: calc(1.3em * 2);
}

.app-card-tagline {
  font-size: 0.9rem;
  color: var(--text-secondary);
  line-height: 1.6;
  /* Cards share a row height (grid stretch); clamping the tagline keeps the
     middle of every card the same shape instead of 1-3 free-flowing lines. */
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  overflow: hidden;
}

.app-card-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: auto;
}

/* Sims, then dev status underneath. Two reasons for stacking rather than
   putting them on one line: six sim icons plus a wide badge overflow a card at
   this width (MAINTENANCE next to +5 collided), and stacking makes every badge
   start at the same x. That was the actual complaint about the old layout -
   ACTIVE, MAINTENANCE and ABANDONED are different widths, so with the badge
   opposite the title no two cards agreed on where anything sat. */
.app-card-footer {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 10px;
}

.app-card-sims {
  display: flex;
  align-items: center;
  gap: 5px;
  min-width: 0;
  font-size: 0.8rem;
  color: var(--text-muted);
}

/* Icons rather than names: the row fits six sims in the width that used to
   hold four names, and the sim artwork is already the recognisable thing. The
   full list stays available to everyone - the title tooltip on hover, the
   sr-only text for a screen reader, and the detail page in full. */
.sim-icon {
  width: 20px;
  height: 20px;
  object-fit: contain;
  border-radius: 5px;
  flex-shrink: 0;
  /* Same tile the homepage pills put behind their icons. Several sim and app
     logos are white-on-transparent, which disappears against the light theme's
     near-white surface; the tile gives every one of them a background to sit
     on and stops the row looking like it has gaps. */
  padding: 1px;
  background: var(--icon-tile-bg);
  border: 1px solid var(--icon-tile-border);
}

.app-card-sims-more {
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* Dev status and pricing together, away from the category chips above. The
   chips say what the app is; these two say how it stands, and pricing was
   reading as another category while it sat among them. Wraps on a narrow card
   rather than squeezing MAINTENANCE next to FREEMIUM. */
.app-card-badges {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

/* Category grouping headers on the hub */
.category-group {
  width: 100%;
  max-width: 1000px;
  margin: 0 auto 40px;
}

.category-group-title {
  font-size: 1rem;
  font-weight: 600;
  color: var(--text-primary);
  margin-bottom: 16px;
  padding-bottom: 8px;
  border-bottom: 1px solid var(--glass-border);
  opacity: 1;
}

/* Dev-status definitions section on the hub */
/* 1000px like the breadcrumbs, the CTA, the filter bar and the grid. It was
   the only panel on the hub sitting at 760px, so its edges lined up with
   nothing. (The hero stays narrow on purpose: that is a block of prose, and
   narrow is what prose wants.) */
.status-definitions {
  width: 100%;
  max-width: 1000px;
  margin: 0 auto 96px;
  padding: 32px 40px;
  border-radius: var(--radius-card);
}

.status-definitions-title {
  font-size: 1.3rem;
  font-weight: 600;
  color: var(--text-primary);
  margin-bottom: 20px;
}

.status-definitions-list {
  list-style: none;
  padding: 0;
  margin: 0 0 20px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.status-definitions-list li {
  display: flex;
  align-items: baseline;
  gap: 12px;
  flex-wrap: wrap;
}

.status-definitions-list p {
  color: var(--text-secondary);
  font-size: 0.95rem;
  line-height: 1.6;
  margin: 0;
}

/* The panel is page-width now, this paragraph is not: across the full 920px
   of content it would run to roughly 130 characters a line, which nobody
   reads. The short definitions above are one line each either way. */
.status-definitions-appeal {
  font-size: 0.9rem;
  color: var(--text-muted);
  line-height: 1.6;
  margin: 0;
  max-width: 90ch;
}

.status-definitions-appeal a {
  color: var(--text-secondary);
  text-decoration: underline;
}

.status-definitions-appeal a:hover {
  color: var(--text-primary);
}

/* Detail page */
.app-detail-header {
  max-width: 760px;
  margin: 0 auto 40px;
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 16px;
}

/* The listing's own identity mark, matching the card it was reached from. The
   header is centred, so the icon and the heading centre as one group rather
   than the icon hanging off a wrapped title. */
.app-detail-title {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 14px;
}

.app-detail-icon {
  width: 40px;
  height: 40px;
  object-fit: contain;
  border-radius: 9px;
  flex-shrink: 0;
  padding: 2px;
  background: var(--icon-tile-bg);
  border: 1px solid var(--icon-tile-border);
}

/* Same tile the card monogram draws: .app-icon-fallback carries only the
   glyph, each slot brings its own box. */
.app-detail-icon.app-icon-fallback {
  font-size: 15px;
  border: 1px solid var(--glass-border);
  background: var(--glass-bg-elevated);
}

.app-detail-header h1 {
  font-size: 2.5rem;
  font-weight: 500;
  line-height: 1.15;
  letter-spacing: -0.02em;
}

.app-detail-tagline {
  font-size: 1.15rem;
  color: var(--text-secondary);
  max-width: 620px;
  line-height: 1.6;
}

.app-detail-badges {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 8px;
}

.app-detail-links {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 12px;
  margin-top: 8px;
}

.app-detail-body {
  max-width: 720px;
  margin: 0 auto 64px;
  width: 100%;
}

.app-detail-body p {
  color: var(--text-secondary);
  font-size: 1rem;
  line-height: 1.75;
  margin-bottom: 16px;
}

.app-detail-body p:last-child {
  margin-bottom: 0;
}

/* Prominent dev-status panel with the backing fact + last-verified date */
.status-panel {
  max-width: 720px;
  margin: 0 auto 64px;
  width: 100%;
  padding: 32px;
  border-radius: var(--radius-card);
}

.status-panel-heading {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 12px;
  flex-wrap: wrap;
}

.status-panel-heading h2 {
  font-size: 1.1rem;
  font-weight: 600;
  color: var(--text-primary);
}

.status-panel p {
  color: var(--text-secondary);
  font-size: 0.95rem;
  line-height: 1.7;
  margin-bottom: 8px;
}

.status-panel p:last-child {
  margin-bottom: 0;
}

.status-panel-verified {
  color: var(--text-muted);
  font-size: 0.85rem;
}

.status-panel-correction {
  margin-top: 16px;
  font-size: 0.9rem;
}

.status-panel-correction a {
  color: var(--text-secondary);
  text-decoration: underline;
}

.status-panel-correction a:hover {
  color: var(--text-primary);
}

/* Facts table */
.facts-table-wrap {
  max-width: 720px;
  margin: 0 auto 64px;
  width: 100%;
  padding: 8px 32px;
  border-radius: var(--radius-card);
}

.facts-table {
  width: 100%;
  border-collapse: collapse;
}

.facts-table tr {
  border-bottom: 1px solid var(--glass-border);
}

.facts-table tr:last-child {
  border-bottom: none;
}

.facts-table th,
.facts-table td {
  text-align: left;
  padding: 16px 0;
  vertical-align: top;
  font-size: 0.95rem;
}

.facts-table th {
  width: 40%;
  color: var(--text-muted);
  font-weight: 500;
}

.facts-table td {
  color: var(--text-primary);
}

/* The sims cell is a real list rather than a comma-joined string, so each entry
   can carry its icon. Two columns keep it from running down the page: even the
   longest listing (SimHub, 16 sims) then stays close to the height of the rows
   around it. Wraps to one column where the table is already narrow. */
.fact-sims {
  list-style: none;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 8px 16px;
  margin: 0;
  padding: 0;
}

.fact-sims li {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
}

/* Same footprint as .sim-icon but no tile: an empty box would read as a broken
   image, while nothing at all would misalign the name beside it. */
.sim-icon-blank {
  width: 20px;
  height: 20px;
  flex-shrink: 0;
}

@media (max-width: 560px) {
  .fact-sims {
    grid-template-columns: 1fr;
  }
}

.facts-table a {
  color: var(--text-primary);
  text-decoration: underline;
}

.facts-table a:hover {
  color: var(--accent);
}

/* Simple content pages (About, Privacy) - same detail-page proportions as
   .app-detail-body, but with real heading structure inside the body, since
   these pages are prose with sections rather than a single description
   block (see 2026-07-12 SEO audit, finding L1, for the same gap on app
   listing pages). */
.content-body {
  max-width: 720px;
  margin: 0 auto 64px;
  width: 100%;
}

.app-detail-body h2,
.facts-table-wrap h2,
.content-body h2 {
  font-size: 1.3rem;
  font-weight: 600;
  color: var(--text-primary);
  margin: 40px 0 12px;
}

.app-detail-body h2:first-child,
.facts-table-wrap h2:first-child,
.content-body h2:first-child {
  margin-top: 0;
}

.content-body p {
  color: var(--text-secondary);
  font-size: 1rem;
  line-height: 1.75;
  margin-bottom: 16px;
}

.content-body p:last-child {
  margin-bottom: 0;
}

.content-body ul {
  margin: 0 0 16px;
  padding-left: 1.25em;
  color: var(--text-secondary);
  font-size: 1rem;
  line-height: 1.75;
}

.content-body li {
  margin-bottom: 8px;
}

.content-body a {
  color: var(--text-primary);
  text-decoration: underline;
}

.content-body a:hover {
  color: var(--accent);
}

/* Related apps: directory tiles (AppCard.astro, the same .app-card the hub
   renders) at the foot of a detail page, so the related block matches the
   index instead of the old text-only link cards. Aligned to the 760px header
   column. Fixed 2-col so the up-to-4 siblings read as a balanced 2x2 (same
   framing as the LP "Related guides" block) and the layout stays stable
   regardless of future column-width tweaks. */
.related-apps {
  max-width: 760px;
  margin: 0 auto 96px;
  padding: 0 20px;
  width: 100%;
}

.related-apps-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 20px;
  margin-top: 40px;
}

@media (max-width: 600px) {
  .apps-hero h1,
  .app-detail-header h1 {
    font-size: 2rem;
  }

  .facts-table-wrap,
  .status-panel,
  .status-definitions {
    padding: 24px 20px;
  }

  /* Stack the related tiles to a single column on phones. */
  .related-apps-grid {
    grid-template-columns: 1fr;
  }
}

/* --- App Index filter: mode + visibility toggles (apps-filter.js) --- */

/* A card or section the filter has excluded. Appended after .app-card and
   .category-group so source order wins without !important. */
.is-filtered-out {
  display: none;
}

/* Flat container is the JS-only alternate view: hidden by default so no-JS
   visitors (and grouped mode) never see it. The grouping switch adds
   .is-flat to #apps-index to swap grouped sections for the flat grid. */
.apps-flat {
  display: none;
}

.apps-index.is-flat .apps-grouped {
  display: none;
}

.apps-index.is-flat .apps-flat {
  display: block;
}

/* Empty state: shown only when the active filters match zero apps. */
.apps-empty {
  display: none;
  max-width: 1000px;
  margin: 0 auto 96px;
  text-align: center;
  color: var(--text-muted);
  font-size: 0.95rem;
}

.apps-index.is-empty .apps-empty {
  display: block;
}

/* --- App Index filter toolbar --- */
.apps-filter[hidden] {
  display: none;
}

/* Compact single-row toolbar (wraps on narrow screens) instead of a tall panel:
   each facet collapses into a dropdown so 25+ chips never stack down the page.
   position + z-index make the toolbar a stacking context so its absolutely
   positioned menus paint above the grid below. */
.apps-filter {
  position: relative;
  z-index: 5;
  width: 100%;
  max-width: 1000px;
  margin: 0 auto 40px;
  padding: 12px 14px;
  border-radius: var(--radius-card);
  background: var(--glass-bg);
  box-shadow: inset 0 0 0 1px var(--glass-border);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
}

.apps-filter-label {
  padding-left: 6px;
  font-size: 0.8rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--text-muted);
}

.apps-filter-label:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 4px;
}

/* Shared button reset for every control in the toolbar (chips, facet triggers,
   grouping switch, clear), so the cursor/border/font reset lives in one place. */
.filter-chip,
.facet-trigger,
.grouping-switch,
.apps-clear,
.apps-clear-inline {
  cursor: pointer;
  border: none;
  font: inherit;
}

/* One facet = a trigger button + a popover menu of chips. The menu is placed by
   CSS (position:absolute under the relative wrapper), never by runtime inline
   styles, so the strict style-src 'self' CSP stays intact. */
.facet-dropdown {
  position: relative;
}

.facet-trigger {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 7px 13px;
  border-radius: var(--radius-pill);
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--text-secondary);
  background: var(--glass-bg-elevated);
}

/* Caret: a rotated CSS square; points down when closed, up when the menu opens. */
.facet-trigger::after {
  content: "";
  width: 6px;
  height: 6px;
  margin-left: 1px;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  opacity: 0.7;
  transform: translateY(-2px) rotate(45deg);
  transition: transform 0.2s ease;
}

.facet-trigger[aria-expanded="true"]::after {
  transform: translateY(1px) rotate(225deg);
}

.facet-trigger.is-active,
.facet-trigger[aria-expanded="true"] {
  color: var(--accent-text, var(--text-primary));
  background: color-mix(in srgb, var(--accent) 18%, var(--glass-bg-elevated));
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent) 45%, transparent);
}

.facet-trigger:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* Selected-count badge on the trigger (e.g. Sim 2). --accent-foreground is the
   theme's on-accent text token, so there is no hard-coded colour. */
.facet-count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 16px;
  height: 16px;
  padding: 0 4px;
  border-radius: var(--radius-pill);
  font-size: 0.65rem;
  font-weight: 700;
  color: var(--accent-foreground);
  background: var(--accent);
}

/* An author `display` beats the UA [hidden] rule by origin, so the badge needs
   its own hide rule (same pattern as .facet-menu[hidden]) or it shows an empty
   accent dot at zero selections. */
.facet-count[hidden] {
  display: none;
}

/* Popover menu of chips. Frosted like the site's elevated surfaces (blur so the
   grid behind reads as a solid panel), hidden via the [hidden] attribute until
   JS opens the dropdown. */
.facet-menu {
  position: absolute;
  z-index: 1;
  top: calc(100% + 6px);
  left: 0;
  min-width: 180px;
  max-width: min(320px, 86vw);
  max-height: 320px;
  overflow-y: auto;
  padding: 12px;
  border-radius: var(--radius-inner);
  background: var(--glass-bg-elevated);
  -webkit-backdrop-filter: blur(20px);
  backdrop-filter: blur(20px);
  box-shadow: inset 0 0 0 1px var(--glass-border), 0 14px 34px rgba(0, 0, 0, 0.45);
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.facet-menu[hidden] {
  display: none;
}

/* Toggle chip inside a menu (same pill language as .category-chip). Active
   state reuses the accent via the shared .filter-chip.is-active selector. */
.filter-chip {
  display: inline-flex;
  align-items: center;
  padding: 5px 12px;
  border-radius: var(--radius-pill);
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--text-secondary);
  background: var(--glass-bg-elevated);
  white-space: nowrap;
}

.filter-chip:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* Grouping switch: the one genuine binary on/off, so role="switch" +
   aria-checked (the app's single-toggle convention), styled as a pill that
   fills with the accent when on. */
.grouping-switch {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 6px 14px;
  border-radius: var(--radius-pill);
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--text-secondary);
  background: var(--glass-bg-elevated);
}

.grouping-switch::before {
  content: "";
  width: 30px;
  height: 16px;
  border-radius: var(--radius-pill);
  background: var(--glass-border);
  box-shadow: inset -14px 0 0 -2px var(--text-muted);
  transition: box-shadow 0.2s ease, background 0.2s ease;
}

.grouping-switch[aria-checked="true"] {
  color: var(--accent-text, var(--text-primary));
  background: color-mix(in srgb, var(--accent) 18%, var(--glass-bg-elevated));
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent) 45%, transparent);
}

.grouping-switch[aria-checked="true"]::before {
  background: var(--accent);
  box-shadow: inset 14px 0 0 -2px var(--text-primary);
}

.grouping-switch:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.apps-result-count {
  font-size: 0.85rem;
  color: var(--text-muted);
  margin-left: auto;
}

.apps-clear {
  padding: 6px 14px;
  border-radius: var(--radius-pill);
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--text-secondary);
  background: var(--glass-bg-elevated);
}

.apps-clear:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.apps-clear:disabled {
  opacity: 0.45;
  cursor: default;
}

/* Inline clear inside the empty-state message. */
.apps-clear-inline {
  background: none;
  color: var(--text-secondary);
  text-decoration: underline;
  padding: 0;
}

.apps-clear-inline:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* On phones the toolbar wraps, so a menu anchored under a right-side trigger
   (Sim) runs off the right edge. Position menus against the whole toolbar
   instead (making .facet-dropdown static hands the absolute menu to the
   position:relative .apps-filter) and drop a full-width panel below the bar:
   robust, never clips, and only one menu is open at a time. */
@media (max-width: 600px) {
  .facet-dropdown {
    position: static;
  }

  .facet-menu {
    left: 8px;
    right: 8px;
    top: calc(100% + 8px);
    min-width: 0;
    max-width: none;
  }
}

/* --- Editorial guide (/best-iracing-apps/, issue #72) --- */

/* Aligned to .content-body's 720px column, because the guide alternates between
   prose sections and entry cards down one page: a wider list would read as a
   different page fused onto the intro. body already supplies the 24px gutter,
   so neither block needs padding of its own and the two stay flush. */
.guide-list {
  max-width: 720px;
  margin: 0 auto 64px;
  width: 100%;
}

.guide-section-title {
  font-size: 1.3rem;
  font-weight: 600;
  color: var(--text-primary);
  margin: 0 0 24px;
}

/* The ordinals are drawn by .guide-rank inside each entry, not by the marker:
   a list-style number cannot be styled as a badge or sit inside the card. The
   ol is kept (rather than a ul or divs) because the ORDER is the content here,
   and that has to survive CSS being stripped, which is also how a crawler and
   a screen reader read it. */
.guide-entries {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 20px;
}

.guide-entry {
  padding: 24px 28px;
  border-radius: var(--radius-card);
}

.guide-entry-head {
  display: flex;
  align-items: flex-start;
  gap: 16px;
  margin-bottom: 14px;
}

.guide-rank {
  flex-shrink: 0;
  width: 34px;
  height: 34px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-pill);
  background: var(--glass-bg-elevated);
  border: 1px solid var(--glass-border);
  color: var(--text-secondary);
  font-size: 0.9rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

.guide-entry-title-group {
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.guide-entry-title {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 1.25rem;
  font-weight: 600;
  line-height: 1.25;
  margin: 0;
}

.guide-entry-title a {
  color: var(--text-primary);
  text-decoration: none;
}

.guide-entry-title a:hover {
  color: var(--accent);
  text-decoration: underline;
}

.guide-entry-icon {
  width: 28px;
  height: 28px;
  object-fit: contain;
  border-radius: 7px;
  flex-shrink: 0;
  padding: 2px;
  background: var(--icon-tile-bg);
  border: 1px solid var(--icon-tile-border);
}

.guide-entry-icon.app-icon-fallback {
  font-size: 11px;
  border: 1px solid var(--glass-border);
  background: var(--glass-bg-elevated);
}

/* The "best X" label, which is the part a reader scans for. Accent-coloured
   rather than heading-sized: it must not compete with the app name above it. */
.guide-superlative {
  margin: 0;
  font-size: 0.8rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--accent);
}

.guide-entry-body {
  color: var(--text-secondary);
  font-size: 1rem;
  line-height: 1.75;
  margin: 0 0 14px;
}

.guide-entry-facts {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 12px;
  padding-top: 14px;
  border-top: 1px solid var(--glass-border);
}

.guide-fact-sims {
  font-size: 0.8rem;
  color: var(--text-muted);
}

/* Pushed to the end of the row so the badges group together and the outbound
   link is the last thing on the line, in both wrap states. */
.guide-entry-link {
  margin-left: auto;
  font-size: 0.85rem;
  color: var(--text-primary);
  text-decoration: underline;
}

.guide-entry-link:hover {
  color: var(--accent);
}

.guide-criteria {
  margin: 0 0 16px;
  padding-left: 1.25em;
  color: var(--text-secondary);
  font-size: 1rem;
  line-height: 1.75;
}

.guide-criteria li {
  margin-bottom: 8px;
}

.guide-criteria strong {
  color: var(--text-primary);
}

/* The niche entries carry the same two badges as the ranked ones, so a reader
   comparing across the page is comparing like with like.

   ONE card around the whole group, not one card each: a card per entry would
   make these read as ranked alongside the twelve above, which is the single
   thing this section exists to deny. Grouping them inside a shared surface
   keeps the page's card language without granting them a rank. Same container
   pattern as .grid-group on the landing page. */
.guide-niche {
  list-style: none;
  margin: 0 0 24px;
  padding: 24px 28px;
  border-radius: var(--radius-card);
  display: flex;
  flex-direction: column;
}

.guide-niche li {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 10px;
  margin: 0;
}

/* Divider rather than a gap: seven stacked rows inside one surface need a
   read of where each entry ends, and the border is the same one the ranked
   cards use above their facts row. */
.guide-niche li + li {
  margin-top: 18px;
  padding-top: 18px;
  border-top: 1px solid var(--glass-border);
}

.guide-niche li > a {
  font-size: 1rem;
  font-weight: 600;
  color: var(--text-primary);
  text-decoration: underline;
}

.guide-niche li > a:hover {
  color: var(--accent);
}

/* Full-width inside the flex row, so the note always starts on its own line
   under the name and badges however the badges wrap. */
.guide-niche li p {
  flex-basis: 100%;
  margin: 0;
  color: var(--text-secondary);
  font-size: 0.95rem;
  line-height: 1.7;
}

/* Accent-tinted rather than plain glass, unlike every other card on the page:
   the tint is the tell that this block is us and not another recommendation.
   A neutral card here would read as a thirteenth entry, which is the one thing
   a self-promo block on a ranked list must not do. Same tint pair as
   .related-link:hover in style.css. */
.guide-pitch {
  max-width: 720px;
  margin: 0 auto 64px;
  width: 100%;
  padding: 24px 28px;
  border-radius: var(--radius-card);
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent) 18%, transparent);
}

.guide-pitch-head {
  margin-bottom: 14px;
}

/* Direct children only: .guide-superlative is also a <p>, and it lives one
   level down inside the head. A `.guide-pitch p` rule would outrank it on
   specificity and repaint the label as body copy. */
.guide-pitch > p {
  color: var(--text-secondary);
  font-size: 1rem;
  line-height: 1.75;
  margin: 0 0 16px;
}

.guide-pitch > p:last-child {
  margin-bottom: 0;
}

@media (max-width: 600px) {
  .guide-entry,
  .guide-niche,
  .guide-pitch {
    padding: 20px;
  }

  .guide-entry-title {
    font-size: 1.1rem;
  }

  /* The link stops being pushed right once the row wraps to two lines: against
     a left-aligned badge row, an auto margin strands it alone on the far side. */
  .guide-entry-link {
    margin-left: 0;
  }
}
