/* AppTracker — one stylesheet for the server-rendered pages.
   Descended from the React build's styles.css: same palette, spacing, table,
   card, badge and chip language. What is new here is what the SPA never had —
   the keyword explorer, the keyword rank chart, result cards, and the CSS-only
   controls that keep every page working with JavaScript switched off. */

:root {
  color-scheme: light;
  --bg: #f6f7f9;
  --surface: #ffffff;
  --surface-2: #f0f1f4;
  --border: #e3e5ea;
  --fg: #161823;
  --muted: #6b7280;
  --faint: #9aa1ad;
  --accent: #5b5bd6;
  --accent-bg: #5b5bd614;
  --star: #e8a33d;
  --up: #119c5b;
  --up-bg: #119c5b1a;
  --down: #d83a52;
  --down-bg: #d83a521a;
  --new: #4f46e5;
  --new-bg: #4f46e51a;
  --shadow: 0 1px 2px rgba(20, 20, 40, 0.06), 0 8px 24px rgba(20, 20, 40, 0.06);

  /* Chart series slots. Fixed order, validated for colour-vision deficiency on
     this surface (adjacent-pair ΔE ≥ 8 OKLab, normal-vision ΔE ≥ 15). Slots are
     assigned per app and never generated: eight is the ceiling, and the chart
     caps its series count to match. Three of them sit under 3:1 against white,
     which is why every line is also named in the legend and repeated in the
     ranking table below the chart. */
  --s1: #2a78d6;
  --s2: #eb6834;
  --s3: #1baf7a;
  --s4: #eda100;
  --s5: #e87ba4;
  --s6: #008300;
  --s7: #4a3aa7;
  --s8: #e34948;
}

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
  color: var(--fg);
  background: var(--bg);
  font-size: 14px;
  -webkit-font-smoothing: antialiased;
}

/* Clip (not hidden/scroll) so a stray wide child can never scroll the page
   sideways, without turning this into the sticky header's scroll container.
   The column layout is what keeps the footer at the bottom of a short page. */
.app-shell {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  overflow-x: clip;
}

a {
  color: var(--accent);
}

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 6px;
}

.home,
.page {
  width: 100%;
  max-width: 1080px;
  margin: 0 auto;
  flex: 1;
}
.home {
  padding: 22px 24px 72px;
}
.page {
  padding: 28px 24px 72px;
}

/* --- site-wide header --- */
.site-header {
  position: sticky;
  top: 0;
  z-index: 20;
  background: color-mix(in srgb, var(--surface) 85%, transparent);
  backdrop-filter: saturate(180%) blur(12px);
  border-bottom: 1px solid var(--border);
}
.site-header-inner {
  max-width: 1080px;
  margin: 0 auto;
  padding: 0 24px;
  height: 56px;
  display: flex;
  align-items: center;
  gap: 20px;
}
.brand {
  font-size: 18px;
  font-weight: 800;
  letter-spacing: -0.02em;
  color: var(--fg);
  text-decoration: none;
  flex-shrink: 0;
}
.brand .accent {
  color: var(--accent);
}

/* Section nav. Hidden below 860px, where the search field and the storefront
   picker need the whole bar; the footer carries the same links there. */
.site-nav {
  display: flex;
  align-items: center;
  gap: 16px;
  flex-shrink: 0;
}
.site-nav a {
  color: var(--muted);
  text-decoration: none;
  font-weight: 600;
  font-size: 13px;
  white-space: nowrap;
}
.site-nav a:hover {
  color: var(--accent);
}
@media (max-width: 860px) {
  .site-nav {
    display: none;
  }
}

