/* services-rows.css — funpay-catalog Slice 2.
 *
 * Row-based catalog primitive shared between desktop (≥720px) and mobile
 * (<720px) via grid-template-areas reflow (Decision 1). Post-Slice-3
 * cleanup (2026-05-04) this is the SOLE /services CSS bundle — the
 * legacy services.css was deleted in that pass.
 *
 * Catalog-density-rework 2026-05-07: 6-column desktop grid collapsed to
 * 4 columns. Dropped the chevron `::after` pseudo (whole row is `<a>`
 * per Decision 14 — chevron was redundant) and the .service-row__delivery
 * cell entirely (the delivery indicator is now a small ⚡/🕒 icon
 * embedded in .service-row__price next to the amount, Funpay-style).
 * Title kept single-line, blurb expanded to 2-line clamp so longer
 * descriptions surface visibly instead of mid-sentence ellipsis. Icon
 * shrunk 40px → 32px. The .delivery-badge-inline* rule block was
 * deleted as orphan (the only consumer was the catalog row pill which
 * collapsed to icon-only). Header rationale + grill answers in CLAUDE.md.
 *
 * Catalog-density-rework iter-2 2026-05-07b — second user-feedback
 * iteration ("still too wide; want Funpay-style avatar+overlay-dot;
 * want column labels Description/Seller/Price"):
 *   - seller column tightened: was minmax(160px, 1fr) sharing flex with
 *     name; now minmax(140px, 220px) bounded — the empty horizontal
 *     space in the seller cell collapses, name takes all the freed
 *     width via the lone 1fr below.
 *   - name column: minmax(0, 2.5fr) → minmax(0, 1fr) (only flex consumer
 *     left now that seller is bounded; result is name fills all
 *     remaining space ~78% of row width).
 *   - seller avatar: 24px → 40px square (Funpay parity).
 *   - online-dot: was inline-flex sibling of seller-name text (sales-
 *     count Slice 1 D8); now absolute-positioned overlay on the avatar's
 *     bottom-right corner with a 2px border-ring matching --background
 *     (the canonical Funpay/Discord/Slack presence affordance). Dot
 *     grew 6px → 12px to match the bigger avatar.
 *   - column header row added: .service-rows-header is the first child
 *     of .service-rows on desktop, grid-aligned with the row template
 *     (Description | Seller | Price labels). Hidden on mobile.
 *
 * Design tokens (--foreground, --background, --muted, --border, --accent,
 * --ring, --primary, --primary-foreground, --radius, --destructive, etc.)
 * are defined globally in /css/shadcn-common.css. We do NOT redefine them
 * here.
 */

/* -------------------------------------------------------------------------
 * Page-level chrome.
 * ------------------------------------------------------------------------- */

.catalog-container {
  /* Iter-8 2026-05-07h (user feedback w/ drawn margin lines): align
   * /services chrome with the global navbar's .shadcn-container so
   * `Service Catalog` h1 sits at the same X as the NEZHNA logo
   * up top. Pre-iter-8 the catalog used a narrower 1024px max-width
   * + 1rem horizontal padding which double-indented the content
   * relative to the navbar (whose .shadcn-container is 1200px wide
   * with 0 horizontal padding, putting NEZHNA at the container's
   * absolute left edge).
   *
   * Match exactly:
   *   - max-width: 1200px (was 1024px)
   *   - horizontal padding: 0 (was 1rem)
   *   - mobile: 1rem horizontal (matches shadcn-container's
   *     @media (max-width: 768px) { padding: 0 1rem }).
   *
   * The visual breathing-room that used to come from container
   * padding now comes from the inner elements' own internal
   * padding (search toolbar's 0.75rem, chip's 0.5rem 0.875rem,
   * row's border-left + 14px padding-left). */
  max-width: 1200px;
  margin: 0 auto;
  padding: 1.5rem 0 4rem;
}

.catalog-page-header {
  margin-bottom: 1.5rem;
}

.catalog-page-header h1 {
  margin: 0 0 0.25rem;
  font-size: 1.75rem;
  font-weight: 700;
  letter-spacing: -0.01em;
  color: var(--foreground);
}

.catalog-page-subtitle {
  margin: 0;
  color: var(--muted-foreground);
  font-size: 0.9375rem;
}

