/* catalog.css — the public catalog pages of extops.dev (EP-134).
 *
 * These pages are rendered by the Go API (services/api/internal/handler/catalogpages.go), not by
 * this static site, but they load the site's own landing.css first and add only what they need on
 * top. Keeping the extra rules in their own file means a card page cannot grow the stylesheet every
 * landing visitor downloads.
 *
 * Every colour, radius and duration below is a landing.css token. No literals: the theme toggle
 * flips one attribute on <html> and everything here has to follow it. */

.cat-main {
  padding-top: 12px;
  padding-bottom: 72px;
}

/* Breadcrumbs. Small, quiet, and the only navigation a reader arriving from search has. */
.cat-crumbs {
  padding-top: 18px;
  font-size: 13px;
  color: var(--fg2);
}

.cat-crumbs ol {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.cat-crumbs li + li::before {
  margin-right: 6px;
  content: '/';
  opacity: 0.5;
}

.cat-crumbs a {
  color: inherit;
}

/* Page head. */
.cat-head {
  max-width: 760px;
  padding-top: 22px;
}

.cat-head h1 {
  margin: 6px 0 0;
}

.cat-lead {
  margin-top: 12px;
  font-size: 17px;
  line-height: 1.55;
  color: var(--fg2);
}

.cat-updated {
  margin-top: 8px;
  font-size: 13px;
  color: var(--fg2);
}

/* Every section below the fold is a PANEL: surface, hairline, radius, the same treatment the fact
   tiles and the charts already had. Stacked headings on the page background read as one continuous
   stream, which is what made the card feel undifferentiated ("выглядят слепо", author 2026-09-08);
   a frame per section gives the eye somewhere to stop. */
.cat-section {
  margin-top: 20px;
  padding: 22px 24px 24px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 14px;
}

.cat-section h2 {
  margin: 0;
  font-size: 18px;
  line-height: 26px;
}

@media (max-width: 640px) {
  .cat-section {
    padding: 18px 16px 20px;
  }
}

/* Two sections that belong beside each other. auto-fit rather than a fixed pair: on a narrow
   window they stack, and a card with no version history leaves the survivor full width instead of
   half a row of nothing. */
.cat-section-pair {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(360px, 1fr));
  gap: 20px;
  align-items: start;
}

.cat-section-pair > .cat-section {
  margin-top: 20px;
}

/* WHEN A SECTION GETS A PANEL, and the answer is one rule (author 2026-09-08).
   A section whose content is prose, a quote or a list takes the panel: those have no shape of their
   own, and a striped row list on the bare page ground sinks into it - both are the same cream.
   A section whose content is ALREADY carded stays plain: framing a grid of tiles is a box in a box,
   which adds a border and no meaning. */
.cat-section--plain {
  padding: 0;
  background: none;
  border: 0;
  border-radius: 0;
}

.cat-section--plain + .cat-section--plain,
.cat-section--plain + .cat-section {
  margin-top: 44px;
}

.cat-section-lead {
  margin-top: 6px;
  max-width: 720px;
  color: var(--fg2);
}

.cat-empty,
.cat-note,
.cat-source {
  max-width: 760px;
  font-size: 13px;
  line-height: 1.6;
  color: var(--fg2);
}

.cat-source {
  margin-top: 40px;
}

.cat-more {
  margin-top: 18px;
}

/* Scenario tiles on the catalog index. */
.cat-tiles {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: 12px;
  margin: 22px 0 0;
  padding: 0;
  list-style: none;
}

.cat-tiles a {
  display: flex;
  flex-direction: column;
  gap: 6px;
  height: 100%;
  padding: 16px 18px;
  color: inherit;
  text-decoration: none;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 12px;
  transition: border-color var(--dur-fast) var(--ease-standard);
}

.cat-tiles a:hover {
  border-color: var(--accent);
}

.cat-tiles span {
  font-size: 13px;
  line-height: 1.5;
  color: var(--fg2);
}

/* Listing rows. One grid shared by the index, the curated lists and the alternatives page. */
.cat-rows {
  display: flex;
  flex-direction: column;
  margin: 22px 0 0;
  padding: 0;
  list-style: none;
  counter-reset: cat-rank;
}

