/* ============================================================================
   Browse - the public bot directory, in BACKBONE.

   Closer to Operate than the landing page is: this is a list people scan, so
   density and legibility outrank expression. The hex vocabulary enters exactly
   once per record, as the bot's mark, where it carries identity without
   costing a single scan line. Everything else is a square, outlined record.

   Builds on home.css (chrome, .btn, .wrap, .lede, .empty) and on the tokens
   backbone.css declares. No palette of its own.
   ========================================================================= */

/* `padding-block`, not the `padding` shorthand — see home.css's `.doc-head` comment (2026-08-04
   review): a 3-value shorthand's middle `0` sets left AND right, silently zeroing the horizontal
   inset `.wrap` (the other class on this element) supplies. `padding-block` only ever touches
   top/bottom, so this can't regress into the same bug by omission. */
.browse-head { padding-block: clamp(52px, 7vw, 92px) clamp(20px, 3vw, 34px); }
.browse-head h1 { text-transform: uppercase; letter-spacing: -0.02em; font-weight: 800; }
.browse-head .lede { max-width: 58ch; }

/* ══ TOOLS ═════════════════════════════════════════════════════════════════ */
.browse-tools {
  display: flex; flex-wrap: wrap; gap: 10px; align-items: center;
  margin-top: 26px;
  padding-bottom: 22px;
  border-bottom: 1px solid var(--line);
}

.search {
  flex: 1 1 260px; min-width: 220px;
  display: flex; align-items: center; gap: 10px;
  padding: 0 14px;
  border: 1px solid var(--control-border);
  background: var(--slab);
  transition: border-color 160ms ease, background-color 160ms ease;
}
.search:hover { border-color: var(--text); }
.search:focus-within { border-color: var(--sig); outline: 2px solid var(--sig); outline-offset: 1px; }
.search svg { color: var(--sig); flex: 0 0 auto; }
.search input {
  flex: 1; min-width: 0;
  border: 0; background: transparent;
  color: var(--text);
  font: 400 14.5px/1.5 var(--sans);
  padding: 13px 0;
}
.search input:focus { outline: none; }
/* --faint is only 3.4:1 against the brightest band; placeholders are read, so
   they take --muted (5.5:1 dark, 5.4:1 light). */
.search input::placeholder { color: var(--muted); opacity: 1; }
.search input::-webkit-search-cancel-button { filter: grayscale(1); cursor: pointer; }

.sort-control select {
  appearance: none;
  min-height: 48px;
  padding: 0 40px 0 14px;
  border: 1px solid var(--control-border);
  background: var(--slab);
  color: var(--text);
  font: 600 11px/1 var(--mono);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  cursor: pointer;
  background-image:
    linear-gradient(45deg, transparent 50%, currentColor 50%),
    linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-position: calc(100% - 21px) 21px, calc(100% - 15px) 21px;
  background-size: 6px 6px, 6px 6px;
  background-repeat: no-repeat;
  transition: border-color 160ms ease, color 160ms ease;
}
.sort-control select:hover { border-color: var(--text); }
.sort-control select:focus-visible { outline: 2px solid var(--sig); outline-offset: 1px; border-color: var(--sig); }
.sort-control option { background: var(--surface); color: var(--text); }

.count {
  margin-left: auto;
  font: 500 10.5px/1 var(--mono);
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--muted);
}

/* ══ CATEGORY FACETS ═══════════════════════════════════════════════════════
   A row of toggles under the search/sort bar, built from the tags the bots
   actually carry (browse.js `renderFacets`). They read as the same family as
   `.card-tag-chip` — mono, uppercase, hairline border — because they name the
   same thing; the difference is that these are pressable, so they get the
   control border, the hover, and a filled pressed state that survives forced
   colours. */
.facets {
  display: flex; flex-wrap: wrap; gap: 8px;
  padding: 16px 0 0;
}
/* An author `display` OUTRANKS the UA's `[hidden] { display: none }`, and browse.js toggles both
   of these with the attribute. Without this the facet row and the featured grid would be visible
   the whole time, empty, above the skeletons. */
