/* Relayed-review wall — shared by /vendors/<slug> and /dashboard/vendors/<id>.

   Extracted verbatim from vendor_public_v2.html (09e942c7) so ONE definition
   serves both surfaces. Every --vp-* token carries an Assay fallback and then a
   literal, because the public vendor page supplies --vp-* and the dashboard does
   not — without the fallback chain the whole wall renders transparent-on-
   transparent there. Same pattern as compound_explorer.css / delivered_dose.css.

   The rules that used to be inherited from vendor_public_v2.html's own
   .vp-card / .vp-card-body / .vp-relayed-note / .vp-rev-more / .vp-star-*
   declarations are RE-DECLARED here onto the wall's own selectors. That is what
   makes the component self-contained on a surface that has none of them. On the
   public page these selectors are equal-or-higher specificity and later in
   source order than the inline block, and every value resolves identically, so
   the rendered page is unchanged.

   ── RELAYED-REVIEW WALL (2026-07-30) ─────────────────────────────────────────
   QSC went 21 -> 81 relayed posts. One full-width card each was ~14,800px of
   scroll — sixteen screens that buried everything under the section and took
   sixteen clicks to reach the end of. The corpus is SHORT (median 136 chars,
   p75 240) and 91% of it carries no author-stated rating, so a star-led,
   one-row-per-post list spends the most space on the thing there is least of.

   The wall tiles the quotes in a column flow so a two-line post occupies two
   lines, gives the 14 posts with a photo real size (they are the only
   first-hand evidence in the block), and puts the tail behind ONE honest
   "show all 81". Every card is still server-rendered — the tail is hidden by
   a .vp-js-gated rule, never withheld.

   What is deliberately NOT here: no transit time, ship date or warehouse lane
   parsed out of the body text. These read as a delivery log, and it is
   tempting to regex "ordered july 17th / arrived 30th july" into a "13-day
   transit" stat — but that asserts a measurement the author never made, from
   free text, with no validation. The delivery framing is carried by the
   block's editorial subtitle and by letting people read the quotes. If
   extraction is ever worth doing it belongs in a spec, not in a template.
   ────────────────────────────────────────────────────────────────────────── */