.cat-row {
  display: grid;
  grid-template-columns: 32px minmax(0, 1fr) auto;
  gap: 14px;
  align-items: center;
  padding: 10px 12px;
  border-radius: 10px;
}

/* Rows are separated by a HAIRLINE, not by a zebra. The stripe filled every other row and left the
   rest taking the ground behind them, so on a light panel half the list looked like rows and half
   looked like gaps ("каждая вторая строка сливается с фоном", author 2026-09-08). A rule between
   equals is also what the version history and the change log on the same card already use. */
.cat-row + .cat-row {
  border-top: 1px solid var(--border);
}

.cat-row:hover {
  background: var(--surface2);
}

.cat-rows--ranked .cat-row {
  grid-template-columns: 26px 32px minmax(0, 1fr) auto;
}

.cat-rows--ranked .cat-row::before {
  font-variant-numeric: tabular-nums;
  color: var(--fg2);
  counter-increment: cat-rank;
  content: counter(cat-rank);
}

.cat-row-icon {
  width: 32px;
  height: 32px;
  border-radius: 7px;
  object-fit: cover;
}

.cat-row-icon--empty {
  background: var(--surface2);
  border: 1px solid var(--border);
}

.cat-row-name {
  min-width: 0;
}

.cat-row-name a {
  font-weight: 600;
  text-decoration: none;
}

.cat-row-name a:hover {
  text-decoration: underline;
}

.cat-row-meta {
  display: block;
  margin-top: 2px;
  overflow: hidden;
  font-size: 12px;
  color: var(--fg2);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.cat-row-nums {
  display: flex;
  flex-direction: column;
  gap: 1px;
  align-items: flex-end;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.cat-row-users {
  font-size: 13px;
  font-weight: 600;
}

.cat-row-rating {
  font-size: 12px;
  color: var(--fg2);
}

/* The card itself. */
/* The store tile rides beside the name and drops below the text on a narrow screen. It is
   decorative: alt is empty, and if Google will not serve it the layout simply closes up. */
.cat-card-tile {
  flex: 0 0 auto;
  width: 220px;
  height: auto;
  align-self: flex-start;
  border: 1px solid var(--border);
  border-radius: 10px;
}

@media (max-width: 720px) {
  .cat-card-tile {
    display: none;
  }
}

.cat-card-head {
  display: flex;
  gap: 20px;
  align-items: flex-start;
  padding-top: 22px;
}

/* The text column takes the slack so the tile sits at the right edge; min-width:0 lets a long
   title wrap instead of pushing the tile off the row. */
.cat-card-head > div {
  flex: 1 1 auto;
  min-width: 0;
}

.cat-card-icon {
  flex: none;
  width: 96px;
  height: 96px;
  border-radius: 18px;
  object-fit: cover;
}

/* The store link is a pictogram beside the name, not a button on a row of its own: it is one link
   out of the page and it was spending a full line. */
/* Set apart from the name and much smaller than it: at the heading's own size it read as the last
   glyph of the title. */
.cat-store-link {
  display: inline-flex;
  margin-left: 16px;
  font-size: 15px;
  color: var(--fg2);
  vertical-align: middle;
  border-radius: 6px;
}

/* The same pictogram beside a value rather than a heading: smaller, and it should not push the
   number's baseline around. */
.cat-store-link--inline {
  margin-left: 8px;
  font-size: 15px;
}

.cat-card-head h1 .cat-store-link {
  position: relative;
  top: -0.35em;
}

.cat-store-link:hover {
  color: var(--accent-text);
}

.cat-store-link:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}

.cat-card-head h1 {
  margin: 0;
  font-size: clamp(26px, 3.6vw, 38px);
}

.cat-card-by {
  margin: 6px 0 0;
  font-size: 14px;
  color: var(--fg2);
}

.cat-answer {
  max-width: 720px;
  margin-top: 12px;
  font-size: 17px;
  line-height: 1.55;
}

.cat-warn {
  color: var(--danger-text);
}

/* Fact table. The numbers a reader came to check, before any prose.
 *
 * Separate tiles rather than one bordered grid: the number of facts we can state differs from
 * listing to listing, so the last row is usually part-empty, and a shared-border grid renders
 * those leftovers as blank cells that look like missing data. */
.cat-facts {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(210px, 1fr));
  gap: 10px;
  margin: 28px 0 0;
}