.facets[hidden], .featured-grid[hidden] { display: none !important; }
.facet {
  display: inline-flex; align-items: center; gap: 8px;
  min-height: 32px;
  padding: 0 11px;
  border: 1px solid var(--control-border);
  background: transparent;
  color: var(--muted);
  font: 500 10.5px/1 var(--mono);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  cursor: pointer;
  transition: border-color 140ms ease, color 140ms ease, background-color 140ms ease;
}
.facet:hover { border-color: var(--text); color: var(--text); }
.facet:focus-visible { outline: 2px solid var(--sig); outline-offset: 1px; border-color: var(--sig); }
.facet[aria-pressed="true"] {
  border-color: var(--sig);
  background: color-mix(in srgb, var(--sig) 12%, transparent);
  color: var(--text);
}
.facet-count {
  padding-left: 8px;
  border-left: 1px solid var(--line);
  color: var(--faint);
}
.facet[aria-pressed="true"] .facet-count { color: var(--muted); }

/* ══ FEATURED ══════════════════════════════════════════════════════════════
   Same card grid as the directory below it — deliberately. A visually distinct
   "featured" treatment would be a badge, and the rule this row runs on is a
   count anyone can check, not an endorsement. The stated rule sits directly
   under the heading so the row explains itself. */
.featured-rule {
  margin: -8px 0 16px;
  max-width: 60ch;
  font: 400 13px/1.6 var(--sans);
  color: var(--muted);
}
.featured-grid { padding-bottom: clamp(24px, 3vw, 36px); }

/* ══ SECTION HEADINGS ══════════════════════════════════════════════════════ */
.browse-section-title {
  margin: clamp(30px, 4vw, 44px) 0 18px;
  font: 700 12px/1.35 var(--mono);
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--muted);
}
#yours-wrap { margin-bottom: 6px; }

/* ══ GRID ══════════════════════════════════════════════════════════════════ */
.grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 16px;
  padding-bottom: clamp(56px, 8vw, 90px);
}

/* THE PUBLIC GRID RESERVES ONE ROW BEFORE IT HAS ANY CARDS.
   This directory is rendered client-side from GET /api/public-bots, so `#grid`
   is 0px tall at first paint and everything after it — the teaching panel, the
   footer, the whole document below — sits high and then drops when the response
   lands. Measured with Lighthouse: CLS 0.38 in production and 0.337 locally,
   which is the single failing audit on this page and the worst layout shift
   left on the site.
   One card's height (~206px) is the honest reservation: the directory always
   renders either at least one card or the empty state, and both are about that
   tall, so the common case shifts by nothing. It is a floor, not a fixed
   height — more cards simply grow past it.
   `#yours-grid` is deliberately NOT given one: it is hidden unless the visitor
   is signed in AND has bots, so reserving space for it would open a permanent
   206px hole on the page every signed-out visitor sees. */
#grid { min-height: 206px; }

/* ══ RECORD ════════════════════════════════════════════════════════════════ */
.card {
  position: relative;
  display: flex; flex-direction: column;
  border: 1px solid var(--line);
  background: var(--slab);
  color: inherit;
  text-decoration: none;
  transition: border-color 180ms ease, background-color 180ms ease;
}
.card:hover { border-color: var(--sig); background: var(--slab-hi); }
.card:focus-within { border-color: var(--sig); }

/* The bot's own accent, straight from its config: the one place per record
   where colour is data rather than decoration. */
.card-accent { height: 3px; flex: none; }

.card-body { padding: 18px 18px 20px; display: flex; flex-direction: column; gap: 11px; flex: 1; }
.card-top { display: flex; align-items: center; gap: 13px; }

/* The one curve in the directory. browse.js paints the fill from the bot's
   accent and the ink from color.js accentForeground, so both stay per-bot. */