/* -------------------------------------------------------------------------
 * Category chip nav.
 * ------------------------------------------------------------------------- */

.catalog-category-tabs {
  display: flex;
  gap: 0.5rem;
  flex-wrap: wrap;
  margin-bottom: 1rem;
}

.catalog-category-tab {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.5rem 0.875rem;
  border: 1px solid var(--border);
  border-radius: 9999px;
  background: var(--background);
  color: var(--foreground);
  font-size: 0.8125rem;
  font-weight: 500;
  cursor: pointer;
  user-select: none;
  text-decoration: none;
}

.catalog-category-tab:hover {
  background: var(--accent);
}

.catalog-category-tab.active {
  background: var(--primary);
  color: var(--primary-foreground);
  border-color: var(--primary);
}

.catalog-category-tab .tab-count {
  opacity: 0.7;
  font-weight: 600;
  font-size: 0.75rem;
}

/* -------------------------------------------------------------------------
 * Toolbar (search + balance only — no qty pills, no sort dropdown per
 * Slice 0 evolution).
 * ------------------------------------------------------------------------- */

.catalog-toolbar {
  display: flex;
  flex-wrap: wrap;
  gap: 0.625rem;
  align-items: center;
  padding: 0.75rem;
  margin-bottom: 1rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--background);
}

.catalog-toolbar form {
  flex: 1 1 220px;
  min-width: 180px;
  display: flex;
  gap: 0.5rem;
}

.catalog-toolbar input[type="search"] {
  flex: 1 1 auto;
  padding: 0.5rem 0.75rem;
  border: 1px solid var(--border);
  border-radius: calc(var(--radius) - 2px);
  font: inherit;
  background: var(--background);
  color: var(--foreground);
  min-width: 0;
}

/* catalog-search-improvements 2026-05-14: visually distinguish rotating
   example phrases from a typed value. Italic + muted-color signals
   "this is a hint, not your input" — standard search-hint convention
   (Google, Algolia, Apple). `opacity: 1` overrides Firefox's default
   placeholder opacity reduction so the italic styling is consistent
   across browsers; the muted color carries the contrast cue instead. */
.catalog-toolbar input[type="search"]::placeholder {
  font-style: italic;
  color: var(--muted-foreground, hsl(0 0% 50%));
  opacity: 1;
}

.catalog-toolbar .balance-widget {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem 0.75rem;
  border: 1px solid var(--border);
  border-radius: calc(var(--radius) - 2px);
  background: var(--muted);
  font-weight: 500;
  font-feature-settings: "tnum" 1;
}

.catalog-toolbar .balance-widget .top-up {
  color: var(--success, hsl(142 70% 40%));
  font-weight: 600;
  text-decoration: none;
}

/* -------------------------------------------------------------------------
 * Service rows — desktop layout (Decision 1, 4, 6).
 *
 * Single shared DOM for desktop + mobile; grid-template-areas reflow
 * via @media (max-width: 719px) below. Whole row is an <a> per Decision
 * 4; no nested interactive content (Decision 14, asserted by the
 * service-row-no-nested-clickables.test.js invariant test).
 * ------------------------------------------------------------------------- */

.service-rows {
  display: flex;
  flex-direction: column;
  gap: 0;
  /* Iter-5 2026-05-07e: dropped the iter-4 max-width: 1024px + auto
   * margins. The chrome container .catalog-container is now the
   * narrowing point (1180px → 1024px) so the row block + chrome share
   * the same horizontal extent without needing a second cap here. Home
   * Section #4 still has .shadcn-container at 1200px around the row
   * block — addressing that asymmetry is queued for iter-6 if user
   * reports the home preview also feels wide. */
}