.cat-facts > div {
  padding: 14px 16px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 10px;
}

/* On the page ground a tile is a white card; INSIDE a section panel that is white on white and the
   grid dissolves into it. There every tile takes the tinted ground instead. One rule for both tile
   grids on the catalog pages: the facts on a card and the signals on the safety page had the same
   defect and only one of them was fixed the first time. */
.cat-section .cat-facts > div,
.cat-section .cat-signals > div {
  background: var(--surface2);
  border-color: transparent;
}

/* The signal tiles keep their coloured edge - it is the whole point of the tile - and it has to
   stay visible against the tint. */
.cat-section .cat-signals > div {
  border-left-color: var(--border);
}

.cat-section .cat-signal--good {
  border-left-color: var(--success);
}

.cat-section .cat-signal--watch {
  border-left-color: var(--accent-fill);
}

.cat-facts dt {
  font-size: 12px;
  color: var(--fg2);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.cat-facts dd {
  margin: 4px 0 0;
  font-size: 17px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

.cat-facts-sub {
  font-size: 13px;
  font-weight: 400;
  color: var(--fg2);
}

.cat-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-top: 22px;
}

/* Dated history: what moved, and when. */
.cat-timeline {
  margin: 18px 0 0;
  padding: 0;
  list-style: none;
}

.cat-timeline li {
  display: grid;
  grid-template-columns: 110px minmax(0, 1fr);
  gap: 14px;
  padding: 9px 0;
  border-top: 1px solid var(--border);
}

.cat-timeline time {
  font-size: 13px;
  color: var(--fg2);
  font-variant-numeric: tabular-nums;
}

/* The call to action reuses the landing's final card, which is a two-column layout built around
   copy plus a form. Here it holds a heading, a line and a button, so it stacks instead. */
.cat-cta {
  display: block;
  margin-top: 48px;
}

.cat-cta p {
  max-width: 640px;
  margin: 10px 0 20px;
  color: var(--fg2);
}

@media (max-width: 640px) {
  /* Fact tiles go to ONE column on a phone. auto-fill with a 210px minimum still fitted two
     columns into a 350px container and pushed the page 26px sideways (measured 2026-09-08); a
     page body that scrolls horizontally is the one layout failure a reader cannot work around. */
  .cat-facts,
  .cat-listing,
  .cat-signals {
    grid-template-columns: minmax(0, 1fr);
  }

  /* The store tile rides beside the name and drops below the text on a narrow screen. It is
   decorative: alt is empty, and if Google will not serve it the layout simply closes up. */
.cat-card-tile {
  flex: 0 0 auto;
  width: 220px;
  height: auto;
  align-self: flex-start;
  border: 1px solid var(--border);
  border-radius: 10px;
}

@media (max-width: 720px) {
  .cat-card-tile {
    display: none;
  }
}

.cat-card-head {
    gap: 14px;
  }

  .cat-card-icon {
    width: 64px;
    height: 64px;
    border-radius: 14px;
  }

  .cat-row {
    grid-template-columns: 28px minmax(0, 1fr) auto;
    gap: 10px;
    padding: 9px 8px;
  }

  .cat-rows--ranked .cat-row {
    grid-template-columns: 20px 28px minmax(0, 1fr) auto;
  }

  .cat-timeline li {
    grid-template-columns: minmax(0, 1fr);
    gap: 2px;
  }
}

/* Compact variant of the tile grid: the permissions examples carry a listing name and one line of
   facts, so they need less room than a scenario card with editorial copy. */
.cat-tiles--compact {
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
}

.cat-tiles--compact a {
  padding: 13px 15px;
}

/* Charts (EP-134.T-05, reworked 2026-09-08 against the app's own chart primitives).
 *
 * Structure, class roles and behaviour are ported from packages/ui-dashboard/src/charts: the plot
 * canvas with a value axis on each side, HTML axis labels over a stretched SVG, a Catmull-Rom
 * curve, and a small bordered window picker. Two things are deliberately NOT ported: the hover
 * tooltip and the clickable legend, because both need scripting and this page is read by crawlers
 * and must work without it.
 *
 * Colours come from landing.css tokens, not from the dashboard's `--color-*` set: the two surfaces
 * have different palettes, and reaching for the app's variables here would silently fall back to
 * unstyled defaults.
 */
.cat-charts {
  margin-top: 40px;
}

/* Window picker, straight from the app's `uid-cm-modes`: ONE frame around the group, each option
   a borderless pill, the chosen one filled. Driven by radios so it needs no script and stays
   keyboard-reachable. The radios are siblings of both this group and the panels (see card.html):
   the CSS-only switch needs `~` to reach the panels, and with the radios inside the group the frame
   had to be drawn around the charts as well. */
.cat-windows {
  display: inline-flex;
  gap: 2px;
  align-items: center;
  margin-top: 16px;
  padding: 2px;
  font-size: 12px;
  background: var(--surface2);
  border: 1px solid var(--border);
  border-radius: 8px;
}

.cat-window-input {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
}

.cat-window-label {
  padding: 4px 12px;
  color: var(--fg2);
  cursor: pointer;
  border-radius: 6px;
}

/* Paired by ID, not by position or by ancestor. A positional pairing tied the stylesheet to one
   exact nesting and to there being exactly two windows: a card that only has the ninety-day window
   would have matched neither rule and shown NO chart, and during a deploy a browser holding the
   previous HTML (the card is cached for half an hour) plus the new CSS did the same. The ids are
   fixed and unique on the page, so this rule only needs the panel to be a sibling of its radio. */
#win30:checked ~ .cat-windows > .cat-window-label[for='win30'],
#win90:checked ~ .cat-windows > .cat-window-label[for='win90'] {
  font-weight: 600;
  color: var(--fg);
  background: var(--surface);
  box-shadow: var(--sh-sm);
}

