/* ==========================================================================
   /WALL-OF-LOVE                          docs/spec/university-wall.md §2 (ADR-22)
   No comp: the page stands on the .zr-bl shell (bl.css — hero wash, eyebrow,
   h1, lede, the hairline/ink/shadow tokens) and borrows the platform pages'
   section head (pill kicker, gradient h2, muted sub) so it reads as the same
   site as /case-studies and /free-tools. Colours are base.css and bl.css
   tokens; the only new ones are the two review platforms' own marks.

     .zr-wall
       .zr-wall__cta · .zr-wall__trust                  hero additions
       .zr-wall__sec > .zr-wall__head                   section + its heading
       .zr-wall__frame > ul.zr-wall__grid > li.zr-wall__item > a.zr-wall__card[data-zoom]
           .zr-wall__shot ( img · .zr-wall__more )  ·  .zr-wall__by ( .zr-wall__who · .zr-wall__src )
       ul.zr-wall__vids > li.zr-wall__vitem > a.zr-wall__vid[data-yt]
           .zr-wall__poster ( img · .zr-wall__play · .zr-wall__yt )  ·  .zr-wall__vbody
       .zr-wall__share                                  "Got love to share?"
   ========================================================================== */
.zr-wall{ --wall-gap:24px; --wall-r:22px; --wall-in:12px; }
/* the platform pages' 1248px column (pp.css), so the cards line up with /case-studies and /free-tools */
.zr-wall__wrap{ width:min(1248px, 100%); margin:0 auto; padding-inline:var(--zr-pad); }

/* ── hero: two buttons and the trust line under the .zr-bl lede ───────────
   The type is the centred hero of /free-tools, /case-studies and /about
   (content.css .zr-pp__hero--center: 54px / 1.12 at 1440, lede 16.5px), one
   step above the .zr-bl default, so the h1 stays the largest heading on a
   page whose shared heart heading is 48px. */
.zr-wall .zr-bl__h1{ font-size:clamp(34px, 4.6vw, 54px); line-height:1.12; letter-spacing:-.032em; }
.zr-wall .zr-bl__lede{ font-size:16.5px; }
.zr-wall__cta{ display:flex; flex-wrap:wrap; justify-content:center; gap:12px; margin-top:8px; }
/* the secondary action as a white face with a hairline: the soft grey fill goes muddy on the hero wash and on the
   tinted share panel. Same four custom properties as every .zr-btn surface, plus the edge. */