/* Shared grid-template-columns variable so .service-rows-header (column
 * label row) can stay byte-for-byte aligned with .service-row without
 * the two declarations drifting apart. Edit ONE place to change track
 * widths.
 *
 * Iter-3 2026-05-07c (user feedback "лесенкой все" + "DESCRIPTION/SELLER
 * хочется влево"): every row uses an INDEPENDENT grid container, so
 * minmax(140px, 220px) on seller + auto on price was producing
 * per-row variable column widths — different sales-count text length
 * ("1.5K sold" vs "744 sold") makes the seller cell size differently
 * per row, shifting the avatar X position between rows (the visible
 * "staircase" pattern the user reported). Fixed widths eliminate the
 * per-row variation: every row's grid renders identically because the
 * track widths don't depend on per-row content. Avatar in row 1 lands
 * at the same X as avatar in row N. The .service-rows-header label
 * "SELLER" lands at the same X as the avatars beneath it (both at
 * cell-left-edge with `text-align:left`). Same for "PRICE" at
 * cell-right-edge with `text-align:right` aligning with right-aligned
 * price values. The 1fr name column eats all remaining width and
 * description naturally pushes seller/price to the right side of the
 * row.
 *
 * Track widths chosen for current data:
 *   - 32px icon (fixed; matches .service-row__icon)
 *   - 1fr name (lone flex consumer; ~600-650px on 1024px container)
 *   - 180px seller (avatar 40 + gap 10 + text-stack 130 = 180; long
 *     marketplace names like "alukosimiloluwa02_..." ellipsis-truncate
 *     inside .service-row__seller-name-text at ~10-11 char limit)
 *   - 150px price (covers "from $1,234.56 ⚡" + "/unit" suffix headroom;
 *     iter-4 2026-05-07d bumped from 110px after user reported
 *     "from $0.002" wrapping to multi-line on prod — bold weight + tnum
 *     spacing + icon gap pushed inline-flex over 110px even for short
 *     prices). The cell still feels tight on display: flex-end content
 *     so 150px is the floor — bumping further would just add empty
 *     space without functional gain.
 */
:root {
  --service-row-grid-columns: 32px minmax(0, 1fr) 180px 150px;
}

.service-row {
  display: grid;
  grid-template-columns: var(--service-row-grid-columns);
  grid-template-areas: "icon name seller price";
  align-items: center;
  gap: 0.75rem;
  min-height: 56px; /* WCAG 2.2 SC 2.5.8 — Decision 4 */
  padding: 0.5rem 1rem 0.5rem 0.875rem;
  border-bottom: 1px solid var(--border);
  border-left: 4px solid var(--service-row-tint, transparent);
  background: var(--background);
  color: var(--foreground);
  text-decoration: none;
  position: relative;
  transition: background-color 0.12s ease;
}

/* Column-label header row — first child of .service-rows on desktop.
 * Grid-aligned with .service-row via the same --service-row-grid-columns
 * custom property + grid-template-areas. Hidden on mobile (Funpay-style
 * mobile is row-stacked, no header).
 *
 * Iter-2 2026-05-07b user-feedback: explicit Description/Seller/Price
 * labels make the wide layout feel intentional ("a table with labeled
 * columns") instead of "sparse rows with empty space". The 4px tint
 * left border + 1rem 0.875rem padding mirrors .service-row's chrome so
 * the header reads as part of the table, not a floating orphan.
 *
 * Iter-6 2026-05-07f: dropped border-top-left-radius +
 * border-top-right-radius. The rounded top corners + 4px transparent
 * border-left combination clipped at the corner curve, exposing the
 * page background underneath as a small diagonal wedge — user reported
 * this as "div не матчится с header по размерам" / "оступов description"
 * artifact at the top-left of the row block. Header now renders as a
 * plain rectangle flush with the rows beneath it. The visual hierarchy
 * (header is muted-grey, rows are background-white) is communicated
 * via background-color alone, no rounded corners needed. */
.service-rows-header {
  display: grid;
  grid-template-columns: var(--service-row-grid-columns);
  grid-template-areas: "icon name seller price";
  align-items: center;
  gap: 0.75rem;
  padding: 0.5rem 1rem 0.5rem 0.875rem;
  border-bottom: 1px solid var(--border);
  /* Match .service-row's left tint-bar width so the header visually
   * lines up with the rows beneath. tint var defaults to transparent
   * so the header stays neutral. */
  border-left: 4px solid transparent;
  background: var(--muted);
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--muted-foreground);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

/* .service-rows-header__placeholder rule DELETED in iter-7 2026-05-07g —
 * the placeholder span itself was dropped from EJS markup. Description
 * label now spans icon+name columns directly via grid-column below. */

/* Iter-7 2026-05-07g: explicit grid-column ranges instead of grid-area
 * names. The "Description" label spans grid-column 1/3 so its text
 * starts at the icon column's left edge — visually labeling the WHOLE
 * row content (icon + title + blurb) instead of sitting in the name
 * sub-cell ~62px to the right of the row's visual leading edge. */
