/* ═══════════════════════════════════════════════════════════════════════════════════════════════════
   REVIEWS — shared by the product page section and the /reviews/ hub
   ═══════════════════════════════════════════════════════════════════════════════════════════════════
   The same five partials in `templates/parts/reviews/` draw both screens, so these rules are shared
   for exactly the reason the markup is: a review row styled twice is a review row that differs.
   🔴 STARS ARE INK. Never orange, never red, never amber on a review. Green appears once, on
   "Verified buyer", because it MEANS verified rather than decorating anything.
   ═══════════════════════════════════════════════════════════════════════════════════════════════════ */
/* ═══ REVIEWS ═══════════════════════════════════════════════════════════════
   Brief of 23 Sep, mockup `reviews-section-mockup.html`.
   🔴 STARS ARE INK. Amber means money held or a task you owe everywhere else on this site. Orange stars
   would be the only exception to that and would read as a warning on a five-star review. */
.pp-rv{max-width:860px}
.rv-stars{display:inline-flex;gap:2px;vertical-align:middle}
.rv-stars svg{width:15px;height:15px;fill:var(--pt-border-strong)}
.rv-stars svg.on{fill:var(--pt-ink)}
.rv-stars.lg svg{width:19px;height:19px}

.rv-summary{display:grid;grid-template-columns:200px minmax(0,1fr) auto;gap:40px;align-items:center;padding:20px 0;border-bottom:1px solid var(--pt-border)}
/* The hub's product view has no "Write a review" in this block — it is up in the header — so the row is
   two columns there, and the average is a size down from the product page's. */
.rv-summary.is-compact{grid-template-columns:200px minmax(0,1fr)}
.rv-summary.is-compact .rv-avg__n{font-size:40px}
.rv-avg__n{margin:0 0 6px;font-family:var(--pt-mono);font-variant-numeric:tabular-nums;font-size:44px;font-weight:600;line-height:1;letter-spacing:-.02em}
.rv-avg__n small{font-size:17px;font-weight:400;color:var(--pt-ink-3)}
.rv-cnt{margin:8px 0 0;font-size:13px;color:var(--pt-ink-3)}

/* Each row is a link — tapping "1 star" to read the worst ones is what people do anyway. */
/* 🔴 52px, NOT 34px. At 34 the label "4 star" wrapped onto two lines while "5 star" happened to fit, so
   the rows were different heights and the bars stopped lining up. `nowrap` too — a column wide enough
   today is not wide enough in another language. (Leon's screenshot, 23 Sep.) */
.rv-dist{display:grid;gap:5px;min-width:0;padding:18px 0}
.rv-dist a{display:grid;grid-template-columns:52px minmax(0,1fr) 30px;gap:10px;align-items:center;font-size:12.5px;color:var(--pt-ink-3);white-space:nowrap}
.rv-dist a:hover,.rv-dist a.is-on{color:var(--pt-ink)}
.rv-bar{height:7px;background:var(--pt-fill-control);border:1px solid var(--pt-border)}
.rv-bar i{display:block;height:100%;background:var(--pt-ink)}
.rv-n{text-align:right}

.rv-photos{padding:18px 0;border-bottom:1px solid var(--pt-border)}
/* 🔴 `text-transform:none`. The theme capitalises headings, so this rendered "Photos From Buyers".
   Sentence case is the site's rule and this is not the first time that theme rule has had to be undone. */
.rv-photos h3{margin:0 0 10px;font-size:13.5px;font-weight:600;text-transform:none}
.rv-photos h3 span{font-weight:400;color:var(--pt-ink-3);font-size:12.5px}
.rv-photos h3 span::before{content:"· "}
/* Up to 50 thumbnails before the "+n" tile, scrolling sideways with NO SNAP — the same free scroll as
   every other rail we own (Leon, 22 Sep). Wrapping fifty would push the reviews off the screen. */
.rv-photos__row{display:flex;gap:8px;flex-wrap:nowrap;overflow-x:auto;scroll-snap-type:none;scrollbar-width:none;padding-bottom:2px}
.rv-photos__row::-webkit-scrollbar{display:none}
.rv-shot{width:74px;height:74px;flex:0 0 auto;padding:0;border:1px solid var(--pt-border);background:var(--pt-fill-control);cursor:pointer;overflow:hidden;border-radius:0}
/* 🔴 CONTAIN, NOT COVER (Leon, 25 Sep 2026). `cover` fills the square by cropping, and a review
   photo is EVIDENCE — somebody photographs the scuff, the stitching, the part that failed, and a
   centre-crop is exactly as likely to cut it out as to keep it. A letterboxed thumbnail that shows
   the whole picture is right here; the hub shows it full size anyway. The tile keeps its own fill
   behind the letterbox so a portrait shot does not sit on a white hole. */