/* search */
.search {
  position: relative;
  flex: 1;
  max-width: 440px;
  min-width: 0; /* let it shrink below the placeholder width instead of overflowing */
  margin: 0;
}
.search-field {
  display: flex;
  align-items: center;
  gap: 8px;
  background: var(--surface-2);
  border: 1px solid transparent;
  border-radius: 10px;
  padding: 0 12px;
  height: 38px;
  color: var(--muted);
}
.search-field:focus-within {
  border-color: var(--accent);
  background: var(--surface);
}
.search-field input {
  border: none;
  background: transparent;
  outline: none;
  width: 100%;
  font: inherit;
  font-size: 14px;
  color: var(--fg);
}
.search-field input::-webkit-search-cancel-button {
  appearance: none;
}
.search-field .go {
  border: none;
  background: transparent;
  color: var(--accent);
  font: inherit;
  font-weight: 600;
  cursor: pointer;
  padding: 0 2px;
}
.search-results {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  right: 0;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 12px;
  box-shadow: var(--shadow);
  padding: 6px;
  max-height: 60vh;
  overflow-y: auto;
  z-index: 30;
}
.search-hit {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  border-radius: 8px;
  padding: 7px 8px;
  text-align: left;
  text-decoration: none;
  color: inherit;
}
.search-hit:hover,
.search-hit.on {
  background: var(--surface-2);
}
.search-hit img,
.search-hit .icon {
  width: 34px;
  height: 34px;
  border-radius: 8px;
  flex-shrink: 0;
  background: var(--surface-2);
}
.hit-body {
  display: flex;
  flex-direction: column;
  min-width: 0;
}
.hit-name {
  font-weight: 600;
  font-size: 14px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.hit-sub {
  font-size: 12px;
  color: var(--muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.search-empty {
  padding: 14px;
  color: var(--muted);
  font-size: 13px;
  text-align: center;
}

/* region picker */
.region-form {
  margin-left: auto;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  gap: 6px;
}
.region-pick {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  height: 38px;
  padding: 0 12px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--surface);
  font-size: 13px;
  font-weight: 600;
}
.region-pick:hover,
.region-pick:focus-within {
  border-color: var(--accent);
}
.region-pick .flag {
  font-size: 15px;
}
.region-pick .region-name {
  white-space: nowrap;
}
.region-pick .caret {
  color: var(--faint);
  font-size: 10px;
}
.region-pick select {
  position: absolute;
  inset: 0;
  width: 100%;
  opacity: 0;
  cursor: pointer;
  font: inherit;
}

/* --- page heading --- */
.page-head {
  margin: 0 0 18px;
}
.page-head h1 {
  margin: 0;
  font-size: 21px;
  font-weight: 700;
  letter-spacing: -0.02em;
}
.page-sub {
  margin: 4px 0 0;
  color: var(--muted);
  font-size: 13px;
}

/* --- controls --- */
.controls {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  margin-bottom: 16px;
}
.spacer {
  flex: 1;
}
.segmented {
  display: inline-flex;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 3px;
  gap: 2px;
}
.seg {
  border: none;
  background: transparent;
  color: var(--muted);
  padding: 6px 12px;
  border-radius: 7px;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  text-decoration: none;
  white-space: nowrap;
}
.seg:hover {
  color: var(--fg);
}
.seg.active {
  background: var(--surface);
  color: var(--fg);
  box-shadow: var(--shadow);
}
.select {
  appearance: none;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--fg);
  border-radius: 10px;
  padding: 8px 30px 8px 12px;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath fill='%239aa1ad' d='M0 0l5 6 5-6z'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 12px center;
}
.ctl-form {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin: 0;
}
.btn {
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--fg);
  border-radius: 10px;
  padding: 8px 14px;
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  text-decoration: none;
}
.btn:hover {
  border-color: var(--accent);
}
.text-input {
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--fg);
  border-radius: 10px;
  padding: 8px 12px;
  font: inherit;
  font-size: 13px;
  min-width: 190px;
}
.text-input:focus {
  outline: none;
  border-color: var(--accent);
}
.check {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-size: 13px;
  font-weight: 600;
  color: var(--muted);
  border: 1px solid var(--border);
  background: var(--surface);
  border-radius: 10px;
  padding: 8px 12px;
  cursor: pointer;
}
.check input {
  accent-color: var(--accent);
  margin: 0;
}