.service-rows-header__label--name {
  grid-column: 1 / 3;
}

.service-rows-header__label--seller {
  grid-column: 3 / 4;
}

.service-rows-header__label--price {
  grid-column: 4 / 5;
  text-align: right;
}

.service-row:first-child {
  border-top: 1px solid var(--border);
}

.service-row:hover {
  background-color: var(--accent);
}

.service-row:focus-visible {
  outline: 2px solid var(--ring);
  outline-offset: -2px;
}

/* No chevron `::after` pseudo — removed in catalog-density-rework
 * 2026-05-07. The whole row is `<a>` per Decision 14 so the chevron was
 * a redundant visual hint adding ~24px of horizontal real estate to
 * every row for no functional benefit. Hover state + cursor:pointer
 * (inherited from the `<a>`) communicate clickability. */

/* Mini image icon — D-Slice0-1: <img> not <span>. Sized small; same
 * URL source as the main service.imageUrl on /service/:id.
 * Catalog-density-rework 2026-05-07: 40px → 32px. The brand-recognition
 * use case (purple Twitch / red YouTube / etc. quick-scan) doesn't need
 * 40px; 32px works at avatar density and frees ~8px back to the name
 * column. */
.service-row__icon {
  grid-area: icon;
  width: 32px;
  height: 32px;
  border-radius: 6px;
  object-fit: cover;
  background: var(--muted);
  display: block;
  flex-shrink: 0;
}

.service-row__name {
  grid-area: name;
  min-width: 0;
}

.service-row__title {
  margin: 0;
  font-size: 0.9375rem;
  font-weight: 600;
  /* Title still bounded to one visual line — long names truncate with
   * ellipsis. line-clamp behaves identically to the prior nowrap+
   * ellipsis chain when -webkit-line-clamp:1, but allows future bump
   * to 2-line via a single token change without restructuring the
   * white-space rules. */
  display: -webkit-box;
  -webkit-line-clamp: 1;
  -webkit-box-orient: vertical;
  overflow: hidden;
  word-break: break-word;
  color: var(--foreground);
}

.service-row__blurb {
  /* No `grid-area` here — this <p> is nested inside <div.service-row__name>,
   * not a direct child of the grid container, so grid-area would be inert.
   * It stacks below the <h3.service-row__title> as a normal flow sibling
   * within the name cell on both desktop and mobile.
   *
   * Visibility: shown on both viewports. EJS only emits the <p> when
   * row.blurb is truthy (services without a blurb render zero rows of
   * extra height). 80-char cap upstream in normalizeServiceRow.
   *
   * Catalog-density-rework 2026-05-07: was 1-line nowrap+ellipsis, now
   * 2-line clamp box. With the dropped chevron + delivery cells, the
   * .service-row__name cell takes ~2.5fr of the row width — at 1180px
   * container, that's ~600px which is enough to fit most 80-char blurbs
   * on a single line; longer ones wrap to 2 lines and show the trailing
   * text instead of mid-sentence "...". Mobile @media keeps the 1-line
   * clamp it had before (icon column eats horizontal real-estate so
   * 2-line blurb on a 360px viewport would push title off-screen). */
  margin: 0.15rem 0 0;
  font-size: 0.8125rem;
  color: var(--muted-foreground);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  word-break: break-word;
}

.service-row__seller {
  grid-area: seller;
  display: flex;
  align-items: center;
  gap: 0.625rem;
  min-width: 0;
  font-size: 0.8125rem;
}

/* Avatar — bumped 24px → 40px in catalog-density-rework iter-2 2026-05-07b
 * (Funpay parity). position:relative anchors the absolute online-dot
 * overlay child. */
.service-row__seller-avatar {
  flex-shrink: 0;
  width: 40px;
  height: 40px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  position: relative;
}

.service-row__seller-avatar img,
.service-row__seller-avatar svg {
  width: 40px;
  height: 40px;
  border-radius: 50%;
  display: block;
}