.rv-shot img{width:100%;height:100%;object-fit:contain;display:block;background:var(--pt-fill-control)}
.rv-shot--more{display:flex;align-items:center;justify-content:center;font-size:12.5px;color:var(--pt-ink-2);cursor:default}

.rv-tools{display:flex;gap:8px;align-items:center;flex-wrap:wrap;padding:18px 0}
.rv-pill{font-size:12.5px;padding:6px 11px;border:1px solid var(--pt-border-strong);background:var(--pt-surface);color:var(--pt-ink)}
/* 🔴 THE TEXT STAYS DARK ON HOVER (Leon, 23 Sep). GeneratePress recolours every `a:hover` to the
   brand red, and a pill is an <a> — so hovering a filter turned its label red, which on this site
   means a dispute. A pill only ever changes its FILL. */
.rv-pill:hover,.rv-pill:focus{background:var(--pt-fill-control);color:var(--pt-ink)}
.rv-pill.is-on{background:var(--pt-ink);border-color:var(--pt-ink);color:#fff}
.rv-pill.is-on:hover,.rv-pill.is-on:focus{background:var(--pt-ink);color:#fff}
.rv-pill span{font-size:11.5px;color:var(--pt-ink-3);margin-left:5px}
.rv-pill.is-on span{color:#BDB8AE}
.rv-sort{margin-left:auto}
.rv-sort select{height:34px;padding:0 10px;border:1px solid var(--pt-border-strong);background:var(--pt-surface);font-size:13px;border-radius:0}
.rv-none{margin:8px 0 0;font-size:14px;color:var(--pt-ink-3)}
.rv-none a{color:var(--pt-accent)}

.rv-item{display:grid;grid-template-columns:var(--rv-gutter,150px) minmax(0,1fr);gap:var(--rv-gap,32px);padding:26px 0;border-top:1px solid var(--pt-border)}
.rv-item[hidden]{display:none}
.rv-who{display:flex;flex-direction:column;gap:8px}
.rv-who__top{display:flex;gap:10px;align-items:center}
.rv-av{width:34px;height:34px;border-radius:50%;border:1px solid var(--pt-border);flex:0 0 auto;object-fit:cover}
.rv-nm{margin:0;font-size:13.5px;font-weight:600;line-height:1.25}
.rv-when{margin:0;font-size:12px;color:var(--pt-ink-3)}
/* The only colour on a review. Green because it means verified, not because it is decorative. */
.rv-vp{display:inline-flex;align-items:center;gap:6px;font-size:11.5px;font-weight:500;color:var(--pt-green-text)}
.rv-vp svg{width:12px;height:12px;fill:var(--pt-green-text)}
.rv-body h4{margin:8px 0 6px;font-size:15.5px;font-weight:600}
.rv-bought{margin:0 0 10px;font-size:12.5px;color:var(--pt-ink-3)}
.rv-bought b{font-weight:500;color:var(--pt-ink-2)}
.rv-text p{margin:0 0 12px;font-size:15px;line-height:1.6;color:var(--pt-ink-2)}
.rv-revshots{display:flex;gap:8px;margin:0 0 12px;flex-wrap:wrap}
.rv-revshots .rv-shot{width:66px;height:66px}
/* ── REVIEW ACTIONS (mockup `Plan/review-actions-mockup.html`, 25 Sep 2026) ─────────────────
   24px gap, 16px above, and a min-height so the row does not jump when a control is swapped for the
   muted "Reported." text. */
.rv-foot{display:flex;gap:24px;align-items:center;flex-wrap:wrap;margin-top:16px;min-height:36px;
    font-size:14px;color:var(--pt-ink-3)}

/* Helpful is a LINK, not a button: no background, no border, no pill. */
.rv-help{display:inline-flex;gap:6px;align-items:center;background:none;background-image:none;border:0;
    border-radius:0;padding:4px 0;font:inherit;font-size:14px;color:var(--pt-ink-3);cursor:pointer}
/* 🔴 NO DARK BOX. These are <button> elements, so GeneratePress's
   `button:hover, button:focus { background-color:#333; color:var(--base-3) }` applies to them. The
   resting rule sets `background:none` but the HOVER rule only set a colour — so on hover the theme
   put a charcoal block behind a text link. Every state has to say `background:none` out loud, and
   `:focus` has to be named too because a mouse click sets it without setting `:focus-visible`.
   Leon: "a weird black or dark hover that I do not want". [[reference-css-specificity-theme-override]] */
.rv-help:hover,.rv-help:focus,.rv-help:active{background:none;color:var(--pt-ink)}
.rv-help:focus-visible{outline:2px solid var(--pt-accent);outline-offset:2px}
.rv-help svg{width:16px;height:16px;fill:none;stroke:currentColor;stroke-width:1.3}
/* Voted: the whole link goes ink at 600 and the thumb FILLS. */
.rv-help[aria-pressed="true"]{color:var(--pt-ink);font-weight:600}
.rv-help[aria-pressed="true"] svg path{fill:currentColor}
.rv-help .rv-help__n{font-family:var(--pt-mono);font-size:13px;font-weight:600;color:var(--pt-ink)}
/* 🔴 NO COUNT AT ZERO. "Helpful 0" reads as a verdict on the review; "Helpful" reads as an
   invitation. The span is emitted empty and hidden rather than conditionally printed, so the JS has
   something to write into when the first vote lands. */
.rv-help .rv-help__n:empty{display:none}

/* Report: a quiet muted link with a hairline underline. NEVER red — red is disputes. */
.rv-report{background:none;background-image:none;border:0;border-radius:0;padding:0;font:inherit;
    font-size:14px;color:var(--pt-ink-3);cursor:pointer;text-decoration:underline;
    text-underline-offset:3px;text-decoration-color:var(--pt-border-strong)}
.rv-report:hover,.rv-report:focus,.rv-report:active{background:none;color:var(--pt-ink)}
.rv-report:focus-visible{outline:2px solid var(--pt-accent);outline-offset:2px}
.rv-reported,.rv-own{font-size:14px;color:var(--pt-ink-3)}
.rv-own a{color:var(--pt-accent)}

/* ── THE REPORT PANEL ── inline, never an overlay. The 3px ink left border is the same device the
   site uses wherever something is quoted or answered in place. */
.rv-rp{max-width:560px;margin-top:14px;padding:20px 22px;background:var(--pt-surface);
    border:1px solid var(--pt-border);border-left:3px solid var(--pt-ink)}
.rv-rp[hidden]{display:none}
.rv-rp h4{margin:0 0 4px;font-size:16px}
.rv-rp__lead{margin:0 0 14px;font-size:14px;color:var(--pt-ink-3)}
.rv-rp__opts{display:grid;gap:2px;margin-bottom:14px}
.rv-rp__opt{display:flex;gap:10px;align-items:flex-start;padding:7px 0;font-size:15px;cursor:pointer}
.rv-rp__opt input{margin:4px 0 0;accent-color:var(--pt-ink)}
.rv-rp__opt small{display:block;font-size:13px;color:var(--pt-ink-3)}
.rv-rp textarea{width:100%;min-height:84px;padding:10px 12px;font:inherit;font-size:15px;resize:vertical;
    border:1px solid var(--pt-border-strong);border-radius:0;background:var(--pt-surface)}
.rv-rp__count{margin:6px 0 0;font-size:13px;color:var(--pt-ink-3)}
/* The ONLY red on this feature, and it is validation red, not the brand red. */
.rv-rp__err{margin:6px 0 0;font-size:13px;color:#A12622}
.rv-rp__err[hidden]{display:none}
.rv-rp__acts{display:flex;gap:10px;margin-top:16px;flex-wrap:wrap}
.rv-rp__send{padding:10px 18px;border:1px solid var(--pt-ink);background:var(--pt-ink);
    color:var(--pt-surface-dim);font:inherit;font-size:15px;font-weight:600;cursor:pointer;border-radius:0}
/* 🔴 EVERY STATE DECLARES BOTH background AND color. GeneratePress sets BOTH on `button:hover`
   and `button:focus`, so naming only one leaves the theme to pick the other — which is how the
   Cancel button ended up with light text on white. A mouse click sets `:focus` too. */
.rv-rp__send:hover,.rv-rp__send:focus,.rv-rp__send:active{background:#2E2E2E;border-color:#2E2E2E;color:var(--pt-surface-dim)}
.rv-rp__send:focus-visible{outline:2px solid var(--pt-accent);outline-offset:2px}
.rv-rp__cancel{padding:9px 17px;border:1.5px solid var(--pt-ink);background:var(--pt-surface);
    color:var(--pt-ink);font:inherit;font-size:15px;font-weight:600;cursor:pointer;border-radius:0}
.rv-rp__cancel:hover,.rv-rp__cancel:focus,.rv-rp__cancel:active{background:var(--pt-fill-control);color:var(--pt-ink);border-color:var(--pt-ink)}
.rv-rp__cancel:focus-visible{outline:2px solid var(--pt-accent);outline-offset:2px}

/* Signed out: one small prompt, under the actions, never two at once. */
.rv-gate{max-width:420px;margin-top:14px;padding:16px 18px;background:var(--pt-surface);
    border:1px solid var(--pt-border);font-size:15px}
.rv-gate p{margin:0 0 12px}
.rv-gate__btns{display:flex;gap:10px}
.rv-reply{margin:14px 0 0;padding:14px 16px;background:var(--pt-fill-control);border-left:2px solid var(--pt-ink)}
.rv-reply__h{margin:0 0 5px;font-size:12.5px;font-weight:600;display:flex;gap:8px;align-items:center}
.rv-reply__h span{font-weight:400;color:var(--pt-ink-3)}
.rv-reply p{margin:0;font-size:14px;color:var(--pt-ink-2)}

/* 🔴 LEFT, NOT CENTRED (Leon, 23 Sep): "its the natural placement for this". It sits directly
   under the last review's paragraph, so the eye is already at the left margin when it arrives —
   a centred button there is a jump to nowhere. The hub keeps its own centred reveal control,
   which is a different thing in a different place. */
/* 🔴 LINED UP WITH THE REVIEW TEXT, not the container edge (Leon, 24 Sep).
   `.rv-item` is `150px minmax(0,1fr)` with a 32px gap, so the body starts 182px in. The button sat
   at 0 and read as belonging to the page rather than to the reviews.
   The offset is ONE variable, used by both the row's grid and this padding, so the two cannot drift
   apart — a hard-coded 182px here would go quietly wrong the day the row is re-columned. */
.pt-rvhub,.pp-rv{--rv-gutter:150px;--rv-gap:32px}
.rv-more{display:flex;flex-wrap:wrap;align-items:center;gap:14px;padding:24px 0 0;
    padding-left:calc(var(--rv-gutter,150px) + var(--rv-gap,32px))}
.rv-more[hidden]{display:none}
.rv-prog{font-size:12.5px;color:var(--pt-ink-3)}

.rv-empty{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:24px;align-items:center;max-width:760px;padding:26px 28px;background:var(--pt-surface);border:1px solid var(--pt-border)}
.rv-empty b{display:block;font-size:16px;margin:0 0 4px}
.rv-empty span{font-size:13.5px;color:var(--pt-ink-3)}

@media (max-width:1000px){
    .rv-summary{grid-template-columns:150px minmax(0,1fr);gap:24px}
    .rv-summary > .pp-btn-out{grid-column:1 / -1;justify-self:start}
    .rv-item{grid-template-columns:1fr;gap:12px}
    .rv-who{flex-direction:row;align-items:center;gap:14px;flex-wrap:wrap}
}
@media (max-width:640px){
    .rv-summary{grid-template-columns:1fr;gap:16px}
    .rv-avg__n{font-size:36px}
    .rv-tools{gap:6px;overflow-x:auto;flex-wrap:nowrap;scrollbar-width:none}
    .rv-tools::-webkit-scrollbar{display:none}
    .rv-sort{margin-left:0;width:100%}
    .rv-sort select{width:100%}
    .rv-empty{grid-template-columns:1fr}
}


/* ═══ THE HUB — /reviews/ ═══════════════════════════════════════════════════
   Brief "Reviews hub", 23 Sep 2026. */

/* Full width. `.site-content` is display:flex, so a negative margin alone cannot full-bleed a child —
   it needs a flex basis too. Same fix the shop grid and the product page needed. */
/* 🔴 NO FULL-BLEED TRICK. `flex:0 0 100vw` inside GeneratePress's flex `.site-content` produced an
   element 140px narrower than the window with a negative left margin — a gap down one side. The page
   colour is painted on `body.pt-bg-paper` instead (see 14-global-header-footer.php), so the hub is an
   ordinary block and the colour still reaches the window edge. */
body.pt-bg-paper{background:var(--pt-surface-dim)}
.pt-rvhub{width:100%;max-width:100%;margin:0;color:var(--pt-ink)}
/* Only the dark band breaks out, and by a method that cannot cause horizontal scroll: the box paints
   itself sideways with a shadow and then un-clips it. `100vw` would include the scrollbar and overflow. */
.pt-rvhub .rh-hero{box-shadow:0 0 0 100vmax var(--pt-graphite,#23211E);clip-path:inset(0 -100vmax)}
.pt-rvhub .rh-wrap{max-width:1320px;margin:0 auto;padding:0 32px}

.pt-rvhub .rh-hero{background:var(--pt-graphite,#23211E);color:var(--pt-on-dark,#F3F1EB);padding:44px 0 40px}
.pt-rvhub .rh-rule{display:flex;justify-content:space-between;gap:16px;font-family:var(--pt-mono);font-size:12.5px;color:var(--pt-on-dark-mute,#AFA89C);border-bottom:1px solid var(--pt-dark-line,#3A3631);padding-bottom:12px;margin:0 0 30px}
/* The theme colours every h1 with its own ink, which on graphite is very nearly invisible — the
   heading has to say the colour itself rather than trust inheritance. */
.pt-rvhub .rh-hero h1{margin:0 0 10px;font-size:46px;font-weight:800;letter-spacing:-.03em;line-height:1.05;color:var(--pt-on-dark,#F3F1EB)}
.pt-rvhub .rh-hero p{margin:0;max-width:620px;color:var(--pt-on-dark-mute,#AFA89C)}
.pt-rvhub .rh-stats{display:grid;grid-template-columns:auto auto auto minmax(0,1fr);gap:40px;align-items:end;margin:30px 0 0}
.pt-rvhub .rh-k{margin:0 0 6px;font-family:var(--pt-mono);font-size:11.5px;letter-spacing:.08em;text-transform:uppercase;color:var(--pt-on-dark-mute,#AFA89C)}
.pt-rvhub .rh-v{margin:0;font-family:var(--pt-mono);font-size:30px;font-weight:600;line-height:1}
.pt-rvhub .rh-s{margin:6px 0 0;font-size:13px;color:var(--pt-on-dark-mute,#AFA89C)}
.pt-rvhub .rh-stats__cta{justify-self:end}
/* On the dark panel the empty star needs to be visible against graphite, not against paper. */
.pt-rvhub .rh-hero .rv-stars svg{fill:#4A443C}
.pt-rvhub .rh-hero .rv-stars svg.on{fill:var(--pt-on-dark,#F3F1EB)}

.pt-rvhub .rh-btn{display:inline-flex;align-items:center;justify-content:center;height:44px;padding:0 18px;border:1px solid var(--pt-ink);background:transparent;color:var(--pt-ink);font:inherit;font-size:14px;font-weight:600;cursor:pointer;border-radius:0}
.pt-rvhub .rh-btn:hover{background:var(--pt-fill-control)}
.pt-rvhub .rh-btn--solid{background:var(--pt-ink);border-color:var(--pt-ink);color:var(--pt-surface-dim)}
.pt-rvhub .rh-btn--solid:hover{background:#1f1f1f}
.pt-rvhub .rh-btn--bone{background:var(--pt-on-dark,#F3F1EB);border-color:var(--pt-on-dark,#F3F1EB);color:var(--pt-ink)}

/* Sticky, so changing your mind is not a scroll back up. */
.pt-rvhub .rh-bar{position:sticky;top:0;z-index:4;background:var(--pt-surface-dim);border-bottom:1px solid var(--pt-border);max-width:1320px;margin:0 auto;padding:0 32px}
.pt-rvhub .rh-tools{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:10px;padding:16px 0}
.pt-rvhub .rh-search{display:flex;align-items:center;gap:10px;height:42px;padding:0 12px;background:var(--pt-surface);border:1px solid var(--pt-border-strong)}
.pt-rvhub .rh-search svg{width:16px;height:16px;flex:0 0 auto;color:var(--pt-ink-3)}
.pt-rvhub .rh-search input{flex:1 1 auto;min-width:0;border:0;outline:0;background:transparent;font-size:14px}
.pt-rvhub .rh-sel{height:42px;padding:0 12px;border:1px solid var(--pt-border-strong);background:var(--pt-surface);font-size:13.5px;border-radius:0}

.rv-pills{display:flex;gap:6px;flex-wrap:wrap;padding:0 0 14px}
.rv-pill{font-size:12.5px;padding:6px 11px;border:1px solid var(--pt-border-strong);background:var(--pt-surface);color:var(--pt-ink)}
/* 🔴 THE TEXT STAYS DARK ON HOVER (Leon, 23 Sep). GeneratePress recolours every `a:hover` to the
   brand red, and a pill is an <a> — so hovering a filter turned its label red, which on this site
   means a dispute. A pill only ever changes its FILL. */
.rv-pill:hover,.rv-pill:focus{background:var(--pt-fill-control);color:var(--pt-ink)}
.rv-pill.is-on{background:var(--pt-ink);border-color:var(--pt-ink);color:#fff}
.rv-pill.is-on:hover,.rv-pill.is-on:focus{background:var(--pt-ink);color:#fff}
.rv-pill span{font-size:11.5px;color:var(--pt-ink-3);margin-left:5px}
.rv-pill.is-on span{color:#BDB8AE}
/* A zero pill still renders on the hub — the absence IS the information ("nobody left a 1 star"), and
   hiding it makes people wonder what else is hidden. Muted and unclickable rather than gone. */
.rv-pill.is-off{color:var(--pt-border-strong);border-color:var(--pt-border);cursor:default}
.rv-pill.is-off span{color:var(--pt-border-strong)}
.rv-applied{display:flex;gap:8px;align-items:center;flex-wrap:wrap;padding:0 0 14px}
.rv-chipx{display:inline-flex;gap:8px;align-items:center;background:var(--pt-bone,#EFEBE3);border:1px solid var(--pt-border-strong);padding:5px 8px 5px 12px;font-size:12.5px;color:var(--pt-ink)}
.rv-chipx span{color:var(--pt-ink-3)}
.rv-clear{color:var(--pt-accent);font-size:13px}

.pt-rvhub .rh-resline{display:flex;justify-content:space-between;align-items:baseline;gap:16px;padding:16px 0 8px;font-size:13.5px;color:var(--pt-ink-2)}
.pt-rvhub .rh-resline b{font-weight:600;color:var(--pt-ink)}
.pt-rvhub .rh-none,.pt-rvhub .rh-note{padding:18px 0;font-size:14.5px;color:var(--pt-ink-2)}
.pt-rvhub .rh-none a,.pt-rvhub .rh-note a{color:var(--pt-accent)}
.pt-rvhub .rh-empty{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:24px;align-items:center;max-width:760px;margin:22px 0;padding:26px 28px;background:var(--pt-surface);border:1px solid var(--pt-border)}
.pt-rvhub .rh-empty b{display:block;font-size:16px;margin:0 0 4px}
.pt-rvhub .rh-empty span{font-size:13.5px;color:var(--pt-ink-3)}

/* The product header on a filtered view. */
.pt-rvhub .rh-phead{display:grid;grid-template-columns:120px minmax(0,1fr) 300px;gap:32px;align-items:center;padding:28px 0;border-bottom:1px solid var(--pt-ink)}
.pt-rvhub .rh-phead__im{aspect-ratio:1;border:1px solid var(--pt-border);background:var(--pt-fill-control);position:relative;display:block;overflow:hidden}
.pt-rvhub .rh-phead__im img{position:absolute;inset:10%;width:80%;height:80%;object-fit:contain;mix-blend-mode:multiply}
.pt-rvhub .rh-back{display:inline-block;margin:0 0 8px;font-size:13px;color:var(--pt-accent)}
.pt-rvhub .rh-phead h1{margin:0 0 8px;font-size:28px;font-weight:700;letter-spacing:-.02em}
.pt-rvhub .rh-phead__meta{display:flex;gap:10px;align-items:center;flex-wrap:wrap;font-size:13.5px;color:var(--pt-ink-3)}
.pt-rvhub .rh-price{font-weight:600;color:var(--pt-ink);font-size:15px}
.pt-rvhub .rh-sep{color:var(--pt-border-strong)}
.pt-rvhub .rh-acts{display:flex;gap:8px;justify-self:end}

/* A review row on the hub carries the product it belongs to — what turns this page into a route in. */
.rv-item.has-product{grid-template-columns:230px minmax(0,1fr);gap:36px}
.rv-prod__l{display:flex;gap:12px;align-items:flex-start;color:inherit}
.rv-prod__im{width:66px;height:66px;flex:0 0 auto;border:1px solid var(--pt-border);background:var(--pt-fill-control);position:relative;overflow:hidden;display:block}
.rv-prod__im img{position:absolute;inset:10%;width:80%;height:80%;object-fit:contain;mix-blend-mode:multiply}
.rv-prod__nm{display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden;font-size:13.5px;font-weight:600;line-height:1.3}
.rv-prod__pr{display:block;margin-top:4px;font-size:12.5px;color:var(--pt-ink-3)}
.rv-prod__pr del{color:var(--pt-ink-3)}
.rv-prod__gone{margin:0;font-size:13px;color:var(--pt-ink-3)}
.rv-who--inline{flex-direction:row;align-items:center;gap:10px;margin:0 0 8px}
.rv-who--inline .rv-vp{margin-left:auto}
/* A neutral circle, never a broken image icon. */
.rv-av{width:30px;height:30px;border-radius:50%;border:1px solid var(--pt-border);background:var(--pt-fill-control) center/cover no-repeat;flex:0 0 auto;display:block}
.rv-h{font-size:15.5px;font-weight:600;margin:8px 0 6px}

/* Clamped at about eight lines — a wall of text is skipped, and a review nobody reads helped nobody. */
.rv-text.is-clamped{display:-webkit-box;-webkit-line-clamp:8;-webkit-box-orient:vertical;overflow:hidden}
.rv-readmore{background:none;border:0;padding:0;margin:4px 0 12px;font:inherit;font-size:13px;color:var(--pt-accent);cursor:pointer}

.pt-rvhub .rh-more{padding:26px 0 8px;display:flex;flex-direction:column;align-items:center;gap:10px}
.pt-rvhub .rh-more[hidden]{display:none}
.pt-rvhub .rh-prog{font-size:12.5px;color:var(--pt-ink-3)}
.pt-rvhub .rh-scope{display:flex;gap:10px;align-items:center;justify-content:space-between;flex-wrap:wrap;margin:22px 0 56px;padding:14px 18px;background:var(--pt-surface);border:1px solid var(--pt-border);font-size:13.5px;color:var(--pt-ink-2)}
.pt-rvhub .rh-scope a{color:var(--pt-accent)}

@media (max-width:1000px){
    .pt-rvhub .rh-stats{grid-template-columns:repeat(3,auto);gap:24px}
    .pt-rvhub .rh-stats__cta{grid-column:1 / -1;justify-self:start}
    .pt-rvhub .rh-tools{grid-template-columns:1fr;gap:8px}
    .rv-item.has-product{grid-template-columns:1fr;gap:14px}
    .pt-rvhub .rh-phead{grid-template-columns:90px minmax(0,1fr);gap:20px}
    .pt-rvhub .rh-acts{grid-column:1 / -1;justify-self:start}
}
@media (max-width:640px){
    .pt-rvhub .rh-wrap,.pt-rvhub .rh-bar{padding:0 16px}
    .pt-rvhub .rh-hero{padding:28px 0 24px}
    .pt-rvhub .rh-hero h1{font-size:30px}
    .pt-rvhub .rh-stats{grid-template-columns:1fr 1fr;gap:18px}
    .pt-rvhub .rh-stats__cta .rh-btn{width:100%}
    /* The pills scroll sideways rather than wrapping into three rows above the results. */
    .rv-pills{flex-wrap:nowrap;overflow-x:auto;scrollbar-width:none}
    .rv-pills::-webkit-scrollbar{display:none}
    .rv-pill{white-space:nowrap}
    .pt-rvhub .rh-sel{width:100%}
    .pt-rvhub .rh-empty{grid-template-columns:1fr}
    .pt-rvhub .rh-scope{flex-direction:column;align-items:flex-start;gap:6px}
}

/* ── ONE ROW: filters left, sort hard right (Leon, 23 Sep — from the mockup) ──────────────────
   The sort used to sit on its own line under the pills, which left a half-empty row between the
   filters and the reviews and made the sort read as part of the list rather than part of the
   filtering. `align-items:center` rather than baseline, because the pills are two lines tall on a
   narrow desktop and the select should stay level with them. */
/* 🔴 THE ANCHOR LANDS HERE, SO IT MUST CLEAR WHAT IS STICKY ABOVE IT. The product page has a
   sticky site header AND a sticky `.pp-secnav` beneath it; without this the filter bar scrolls to
   y=0 and sits UNDER both, so the person sees the row above it and thinks nothing moved. Same
   offset `.pp-sec` already uses, so the two land consistently. */
.rv-filterbar{display:flex;align-items:center;justify-content:space-between;gap:16px;flex-wrap:wrap;margin:22px 0 14px;
    scroll-margin-top:calc(var(--pt-header-h,64px) + 60px)}
.rv-filterbar .rv-pills{padding:0;flex:1 1 auto;min-width:0}
.rv-sortline{margin:0;flex:0 0 auto;margin-left:auto}
.rv-sortline select{height:34px;padding:0 10px;border:1px solid var(--pt-border-strong);background:var(--pt-surface);font-size:13px;border-radius:0}

@media (max-width:640px){
    /* Stacked on a phone, sort full width under the pills — a 12px select next to a scrolling pill
       row is unusable with a thumb. */
    .rv-filterbar{flex-direction:column;align-items:stretch;gap:10px}
    .rv-sortline{margin-left:0}
    .rv-sortline select{width:100%}
}
.rv-allink{margin:18px 0 0;font-size:14px}
.rv-allink a{color:var(--pt-accent)}

/* ── SEARCH SUGGESTIONS ──────────────────────────────────────────────────────
   The marketplace's grammar, replicated rather than re-invented: same classes, same padding, same
   highlight, same grouped labels. Two search boxes on one site that behave differently is two things to
   learn. (`.pt-suggest` is scoped to the marketplace wrapper there, so this is the hub's copy of the
   same values — flagged rather than reached across for.) [[feedback-build-habits-not-novelty]] */
.pt-rvhub .rh-search{position:relative}
.pt-rvhub .pt-suggest{position:absolute;left:-1px;right:-1px;top:100%;z-index:8;padding:0;
    background:var(--pt-surface);border:1px solid var(--pt-border-strong);border-top:0;border-radius:0;
    box-shadow:0 8px 24px rgba(0,0,0,.10);text-align:left;max-height:360px;overflow-y:auto}
.pt-rvhub .pt-suggest[hidden]{display:none}
.pt-rvhub .pt-suggest__group + .pt-suggest__group{border-top:1px solid var(--pt-border);margin-top:6px}
.pt-rvhub .pt-suggest__label{margin:0;padding:10px 14px 4px;font-size:11.5px;font-weight:600;line-height:1.3;color:var(--pt-ink-3);text-transform:none;letter-spacing:0}
.pt-rvhub .pt-suggest__row{display:flex;align-items:center;gap:9px;width:100%;padding:7px 14px;background:transparent;border:0;color:var(--pt-ink);font:inherit;font-size:14px;font-weight:400;text-align:left;cursor:pointer}
.pt-rvhub .pt-suggest__row.is-active,.pt-rvhub .pt-suggest__row:hover{background:var(--pt-fill-control)}
.pt-rvhub .pt-suggest__row:focus-visible{outline:2px solid var(--pt-accent);outline-offset:-2px}
.pt-rvhub .pt-suggest__row .n{margin-left:auto;font-family:var(--pt-mono);font-size:11.5px;color:var(--pt-ink-3)}
.pt-rvhub .pt-suggest__row .g{width:18px;flex:0 0 18px;text-align:center;color:var(--pt-ink-3);font-size:12px}


/* The product page states its sort rather than offering one — five reviews are not worth a dropdown
   that reloads the page. The control lives on /reviews/?product_id=, where there is something to sort. */
.rv-sortnote{margin:0;flex:0 0 auto;font-size:12.5px;color:var(--pt-ink-3);white-space:nowrap}


@media (max-width:760px){
    /* The review row collapses to one column on a phone, so the indent goes with it. */
    .rv-more{padding-left:0}
}