/* --- table --- */
.window-note {
  color: var(--muted);
  font-size: 12px;
  margin: 0 4px 8px;
}
.table {
  border: 1px solid var(--border);
  border-radius: 14px;
  overflow: hidden;
  background: var(--surface);
  box-shadow: var(--shadow);
}
.row {
  display: grid;
  grid-template-columns: 44px 70px 1fr 170px 70px;
  align-items: center;
  gap: 10px;
  width: 100%;
  text-align: left;
  border: none;
  border-bottom: 1px solid var(--border);
  background: transparent;
  color: inherit;
  font: inherit;
  padding: 10px 16px;
}
.row:last-child {
  border-bottom: none;
}
/* Rows are links; out-specify the UA :any-link blue + underline. */
a.row,
a.row:visited {
  color: inherit;
  text-decoration: none;
}
.row.head {
  background: var(--surface-2);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--faint);
  font-weight: 700;
  padding-top: 9px;
  padding-bottom: 9px;
}
a.row:hover {
  background: var(--surface-2);
}
.col-rank {
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  color: var(--muted);
  text-align: center;
}
.col-app {
  display: flex;
  align-items: center;
  gap: 11px;
  min-width: 0;
}
.icon {
  width: 38px;
  height: 38px;
  border-radius: 9px;
  flex-shrink: 0;
  background: var(--surface-2);
  object-fit: cover;
}
.icon.ph {
  display: inline-block;
}
.appmeta {
  display: flex;
  flex-direction: column;
  min-width: 0;
}
.appname {
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.artist {
  color: var(--muted);
  font-size: 12px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.chip {
  display: inline-block;
  background: var(--surface-2);
  border: 1px solid var(--border);
  color: var(--muted);
  border-radius: 999px;
  padding: 2px 9px;
  font-size: 11px;
  font-weight: 600;
  white-space: nowrap;
}
.col-price {
  text-align: right;
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  color: var(--muted);
}

/* --- movement badge --- */
.badge {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  font-size: 12px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  border-radius: 7px;
  padding: 3px 7px;
}
.badge.up {
  color: var(--up);
  background: var(--up-bg);
}
.badge.down {
  color: var(--down);
  background: var(--down-bg);
}
.badge.new {
  color: var(--new);
  background: var(--new-bg);
}
.badge.flat {
  color: var(--muted);
  background: var(--surface-2);
}
.badge.track {
  color: var(--accent);
  background: var(--accent-bg);
}

/* --- notices --- */
.notice {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 16px;
  color: var(--muted);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 12px;
}
.notice.subtle {
  background: transparent;
  border-style: dashed;
  margin-bottom: 12px;
}
.notice.error {
  color: var(--down);
  border-color: var(--down);
  background: var(--down-bg);
}
.muted {
  color: var(--muted);
  font-weight: 400;
}
.intro {
  color: var(--muted);
  font-size: 14px;
  margin: 0 0 18px;
  max-width: 74ch;
  line-height: 1.55;
}
.empty {
  color: var(--faint);
  font-size: 13px;
  margin: 0;
}
/* The standing caveat lines: what a number here does and does not mean. */
.caveat {
  color: var(--faint);
  font-size: 12px;
  line-height: 1.55;
  margin: 12px 0 0;
  max-width: 82ch;
  border-left: 2px solid var(--border);
  padding-left: 12px;
}

/* --- app page: identity --- */
.identity {
  display: flex;
  gap: 20px;
  align-items: flex-start;
}
.app-icon {
  width: 108px;
  height: 108px;
  border-radius: 24px;
  flex-shrink: 0;
  box-shadow: var(--shadow);
  background: var(--surface-2);
}
.id-body {
  flex: 1;
  min-width: 0;
  padding-top: 2px;
}
.id-body h1 {
  margin: 0;
  font-size: 27px;
  font-weight: 700;
  letter-spacing: -0.02em;
}
.id-sub {
  margin: 3px 0 0;
  color: var(--muted);
  font-size: 15px;
}
.id-sub .dev {
  color: var(--accent);
  font-weight: 600;
}
/* The developer name links to that developer's apps when Apple gave us an artist
   id. It keeps the colour and weight of the text it replaces — the underline is
   the only thing marking it as a link, and only on hover. Scoped to the class,
   not to its containers: a future link in an .id-sub or a .drow should get the
   normal link treatment, not silently inherit this one. */
.devlink {
  color: inherit;
  font-weight: inherit;
  text-decoration: none;
}
.devlink:hover {
  text-decoration: underline;
}
.id-facts {
  display: flex;
  align-items: center;
  gap: 16px;
  margin-top: 14px;
  flex-wrap: wrap;
}
.rating {
  display: inline-flex;
  align-items: baseline;
  gap: 7px;
}
.rating .star {
  color: var(--star);
  font-size: 14px;
}
.rating .score {
  font-size: 17px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.rating .count {
  color: var(--faint);
  font-size: 13px;
}
.id-price {
  color: var(--muted);
  font-size: 14px;
  font-weight: 600;
}
.store-link {
  margin-left: auto;
  align-self: center;
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  color: var(--accent);
  font-weight: 600;
  font-size: 14px;
  text-decoration: none;
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 9px 15px;
  background: var(--surface);
}
.store-link:hover {
  border-color: var(--accent);
}

/* --- page sections --- */
.page section {
  margin-top: 40px;
}
.eyebrow {
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--faint);
  font-weight: 700;
  margin: 0 0 14px;
}
.eyebrow .sub {
  text-transform: none;
  letter-spacing: 0;
  font-weight: 500;
  color: var(--faint);
}

/* signature: chart standing */
.standing {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 12px;
}
.rank-card {
  border: 1px solid var(--border);
  border-radius: 14px;
  padding: 16px;
  background: var(--surface);
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-height: 172px;
}
.rank-card.lead {
  border-color: transparent;
  background: linear-gradient(180deg, var(--accent-bg), var(--surface) 55%);
  box-shadow:
    inset 0 0 0 1px var(--accent-bg),
    var(--shadow);
}
.rc-chart {
  font-size: 13px;
  font-weight: 600;
}
.rc-genre {
  font-size: 12px;
  color: var(--muted);
}
.rc-rank {
  display: flex;
  align-items: baseline;
  gap: 3px;
  margin-top: auto;
  padding-top: 12px;
}
.rc-rank .hash {
  font-size: 18px;
  font-weight: 600;
  color: var(--faint);
}
.rc-rank .num {
  font-size: 42px;
  font-weight: 800;
  letter-spacing: -0.03em;
  line-height: 1;
}
.rank-card.lead .rc-rank .num,
.rank-card.lead .rc-rank .hash {
  color: var(--accent);
}
.rc-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-top: 12px;
  font-size: 12px;
}
.rc-best {
  color: var(--faint);
  font-variant-numeric: tabular-nums;
}
.delta {
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.delta.up {
  color: var(--up);
}
.delta.down {
  color: var(--down);
}
.delta.flat {
  color: var(--faint);
}
.spark {
  display: block;
  width: 100%;
  height: 30px;
  margin-top: 12px;
}
.spark-line {
  stroke: var(--accent);
  stroke-width: 1.75;
  vector-effect: non-scaling-stroke;
}
.spark-line.spark-flat {
  stroke: var(--faint);
}
.spark-dot {
  fill: var(--accent);
}
.spark-base {
  stroke: var(--border);
  stroke-width: 1;
  stroke-dasharray: 2 3;
  vector-effect: non-scaling-stroke;
}
.spark-wait {
  fill: var(--faint);
}

/* what changed: the listing-event timeline. Same bordered surface and row rhythm
   as the details card, so the two read as one family. The dot is colour only —
   it is aria-hidden and the label beside it carries the meaning, so nothing here
   depends on telling the five hues apart. */
.timeline {
  border: 1px solid var(--border);
  border-radius: 14px;
  background: var(--surface);
  overflow: hidden;
}
.tl-row {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 4px 12px;
  padding: 12px 16px;
  font-size: 13px;
}
.tl-row + .tl-row {
  border-top: 1px solid var(--border);
}
.tl-date {
  flex: 0 0 110px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.tl-dot {
  flex: 0 0 8px;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--faint);
}
.tl-dot.version {
  background: var(--accent);
}
.tl-dot.price {
  background: var(--star);
}
.tl-dot.rating {
  background: var(--faint);
}
.tl-dot.availability {
  background: var(--down);
}
.tl-dot.promo {
  background: var(--s5);
}
/* The body takes the rest of the row and wraps inside itself, so a long promo
   line pushes the notes down rather than widening the card. */
.tl-body {
  display: flex;
  flex: 1;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 4px 10px;
  min-width: 0;
}
.tl-label {
  font-weight: 600;
}
.tl-text {
  color: var(--muted);
  overflow-wrap: anywhere;
}
.tl-notes {
  flex-basis: 100%;
  margin: 6px 0 0;
  color: var(--muted);
  font-size: 12px;
  line-height: 1.55;
  white-space: pre-wrap;
}

/* ratings breakdown: five bars, one per star. Same bordered surface as the
   timeline and the details card, so the app page keeps one card language. The
   grid is label / bar / percent / count — the bar takes whatever the four fixed
   columns leave, which is what keeps every fill measured against the same track
   however wide the page is. */
.stars {
  border: 1px solid var(--border);
  border-radius: 14px;
  background: var(--surface);
  overflow: hidden;
}
.star-row {
  display: grid;
  grid-template-columns: 34px 1fr 42px 56px;
  align-items: center;
  gap: 12px;
  padding: 10px 16px;
  font-size: 13px;
}
.star-row + .star-row {
  border-top: 1px solid var(--border);
}
.star-label {
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.star-track {
  display: block;
  height: 8px;
  border-radius: 999px;
  background: var(--surface-2);
  overflow: hidden;
}
/* The width comes from the server as an inline style; everything else about the
   fill lives here. A zero-count star still draws its track, which is the point —
   an empty bar next to four full ones is the shape being read. */
.star-fill {
  display: block;
  height: 100%;
  border-radius: 999px;
  background: var(--star);
}
.star-pct {
  color: var(--fg);
  font-variant-numeric: tabular-nums;
  text-align: right;
}
.star-count {
  color: var(--faint);
  font-variant-numeric: tabular-nums;
  text-align: right;
}

/* in-app purchases: one card, one heading per kind of purchase, one row per
   product. Same bordered surface as the ratings breakdown and the timeline, so
   the app page keeps one card language. Two columns — what it is on the left,
   what it costs on the right — because that is the order the row is read in, and
   the price column stacks its terms under the amount rather than widening into a
   third. */
.iaps {
  border: 1px solid var(--border);
  border-radius: 14px;
  background: var(--surface);
  overflow: hidden;
}
.iap-row {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: baseline;
  gap: 12px;
  padding: 10px 16px;
  font-size: 13px;
}
.iap-row + .iap-row,
.iap-row + .iap-head {
  border-top: 1px solid var(--border);
}
/* The group heading, in the same key as the .row.head strip that labels a table
   inside a card: quiet, uppercase, on the recessed surface. It carries the fact
   the rows under it no longer repeat, and it keeps the two lists reading as one
   card divided rather than as two cards stacked. */
.iap-head {
  padding: 9px 16px;
  background: var(--surface-2);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--faint);
  font-weight: 700;
}
.iap-name {
  min-width: 0;
  overflow-wrap: anywhere;
}
.iap-price {
  text-align: right;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
/* Its own line under the price: the recurrence and the trial qualify the amount,
   and a reader scanning the column for amounts should not have to step over
   them. */
.iap-note {
  display: block;
  color: var(--faint);
  font-size: 12px;
  white-space: normal;
}

/* screenshots */
.shots {
  display: flex;
  gap: 14px;
  overflow-x: auto;
  padding-bottom: 6px;
  scroll-snap-type: x mandatory;
}
.shots img {
  height: 340px;
  border-radius: 16px;
  border: 1px solid var(--border);
  flex-shrink: 0;
  scroll-snap-align: start;
  background: var(--surface-2);
}

/* about + details */
.cols {
  display: grid;
  grid-template-columns: 1fr 300px;
  gap: 44px;
  align-items: start;
}
.about p {
  margin: 0 0 12px;
  white-space: pre-wrap;
  font-size: 15px;
  color: var(--fg);
}
/* No-JS expand: a visually hidden checkbox drives the clamp, and the <label>
   is the visible, keyboard-reachable control. The input stays focusable (not
   `hidden`) so Space toggles it; the ring is drawn on the label. */
.desc-toggle {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}
.about .desc {
  display: -webkit-box;
  -webkit-line-clamp: 12;
  line-clamp: 12;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.desc-toggle:checked ~ .desc {
  -webkit-line-clamp: none;
  line-clamp: none;
  overflow: visible;
}
.about .more {
  display: inline-block;
  margin-top: 6px;
  color: var(--accent);
  font: inherit;
  font-weight: 600;
  cursor: pointer;
}
.desc-toggle:focus-visible ~ .more {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 6px;
}
.more .less-label,
.desc-toggle:checked ~ .more .more-label {
  display: none;
}
.desc-toggle:checked ~ .more .less-label {
  display: inline;
}
.about .whatsnew {
  margin-top: 32px;
}
.details {
  border: 1px solid var(--border);
  border-radius: 14px;
  background: var(--surface);
  overflow: hidden;
}
.drow {
  display: flex;
  justify-content: space-between;
  gap: 16px;
  padding: 12px 16px;
  font-size: 13px;
}
.drow + .drow {
  border-top: 1px solid var(--border);
}
.drow .k {
  color: var(--muted);
  flex-shrink: 0;
}
.drow .v {
  font-weight: 600;
  text-align: right;
  font-variant-numeric: tabular-nums;
}
.drow .v.mono {
  font-weight: 500;
  color: var(--muted);
  word-break: break-all;
}
/* The localization list is prose, not a figure: it wraps over several lines,
   so it drops the .v weight that suits a short value and would read as a wall
   of bold here. */
.drow .v.langs {
  font-weight: 500;
  text-align: right;
  line-height: 1.5;
  min-width: 0;
}
/* Same no-JS disclosure as the description: a visually hidden but still
   focusable checkbox drives the clamp, the <label> is the visible control, and
   the ring is drawn on the label because the input itself is unseeable. */
.lang-toggle {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}
.langs .lang-list {
  display: -webkit-box;
  -webkit-line-clamp: 3;
  line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.lang-toggle:checked ~ .langs .lang-list {
  -webkit-line-clamp: none;
  line-clamp: none;
  overflow: visible;
}
/* The .about .more rules are scoped to the description column, so the control
   is restyled here rather than by widening theirs. The default-hidden
   .less-label comes from the shared `.more .less-label` rule. */
.langs .more {
  display: block;
  margin-top: 6px;
  color: var(--accent);
  font-weight: 600;
  cursor: pointer;
}
.lang-toggle:focus-visible ~ .langs .more {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 6px;
}
.lang-toggle:checked ~ .langs .more .more-label {
  display: none;
}
.lang-toggle:checked ~ .langs .more .less-label {
  display: inline;
}
.genre-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  justify-content: flex-end;
}
.genre-tags .tag {
  font-size: 12px;
  font-weight: 600;
  color: var(--muted);
  background: var(--surface-2);
  border-radius: 6px;
  padding: 2px 8px;
}

/* --- keyword chips (app page) --- */
.kwchips {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}
.kwchip {
  display: inline-flex;
  align-items: baseline;
  gap: 8px;
  border: 1px solid var(--border);
  background: var(--surface);
  border-radius: 999px;
  padding: 6px 13px;
  text-decoration: none;
  color: inherit;
  font-size: 13px;
}
.kwchip:hover {
  border-color: var(--accent);
}
.kwchip .term {
  font-weight: 600;
}
.kwchip.title .term::before {
  content: "❝";
  color: var(--accent);
  margin-right: 5px;
  font-size: 12px;
}
.kwchip .freq {
  color: var(--faint);
  font-size: 12px;
  font-variant-numeric: tabular-nums;
}
.kwchip .rank {
  color: var(--accent);
  font-weight: 700;
  font-size: 12px;
  font-variant-numeric: tabular-nums;
}

/* --- app cards (similar apps, search results) --- */
.cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(232px, 1fr));
  gap: 12px;
}
.card {
  display: flex;
  gap: 12px;
  align-items: flex-start;
  border: 1px solid var(--border);
  border-radius: 14px;
  background: var(--surface);
  padding: 14px;
  text-decoration: none;
  color: inherit;
}
.card:hover {
  border-color: var(--accent);
}
.card .icon {
  width: 48px;
  height: 48px;
  border-radius: 11px;
}
.card-body {
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.card-name {
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.card-dev,
.card-meta {
  color: var(--muted);
  font-size: 12px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.card-meta {
  color: var(--faint);
}
.card-hint {
  color: var(--accent);
  font-size: 11px;
  font-weight: 600;
  margin-top: 2px;
}

/* --- keyword explorer --- */
.krow {
  display: grid;
  grid-template-columns: 1fr 92px 92px 84px 220px;
  align-items: center;
  gap: 10px;
  padding: 11px 16px;
  border-bottom: 1px solid var(--border);
}
.krow:last-child {
  border-bottom: none;
}
a.krow,
a.krow:visited {
  color: inherit;
  text-decoration: none;
}
a.krow:hover {
  background: var(--surface-2);
}
.krow.head {
  background: var(--surface-2);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--faint);
  font-weight: 700;
  padding: 9px 16px;
}
.krow .term {
  font-weight: 600;
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
}
.krow .term span.t {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.krow .num {
  font-variant-numeric: tabular-nums;
  text-align: right;
  color: var(--muted);
}
.krow .num.share {
  color: var(--faint);
}
.krow .topapp {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
  color: var(--muted);
  font-size: 12px;
}
.krow .topapp img {
  width: 22px;
  height: 22px;
  border-radius: 6px;
  flex-shrink: 0;
  background: var(--surface-2);
}
.krow .topapp span {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* --- site-wide feeds (/updates, /price-drops, /new-apps) ---
   Same table shell as the charts, one column narrower: an app, its category, and
   what changed. The change and its date share a cell so the date sits under the
   value it belongs to rather than in a column of its own. */
.frow {
  display: grid;
  grid-template-columns: 1fr 180px 170px;
  align-items: center;
  gap: 10px;
  padding: 10px 16px;
  border-bottom: 1px solid var(--border);
}
.frow:last-child {
  border-bottom: none;
}
a.frow,
a.frow:visited {
  color: inherit;
  text-decoration: none;
}
a.frow:hover {
  background: var(--surface-2);
}
.frow.head {
  background: var(--surface-2);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--faint);
  font-weight: 700;
  padding: 9px 16px;
}
.fd-change {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  min-width: 0;
  text-align: right;
}
.fd-detail {
  font-weight: 600;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.fd-date {
  color: var(--faint);
  font-size: 12px;
}
@media (max-width: 720px) {
  /* .col-cat is already hidden at this width, so the row is app + change. */
  .frow {
    grid-template-columns: 1fr 130px;
    padding: 10px 12px;
  }
}

/* pager */
.pager {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-top: 16px;
  font-size: 13px;
  color: var(--muted);
}
.pager .count {
  margin-left: auto;
  color: var(--faint);
  font-variant-numeric: tabular-nums;
}
.pager .disabled {
  opacity: 0.4;
  pointer-events: none;
}

/* --- keyword page --- */
.kw-head h1 {
  margin: 0;
  font-size: 30px;
  font-weight: 700;
  letter-spacing: -0.02em;
  display: inline;
}
.kw-head .term-quote {
  color: var(--faint);
  font-weight: 400;
}
.kw-flags {
  display: inline-flex;
  gap: 8px;
  margin-left: 12px;
  vertical-align: 5px;
}
.kw-sub {
  margin: 10px 0 0;
  color: var(--muted);
  font-size: 13px;
}
.stat-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 16px;
}
.stat-chip {
  border: 1px solid var(--border);
  background: var(--surface);
  border-radius: 10px;
  padding: 8px 12px;
  font-size: 12px;
  color: var(--muted);
  display: flex;
  align-items: baseline;
  gap: 8px;
}
.stat-chip .g {
  font-weight: 600;
  color: var(--fg);
}
.stat-chip .n {
  font-variant-numeric: tabular-nums;
}

/* the rank-over-time chart */
.chart-card {
  border: 1px solid var(--border);
  border-radius: 14px;
  background: var(--surface);
  box-shadow: var(--shadow);
  padding: 18px 18px 14px;
}
/* Stacked charts (the app page shows price above rating) need a gap; a single
   card is spaced by its section. */
.chart-card + .chart-card {
  margin-top: 14px;
}
/* Below ~700px the chart scrolls sideways instead of scaling its axis labels
   into illegibility. The card, not the page, does the scrolling. */
.chart-scroll {
  overflow-x: auto;
}
.rank-chart {
  display: block;
  width: 100%;
  min-width: 700px;
  height: auto;
}
.rc-grid {
  stroke: var(--border);
  stroke-width: 1;
}
.rc-axis {
  fill: var(--faint);
  font-size: 12px;
  font-family: inherit;
  font-variant-numeric: tabular-nums;
}
.rc-line {
  fill: none;
  stroke: var(--c);
  stroke-width: 2;
  stroke-linejoin: round;
  stroke-linecap: round;
}
.rc-dot {
  fill: var(--c);
  stroke: var(--surface);
  stroke-width: 2;
}
.slot1 {
  --c: var(--s1);
}
.slot2 {
  --c: var(--s2);
}
.slot3 {
  --c: var(--s3);
}
.slot4 {
  --c: var(--s4);
}
.slot5 {
  --c: var(--s5);
}
.slot6 {
  --c: var(--s6);
}
.slot7 {
  --c: var(--s7);
}
.slot8 {
  --c: var(--s8);
}
.legend {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 18px;
  margin-top: 14px;
  padding-top: 14px;
  border-top: 1px solid var(--border);
}
.legend-item {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  text-decoration: none;
  color: inherit;
  font-size: 13px;
  max-width: 100%;
}
.legend-item:hover .legend-name {
  color: var(--accent);
}
.legend-key {
  width: 14px;
  height: 3px;
  border-radius: 2px;
  background: var(--c);
  flex-shrink: 0;
}
.legend-item img {
  width: 20px;
  height: 20px;
  border-radius: 5px;
  background: var(--surface-2);
}
.legend-rank {
  color: var(--faint);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.legend-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.chart-note {
  color: var(--faint);
  font-size: 12px;
  margin: 12px 0 0;
}

/* Chart interaction. Every rule below is switched on by a class app.js adds, so
   with the script blocked the figure is exactly what the server sent: the
   crosshair and focus dots stay hidden and no control appears that does
   nothing. Dimming is never the only signal — the toggle carries a pressed
   state and the tooltip names every line. */
.rank-figure {
  position: relative;
}
.rc-series.is-dim .rc-line,
.rc-series.is-dim .rc-dot {
  opacity: 0.22;
}
.rc-series.is-focus .rc-line {
  stroke-width: 3;
}
.rc-series.is-off {
  display: none;
}
.rc-cross {
  stroke: var(--faint);
  stroke-width: 1;
  stroke-dasharray: 3 4;
  visibility: hidden;
  pointer-events: none;
}
.rc-focus {
  fill: var(--c);
  stroke: var(--surface);
  stroke-width: 2;
  visibility: hidden;
  pointer-events: none;
}
.rc-cross.is-on,
.rc-focus.is-on {
  visibility: visible;
}
/* Only once app.js has claimed the figure: a crosshair cursor over a surface
   that does nothing would be a dead control. */
.rank-figure.is-live .rc-surface {
  cursor: crosshair;
}
.rc-tip {
  position: absolute;
  z-index: 2;
  max-width: 280px;
  padding: 8px 10px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--surface);
  box-shadow: var(--shadow);
  font-size: 12px;
  pointer-events: none;
}
.rc-tip[hidden] {
  display: none;
}
.rc-tip-date {
  color: var(--faint);
  font-weight: 700;
  margin-bottom: 6px;
}
.rc-tip-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 1px 0;
  opacity: 0.62;
}
.rc-tip-row.is-focus {
  opacity: 1;
  font-weight: 600;
}
.rc-tip-key {
  width: 12px;
  height: 3px;
  border-radius: 2px;
  background: var(--c);
  flex-shrink: 0;
}
.rc-tip-rank {
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  min-width: 30px;
}
.rc-tip-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* The wrapper app.js puts around a legend link so the toggle can sit beside it
   instead of inside it. The bar stays 14x3 so the legend looks the same either
   way; the button around it is 18x18 so it can be hit. */
.legend-entry {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  max-width: 100%;
}
.legend-entry.is-dim {
  opacity: 0.42;
}
.legend-entry.is-off .legend-rank,
.legend-entry.is-off .legend-name {
  color: var(--faint);
  text-decoration: line-through;
}
.legend-toggle {
  appearance: none;
  -webkit-appearance: none;
  border: 0;
  background: none;
  padding: 0;
  width: 18px;
  height: 18px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  cursor: pointer;
}
.legend-toggle::before {
  content: "";
  width: 14px;
  height: 3px;
  border-radius: 2px;
  background: var(--c);
}
.legend-toggle[aria-pressed="false"]::before {
  background: var(--faint);
}
@media (prefers-reduced-motion: no-preference) {
  .rc-line,
  .rc-dot,
  .legend-entry {
    transition:
      opacity 120ms ease,
      stroke-width 120ms ease;
  }
}

/* current ranking list */
.ranklist {
  border: 1px solid var(--border);
  border-radius: 14px;
  overflow: hidden;
  background: var(--surface);
  box-shadow: var(--shadow);
}
.rankrow {
  display: grid;
  grid-template-columns: 44px 1fr 200px 120px;
  align-items: center;
  gap: 12px;
  padding: 10px 16px;
  border-bottom: 1px solid var(--border);
  text-decoration: none;
  color: inherit;
}
.rankrow:last-child {
  border-bottom: none;
}
.rankrow:hover {
  background: var(--surface-2);
}
.rankrow .rating-cell {
  text-align: right;
  color: var(--muted);
  font-size: 13px;
  font-variant-numeric: tabular-nums;
}

/* --- categories (/categories, /category/{slug}) ---
   The chip strip is the same control on the charts page and on a category page:
   plain links, wrapping, with the one you are on marked. It is a nav rather than
   a .segmented group because there are twenty-six of them and they cannot sit on
   one line. */
.catnav {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}
.catchip {
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--surface);
  padding: 5px 12px;
  font-size: 13px;
  color: var(--muted);
  text-decoration: none;
  white-space: nowrap;
}
.catchip:hover {
  border-color: var(--accent);
  color: var(--fg);
}
.catchip.active {
  border-color: var(--accent);
  background: var(--accent-bg);
  color: var(--accent);
  font-weight: 600;
}
.catnav-section {
  margin-top: 32px;
}

/* Three charts side by side; below ~900px they stack, because a Top 10 whose app
   names are all ellipsed is not a Top 10. */
.chartcols {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 16px;
}
.chartcols.two {
  grid-template-columns: repeat(2, 1fr);
}
@media (max-width: 900px) {
  .chartcols,
  .chartcols.two {
    grid-template-columns: 1fr;
  }
}
.chartcol .eyebrow {
  margin-bottom: 10px;
}
.toplist {
  border: 1px solid var(--border);
  border-radius: 14px;
  overflow: hidden;
  background: var(--surface);
}
.toprow {
  display: grid;
  grid-template-columns: 24px 52px 30px 1fr;
  align-items: center;
  gap: 8px;
  padding: 7px 12px;
  border-bottom: 1px solid var(--border);
  text-decoration: none;
  color: inherit;
}
.toprow:last-child {
  border-bottom: none;
}
.toprow:hover {
  background: var(--surface-2);
}
.tr-rank {
  color: var(--faint);
  font-size: 12px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  text-align: right;
}
.toprow .icon {
  width: 30px;
  height: 30px;
  border-radius: 7px;
}
.tr-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-weight: 500;
}
.toplist-empty {
  color: var(--faint);
  font-size: 13px;
  margin: 0;
  padding: 10px 0;
}

/* The hub's tiles borrow .cards/.card and say something else inside them: the
   category is the heading, the app leading it is the supporting line. */
.catcard .card-name {
  font-size: 15px;
}
.cat-lead {
  color: var(--muted);
  font-size: 12px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.cat-lead-dev {
  color: var(--faint);
  font-size: 12px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* --- 404 --- */
.nf {
  max-width: 560px;
  margin: 60px auto;
  text-align: center;
}
.nf h1 {
  font-size: 44px;
  margin: 0;
  letter-spacing: -0.03em;
}
.nf p {
  color: var(--muted);
  margin: 10px 0 24px;
}
.nf .search-field {
  max-width: 400px;
  margin: 0 auto 18px;
}
.nf-links {
  display: flex;
  gap: 10px;
  justify-content: center;
  flex-wrap: wrap;
}

/* --- footer --- */
.site-footer {
  border-top: 1px solid var(--border);
  background: var(--surface);
  margin-top: 40px;
}
.footer-inner {
  max-width: 1080px;
  margin: 0 auto;
  padding: 24px;
  display: flex;
  flex-wrap: wrap;
  gap: 14px 28px;
  align-items: baseline;
}
.footer-links {
  display: flex;
  gap: 18px;
  flex-wrap: wrap;
}
.footer-links a {
  color: var(--muted);
  text-decoration: none;
  font-weight: 600;
  font-size: 13px;
}
.footer-links a:hover {
  color: var(--accent);
}
.footer-note {
  color: var(--faint);
  font-size: 12px;
  max-width: 62ch;
  margin: 0;
  line-height: 1.5;
}

@media (max-width: 980px) {
  .krow {
    grid-template-columns: 1fr 78px 78px 72px;
  }
  .krow .topapp {
    display: none;
  }
  .rankrow {
    grid-template-columns: 40px 1fr 110px;
  }
  .rankrow .genre-cell {
    display: none;
  }
}
@media (max-width: 860px) {
  .standing {
    grid-template-columns: repeat(2, 1fr);
  }
  .cols {
    grid-template-columns: 1fr;
    gap: 32px;
  }
  .store-link {
    margin-left: 0;
  }
}
@media (max-width: 720px) {
  .home {
    padding: 16px 14px 56px;
    overflow-x: clip;
  }
  .page {
    padding: 20px 14px 56px;
    overflow-x: clip;
  }
  .site-header-inner {
    padding: 0 14px;
    gap: 10px;
  }
  .region-name {
    display: none;
  }
  .region-pick {
    padding: 0 10px;
  }
  /* Controls stack on mobile: each group takes a full-width row and its buttons
     share the width, instead of fighting to fit on one line. */
  .controls {
    flex-direction: column;
    align-items: stretch;
  }
  .spacer {
    display: none;
  }
  .segmented,
  .ctl-form,
  .select,
  .text-input,
  .check {
    width: 100%;
  }
  .seg {
    flex: 1;
    text-align: center;
  }
  .col-cat {
    display: none;
  }
  .row {
    grid-template-columns: 32px 52px 1fr 64px;
    gap: 8px;
    padding: 10px 12px;
  }
  .krow {
    grid-template-columns: 1fr 62px 62px;
  }
  .krow .col-share {
    display: none;
  }
  .footer-inner {
    padding: 20px 14px;
  }
}
@media (max-width: 520px) {
  .identity {
    flex-wrap: wrap;
    gap: 16px;
  }
  .app-icon {
    width: 84px;
    height: 84px;
    border-radius: 19px;
  }
  .id-body h1 {
    font-size: 23px;
  }
  /* Store link drops to its own full-width line below the identity. */
  .store-link {
    flex-basis: 100%;
    justify-content: center;
    margin-top: 2px;
  }
  .standing {
    grid-template-columns: 1fr 1fr;
    gap: 10px;
  }
  .rank-card {
    min-height: 156px;
    padding: 14px;
  }
  .rc-rank .num {
    font-size: 36px;
  }
  .shots img {
    height: 280px;
  }
  /* Too narrow for a date column: the date takes its own line and the change
     itself gets the full width below it. */
  .tl-date {
    flex-basis: 100%;
  }
  /* The breakdown keeps all four columns — dropping the count would lose the
     only absolute number in the section — so the fixed ones give the bar back
     what they can instead. */
  .star-row {
    grid-template-columns: 30px 1fr 36px 46px;
    gap: 8px;
    padding: 10px 12px;
  }
  /* The price column keeps its own line, but the note under it is free to wrap
     into the width the name gives back. */
  .iap-row {
    padding: 10px 12px;
    gap: 8px;
  }
  .iap-head {
    padding: 9px 12px;
  }
  .kw-head h1 {
    font-size: 24px;
  }
}