.zr-wall .zr-btn--soft{
  --btn-face:#fff; --btn-face-hi:#F7F9FC;
  box-shadow:inset 0 0 0 1px var(--bl-hair), inset 0 3px 14px var(--btn-sheen), 0 1px 2px rgba(31,36,41,.05);
}
.zr-wall .zr-btn--soft:hover{ box-shadow:inset 0 0 0 1px #D3D8E0, inset 0 3px 14px var(--btn-sheen-hi), 0 1px 2px rgba(31,36,41,.05); }
.zr-wall .zr-btn--soft:active{ box-shadow:inset 0 0 0 1px #D3D8E0, inset 0 4px 9px var(--btn-press), inset 0 1px 2px var(--btn-press); }
.zr-wall__trust{
  display:inline-flex; align-items:center; gap:10px; margin:4px 0 0;
  font-size:13.5px; line-height:20px; color:var(--bl-body);
}
.zr-wall__faces{ display:inline-flex; flex:0 0 auto; }
.zr-wall__faces img{
  width:28px; height:28px; border-radius:50%; object-fit:cover;
  border:2px solid #fff; background:var(--bl-ph);
  box-shadow:0 1px 2px rgba(31,36,41,.10);
}
.zr-wall__faces img + img{ margin-left:-9px; }

/* ── sections ──────────────────────────────────────────────────────────────
   The nav pill floats 20px from the top once the page scrolls; an anchored
   section has to land under it, not behind it. */
.zr-wall__sec{ padding-block:clamp(36px, 4.2vw, 68px); scroll-margin-top:104px; }
.zr-wall__hero + .zr-wall__sec{ padding-top:clamp(12px, 1.6vw, 24px); }
.zr-wall__head{
  display:flex; flex-direction:column; align-items:center; text-align:center; gap:14px;
  max-width:760px; margin:0 auto clamp(28px, 3.2vw, 44px);
}
.zr-wall__kicker{
  display:inline-flex; align-items:center; padding:5px 13px;
  border:1px solid var(--bl-hair); border-radius:999px; background:#fff;
  font-size:12.5px; line-height:18px; color:var(--bl-body);
}
.zr-wall__h2{
  margin:0; text-wrap:balance;
  font-size:clamp(28px, 3.4vw, 46px); font-weight:500; line-height:1.16; letter-spacing:-.03em;   /* = .zr-pp__h2 */
  background:var(--zr-head-grad);
  -webkit-background-clip:text; background-clip:text; color:transparent; -webkit-text-fill-color:transparent;
}
.zr-wall__sub{ margin:0; max-width:600px; font-size:16.5px; line-height:26px; color:var(--zr-muted); text-wrap:pretty; }

/* ── the receipts: a CSS-columns masonry inside the site's soft frame ──────
   2 columns from 680px, 1 below, and never 3 (a deliberate departure from
   spec §2): fourteen of the fifteen captures are 1736px wide, and at a third
   of the wrap they render at 0.20x, about 6px type, so the desktop wall read
   as grey texture. At half the wrap they are ~545px, about 10px type, which
   is also what the live page does. Every <img> carries its width/height, so
   a card's box is known before a byte of it arrives and the columns never
   re-balance. Each item reveals on its own: the wall as a whole is taller
   than the observer's 15% threshold can ever see on a phone.

   CSS columns fill in order, so the last column ends where the cards run
   out — up to a card short of the others. The frame (the platform pages'
   .zr-pp__frame: #F6F7F9, a hairline, 10px of padding) is what makes that
   read as the wall's own negative space instead of a hole in the page. */
.zr-wall__frame{
  padding:var(--wall-in); border-radius:28px;
  background:#F6F7F9; border:1px solid #EEF1F5;
}
.zr-wall__grid{
  list-style:none; padding:0; margin:0 0 calc(-1 * var(--wall-in));
  columns:1; column-gap:var(--wall-in);
}
/* The gap under a card is padding, not margin: a margin next to a column break is truncated, so the tallest
   column would end flush with its last card and the grid's negative margin would then pull the frame over it. */
.zr-wall__item{ margin:0; padding:0 0 var(--wall-in); break-inside:avoid; -webkit-column-break-inside:avoid; }

.zr-wall__card{
  container-type:inline-size;
  display:flex; flex-direction:column; min-width:0;
  padding:10px 10px 0;
  background:#fff; border:1px solid #EDF1F7; border-radius:18px;
  box-shadow:0 6px 16px -6px rgba(31,36,41,.05);
  text-decoration:none; cursor:zoom-in;
  transition:border-color .18s var(--zr-ease-out-quad),
             box-shadow .18s var(--zr-ease-out-quad);
}
.zr-wall__card:hover{ border-color:#D8E2F6; box-shadow:0 10px 22px -8px rgba(31,36,41,.10); }
.zr-wall__card:focus-visible{ outline:2px solid var(--zr-blue); outline-offset:2px; border-radius:18px; }
/* the two the data marks as featured: the blue hairline the site uses for "this one" */
.zr-wall__card.is-featured{ border-color:#CFE0FF; box-shadow:0 0 0 3px rgba(20,99,245,.07), 0 6px 16px -6px rgba(31,36,41,.05); }
.zr-wall__card.is-featured:hover{ border-color:#B9D1FF; box-shadow:0 0 0 3px rgba(20,99,245,.10), 0 10px 22px -8px rgba(31,36,41,.10); }

/* The captures are light review cards with their own baked-in grey border and
   corner radius, a different one per platform. The frame overscales the image
   by 1% and clips it, which takes that border off, then draws one hairline of
   its own — fifteen captures, one chrome. Margins in % resolve against the
   frame's width on all four sides, so the trim is the same number of pixels
   all round and the box is still fixed by width/height alone. */
.zr-wall__shot{ position:relative; display:block; overflow:hidden; border-radius:10px; background:#fff; }
/* Only a capture the data marks as tall (taller than TALL_RATIO × its width: today the one 3702px review) is
   capped, so it does not own a column; the zoom dialog shows all of it. The cap is a multiple of the frame's own
   width (cqw of the card), so the crop is the same at every size, and it is tuned per layout because in CSS columns
   the tall card is the last one: one column wants it short, in two it evens the columns out. Two columns run from
   680px to the full wrap, and the left column holds one card more than the right, so the difference between them
   is a share of the width less one card's fixed chrome (byline, inset, gap: 72px); the two-column cap carries both
   terms and the wall ends flush at every width. Without container units it falls back to a plain pixel cap. */
.zr-wall__card.is-tall .zr-wall__shot{ max-height:560px; }
@supports (width:1cqw){ .zr-wall__card.is-tall .zr-wall__shot{ max-height:160cqw; } }
.zr-wall__shot img{ display:block; width:101%; max-width:none; height:auto; margin:-.5%; }
.zr-wall__shot::after{
  content:""; position:absolute; inset:0; z-index:1; border-radius:inherit; pointer-events:none;
  box-shadow:inset 0 0 0 1px rgba(31,36,41,.09);
}
/* a capped capture says so: it dissolves instead of stopping mid-sentence */
.zr-wall__card.is-tall .zr-wall__shot::before{
  content:""; position:absolute; left:0; right:0; bottom:0; z-index:1; height:132px; pointer-events:none;
  background:linear-gradient(180deg, rgba(255,255,255,0) 0%, rgba(255,255,255,.92) 62%, #fff 100%);
}

/* the zoom hint: on hover and keyboard focus, always on a capped card, and always where nothing can hover */
.zr-wall__more{
  position:absolute; left:50%; bottom:12px; z-index:2; translate:-50% 6px;
  display:inline-flex; align-items:center; gap:6px; padding:6px 13px 6px 9px;
  border:1px solid var(--bl-hair); border-radius:999px; background:#fff;
  box-shadow:0 6px 16px -6px rgba(31,36,41,.22);
  font-size:12.5px; line-height:18px; font-weight:500; color:var(--bl-ink); white-space:nowrap;
  opacity:0;
  transition:opacity .18s var(--zr-ease-out-quad), translate .18s var(--zr-ease-out-quad);
}
.zr-wall__more svg{ flex:0 0 auto; width:16px; height:16px; color:var(--zr-blue); }
.zr-wall__card:hover .zr-wall__more,
.zr-wall__card:focus-visible .zr-wall__more,
.zr-wall__card.is-tall .zr-wall__more{ opacity:1; translate:-50% 0; }
/* On a touch screen there is no hover and no focus ring to bring the hint up, and a phone is where a capture is
   smallest (321px from 1736px), so the cards that most need opening would be the ones that never say they open. */
@media (hover:none){
  .zr-wall__more{ opacity:1; translate:-50% 0; }
}

/* who said it, and where */
.zr-wall__by{
  display:flex; align-items:center; justify-content:space-between; gap:12px;
  min-width:0; padding:12px 8px 13px 10px;
}
.zr-wall__who{
  min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
  font-size:14px; line-height:20px; font-weight:500; letter-spacing:-.006em; color:var(--bl-ink);
}
.zr-wall__src{ flex:0 0 auto; gap:5px; padding-left:8px; }
.zr-wall__src svg{ width:13px; height:13px; flex:0 0 auto; }
.zr-wall__src--trustpilot svg{ fill:#00B67A; }   /* Trustpilot's green, as in wol.css */
.zr-wall__src--appsumo svg{ fill:#FFBC00; }      /* AppSumo's yellow */

/* ── video reviews ─────────────────────────────────────────────────────────
   Five cards on a six-track grid: two wide, then three, so no row ends in a
   hole. Between 680 and 959px the first lies on its side across both columns
   (poster left, title right, like the blog's featured post) above 2 + 2. */
.zr-wall__vids{
  list-style:none; padding:0; margin:0;
  display:grid; grid-template-columns:minmax(0,1fr); gap:var(--wall-gap);
}
.zr-wall__vitem{ display:flex; min-width:0; }
.zr-wall__vid{
  display:flex; flex-direction:column; width:100%; min-width:0; overflow:hidden;
  background:#fff; border:1px solid var(--bl-hair); border-radius:var(--wall-r);
  box-shadow:var(--bl-card-sh); text-decoration:none;
  transition:border-color .18s var(--zr-ease-out-quad),
             box-shadow .18s var(--zr-ease-out-quad);
}
.zr-wall__vid:hover{ border-color:#D8E2F6; box-shadow:0 10px 22px -8px rgba(31,36,41,.10); }
.zr-wall__vid:focus-visible{ outline:2px solid var(--zr-blue); outline-offset:3px; border-radius:var(--wall-r); }

.zr-wall__poster{
  position:relative; display:block; aspect-ratio:16 / 9; overflow:hidden;
  background:linear-gradient(140deg,#1B2A44,#0B1730);
}
.zr-wall__poster img{
  display:block; width:100%; height:100%; object-fit:cover;
  transition:scale .6s var(--zr-ease-out-quart);
}
.zr-wall__vid:hover .zr-wall__poster img{ scale:1.03; }
.zr-wall__poster::after{
  content:""; position:absolute; inset:0; pointer-events:none;
  background:linear-gradient(180deg, rgba(11,23,48,.10) 0%, rgba(11,23,48,0) 34%, rgba(11,23,48,.22) 100%);
}
.zr-wall__play{
  position:absolute; left:50%; top:50%; z-index:1; translate:-50% -50%;
  width:60px; height:60px; border-radius:50%;
  display:grid; place-items:center;
  background:#fff; color:var(--zr-blue);
  box-shadow:0 12px 28px -8px rgba(11,23,48,.55), 0 0 0 6px rgba(255,255,255,.22);
  transition:background .18s var(--zr-ease-out-quad), color .18s var(--zr-ease-out-quad),
             scale .22s var(--zr-ease-out-quad);
}
.zr-wall__play svg{ width:24px; height:24px; margin-left:3px; fill:currentColor; }
.zr-wall__vid:hover .zr-wall__play,
.zr-wall__vid:focus-visible .zr-wall__play{ background:var(--zr-blue); color:#fff; scale:1.06; }
.zr-wall__yt{
  position:absolute; right:12px; top:12px; z-index:1;
  display:inline-flex; align-items:center; gap:6px; padding:4px 10px 4px 8px; border-radius:999px;
  background:rgba(12,20,38,.72); -webkit-backdrop-filter:blur(8px); backdrop-filter:blur(8px);
  font-family:'Inter', var(--zr-font); font-size:12px; line-height:18px; font-weight:500; color:#fff;
}
.zr-wall__yt svg{ width:14px; height:14px; fill:currentColor; }

.zr-wall__vbody{ display:flex; flex-direction:column; gap:6px; padding:16px 20px 20px; }
.zr-wall__vtitle{
  margin:0; font-size:16px; line-height:24px; font-weight:500; letter-spacing:-.012em;
  color:var(--bl-ink); text-wrap:pretty;
}
.zr-wall__vby{ font-size:13.5px; line-height:20px; color:var(--zr-muted); }

/* ── "Got love to share?" ────────────────────────────────────────────────── */
.zr-wall__sec--share{ padding-top:clamp(8px, 1vw, 16px); padding-bottom:clamp(56px, 6vw, 96px); }
.zr-wall__share{
  display:flex; flex-direction:column; align-items:center; gap:14px; text-align:center;
  padding:clamp(32px, 4vw, 52px) clamp(20px, 4vw, 44px);
  background:#FBFCFE; border:1px solid var(--bl-hair); border-radius:24px;
}
.zr-wall__share-h{
  margin:0; font-size:clamp(22px, 2.2vw, 30px); font-weight:500; line-height:1.25;
  letter-spacing:-.025em; color:var(--bl-ink); text-wrap:balance;
}
.zr-wall__share-p{ margin:0; max-width:560px; font-size:16px; line-height:26px; color:var(--bl-body); text-wrap:balance; }
.zr-wall__share-p a{ color:var(--zr-blue); text-decoration:none; }
.zr-wall__share-p a:hover{ text-decoration:underline; text-underline-offset:2px; }
.zr-wall__share .zr-btn{ margin-top:6px; }
.zr-wall__share .zr-btn svg{ width:15px; height:15px; fill:currentColor; }

/* ── breakpoints ─────────────────────────────────────────────────────────── */
@media (min-width:680px){
  .zr-wall__grid{ columns:2; }
  @supports (width:1cqw){ .zr-wall__card.is-tall .zr-wall__shot{ max-height:calc(105cqw + 72px); } }
  .zr-wall__vids{ grid-template-columns:repeat(2, minmax(0,1fr)); }
}
@media (min-width:680px) and (max-width:959px){
  .zr-wall__vitem:first-child{ grid-column:1 / -1; }
  .zr-wall__vitem:first-child .zr-wall__vid{ flex-direction:row; }
  /* stretch, not flex-start: if a longer title ever outgrows the 16:9 box, the poster covers the taller card */
  .zr-wall__vitem:first-child .zr-wall__poster{ flex:0 0 56%; align-self:stretch; }
  .zr-wall__vitem:first-child .zr-wall__vbody{ justify-content:center; gap:8px; padding:20px 24px; }
  .zr-wall__vitem:first-child .zr-wall__vtitle{ font-size:20px; line-height:28px; letter-spacing:-.018em; }
}
@media (min-width:960px){
  .zr-wall__vids{ grid-template-columns:repeat(6, minmax(0,1fr)); }
  .zr-wall__vitem{ grid-column:span 2; }
  .zr-wall__vitem--wide{ grid-column:span 3; }
  .zr-wall__vitem--wide .zr-wall__vtitle{ font-size:18px; line-height:26px; }
  .zr-wall__vitem--wide .zr-wall__play{ width:68px; height:68px; }
  .zr-wall__vitem--wide .zr-wall__play svg{ width:27px; height:27px; }
}
@media (max-width:679px){
  .zr-wall{ --wall-gap:16px; --wall-in:8px; }
  .zr-wall__frame{ border-radius:24px; }
  /* thumb-sized targets: the two hero buttons share the row, full width */
  .zr-wall__cta{ width:100%; }
  .zr-wall__cta .zr-btn{ flex:1 1 0; min-width:0; padding-inline:16px; }
  .zr-wall__vbody{ padding:14px 16px 18px; }
}

/* Without scripting nothing ever adds .is-in, and base.css parks every [data-reveal] at opacity 0: the h1, the
   buttons, all twenty cards. The cards are plain links that work without JavaScript (spec §1), so they have to be
   seen without it. This sheet only loads on /wall-of-love, so the rule is the page's; the same three declarations
   belong in base.css for every page (reported to the integrator). */
@media (scripting:none){
  [data-reveal]{ opacity:1; translate:none; filter:none; }
}
/* a block that holds the keyboard focus is shown even if the reveal observer never saw it (see uni.css) */
.zr-wall [data-reveal]:focus-within{ opacity:1; translate:none; filter:none; }
/* The frame is painted from the start but its cards wait for the observer, so the first screen could end in an
   empty grey tray. The page reveals the two cards that head the columns at load; should the data change and a
   column head wait after all, the tray itself stays invisible until the first card is in. */
@media (scripting:enabled){
  .zr-wall__frame{ transition:background-color .4s var(--zr-ease-out-quad), border-color .4s var(--zr-ease-out-quad); }
  .zr-wall__frame:not(:has(.zr-wall__item.is-in)){ background-color:transparent; border-color:transparent; }
}

@media (prefers-reduced-motion:reduce){
  .zr-wall__more{ translate:-50% 0; }
  .zr-wall__vid:hover .zr-wall__poster img,
  .zr-wall__vid:hover .zr-wall__play,
  .zr-wall__vid:focus-visible .zr-wall__play{ scale:1; }
}