.card-logo {
  width: 42px; height: 48.5px;         /* a true pointy-top hexagon: w * 2/root3 */
  flex: 0 0 auto;
  display: grid; place-items: center;
  clip-path: var(--hex-clip);
  font: 700 16px/1 var(--mono);
  text-decoration: none;
  overflow: hidden;
}
.card-logo img { width: 100%; height: 100%; object-fit: cover; }

/* inline-flex + min-height, not padding: the name sits directly above the
   description, so padding would open a gap in the card's vertical rhythm. This
   grows the hit area to the WCAG 2.2 target-size floor (2.5.8, 24 x 24) while
   the text keeps its position. At 1.05rem/1.2 the line box is only 20.2px, which
   is what axe was failing on. */
.card-name {
  display: inline-flex;
  align-items: center;
  min-height: 24px;
  font-size: 1.05rem; font-weight: 700; line-height: 1.2;
  letter-spacing: -0.01em;
  color: var(--text);
  text-decoration: none;
}
.card:hover .card-name { color: var(--sig-text); }

/* The logo/name are their own links, stacked above the card-chat overlay so they win their own clicks. */
.card-profile { position: relative; z-index: 1; }
.card-profile:focus-visible { outline: 2px solid var(--sig); outline-offset: 3px; }

/* Stretched-link overlay: a click anywhere on the card body (below the logo/name) opens the chat.
   The anchor itself (not just a ::after) covers the card so its focus ring is visible on Tab. */
.card-chat, .card-chat::after { position: absolute; inset: 0; z-index: 0; }
.card-chat::after { content: ''; }
.card-chat:focus-visible { outline: 2px solid var(--sig); outline-offset: -3px; }

.card-tag { margin: 0; color: var(--muted); font-size: 14px; line-height: 1.55; }
.card-tags { display: flex; flex-wrap: wrap; gap: 6px; }
.card-tag-chip {
  padding: 3px 9px;
  border: 1px solid var(--line);
  font: 500 10px/1.5 var(--mono);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
}
.card-rating { font: 600 12.5px/1 var(--mono); color: var(--text); letter-spacing: 0.06em; }

.card-foot {
  margin-top: auto;
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding-top: 12px;
  border-top: 1px solid var(--line);
}
.card-kb {
  font: 500 10px/1.4 var(--mono);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--muted);
}
.card-open {
  display: inline-flex; align-items: center; gap: 6px;
  font: 700 10px/1 var(--mono);
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--sig);
  white-space: nowrap;
}

/* ══ LOADING ═══════════════════════════════════════════════════════════════
   A skeleton shaped like the record it becomes: hex mark, name, two prose
   lines, a foot rule. Not a centred spinner. */
.card-skeleton { pointer-events: none; }
.card-skeleton .card-accent { background: var(--line); }
.sk-hex { width: 42px; height: 48.5px; flex: none; clip-path: var(--hex-clip); }
.sk-line { height: 11px; }
.sk-name { height: 15px; width: 58%; }
.sk-w70 { width: 70%; }
.sk-w45 { width: 45%; }

/* ══ EMPTY ═════════════════════════════════════════════════════════════════ */
.empty { grid-column: 1 / -1; }

/* ══ PRIMER ════════════════════════════════════════════════════════════════
   Unhidden by browse.js only when the directory holds zero or one public bot.
   A search box over a vast blank canvas reads as a product that failed rather
   than one that is new, so the canvas teaches instead: what a listing is, the
   three steps that earn one, and the way in.

   Deliberately NOT card-shaped — no accent bar, no hex mark, no foot rule.
   Anything card-shaped in this column is read as a bot, and there is no bot to
   read; the panel has to be visibly the platform speaking. */
.primer {
  border: 1px solid var(--line);
  background: var(--slab);
  padding: clamp(24px, 3.4vw, 38px);
  /* The tail air `.grid` gives up while the primer is showing (see .has-primer
     below): the empty block and the panel explaining it are one thought, and
     two stacked bottom paddings opened a canyon between them. */
  margin-bottom: clamp(56px, 8vw, 90px);
}
/* No `display` here on purpose: the panel is toggled with the `hidden`
   attribute, and any display declaration on this selector would outrank it. */