.cat-window-input:focus-visible ~ .cat-windows {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.cat-window {
  display: none;
  margin-top: 16px;
}

#win30:checked ~ .cat-window[data-window='win30'],
#win90:checked ~ .cat-window[data-window='win90'] {
  display: grid;
  gap: 18px;
}

/* Inside the section panel a figure needs no frame of its own: two nested borders is a box in a
   box. What separates the two charts is a hairline. */
.cat-chart {
  margin: 0;
  padding: 0;
}

.cat-chart + .cat-chart {
  padding-top: 18px;
  border-top: 1px solid var(--border);
}

.cat-chart-title {
  font-size: 12px;
  color: var(--fg2);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

/* Title row: the name of the figure and how far the series moved over the window, the way the
   app's chart head carries its delta chip. */
.cat-chart-head {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 10px;
  align-items: baseline;
}

.cat-chart-delta {
  margin-left: auto;
  font-size: 13px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

/* What the change is measured from. Without it the figure in the corner is a number with no
   question attached, which is how "-0.00" got there and stayed unnoticed. */
.cat-chart-since {
  font-size: 11px;
  font-weight: 400;
  color: var(--fg2);
}

/* Legend names the series; the hint on the right states the range and gives way to the hovered
   reading. Static: in the app the legend doubles as a layer switch, which needs more script than a
   public card should carry. */
.cat-chart-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 14px;
  justify-content: space-between;
  align-items: center;
  min-height: 16px;
  margin: 6px 0 0;
  padding: 0;
  font-size: 12px;
  color: var(--fg2);
  list-style: none;
}

.cat-chart-series {
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
}

.cat-chart-hint {
  margin-left: auto;
  font-variant-numeric: tabular-nums;
}

.cat-chart-legend li::before,
.cat-chart-series > span::before {
  display: inline-block;
  width: 9px;
  height: 9px;
  margin-right: 6px;
  content: '';
  border-radius: 2px;
}

.cat-legend--reviews::before {
  background: var(--fg2);
}

/* The hovered reading takes the hint's place; without script it never appears. */
.cat-chart-hover {
  display: none;
  margin-left: auto;
  font-variant-numeric: tabular-nums;
}

.cat-chart--hovering .cat-chart-hint {
  display: none;
}

.cat-chart--hovering .cat-chart-hover {
  display: inline;
}

.cat-legend--rating::before,
.cat-legend--users::before {
  background: var(--accent-fill);
}

/* Canvas: axis, plot, axis. The axes are HTML because the SVG stretches with
   preserveAspectRatio="none" and text inside it would stretch with the paths. */
.cat-chart-canvas {
  position: relative;
  display: flex;
  gap: 8px;
  align-items: stretch;
  height: 150px;
  margin-top: 10px;
}

.cat-chart-plotarea {
  position: relative;
  flex: 1 1 auto;
  min-width: 0;
}

.cat-chart-svg {
  display: block;
  width: 100%;
  height: 100%;
}

.cat-chart-yaxis {
  position: relative;
  flex: 0 0 46px;
  font-size: 11px;
  color: var(--fg2);
  font-variant-numeric: tabular-nums;
}

.cat-chart-yaxis span {
  position: absolute;
  right: 0;
  transform: translateY(-50%);
  white-space: nowrap;
}

.cat-chart-yaxis.is-right span {
  right: auto;
  left: 0;
}

/* The extreme labels are pulled inside the box so the top and bottom figures cannot clip. */
.cat-chart-yaxis span:first-child {
  transform: translateY(-10%);
}

.cat-chart-yaxis span:last-child {
  transform: translateY(-90%);
}

.cat-chart-line {
  fill: none;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}

.cat-series--primary,
.cat-series--rating {
  stroke: var(--accent-fill);
}

.cat-series--reviews {
  stroke: var(--fg2);
}

/* Areas are FILL ONLY. The series rules above set a stroke, and without this the fill got the same
   stroke and drew a hard edge down the side of the chart. */
.cat-chart-area {
  stroke: none;
}

.cat-chart-area.cat-series--primary,
.cat-chart-area.cat-series--rating {
  fill: var(--soft-accent);
  stroke: none;
}

.cat-chart-area.cat-series--reviews {
  fill: color-mix(in srgb, var(--fg2) 12%, transparent);
  stroke: none;
}

.cat-chart-grid {
  stroke: var(--border);
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
}

/* A milestone is a thin dashed rule, toned by what the STORE did: a release or a badge gained is
   accent, a badge lost or a delisting is danger, a first sighting neutral. Never a judgement of the
   developer, exactly as the app's ChartMilestones puts it. */
.cat-chart-mark {
  stroke-width: 1;
  stroke-dasharray: 3 3;
  vector-effect: non-scaling-stroke;
}

.cat-mark--accent {
  stroke: var(--accent);
  color: var(--accent-text);
}

.cat-mark--danger {
  stroke: var(--danger);
  color: var(--danger-text);
}

.cat-mark--muted {
  stroke: var(--fg2);
  color: var(--fg2);
}

/* Dates sit at the fraction of the width where their day is, on the same scale as the dots.
   The INSET lives on the wrapper, not on the positioned row: percentages of an absolutely
   positioned child resolve against its container's PADDING box, so a padding here shifted every
   label left of the point it names by the width of the value axis. Same split the app uses between
   .uid-chart-axisrow and .uid-chartaxis. */
.cat-chart-axisrow {
  margin: 4px 0 0;
}

.cat-chart.has-yaxis .cat-chart-axisrow {
  padding-left: 54px;
}

.cat-chart.has-yaxis-right .cat-chart-axisrow {
  padding-right: 54px;
}

.cat-chart-xaxis {
  position: relative;
  height: 15px;
  font-size: 11px;
  color: var(--fg2);
  font-variant-numeric: tabular-nums;
}

.cat-chart-xaxis span {
  position: absolute;
  top: 0;
  white-space: nowrap;
}

/* Only the browser knows how wide the plot ended up, so thinning the labels is CSS's job: seven
   dates fit a desktop card and collide on a phone. Dropping every other one keeps the first and
   the last, which are the two a reader looks for. */
@media (max-width: 640px) {
  .cat-chart-xaxis span:nth-child(even) {
    display: none;
  }
}

/* Observations, drawn as HTML circles over the plot: the svg stretches to the container, so a
   circle inside it would come out an oval. Every point we recorded gets one, which is also how the
   card shows honestly how often it has anything to report. */
.cat-chart-dots {
  position: absolute;
  inset: 0;
  pointer-events: none;
}

.cat-chart-dot {
  position: absolute;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  transform: translate(-50%, -50%);
  background: var(--accent-fill);
}

.cat-chart-dot.cat-series--reviews {
  background: var(--fg2);
}

/* A day the numbers held gets a quieter mark than a day they moved: the series still reads as the
   continuous daily record it is, and the days that actually moved are the ones the eye lands on. */
.cat-chart-dot.is-still {
  width: 3px;
  height: 3px;
  opacity: 0.4;
}

/* On a phone a ninety-day window puts these a few pixels apart, which is a dotted texture rather
   than a set of marks. The readings stay; the quiet days step back. */
@media (max-width: 640px) {
  .cat-chart-dot.is-still {
    display: none;
  }
}

/* The dot under the cursor, and the rule that carries it down to the date axis. */
.cat-chart-dot.is-active {
  width: 9px;
  height: 9px;
  box-shadow: 0 0 0 2px var(--surface);
}

.cat-chart-cursor {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 1px;
  background: var(--fg2);
  opacity: 0.45;
  pointer-events: none;
}

.cat-chart-plotarea.is-live {
  cursor: crosshair;
}

/* Tooltip riding the cursor, the app's ChartTooltip in landing tokens. pointer-events:none keeps it
   clear of the plot's own pointer tracking. */
.cat-chart-tip {
  position: absolute;
  z-index: 6;
  min-width: 84px;
  padding: 6px 8px;
  font-size: 12px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 8px;
  box-shadow: var(--sh-md);
  pointer-events: none;
}

.cat-chart-tip .cat-reading-date {
  display: block;
  margin-bottom: 4px;
  font-size: 11px;
  color: var(--fg2);
  white-space: nowrap;
}

/* "no change" rides on the tooltip's date line rather than taking a line of its own: it qualifies
   the date, and the card should stay the size of the answer it gives. */
.cat-chart-tip .cat-reading-still {
  color: var(--fg2);
  opacity: 0.8;
}

.cat-chart-tip .cat-reading-row {
  display: flex;
  gap: 8px;
  align-items: center;
  line-height: 1.5;
  white-space: nowrap;
}

.cat-chart-tip .cat-reading-row::before {
  display: inline-block;
  flex: 0 0 auto;
  width: 8px;
  height: 8px;
  content: '';
  border-radius: 2px;
  background: var(--accent-fill);
}

.cat-chart-tip .cat-reading-row.cat-series--reviews::before {
  background: var(--fg2);
}

.cat-chart-tip .cat-reading-name {
  color: var(--fg2);
}

.cat-chart-tip .cat-reading-val {
  margin-left: auto;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

.cat-chart-tip .cat-reading-mark {
  display: block;
  max-width: 200px;
  margin-top: 4px;
  font-size: 11px;
  color: var(--accent-text);
  white-space: normal;
}

/* Every reading as text: the accessible copy of what the hover shows, and the source the hover
   script reads. Visually hidden rather than absent, so it is in the page for a crawler too. */
.cat-chart-readings {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  white-space: nowrap;
  border: 0;
  clip-path: inset(50%);
}

.cat-up {
  color: var(--success-text);
}

.cat-down {
  color: var(--danger-text);
}

/* The store's own description and screenshots, revealed on demand. The disclosure is the same
   native one every other long block uses. */
.cat-storelisting {
  display: block;
  margin-top: 18px;
}

.cat-storelisting > summary {
  /* list-item, not inline-block: inline-block drops the disclosure triangle in Chrome, and the
     triangle is what says this opens. */
  display: list-item;
  width: fit-content;
  font-size: 14px;
  color: var(--accent-text);
  cursor: pointer;
}

/* The store's material sits on its own ground. Two reasons, and the second is the important one:
   a long stretch of borrowed prose on the page ground runs into our own copy above it, and the
   panel says plainly where our words stop and the developer's begin. */
.cat-storelisting-slot {
  margin-top: 12px;
  padding: 18px 20px;
  background: var(--surface2);
  border: 1px solid var(--border);
  border-radius: 12px;
}

@media (max-width: 640px) {
  .cat-storelisting-slot {
    padding: 14px;
  }
}

.cat-storelisting-desc {
  max-width: 70ch;
  font-size: 14px;
  line-height: 1.65;
}

.cat-storelisting-desc p {
  margin: 0 0 10px;
}

/* Thumbnails in a row that scrolls sideways rather than reflowing the page: the count varies from
   one to eight and a grid of eight would push everything below it off the screen. */
.cat-shots {
  display: flex;
  gap: 10px;
  margin: 0 0 16px;
  padding: 0 0 6px;
  overflow-x: auto;
  list-style: none;
  /* The browser's own scrollbar is painted for the browser's theme, not ours: on a dark page it
     came out a bright white bar under the thumbnails. Both properties are needed - the standard
     one for Firefox and current Chrome, the -webkit- pseudo-elements for the rest. */
  scrollbar-width: thin;
  scrollbar-color: var(--border) transparent;
}

.cat-shots::-webkit-scrollbar {
  height: 8px;
}

.cat-shots::-webkit-scrollbar-track {
  background: transparent;
}

.cat-shots::-webkit-scrollbar-thumb {
  background: var(--border);
  border-radius: 999px;
}

.cat-shots::-webkit-scrollbar-thumb:hover {
  background: var(--fg2);
}

.cat-shots li {
  flex: 0 0 auto;
}

.cat-shots img {
  display: block;
  width: 200px;
  height: auto;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 8px;
}

/* The screenshot viewer. A native <dialog>, so Esc, focus handling and the backdrop are the
   browser's job and not ours. */
.cat-shotview {
  max-width: min(1100px, 92vw);
  max-height: 92vh;
  padding: 12px;
  color: var(--fg);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 14px;
  box-shadow: var(--sh-md);
}

.cat-shotview::backdrop {
  background: rgba(20, 16, 10, 0.62);
}

.cat-shotview-img {
  display: block;
  max-width: 100%;
  max-height: calc(92vh - 92px);
  margin: 0 auto;
  border-radius: 8px;
}

/* Where the shot came from. It is the point of showing it here rather than in a bare tab: the
   reader can go to the listing instead of landing on an image with no way back. */
.cat-shotview-store {
  display: block;
  margin-top: 12px;
  font-size: 13px;
  text-align: center;
}

.cat-shotview-close {
  display: flex;
  justify-content: flex-end;
  margin-bottom: 6px;
}

.cat-shotview-close button {
  width: 30px;
  height: 30px;
  font-size: 18px;
  line-height: 1;
  color: var(--fg2);
  cursor: pointer;
  background: var(--surface2);
  border: 1px solid var(--border);
  border-radius: 8px;
}

.cat-shot {
  display: block;
  cursor: zoom-in;
}

/* Who publishes it. The NAME and the audience are what a reader is scanning for, so they carry the
   weight; the rest of the sentence is context around them. */
.cat-publisher-name {
  font-size: 16px;
}

.cat-publisher-site {
  white-space: nowrap;
}

/* Long lists show a few rows and put the rest behind a native disclosure: no script, keyboard
   support and find-in-page come for free. */
.cat-more-block {
  margin-top: 6px;
}

.cat-more-block > summary {
  padding: 8px 0;
  font-size: 13px;
  color: var(--accent-text);
  cursor: pointer;
}

/* Permissions in plain language. The manifest's own word stays beside every line: it is what a
   reader can verify against the extension itself. */
.cat-perms {
  margin: 18px 0 0;
  padding: 0;
  list-style: none;
}

.cat-perms li {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 10px;
  align-items: baseline;
  padding: 10px 0;
  border-top: 1px solid var(--border);
}

.cat-perms strong {
  font-weight: 500;
}

.cat-perm--broad strong {
  font-weight: 700;
}

.cat-perms code,
.cat-hosts code {
  padding: 1px 6px;
  font-size: 12px;
  color: var(--fg2);
  background: var(--surface2);
  border-radius: 5px;
}

.cat-hosts {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 14px 0 0;
  padding: 0;
  list-style: none;
}

.cat-answer--access {
  margin-top: 8px;
  font-size: 15px;
  color: var(--fg2);
}

@media (max-width: 720px) {
  .cat-chart {
    grid-template-columns: minmax(0, 1fr);
    gap: 14px;
  }

  /* The three numbers are a nowrap row; on a phone "7 000 000 / +1 000 000 / 7 000 000 2026-09-07"
     is wider than the screen and pushed the page 26px sideways (measured 2026-09-08). They wrap
     here rather than shrink: these are the numbers the section exists for. */
  .cat-chart-nums {
    flex-wrap: wrap;
    gap: 14px 18px;
  }
}

/* Safety signals: facts side by side, with a quiet mark for the reassuring ones and the ones worth
   a second look. Colour is never the only carrier — every signal states its value in words. */
.cat-signals {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: 10px;
  margin: 22px 0 0;
}

.cat-signals > div {
  padding: 14px 16px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-left-width: 3px;
  border-radius: 10px;
}

.cat-signal--good {
  border-left-color: var(--success);
}

.cat-signal--watch {
  border-left-color: var(--accent-fill);
}

.cat-signals dt {
  font-size: 12px;
  color: var(--fg2);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.cat-signals dd {
  margin: 5px 0 0;
  font-size: 15px;
  line-height: 1.5;
}

/* The worked example of a meaning-based request. Set as a quotation because that is what it is:
   the reader's words, not ours. */
.cat-demo-need {
  max-width: 720px;
  margin: 20px 0 0;
  padding: 14px 20px;
  font-size: 18px;
  line-height: 1.5;
  border-left: 3px solid var(--accent-fill);
}

/* What a curated list actually holds, on its tile. Set apart from the teaser: the teaser is our
   sentence about the scenario, this is measured from the list itself. */
.cat-tile-meta {
  margin-top: 8px;
  padding-top: 8px;
  font-size: 12px;
  color: var(--fg2);
  border-top: 1px solid var(--border);
}

/* Store badges. Only the ones the store actually shows are rendered: absence is never printed as a
   negative, because a listing without a "good record" badge has not been marked bad by anyone. */
.cat-badges {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin: 10px 0 0;
  padding: 0;
  list-style: none;
}

.cat-badges li {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 3px 10px;
  font-size: 12px;
  color: var(--accent-text);
  background: var(--soft-accent);
  border-radius: 999px;
}

/* The two rows the STORE awards carry the store's own shape and blue, so a reader recognises them
   from the listing rather than reading them as our verdict. --cws-blue is deliberately a foreign
   brand colour and is used for nothing else on the site. The trader row is a publisher declaration,
   not an award, so it keeps the plain accent chip. */
.cat-badges .cat-badge--featured,
.cat-badges .cat-badge--publisher {
  color: var(--fg);
  background: var(--surface2);
  border: 1px solid var(--border);
}

.cat-badge-icon {
  flex: 0 0 auto;
  color: var(--cws-blue);
}

.cat-badge--featured .cat-badge-icon {
  color: var(--cws-green);
}

/* What the listing is made of. Each fact carries its meaning as TEXT, not as a hover tooltip:
   a tooltip is invisible on a phone and to an answer engine. */
.cat-listing {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  gap: 10px;
  margin: 22px 0 0;
}

.cat-listing > div {
  padding: 14px 16px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 10px;
}

.cat-listing dt {
  font-size: 12px;
  color: var(--fg2);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.cat-listing dd {
  margin: 4px 0 0;
  font-size: 16px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

.cat-listing-hint {
  font-size: 12px;
  font-weight: 400;
  line-height: 1.5;
  color: var(--fg2);
}