.service-row__seller-text {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.service-row__seller-name {
  font-weight: 500;
  color: var(--foreground);
  /* Iter-2 2026-05-07b: simplified from inline-flex (which carried the
     online-dot as a flex sibling per sales-count Slice 1 D8) to plain
     block. Dot relocated to .service-row__seller-avatar overlay. The
     inner .service-row__seller-name-text wrapper still owns
     overflow:ellipsis below and stays load-bearing for long marketplace
     seller names. */
  display: block;
  max-width: 100%;
  min-width: 0;
}

/* Inner text wrapper — owns overflow:ellipsis on long marketplace seller
   names like "sLeiter cs2 boosting/coaching account selling".
   `display: block` is LOAD-BEARING per CSS spec: text-overflow:ellipsis
   + overflow:hidden have NO effect on inline elements (default span
   display). Pre-iter-2 (sales-count polish slice 2026-04-29) the parent
   .service-row__seller-name was inline-flex, which made this inner span
   a flex item — flex items establish their own block formatting context,
   so ellipsis worked. Iter-2 2026-05-07b moved the online-dot to the
   avatar overlay and simplified the parent to plain `display: block`,
   which made this inner span a regular inline run again — silently
   breaking ellipsis until 2026-05-07i (this rule). With the seller
   column at fixed 180px (~130px after avatar+gap), names longer than
   ~16 chars overflowed visually into the price column. The user-visible
   bug surfaced when seller "sLeiter cs2 boosting/coaching account
   selling" registered (~46 chars). Drift guard: locked by
   tests/sales-count/follow-up-polish.test.js Section 1. */
.service-row__seller-name-text {
  display: block;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}

.service-row__seller-meta {
  color: var(--muted-foreground);
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  font-size: 0.75rem;
  /* sales-count Slice 1 / D8 / A10: meta-line is OPTIONAL — rendered
     only when there's a rating OR sales-count badge to show. The EJS
     gates the entire <span> so layout collapses cleanly when neither
     applies (no `min-height` cheat needed; the spans simply don't exist
     in DOM). */
}

/* sales-count Slice 1 / Decision 2 + 3: "X sold" badge slots into the
   rating-or-empty fall-through chain at priority 2 (rating > sales >
   absent). Visual treatment matches the rating-count caption (muted
   foreground, same font-size as seller-meta). */
.service-row__sales-badge {
  color: var(--muted-foreground);
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

/* Online-dot — relocated from inline-next-to-name (sales-count Slice 1
 * D8) to absolute-overlay on the avatar's bottom-right corner per iter-2
 * 2026-05-07b. The 2px border-ring matching --background carves visual
 * separation between dot and avatar so it doesn't blend on busy avatar
 * imagery; canonical Funpay/Discord/Slack pattern. Size 6px → 12px to
 * match the bigger avatar. */
.service-row__online-dot {
  position: absolute;
  bottom: -2px;
  right: -2px;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  border: 2px solid var(--background);
  box-sizing: border-box;
  z-index: 1;
}

.service-row__online-dot--online {
  background: var(--success, hsl(142 70% 40%));
}

.service-row__online-dot--offline {
  background: var(--muted-foreground);
  opacity: 0.6;
}

.service-row__rating {
  color: var(--foreground);
  font-weight: 500;
  white-space: nowrap;
}

.service-row__rating-star {
  color: hsl(45 95% 50%);
}

.service-row__rating-count {
  opacity: 0.7;
}

/* .service-row__delivery cell — DELETED in catalog-density-rework
 * 2026-05-07. Delivery indicator is now an inline ⚡/🕒 icon next to
 * .service-row__price-amount (see .service-row__delivery-icon below). */

.service-row__price {
  grid-area: price;
  text-align: right;
  font-feature-settings: "tnum" 1;
  /* Iter-5 2026-05-07e: simplified from column-flex stack (which placed
   * "/ unit" on a second line below the amount) to right-aligned single
   * cell. Inner .service-row__price-line is the inline-flex container
   * for [amount + qty + ⚡] all on one baseline. */
  display: flex;
  justify-content: flex-end;
  align-items: center;
}

/* Inline-flex wrapper keeps amount + qty + delivery icon on a single
 * baseline. Iter-5 2026-05-07e absorbed the qty span into this row
 * (was a sibling block below pre-iter-5). */
.service-row__price-line {
  display: inline-flex;
  align-items: baseline;
  gap: 0.3rem;
  white-space: nowrap;
}

.service-row__price-amount {
  font-size: 0.9375rem;
  font-weight: 700;
  color: var(--foreground);
}

/* Delivery indicator — collapsed from the prior pill (.delivery-badge-inline,
 * deleted) to an icon-only signal. Variant comes from deliveryBadge.variant
 * (auto / manual). The full label ("Auto delivery" / "Manual delivery" /
 * admin-overridden text) lives in the title= attr + aria-label= so screen
 * readers + long-press tooltips still surface the human-readable string.
 *
 * --auto color: green (success token) — fast/instant signal, mirrors the
 *   Funpay convention of a green lightning ⚡.
 * --manual color: muted-foreground — neutral. Clock 🕒 communicates
 *   "delivery is human-paced" without alarm.  */
.service-row__delivery-icon {
  font-size: 0.875rem;
  line-height: 1;
  flex-shrink: 0;
  display: inline-block;
}

.service-row__delivery-icon--auto {
  color: var(--success, hsl(142 70% 40%));
}

.service-row__delivery-icon--manual {
  color: var(--muted-foreground);
}

/* Iter-5 2026-05-07e: was display:block (rendered on its own line below
 * amount). Now inline-flex item (default display:inline) sitting on the
 * same baseline as amount, separated by 0.3rem gap. Smaller font +
 * muted color visually subordinates it to the price number. */
.service-row__price-qty {
  font-size: 0.75rem;
  color: var(--muted-foreground);
  font-weight: 400;
}

/* -------------------------------------------------------------------------
 * Group header (Phase 6 forward-compat — only renders when
 * row.templateGroupCount > 1, which is false today; the conditional in
 * the EJS template keeps this dead until Phase 6 schema activates).
 * ------------------------------------------------------------------------- */

.service-group-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.5rem 1rem 0.5rem 0.875rem;
  border-bottom: 1px solid var(--border);
  border-left: 4px solid var(--service-row-tint, transparent);
  background: var(--muted);
  font-size: 0.8125rem;
}

.service-group-header__title {
  font-weight: 600;
  color: var(--foreground);
}

.service-group-header__meta {
  color: var(--muted-foreground);
  flex: 1;
}

/* Delivery badge pill block REMOVED in catalog-density-rework 2026-05-07.
 * Was a 5-rule block (.delivery-badge-inline base + 4 modifier/element
 * children) used exclusively by views/partials/service-row.ejs to render
 * an "Auto delivery" / "Manual delivery" pill in its own .service-row__
 * delivery grid cell. That cell was deleted in the rework; the delivery
 * indicator is now an inline ⚡/🕒 icon next to .service-row__price-amount
 * styled by .service-row__delivery-icon* above. The .delivery-badge-inline
 * class is no longer referenced by any EJS, JS, or CSS in the repo —
 * verified via `rg "delivery-badge-inline"` returning only test files
 * which were updated alongside this rule deletion. */

/* -------------------------------------------------------------------------
 * Empty state.
 * ------------------------------------------------------------------------- */

.catalog-empty-state {
  text-align: center;
  padding: 3rem 1rem;
  color: var(--muted-foreground);
}

.catalog-empty-state h3 {
  margin: 0 0 0.5rem;
  font-size: 1.125rem;
  color: var(--foreground);
}

.catalog-empty-state .catalog-empty-cta {
  display: inline-block;
  margin-top: 1rem;
  padding: 0.5rem 1rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  text-decoration: none;
  color: var(--foreground);
  background: var(--background);
}

.catalog-empty-state .catalog-empty-cta:hover {
  background: var(--accent);
}

/* -------------------------------------------------------------------------
 * Load more.
 * ------------------------------------------------------------------------- */

.catalog-load-more {
  margin: 1.5rem auto 0;
  display: block;
  text-align: center;
  padding: 0.625rem 1.25rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--background);
  color: var(--foreground);
  text-decoration: none;
  font-weight: 500;
  width: fit-content;
}