#public-wrap.has-primer .grid { padding-bottom: clamp(20px, 3vw, 28px); }

.primer .eyebrow { margin-bottom: 14px; }
.primer-title { font-size: clamp(1.3rem, 2.2vw, 1.75rem); margin-bottom: 12px; }
.primer-lede { margin: 0; max-width: 64ch; color: var(--muted); font-size: 14.5px; line-height: 1.62; }

/* auto-fit rather than a breakpoint: at 390px the inner width is ~306px, so the
   three steps fold to one column on their own without a rule that could drift
   out of step with the .grid breakpoints above. */
.primer-steps {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  gap: 16px;
  margin: clamp(22px, 3vw, 30px) 0 0;
  padding: 0;
  list-style: none;
}
.primer-steps li { padding-top: 14px; border-top: 1px solid var(--line); }
.primer-step-n {
  display: block;
  margin-bottom: 10px;
  font: 700 10.5px/1 var(--mono);
  letter-spacing: 0.22em;
  color: var(--sig);
}
.primer-steps h3 { margin-bottom: 6px; font-size: 1rem; }
.primer-steps p { margin: 0; color: var(--muted); font-size: 13.5px; line-height: 1.6; }

/* Illustrative copy that says so, in prose, with a label on it. The directory
   must never imply inventory it does not have, so an example is a sentence and
   never a record. */
.primer-eg { margin: clamp(20px, 2.6vw, 26px) 0 0; max-width: 68ch; color: var(--muted); font-size: 13.5px; line-height: 1.6; }
.primer-eg-label {
  margin-right: 8px;
  font: 700 10px/1.6 var(--mono);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--text);
}

.primer-cta { display: flex; flex-wrap: wrap; gap: 10px; margin-top: clamp(22px, 3vw, 28px); }

/* ══ RESPONSIVE ════════════════════════════════════════════════════════════ */
@media (max-width: 900px) {
  .grid { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 560px) {
  .grid { grid-template-columns: 1fr; }
  .browse-tools { gap: 10px; }
  .count { margin-left: 0; width: 100%; }
  .sort-control, .sort-control select { width: 100%; }
}

/* ══ TOUCH ═════════════════════════════════════════════════════════════════
   Coarse pointers, and the narrow viewports that are one in practice — the same
   pairing public/styles.css uses to float the chat composer. Every control here
   clears 44 x 44, and the smallest microtype goes up one step. Fine-pointer
   density above 560px is untouched on purpose: the desktop directory is a scan
   list whose 10px mono labels are read at arm's length, not tapped. */
@media (pointer: coarse), (max-width: 560px) {
  /* 16px is also the size under which iOS Safari zooms the whole page on focus,
     which is why styles.css floors the composer and the gate inputs there too.
     12px of block padding keeps the field at 48px tall with the bigger text. */
  .search input { font-size: 16px; padding: 12px 0; }
  .sort-control select { font-size: 12px; }
  .count { font-size: 11.5px; }

  /* Width is the SHORT axis of a pointy-top hexagon, so the height has to move
     with it (w * 2/root3) or the clip-path skews into a squashed mark. */
  .card-logo, .sk-hex { width: 44px; height: 50.8px; }
  /* The card-top row is already 50.8px tall (the mark sets it), so lifting the
     name link from the 24px WCAG 2.5.8 floor to the 44px touch floor moves
     nothing on the card — it only grows the thumb target. */
  .card-name { min-height: 44px; }

  .card-tag { font-size: 15px; }
  .card-tag-chip { font-size: 11px; }
  .card-kb { font-size: 11px; }
  .card-open { font-size: 11px; }

  .primer-lede { font-size: 15px; }
  .primer-steps p, .primer-eg { font-size: 14px; }
  .primer-eg-label { font-size: 11px; }
}

@media (prefers-reduced-motion: reduce) {
  .card, .search, .sort-control select { transition: none; }
}