.vp-rl-block { margin-top: 26px; scroll-margin-top: 52px; }
.vp-rl-head { margin: 0 0 18px; padding-left: 13px; border-left: 2px solid rgba(240,181,68,.38); }
.vp-rl-head h3 {
  margin: 0 0 7px; font-family: var(--vp-serif, var(--display, Georgia, serif));
  font-size: 19px; font-weight: 600; letter-spacing: -0.01em;
  color: var(--vp-bright, var(--txt, #ECE7DD));
}
.vp-rl-sub {
  margin: 0 0 10px; max-width: 78ch; font-size: 13.5px; line-height: 1.62;
  color: var(--vp-muted, var(--mut, #8b949e));
}
.vp-rl-meta {
  margin: 0; font-family: var(--vp-mono, var(--mono, ui-monospace, monospace));
  font-size: 11px; line-height: 1.75; letter-spacing: .03em;
  color: var(--vp-faint, var(--faint, #5d6571));
}
.vp-rl-meta b { color: var(--vp-amber, var(--accent, #F0B544)); font-weight: 600; }
.vp-rl-meta .sep { color: var(--vp-line, var(--bord, #23262b)); margin: 0 8px; }

/* Masonry via CSS columns: short quotes tile instead of stacking. Overrides
   the .vp-cards grid on the public page — the wall keeps that class so the
   shared .vp-js reveal rule and the reveal JS both still apply to it. */
.vp-rl-wall { display: block; columns: 3; column-gap: 14px; }
.vp-rl-card {
  /* card material: inherited from .vp-card on the public page, declared here so
     the wall is not a transparent rectangle on a surface without it */
  position: relative;
  background: var(--vp-panel, var(--panel, #101113));
  border: 1px solid var(--vp-line, var(--bord, rgba(255,255,255,.075)));
  /* the left rule is half of what distinguishes a relayed post from a member
     review at a glance; the provenance line is the other half */
  border-left: 2px solid rgba(240,181,68,.38);
  border-radius: var(--vp-r-md, var(--radius-sm, 11px));
  break-inside: avoid; -webkit-column-break-inside: avoid; page-break-inside: avoid;
  width: 100%; margin: 0 0 14px; padding: 15px 17px 14px;
  transition: border-color .16s ease;
}
.vp-rl-card:hover { border-color: rgba(240,181,68,.24); }
.vp-rl-card .vp-card-body {
  margin: 0; color: var(--vp-text, var(--txt, #c9d1d9));
  font-size: 14px; line-height: 1.55;
  /* The authors wrote these as line-broken delivery logs ("Ordered july
     17th / Arrived 30th july"). Collapsing the breaks made them read as one
     run-on sentence; keeping them is faithful and costs no parsing. */
  white-space: pre-line; overflow-wrap: anywhere;
}
/* Provenance: same words, same place, on all 81 cards — but set as a label
   rather than a headline. At 10.5px/0.92 amber it wrapped to two lines and
   was the loudest thing on every card, so a wall of 81 read as a wall of
   disclaimers instead of a wall of quotes. This is a type-weight change
   only: the text is unchanged, it still leads the card, it is still amber,
   and it is still legible at a glance. Do NOT collapse it into the footer,
   shorten it, or put it behind a toggle. */
.vp-rl-card .vp-relayed-note {
  margin: 0 0 9px; font-family: var(--vp-mono, var(--mono, ui-monospace, monospace));
  color: var(--vp-amber, var(--accent, #F0B544));
  font-size: 9.5px; line-height: 1.5; letter-spacing: .045em;
  opacity: .72;
}
/* The photo is the card's lead, bled to the card edge — at 84px inside a
   text block it was an afterthought, and it is the most persuasive thing in
   the set. Square centre-crop: the corpus is portrait phone shots of
   packages with the subject centred. Full frame opens in the lightbox. */
.vp-rl-photo {
  display: block; margin: -15px -17px 12px; overflow: hidden;
  border-radius: var(--vp-r-md, var(--radius-sm, 11px)) var(--vp-r-md, var(--radius-sm, 11px)) 0 0;
  background: var(--vp-panel-2, var(--raised, #0b0d10));
}
.vp-rl-photo img {
  display: block; width: 100%; aspect-ratio: 1 / 1; object-fit: cover;
  object-position: center; transition: transform .35s ease;
}
.vp-rl-photo:hover img { transform: scale(1.035); }
.vp-rl-photo:focus-visible { outline: 2px solid var(--vp-blue, var(--accent, #F0B544)); outline-offset: 2px; }
/* The handful of relayed posts that carry a clip rather than a still. */
.vp-rl-card .vp-media-video {
  max-width: 100%; width: 280px; border-radius: 6px;
  border: 1px solid rgba(255,255,255,0.12);
}
.vp-rl-foot {
  display: flex; align-items: baseline; flex-wrap: wrap; gap: 3px 7px; margin-top: 11px;
  font-family: var(--vp-mono, var(--mono, ui-monospace, monospace));
  font-size: 10.5px; letter-spacing: .02em; color: var(--vp-faint, var(--faint, #5d6571));
}
.vp-rl-foot .who { color: var(--vp-muted, var(--mut, #8b949e)); }
.vp-rl-foot .sep { color: var(--vp-line, var(--bord, #23262b)); }
.vp-rl-foot .vp-card-date {
  font-family: var(--vp-mono, var(--mono, ui-monospace, monospace));
  font-size: 10.5px; color: var(--vp-faint, var(--faint, #5d6571));
}
/* Rating sits at the trailing edge, quiet. 74 of 81 posts state none, so a
   rating-led card top was 74 repetitions of "NO RATING GIVEN" as the
   loudest element on the wall. It stays on every unrated card — an empty
   star row would read as a 1-star verdict nobody gave — just not shouting. */
.vp-rl-foot .vp-card-stars, .vp-rl-foot .vp-card-norating { margin-left: auto; }
.vp-rl-foot .vp-card-stars { font-size: 11px; letter-spacing: 1px; }
.vp-rl-foot .vp-star-on { color: var(--vp-blue, var(--accent, #F0B544)); }
.vp-rl-foot .vp-star-off { color: var(--vp-line, var(--bord, #23262b)); }
/* "no rating given" — never render zero stars for an unrated review; empty
   stars read as a 1-star verdict the author never gave. */
.vp-rl-foot .vp-card-norating {
  font-family: var(--vp-mono, var(--mono, ui-monospace, monospace));
  font-size: 9.5px; text-transform: uppercase; letter-spacing: .08em; opacity: .42;
}

/* ── Progressive reveal ────────────────────────────────────────────────────
   Cards past the default window are hidden, NOT omitted. .vp-js gates the
   whole rule (relayed_reviews.js adds it) so a JS-off browser — and any
   crawler that ignores our JS — shows every card; hiding content that cannot
   be revealed would be cloaking. */
.vp-js .vp-rl-wall .vp-card-more { display: none; }
.vp-js .vp-rl-wall .vp-card-more.vp-shown { display: block; animation: vpRlFade .26s ease both; }
@keyframes vpRlFade { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: none; } }
.vp-rl-more {
  display: none; align-items: center; gap: 10px; margin: 2px 0 0; cursor: pointer;
  font-family: var(--vp-mono, var(--mono, ui-monospace, monospace));
  font-size: 12px; letter-spacing: .05em; text-transform: uppercase;
  padding: 11px 20px; border: 1px solid var(--vp-line, var(--bord, rgba(255,255,255,.075)));
  border-radius: 999px; background: none;
  /* --vp-ink / --vp-ink-2 are referenced by the public page's .vp-rev-more but
     defined nowhere, so BOTH surfaces fall through to the next link in the
     chain. Kept as the head of the chain rather than deleted so that defining
     them later still works, and so the public page's computed colour is
     byte-for-byte what it was before this file existed (#c9d1d9 either way). */
  color: var(--vp-ink-2, var(--vp-text, var(--txt, #82858C)));
  transition: border-color .18s, color .18s;
}
.vp-js .vp-rl-more { display: inline-flex; }
.vp-rl-more:hover {
  border-color: var(--vp-amber, var(--accent, #F0B544));
  color: var(--vp-ink, var(--vp-text, var(--txt, #F4F4F5)));
}
.vp-rl-more:focus-visible { outline: 2px solid var(--vp-amber, var(--accent, #F0B544)); outline-offset: 2px; }
.vp-rl-more .n { font-size: 11px; opacity: .6; text-transform: none; letter-spacing: 0; }

@media (max-width: 899px) { .vp-rl-wall { columns: 2; } }
@media (max-width: 619px) {
  .vp-rl-wall { columns: 1; }
  /* One column is a full-width card, so a square photo is a whole screen.
     Wider crop keeps the post readable without scrolling past the picture. */
  .vp-rl-photo img { aspect-ratio: 4 / 3; }
  .vp-rl-sub { font-size: 13px; }
}
@media (prefers-reduced-motion: reduce) {
  .vp-rl-card, .vp-rl-photo img, .vp-rl-more { transition: none; }
  .vp-js .vp-rl-wall .vp-card-more.vp-shown { animation: none; }
}