.catalog-load-more:hover {
  background: var(--accent);
}

/* -------------------------------------------------------------------------
 * Mobile (<720px) — Funpay-style flat row list (UX decision 2026-04-29,
 * grid rectified 2026-04-28: prior 3-row template had `icon` in row 1 col 1
 * AND row 3 col 1 with `blurb blurb` in row 2 — non-rectangular per CSS
 * Grid spec → entire grid-template-areas declaration was invalid and
 * discarded. Browser fell back to auto-flow placement, which pushed
 * .service-row__seller into col 3 row 1 next to the title and squeezed
 * the title column down to ~40% of width, causing 3-4 line title wraps
 * on iPhone-width viewports. Fix: make icon span both rows in a valid
 * 2-row template. The standalone `blurb` grid area was also dead — the
 * <p class="service-row__blurb"> sits inside <div class="service-row__name">,
 * not as a direct grid child, so its grid-area declaration was inert.
 * Blurb stacks naturally inside the name cell as a flow sibling).
 *
 * Same DOM as desktop; this @media block reflows via grid-template-areas
 * AND prunes desktop-only chrome (border, radius, gap, "from"/"unit"
 * price scaffolding, delivery label text). Result: dense list of
 * compact rows separated by hairline border, like funpay.com mobile.
 *
 * Catalog-density-rework 2026-05-07: dropped the .service-row__delivery
 * grid cell + the .delivery-badge-inline pill chrome from mobile (was
 * already icon-only — overrides went away with the desktop cell removal).
 * Mobile grid 3 cols × 2 rows; row 2 col 3 is now an empty `.` cell since
 * the delivery icon now lives inline with the price on row 1 col 3.
 *
 * What the user sees per row (mobile):
 *   ┌─────────────────────────────────────────────────────────┐
 *   │ [32px icon] Twitch Chat Bot for 1 Hour       $0.20 ⚡  │
 *   │             Fully AI-Powered & Human-Like…              │
 *   │ [32px col]  [16px avatar] NEZHNA ●                      │
 *   └─────────────────────────────────────────────────────────┘
 *
 * Title is bounded to 2 lines via -webkit-line-clamp; longer names
 * truncate with ellipsis. Blurb is also clamped to 1 line on mobile
 * (vs 2 on desktop — narrower viewport, less room for descender text).
 * The 32px icon column extends the full row height (top-aligned via
 * align-self).
 *
 * Removed vs prior card-style mobile:
 *   - 4px purple top border (.service-row tint)
 *   - 1px box border + radius
 *   - 0.625rem inter-row gap
 *   - "from" price prefix
 *   - "/ unit" price suffix
 *   - delivery-badge text label ("AI-Powered" / "Cheap")
 *
 * Single breakpoint; iPad-landscape gets desktop layout.
 * ------------------------------------------------------------------------- */

@media (max-width: 719px) {
  .catalog-container {
    /* Iter-8 2026-05-07h: 1rem horizontal mirrors shadcn-container's
     * mobile padding (was 0.75rem pre-iter-8) so the navbar and
     * catalog content stay column-aligned at every viewport. */
    padding: 1rem 1rem 3rem;
  }

  .catalog-toolbar {
    padding: 0.5rem;
  }

  .catalog-toolbar form {
    flex: 1 1 100%;
  }

  .catalog-toolbar .balance-widget {
    flex: 1 1 100%;
    justify-content: center;
  }

  /* Flat list — no inter-row gap, hairline divider. */
  .service-rows {
    gap: 0;
  }

  .service-row {
    /* 3-col grid: icon (full-height, spans both rows) | name+blurb stacked
       in col 2 row 1 with seller in col 2 row 2 | price row 1 col 3
       (with the inline delivery icon next to the amount); row 2 col 3 is
       a `.` null cell (the delivery cell was deleted in
       catalog-density-rework 2026-05-07). `icon` still forms a valid
       2-row rectangle so the whole grid-template-areas declaration is
       honored. */
    grid-template-columns: 32px minmax(0, 1fr) auto;
    grid-template-areas:
      "icon name   price"
      "icon seller .    ";
    column-gap: 0.625rem;
    row-gap: 0.125rem;
    align-items: start;
    padding: 0.625rem 0.75rem;
    /* Funpay-style: hairline separator only. No card border, no top tint
       bar, no border-radius. */
    border: 0;
    border-bottom: 1px solid var(--border);
    border-radius: 0;
    border-left: 0;
  }

  .service-row:first-child {
    /* Symmetric hairline above first row so the list reads as a
       bordered group rather than detached rows. */
    border-top: 1px solid var(--border);
  }

  /* Note: the `.service-row::after` chevron pseudo was DELETED entirely
     in catalog-density-rework 2026-05-07. No mobile override needed —
     no element to hide. */

  .service-row__icon {
    /* Same 32px size as desktop now (was 40px desktop / 32px mobile
       pre-rework). border-radius differs slightly — 6px on mobile gives
       a tighter card feel at smaller sizes. */
    border-radius: 6px;
    align-self: start;
    margin-top: 0.125rem;
  }

  .service-row__title {
    /* 2-line clamp on mobile (vs 1-line on desktop) — narrower viewport
       can't fit "Twitch Live Viewers for 4 Hours" on a single line at
       360px width. White-space normal allows wrap; -webkit-line-clamp
       truncates beyond 2 lines with ellipsis. */
    -webkit-line-clamp: 2;
    word-break: break-word;
    font-size: 0.9375rem;
    line-height: 1.25;
  }

  .service-row__blurb {
    /* Mobile bumps line-clamp DOWN to 1 (desktop is 2). The title can
       eat up to 2 lines on mobile which limits the visible row height
       budget for blurb. Title 2-line + blurb 1-line keeps mobile rows
       at most 3 visual lines of text — comparable to current density. */
    -webkit-line-clamp: 1;
    margin-top: 0;
    font-size: 0.8125rem;
    line-height: 1.3;
  }

  /* Seller cell sits inline (avatar + name + dot) under the name —
     not the desktop's stacked column layout. */
  .service-row__seller {
    font-size: 0.75rem;
    align-items: center;
    gap: 0.4rem;
  }

  /* Mobile: column header hidden — Funpay-style mobile is row-stacked
     with no header (per iter-2 2026-05-07b). */
  .service-rows-header {
    display: none;
  }

  /* Mobile avatar: 28px (vs 40px desktop). Smaller-than-desktop for the
     compact viewport but big enough to host a 10px dot overlay. */
  .service-row__seller-avatar,
  .service-row__seller-avatar img,
  .service-row__seller-avatar svg {
    width: 28px;
    height: 28px;
  }

  /* Mobile dot overlay: scaled down 12px → 10px to fit the smaller
     avatar without overpowering it. */
  .service-row__online-dot {
    width: 10px;
    height: 10px;
    bottom: -2px;
    right: -2px;
  }

  .service-row__seller-text {
    /* Mobile: collapse the desktop's column stack to a single inline
       row so name + status sit on one baseline. */
    flex-direction: row;
    align-items: center;
    gap: 0.35rem;
  }

  .service-row__seller-name {
    font-weight: 500;
    font-size: 0.75rem;
  }

  .service-row__seller-meta {
    font-size: 0.6875rem;
    gap: 0.25rem;
  }

  /* Note: the .service-row__delivery cell + .delivery-badge-inline pill
     were deleted in catalog-density-rework 2026-05-07. The delivery
     indicator is now an inline ⚡/🕒 icon (.service-row__delivery-icon)
     adjacent to .service-row__price-amount inside the price cell —
     visible on both desktop and mobile via the desktop rules. The
     prior 4 mobile overrides (`.service-row__delivery` align-self,
     `.delivery-badge-inline` background-strip, `.delivery-badge-inline__
     icon` font-size, `.delivery-badge-inline__label` display:none) all
     went away with the cell deletion. */

  /* Price: bare amount + delivery icon on mobile, no "from" prefix,
     no "/ unit" suffix. */
  .service-row__price {
    align-self: start;
    padding-top: 0.125rem;
  }

  .service-row__price-amount {
    font-size: 1rem;
  }

  /* Slightly larger delivery icon on mobile (matches the prior pre-rework
     mobile pill icon size of 0.875rem; desktop is 0.875rem too — keep
     parity here in case desktop ever shrinks in a future polish slice). */
  .service-row__delivery-icon {
    font-size: 0.875rem;
  }

  /* Iter-5 2026-05-07e: dropped the .service-row__price-prefix +
     .service-row__price-qty hide rules. The prefix span ("from ") was
     deleted from EJS markup entirely (no longer needs hiding); the qty
     suffix ("/ unit") moved to inline-flex with amount on a single
     baseline, so it's both visually compact AND informational on every
     viewport (mobile no longer drops the per-unit hint). */

  .service-group-header {
    border-radius: 0;
    border-left: 0;
    margin-top: 0;
  }
}
