/* ============================================================
   VerityLoft — LAYER 3: Product Review  (vl-article.css)

   Loads site-wide, scoped to .vl-review, so it only takes effect
   on pages you tag. Needs vl-base.css first, for the palette.

   To turn a page into a review:
     Block, then Advanced, then Additional CSS classes: vl-review

   NAMING. The root class is .vl-review, NOT .vl-article, because
   .vl-article is already the root of the older vla- component set
   used by the AdBlocker review. Two layers claiming one class
   would fight each other silently. Keep them apart.

   THE THREE PARENTS
     PCMag        the apparatus. Score dial, verdict card, a
                  stated test bed.
     Wirecutter   the comparison. What else was considered, what
                  it lost on, who should skip this.
     Scouting     the voice, and the distinctive part. A comp
     report       line, a floor and a ceiling instead of one
                  number, and a stated risk.

     Invisible    the fourth parent, and the reason this layer
     friction     cannot be copied by someone who has not used
                  the product. Every other review prices a thing
                  at its sticker and stops. This one accounts for
                  what it takes from you afterwards: the hours,
                  the price drift, the exit. Sticker price is the
                  smallest number in the deal.

   ONE SCALE ONLY. Scouts grade on 20 to 80. We took the ideas
   and left the scale, because two scales on one page makes a
   reader ask which one is the score, and because only a rating
   out of five earns the stars in a search result. Sub-scores are
   out of five, same as the dial. The one thing kept from the
   scale is the optional par marker, which needs no explaining.

   DELIBERATELY NOT HERE. Tick marks, evidence cross-references,
   materiality, an engagement-file header, a sign-off block. Those
   belong to vl-paper and they stay there, so that a reader can
   tell a tested product review from original research at a
   glance. Same reason paragraphs here are flush left and only
   the paper indents.

   No angle brackets anywhere, so the save will not be rejected.
   ============================================================ */

.vl-review{
  --vlr-navy:    var(--vl-blue,      #1E293B);
  --vlr-gold:    var(--vl-gold,      #DDB849);
  --vlr-goldink: var(--vl-gold-ink,  #7A5F1A);
  --vlr-wash:    var(--vl-gold-wash, #FBF3DC);
  --vlr-rule:    var(--vl-rule,      #D8D3C8);
  --vlr-muted:   var(--vl-muted,     #5C6472);
  --vlr-ink:     var(--vl-graphite,  #333333);
  --vlr-pass:    #2F6F52;
  --vlr-fail:    #9A4B3F;
  --vlr-partial: #8A6512;

  container-type: inline-size;
  max-width: var(--vl-measure, 74ch);
  margin-inline: auto;
  font-family: var(--vl-sans, "Inter", sans-serif);
  font-size: 1.0125rem;
  line-height: 1.65;
  color: var(--vlr-ink);
}

.vl-review.is-wide{
  max-width: 1100px;
}

/* Flush left, always. The indent is the research paper's signal
   and this layer does not borrow it. */
.vl-review p{
  text-indent: 0;
}

/* ============================================================
   A0. KEY FACTS

   The answer block, and the highest-value thing on the page for
   an AI overview. A definition list of short, self-contained
   facts near the top, in plain server-rendered HTML.

   Two rules when writing it. Name the product in every value
   rather than saying "it", because a model lifts one row at a
   time and the row has to stand alone. And keep every value to
   one line: a fact, not a sentence about a fact.
   ============================================================ */
.vl-review .vl-keyfacts{
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
  gap: 0;
  margin: 1.4rem 0 1.8rem;
  padding: 0;
  border: 1px solid var(--vlr-rule);
  border-top: 3px solid var(--vlr-navy);
  border-radius: 4px;
  background: #fff;
}

.vl-review .vl-keyfacts div{
  padding: .75rem 1.05rem;
  border-right: 1px solid var(--vlr-rule);
  border-bottom: 1px solid var(--vlr-rule);
}

.vl-review .vl-keyfacts div:last-child{
  border-right: 0;
}

.vl-review .vl-keyfacts dt{
  margin: 0 0 .2rem;
  font-size: .6rem;
  font-weight: 700;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--vlr-muted);
}

.vl-review .vl-keyfacts dd{
  margin: 0;
  font-size: .9rem;
  font-weight: 600;
  line-height: 1.4;
  color: var(--vlr-navy);
}

/* ============================================================
   A. THE COMP
   The scouting report's opening move, and the most useful
   sentence on the whole page: this thing, described in terms of
   a thing the reader already knows.

     If you have run Hubdoc, this is Hubdoc with real OCR and a
     worse export.

   It goes near the top, above the verdict card. Write it so it
   survives being quoted on its own, because it will be.
   ============================================================ */
.vl-review .vl-comp{
  position: relative;
  margin: 1.6rem 0 2rem;
  padding: 1.1rem 1.3rem 1.1rem 1.5rem;
  border-left: 4px solid var(--vlr-gold);
  background: var(--vlr-wash);
  border-radius: 0 8px 8px 0;
}

.vl-review .vl-comp__label{
  display: block;
  margin-bottom: .35rem;
  font-size: .62rem;
  font-weight: 700;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--vlr-goldink);
}

.vl-review .vl-comp__line{
  margin: 0;
  font-family: var(--vl-serif, Georgia, "Times New Roman", serif);
  font-size: 1.28rem;
  line-height: 1.4;
  font-weight: 600;
  color: var(--vlr-navy);
}

.vl-review .vl-comp__line em{
  font-style: italic;
  color: var(--vlr-goldink);
}

/* ============================================================
   B. VERDICT CARD  (PCMag apparatus)
   ============================================================ */
.vl-review__verdict{
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 1.2rem 1.6rem;
  align-items: center;
  margin: 1.8rem 0 1rem;
  padding: 1.4rem 1.5rem;
  background: #fff;
  border: 1px solid var(--vlr-rule);
  border-top: 3px solid var(--vlr-gold);
  border-radius: 10px;
}

/* Score dial. The arc is driven by --vl-score set inline on the
   dial. Circumference at r=54 is 339.292, so the visible arc is
   the score divided by five of that. No JavaScript needed. */
.vl-review .vl-score{
  position: relative;
  display: grid;
  place-items: center;
  width: 124px;
  height: 124px;
}

.vl-review .vl-score svg{
  transform: rotate(-90deg);
}

.vl-review .vl-score__track{
  fill: none;
  stroke: rgba(30, 41, 59, .12);
  stroke-width: 9;
}

.vl-review .vl-score__arc{
  fill: none;
  stroke: var(--vlr-gold);
  stroke-width: 9;
  stroke-linecap: round;
  stroke-dasharray: calc(var(--vl-score, 0) / 5 * 339.292) 339.292;
}

.vl-review .vl-score__value{
  position: absolute;
  inset: 0;
  display: grid;
  place-content: center;
  text-align: center;
  line-height: 1.05;
}

.vl-review .vl-score__num{
  font-size: 2rem;
  font-weight: 800;
  letter-spacing: -.02em;
  color: var(--vlr-navy);
  font-variant-numeric: tabular-nums;
}

.vl-review .vl-score__den{
  margin-top: .15rem;
  font-size: .6rem;
  font-weight: 700;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--vlr-muted);
}

.vl-review__line{
  margin: 0 0 .8rem;
  font-size: 1.1rem;
  font-weight: 600;
  line-height: 1.4;
  color: var(--vlr-navy);
}

.vl-review__meta{
  margin: 0 0 .9rem;
  font-size: .82rem;
  color: var(--vlr-muted);
}

/* ============================================================
   C. TEST BED
   One line under the verdict saying what the review is made of:
   how long, on whose money, how many runs. The run count is
   filled in by vl-article.js from the claims table, so it can
   never disagree with the numbers under it.
   ============================================================ */
.vl-review .vl-testbed{
  display: flex;
  flex-wrap: wrap;
  gap: .35rem .9rem;
  margin: 0 0 2rem;
  padding: .6rem .9rem;
  border: 1px dashed var(--vlr-rule);
  border-radius: 6px;
  font-size: .78rem;
  color: var(--vlr-muted);
}

.vl-review .vl-testbed b{
  color: var(--vlr-navy);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

/* ============================================================
   D. SUB-SCORES

   The same five-point scale as the dial, so there is only ever
   one scale on the page and nothing needs explaining. Write the
   number once, in data-score, and vl-article.js draws the bar,
   colours it and writes the word underneath.

   The one idea kept from the scouting report is the par marker:
   an optional tick showing what is typical for this category, so
   a reader can see at a glance whether 3.5 is a disappointment
   or a decent showing in a weak field. Add data-par to a row to
   draw it. Leave it off and the row is a plain bar.
   ============================================================ */
.vl-review .vl-subs{
  margin: 1.6rem 0 2rem;
  padding: 0;
  list-style: none;
  border-top: 1px solid var(--vlr-rule);
}

.vl-review .vl-sub{
  display: grid;
  grid-template-columns: minmax(6rem, 9rem) minmax(0, 1fr) 2.4rem;
  gap: .35rem .9rem;
  align-items: center;
  padding: .7rem .2rem;
  border-bottom: 1px solid var(--vlr-rule);
}

.vl-review .vl-sub__name{
  font-size: .88rem;
  font-weight: 700;
  color: var(--vlr-navy);
}

.vl-review .vl-sub__bar{
  position: relative;
  height: 12px;
  border-radius: 6px;
  background: rgba(30, 41, 59, .08);
}

/* Filled portion. Width is set by vl-article.js as a percentage
   in --vlr-now, so the markup only ever holds the score itself. */
.vl-review .vl-sub__fill{
  position: absolute;
  top: 0;
  left: 0;
  height: 100%;
  width: var(--vlr-now, 0%);
  border-radius: 6px;
  background: var(--vlr-muted);
  transition: width .5s ease;
}

.vl-review .vl-sub.is-below .vl-sub__fill{ background: var(--vlr-fail); }
.vl-review .vl-sub.is-avg   .vl-sub__fill{ background: var(--vlr-muted); }
.vl-review .vl-sub.is-above .vl-sub__fill{ background: var(--vlr-gold); }
.vl-review .vl-sub.is-plus  .vl-sub__fill{ background: var(--vlr-pass); }

/* Par. A thin upright tick, not a dot, so nobody mistakes it for
   a second score. */
.vl-review .vl-sub__par{
  position: absolute;
  top: -3px;
  bottom: -3px;
  left: var(--vlr-par, 0%);
  width: 2px;
  margin-left: -1px;
  background: var(--vlr-navy);
  opacity: .55;
}

.vl-review .vl-sub__num{
  font-size: .92rem;
  font-weight: 800;
  text-align: right;
  color: var(--vlr-navy);
  font-variant-numeric: tabular-nums;
}

.vl-review .vl-sub__word{
  grid-column: 2 / -1;
  margin-top: -.15rem;
  font-size: .76rem;
  color: var(--vlr-muted);
}

/* One short line, injected only when at least one row uses par,
   so a page that does not use it carries no explanation at all. */
.vl-review .vl-subkey{
  margin: .7rem 0 2rem;
  padding: .55rem .85rem;
  background: rgba(30, 41, 59, .03);
  border-radius: 5px;
  font-size: .74rem;
  line-height: 1.5;
  color: var(--vlr-muted);
}

.vl-review .vl-subkey b{
  color: var(--vlr-navy);
}

/* ============================================================
   E. FLOOR AND CEILING
   A scout does not give one number, because a prospect is a
   range. Neither is a piece of software: it behaves differently
   for a one-person practice and for a twelve-seat firm. Say the
   worst realistic outcome and the best one, and let the reader
   place themselves.
   ============================================================ */
.vl-review .vl-range{
  margin: 1.8rem 0;
}

.vl-review .vl-range__bar{
  height: 8px;
  border-radius: 4px;
  background: linear-gradient(to right, var(--vlr-fail), var(--vlr-partial), var(--vlr-pass));
  opacity: .55;
}

.vl-review .vl-range__ends{
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
  gap: 1rem;
  margin-top: .9rem;
}

.vl-review .vl-range__end{
  padding: .9rem 1.05rem;
  background: #fff;
  border: 1px solid var(--vlr-rule);
  border-radius: 8px;
}

.vl-review .vl-range__end.is-floor{ border-top: 3px solid var(--vlr-fail); }
.vl-review .vl-range__end.is-ceiling{ border-top: 3px solid var(--vlr-pass); }

.vl-review .vl-range__label{
  display: block;
  margin-bottom: .3rem;
  font-size: .62rem;
  font-weight: 700;
  letter-spacing: .14em;
  text-transform: uppercase;
}

.vl-review .vl-range__end.is-floor   .vl-range__label{ color: var(--vlr-fail); }
.vl-review .vl-range__end.is-ceiling .vl-range__label{ color: var(--vlr-pass); }

.vl-review .vl-range__end p{
  margin: 0;
  font-size: .92rem;
  line-height: 1.55;
}

/* ============================================================
   F. RISK
   One badge and one sentence. What has to go wrong for this to
   have been the wrong call.
   ============================================================ */
.vl-review .vl-risk{
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: .6rem .9rem;
  margin: 1.6rem 0;
  padding: .9rem 1.1rem;
  border: 1px solid var(--vlr-rule);
  border-radius: 8px;
  background: #fff;
}

.vl-review .vl-risk__badge{
  padding: .2rem .6rem;
  border-radius: 999px;
  font-size: .64rem;
  font-weight: 700;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: #fff;
  white-space: nowrap;
}

.vl-review .vl-risk.is-low  .vl-risk__badge{ background: var(--vlr-pass); }
.vl-review .vl-risk.is-med  .vl-risk__badge{ background: var(--vlr-partial); }
.vl-review .vl-risk.is-high .vl-risk__badge{ background: var(--vlr-fail); }

.vl-review .vl-risk__why{
  flex: 1 1 16rem;
  min-width: 0;
  margin: 0;
  font-size: .92rem;
}

/* ============================================================
   G. CLAIMS AGAINST MEASUREMENTS
   What the vendor says, and what we got. A plain table, on
   purpose: no marks, no symbols, no legend to learn. The number
   in the right-hand column does all the arguing.

   data-runs on each row is totalled into the test-bed line.
   ============================================================ */
.vl-review .vl-claims{
  width: 100%;
  margin: 1.6rem 0 2rem;
  border-collapse: collapse;
  font-size: .9rem;
}

.vl-review .vl-claims caption{
  padding: 0 0 .5rem;
  text-align: left;
  font-size: .62rem;
  font-weight: 700;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--vlr-goldink);
}

.vl-review .vl-claims th,
.vl-review .vl-claims td{
  padding: .6rem .7rem;
  text-align: left;
  vertical-align: top;
  border-bottom: 1px solid var(--vlr-rule);
}

.vl-review .vl-claims thead th{
  font-size: .66rem;
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--vlr-muted);
  border-bottom: 1px solid var(--vlr-navy);
}

.vl-review .vl-claims tbody th{
  font-weight: 500;
  color: var(--vlr-ink);
}

.vl-review .vl-claims__got{
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.vl-review .vl-claims__got.is-under{ color: var(--vlr-fail); }
.vl-review .vl-claims__got.is-met{ color: var(--vlr-pass); }
.vl-review .vl-claims__got.is-over{ color: var(--vlr-pass); }
.vl-review .vl-claims__got.is-none{ color: var(--vlr-muted); font-weight: 500; }

/* ============================================================
   H. WHAT ELSE WE LOOKED AT  (Wirecutter)
   The section most review sites omit. Naming what lost, and on
   which criterion, is what makes the winner mean anything.
   ============================================================ */
.vl-review .vl-alsoran{
  margin: 2rem 0;
  padding: 0;
  list-style: none;
}

.vl-review .vl-alsoran li{
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: .2rem;
  padding: .85rem 0;
  border-bottom: 1px solid var(--vlr-rule);
}

.vl-review .vl-alsoran__name{
  font-size: .98rem;
  font-weight: 700;
  color: var(--vlr-navy);
}

.vl-review .vl-alsoran__why{
  font-size: .92rem;
  color: var(--vlr-ink);
}

.vl-review .vl-alsoran__why strong{
  color: var(--vlr-fail);
  font-weight: 700;
}

/* ============================================================
   I. WHO IT IS FOR
   ============================================================ */
.vl-review .vl-forwhom{
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  gap: 1rem;
  margin: 1.8rem 0;
}

.vl-review .vl-forwhom__col{
  padding: 1rem 1.15rem;
  background: #fff;
  border: 1px solid var(--vlr-rule);
  border-radius: 8px;
}

.vl-review .vl-forwhom__col.is-yes{ border-top: 3px solid var(--vlr-pass); }
.vl-review .vl-forwhom__col.is-no{ border-top: 3px solid var(--vlr-fail); }

.vl-review .vl-forwhom__col h4{
  margin: 0 0 .5rem;
  font-size: .72rem;
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
}

.vl-review .vl-forwhom__col.is-yes h4{ color: var(--vlr-pass); }
.vl-review .vl-forwhom__col.is-no  h4{ color: var(--vlr-fail); }

.vl-review .vl-forwhom__col ul{
  margin: 0;
  padding-left: 1.1rem;
  font-size: .92rem;
}

.vl-review .vl-forwhom__col li{
  margin-bottom: .35rem;
}

/* ============================================================
   J0. THE INVISIBLE TAX  (the friction parent)

   Every review on the internet prices a product at its sticker
   and stops. The sticker is the smallest number in the deal. The
   real cost is the re-authentication every sixty days, the four
   exports in thirty that need repairing by hand, the afternoon
   lost to a support queue, the supplier rules retrained after
   someone leaves, and the price that climbs faster than
   inflation while you are not looking.

   None of that appears on a pricing page and none of it appears
   in a competitor's review, because measuring it means using the
   product for ten weeks and writing down what it took from you.
   That is the whole moat.

   TWO RULES when you fill this in. Every row names WHO in the
   firm pays it, because friction is never evenly spread and the
   person choosing the software is usually not the person who
   pays. And every row is annualised, because a nuisance that
   takes twenty minutes a fortnight is nine hours a year and
   nobody notices until it is written down.
   ============================================================ */
.vl-review .vl-friction{
  margin: 2rem 0;
  border: 1px solid var(--vlr-rule);
  border-top: 3px solid var(--vlr-fail);
  border-radius: 4px;
  background: #fff;
}

.vl-review .vl-friction__head{
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: .3rem 1rem;
  padding: .7rem 1.1rem;
  border-bottom: 1px solid var(--vlr-rule);
  background: rgba(154, 75, 63, .05);
  font-size: .66rem;
  font-weight: 700;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--vlr-fail);
}

.vl-review .vl-friction table{
  width: 100%;
  border-collapse: collapse;
  font-size: .88rem;
}

.vl-review .vl-friction th,
.vl-review .vl-friction td{
  padding: .6rem .7rem;
  text-align: left;
  vertical-align: top;
  border-bottom: 1px solid var(--vlr-rule);
}

.vl-review .vl-friction thead th{
  font-size: .62rem;
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--vlr-muted);
}

.vl-review .vl-friction tbody th{
  font-weight: 500;
  color: var(--vlr-ink);
}

/* Who in the firm actually absorbs it. Set apart, because it is
   the column that changes buying decisions. */
.vl-review .vl-friction__who{
  font-size: .78rem;
  color: var(--vlr-muted);
  white-space: nowrap;
}

.vl-review .vl-friction__cost{
  font-weight: 700;
  color: var(--vlr-fail);
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

/* The number that makes the section worth writing. */
.vl-review .vl-friction__total{
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: .4rem .9rem;
  padding: .9rem 1.1rem;
  background: rgba(154, 75, 63, .05);
  border-top: 1px solid var(--vlr-rule);
}

.vl-review .vl-friction__label{
  font-size: .62rem;
  font-weight: 700;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--vlr-muted);
}

.vl-review .vl-friction__figure{
  font-size: 1.28rem;
  font-weight: 800;
  color: var(--vlr-fail);
  font-variant-numeric: tabular-nums;
}

.vl-review .vl-friction__note{
  flex: 1 1 14rem;
  min-width: 0;
  margin: 0;
  font-size: .82rem;
  color: var(--vlr-muted);
}

/* ============================================================
   J1. PRICE DRIFT

   The second invisible tax, and the one people notice last. What
   the product cost when we started watching, what it costs now,
   and what changed in the terms along the way. A subscription
   that rises six per cent a year has doubled inside a decade and
   nobody ever made a decision about it.
   ============================================================ */
.vl-review .vl-drift{
  margin: 1.8rem 0;
  padding: 0;
  list-style: none;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
  gap: 0;
  border: 1px solid var(--vlr-rule);
  border-radius: 4px;
  background: #fff;
  overflow: hidden;
}

.vl-review .vl-drift li{
  padding: .75rem 1rem;
  border-right: 1px solid var(--vlr-rule);
}

.vl-review .vl-drift li:last-child{
  border-right: 0;
  background: rgba(154, 75, 63, .05);
}

.vl-review .vl-drift__when{
  display: block;
  margin-bottom: .15rem;
  font-size: .6rem;
  font-weight: 700;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--vlr-muted);
}

.vl-review .vl-drift__what{
  display: block;
  font-size: 1rem;
  font-weight: 800;
  color: var(--vlr-navy);
  font-variant-numeric: tabular-nums;
}

.vl-review .vl-drift__note{
  display: block;
  margin-top: .1rem;
  font-size: .74rem;
  font-weight: 400;
  color: var(--vlr-muted);
  line-height: 1.35;
}

.vl-review .vl-drift li:last-child .vl-drift__what{
  color: var(--vlr-fail);
}

/* ============================================================
   J. THE EXIT ROUTE
   The graft, and the reason to read VerityLoft rather than the
   vendor's own comparison page. Every review covers getting in.
   This covers getting out: what your data looks like on the way
   through the door, what notice you owe, and how much of your
   work does not come with you.
   ============================================================ */
.vl-review .vl-exit{
  margin: 2rem 0;
  border: 1px solid var(--vlr-rule);
  border-top: 3px solid var(--vlr-navy);
  border-radius: 4px;
  background: #fff;
}

.vl-review .vl-exit__head{
  padding: .7rem 1.1rem;
  border-bottom: 1px solid var(--vlr-rule);
  background: rgba(30, 41, 59, .03);
  font-size: .66rem;
  font-weight: 700;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--vlr-navy);
}

.vl-review .vl-exit__grid{
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
}

.vl-review .vl-exit__cell{
  padding: .8rem 1.1rem;
  border-right: 1px solid var(--vlr-rule);
}

.vl-review .vl-exit__cell:last-child{
  border-right: 0;
}

.vl-review .vl-exit__label{
  display: block;
  margin-bottom: .2rem;
  font-size: .6rem;
  font-weight: 700;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--vlr-muted);
}

.vl-review .vl-exit__value{
  display: block;
  font-size: .9rem;
  font-weight: 600;
  color: var(--vlr-navy);
  line-height: 1.4;
}

.vl-review .vl-exit__note{
  margin: 0;
  padding: .8rem 1.1rem;
  border-top: 1px solid var(--vlr-rule);
  font-size: .88rem;
  color: var(--vlr-ink);
}

/* ============================================================
   J2. THE CALL TO ACTION

   Defined here so the layer stands on its own. If vl-base.css
   already styles .vl-btn-affiliate, this wins inside a review
   because it is scoped and loads later, and nothing outside a
   review is touched either way.

   rel="sponsored nofollow noopener" belongs on every paid link.
   Never let a script rebuild that attribute.
   ============================================================ */
.vl-review .vl-btn-affiliate {
  display: inline-block;
  min-height: 44px;
  padding: .7rem 1.4rem;
  background: var(--vlr-gold);
  color: var(--vlr-navy);
  border-radius: 6px;
  font-family: inherit;
  font-size: .95rem;
  font-weight: 700;
  line-height: 1.6;
  text-decoration: none;
  transition: background .15s ease;
}

.vl-review .vl-btn-affiliate:hover {
  background: #C9A63B;
}

.vl-review .vl-btn-affiliate:focus-visible {
  outline: 2px solid var(--vlr-navy);
  outline-offset: 2px;
}

/* ============================================================
   K. STICKY VERDICT BAR
   Appears once the verdict card has scrolled off. Score and the
   call to action follow the reader down a long review.
   vl-article.js toggles data-visible.

   THIS BLOCK USES THE BASE TOKENS DIRECTLY, not the --vlr- ones
   every other section uses, and that is deliberate.

   The bar is attached to the body, outside .vl-review. It has to
   be: .vl-review sets container-type, which makes it the
   containing block for any fixed-position child, so a bar left
   inside it would stick to the article instead of the window.
   But --vlr-navy and friends are declared ON .vl-review, so out
   there they do not exist, every colour resolves to nothing, and
   the bar renders as light text on no background at all.

   So: base tokens, with a hex fallback on each one.
   ============================================================ */
.vl-stickybar{
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 70;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .6rem 1.1rem;
  padding: .7rem max(1rem, calc((100% - 1100px) / 2));
  background: var(--vl-blue, #1E293B);
  color: var(--vl-linen, #E6E2DA);
  box-shadow: 0 -4px 20px rgba(30, 41, 59, .22);
  transform: translateY(100%);
  transition: transform .22s ease;
}

.vl-stickybar[data-visible="true"]{
  transform: translateY(0);
}

.vl-stickybar__score{
  font-size: 1.15rem;
  font-weight: 800;
  color: var(--vl-gold, #DDB849);
  font-variant-numeric: tabular-nums;
}

.vl-stickybar__name{
  flex: 1 1 10rem;
  min-width: 0;
  font-size: .92rem;
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.vl-stickybar .vl-btn-affiliate{
  min-height: 42px;
  padding: .55rem 1.2rem;
  background: var(--vl-gold, #DDB849);
  color: var(--vl-blue, #1E293B);
  font-size: .88rem;
}

.vl-stickybar__close{
  background: none;
  border: 1px solid rgba(230, 226, 218, .4);
  color: inherit;
  width: 1.9rem;
  height: 1.9rem;
  border-radius: 50%;
  cursor: pointer;
  line-height: 1;
}

/* ============================================================
   L. RESPONSIVE
   Container queries, so the layout is correct inside the block
   editor iframe as well as on the front end. The editor renders
   the canvas in an iframe, which is why viewport media queries
   look wrong there and these do not.
   ============================================================ */
@container (max-width: 640px) {
  .vl-review__verdict{
    grid-template-columns: 1fr;
    justify-items: center;
    text-align: center;
  }
  .vl-review .vl-comp__line{
    font-size: 1.12rem;
  }
  .vl-review .vl-sub{
    grid-template-columns: minmax(0, 1fr) 2.4rem;
  }
  .vl-review .vl-sub__bar{
    grid-column: 1 / -1;
    order: 3;
  }
  .vl-review .vl-sub__word{
    grid-column: 1 / -1;
    order: 4;
  }
  .vl-review .vl-exit__cell{
    border-right: 0;
    border-bottom: 1px solid var(--vlr-rule);
  }
  .vl-review .vl-drift li{
    border-right: 0;
    border-bottom: 1px solid var(--vlr-rule);
  }
  .vl-review .vl-friction__who{
    white-space: normal;
  }
}

@supports not (container-type: inline-size) {
  @media (max-width: 640px) {
    .vl-review__verdict{ grid-template-columns: 1fr; justify-items: center; }
    .vl-review .vl-sub{ grid-template-columns: minmax(0, 1fr) 2.4rem; }
    .vl-review .vl-sub__bar{ grid-column: 1 / -1; order: 3; }
    .vl-review .vl-exit__cell{ border-right: 0; }
  }
}

@media (prefers-reduced-motion: reduce) {
  .vl-review .vl-sub__fill,
.vl-stickybar{
    transition: none;
  }
}

/* ============================================================
   M. PRINT
   ============================================================ */
@media print {
  .vl-stickybar{
    display: none !important;
  }
  .vl-review{
    max-width: none;
    font-size: 10.5pt;
    color: #000;
  }
  .vl-review .vl-comp,
.vl-review .vl-friction,
.vl-review__verdict,
.vl-review .vl-range,
.vl-review .vl-exit,
.vl-review .vl-claims{
    page-break-inside: avoid;
    border-color: #000;
  }
  .vl-review .vl-comp{
    background: none;
    border: 1px solid #000;
  }
  .vl-review .vl-score__arc{
    stroke: #000;
  }
  .vl-review .vl-sub__fill{
    background: #000 !important;
  }
}

/* ============================================================
   APPEND TO vl-article.css
   Paste at the very END of the file. Nothing above changes.

   Two additions, both small, both driven by the ACF fields you
   already have.

   1. THE SCORE SCALE STOPS BEING HARDCODED.

      The dial currently divides by five, always:

          calc(var(--vl-score, 0) / 5 * 339.292)

      You have a score_out_of field, so the scale is data. Set
      --vl-score-max alongside --vl-score and the arc follows it.
      A single review stays out of five and needs no change at
      all, because five is the fallback. A comparison can be out
      of 100, which is the only way two contenders 1.3 points
      apart show as two different numbers.

   2. VERDICT CARDS SIT SIDE BY SIDE.

      Wrap two or three .vl-review__verdict blocks in .vl-versus
      and they become columns instead of a stack. Each card goes
      single-column inside, dial centred above the text, so a
      narrow column still reads. Below 640px they stack anyway,
      which is correct on a phone.

   No angle brackets anywhere, so the save will not be rejected.
   ============================================================ */


/* --- 14. Score scale from data ---------------------------------
   Later in the file than section B, same specificity, so this
   wins on source order and section B needs no edit. */
.vl-review .vl-score__arc {
  stroke-dasharray: calc(var(--vl-score, 0) / var(--vl-score-max, 5) * 339.292) 339.292;
}

/* Four characters where there used to be three. 84.5 needs a
   little less letter-spacing than 4.2 did. */
.vl-review .vl-score__num {
  letter-spacing: -.04em;
}


/* --- 15. Verdict cards side by side ----------------------------
   The head-to-head layout, built from the card you already have
   rather than a new component. */
.vl-review .vl-versus {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr));
  gap: 1rem;
  margin: 1.8rem 0;
}

.vl-review .vl-versus .vl-review__verdict {
  grid-template-columns: minmax(0, 1fr);
  gap: .9rem;
  margin: 0;
}

.vl-review .vl-versus .vl-score {
  justify-self: center;
}

/* The one we recommend gets the gold rule. The other keeps a
   neutral one, because it is the alternative, not the loser. */
.vl-review .vl-versus .vl-review__verdict {
  border-top-color: var(--vlr-rule);
}

.vl-review .vl-versus .vl-review__verdict.is-pick {
  border-top-color: var(--vlr-gold);
}

.vl-review .vl-versus .vl-review__line {
  font-size: 1.02rem;
}

/* ============================================================
   APPEND TO vl-article.css
   Paste at the very END of the file. Nothing above changes.

   Two additions, both small, both driven by the ACF fields you
   already have.

   1. THE SCORE SCALE STOPS BEING HARDCODED.

      The dial currently divides by five, always:

          calc(var(--vl-score, 0) / 5 * 339.292)

      You have a score_out_of field, so the scale is data. Set
      --vl-score-max alongside --vl-score and the arc follows it.
      A single review stays out of five and needs no change at
      all, because five is the fallback. A comparison can be out
      of 100, which is the only way two contenders 1.3 points
      apart show as two different numbers.

   2. VERDICT CARDS SIT SIDE BY SIDE.

      Wrap two or three .vl-review__verdict blocks in .vl-versus
      and they become columns instead of a stack. Each card goes
      single-column inside, dial centred above the text, so a
      narrow column still reads. Below 640px they stack anyway,
      which is correct on a phone.

   No angle brackets anywhere, so the save will not be rejected.
   ============================================================ */


/* --- 14. Score scale from data ---------------------------------
   Later in the file than section B, same specificity, so this
   wins on source order and section B needs no edit. */
.vl-review .vl-score__arc {
  stroke-dasharray: calc(var(--vl-score, 0) / var(--vl-score-max, 5) * 339.292) 339.292;
}

/* Four characters where there used to be three. 84.5 needs a
   little less letter-spacing than 4.2 did. */
.vl-review .vl-score__num {
  letter-spacing: -.04em;
}


/* --- 15. Verdict cards side by side ----------------------------
   The head-to-head layout, built from the card you already have
   rather than a new component. */
.vl-review .vl-versus {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr));
  gap: 1rem;
  margin: 1.8rem 0;
}

.vl-review .vl-versus .vl-review__verdict {
  grid-template-columns: minmax(0, 1fr);
  gap: .9rem;
  margin: 0;
}

.vl-review .vl-versus .vl-score {
  justify-self: center;
}

/* The one we recommend gets the gold rule. The other keeps a
   neutral one, because it is the alternative, not the loser. */
.vl-review .vl-versus .vl-review__verdict {
  border-top-color: var(--vlr-rule);
}

.vl-review .vl-versus .vl-review__verdict.is-pick {
  border-top-color: var(--vlr-gold);
}

.vl-review .vl-versus .vl-review__line {
  font-size: 1.02rem;
}


/* --- 16. Sticky bar with more than one product -----------------
   A comparison carries a button per product. The bar clones them
   all, drops the single score, and lets the buttons share the
   room. Below 640px the name goes onto its own line so the
   buttons stay side by side and both remain tappable.

   The old behaviour cloned only the FIRST affiliate button, so a
   two-product page followed every reader down with product A's
   link no matter which one they were leaning toward. */
.vl-stickybar.has-many {
  gap: .5rem .6rem;
}

.vl-stickybar.has-many .vl-btn-affiliate {
  flex: 1 1 auto;
  min-width: 0;
  padding: .55rem .9rem;
  font-size: .84rem;
  text-align: center;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* On a phone the name goes entirely. Two products means two
   buttons, and 152px of a 700px screen is a quarter of the
   viewport spent on a title the reader already scrolled past.
   The buttons are the reason the bar exists. */
.vl-stickybar.has-many .vl-stickybar__name {
  display: none;
}

@media (min-width: 641px) {
  .vl-stickybar.has-many .vl-stickybar__name {
    flex: 1 1 12rem;
    white-space: nowrap;
  }
  .vl-stickybar.has-many .vl-btn-affiliate {
    flex: 0 1 auto;
  }
}


/* --- 17. The cloned buttons, again ------------------------------
   THE SAME TRAP AS THE BAR'S BACKGROUND. .vl-btn-affiliate is
   styled as .vl-review .vl-btn-affiliate, and the sticky bar is
   attached to the body, outside .vl-review. So a cloned button
   keeps its colours from the .vl-stickybar rule but loses the
   radius, the weight and text-decoration none, and renders as an
   underlined gold rectangle.

   Anything the cloned button needs has to be declared here, in a
   rule that does not depend on being inside the article. */
.vl-stickybar .vl-btn-affiliate {
  border-radius: 6px;
  font-family: inherit;
  font-weight: 700;
  line-height: 1.4;
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.vl-stickybar .vl-btn-affiliate:hover {
  background: #C9A63B;
}

.vl-stickybar .vl-btn-affiliate:focus-visible {
  outline: 2px solid var(--vl-linen, #E6E2DA);
  outline-offset: 2px;
}

/* ============================================================
   APPEND TO vl-article.css
   Paste at the very END of the file. Nothing above changes.

   Two additions, both small, both driven by the ACF fields you
   already have.

   1. THE SCORE SCALE STOPS BEING HARDCODED.

      The dial currently divides by five, always:

          calc(var(--vl-score, 0) / 5 * 339.292)

      You have a score_out_of field, so the scale is data. Set
      --vl-score-max alongside --vl-score and the arc follows it.
      A single review stays out of five and needs no change at
      all, because five is the fallback. A comparison can be out
      of 100, which is the only way two contenders 1.3 points
      apart show as two different numbers.

   2. VERDICT CARDS SIT SIDE BY SIDE.

      Wrap two or three .vl-review__verdict blocks in .vl-versus
      and they become columns instead of a stack. Each card goes
      single-column inside, dial centred above the text, so a
      narrow column still reads. Below 640px they stack anyway,
      which is correct on a phone.

   No angle brackets anywhere, so the save will not be rejected.
   ============================================================ */


/* --- 14. Score scale from data ---------------------------------
   Later in the file than section B, same specificity, so this
   wins on source order and section B needs no edit. */
.vl-review .vl-score__arc {
  stroke-dasharray: calc(var(--vl-score, 0) / var(--vl-score-max, 5) * 339.292) 339.292;
}

/* Four characters where there used to be three. 84.5 needs a
   little less letter-spacing than 4.2 did. */
.vl-review .vl-score__num {
  letter-spacing: -.04em;
}


/* --- 15. Verdict cards side by side ----------------------------
   The head-to-head layout, built from the card you already have
   rather than a new component. */
.vl-review .vl-versus {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr));
  gap: 1rem;
  margin: 1.8rem 0;
}

.vl-review .vl-versus .vl-review__verdict {
  grid-template-columns: minmax(0, 1fr);
  gap: .9rem;
  margin: 0;
}

.vl-review .vl-versus .vl-score {
  justify-self: center;
}

/* The one we recommend gets the gold rule. The other keeps a
   neutral one, because it is the alternative, not the loser. */
.vl-review .vl-versus .vl-review__verdict {
  border-top-color: var(--vlr-rule);
}

.vl-review .vl-versus .vl-review__verdict.is-pick {
  border-top-color: var(--vlr-gold);
}

.vl-review .vl-versus .vl-review__line {
  font-size: 1.02rem;
}

/* Cards in a pair are rarely the same height: one has a button,
   the other has not been tested yet. Without this the shorter
   card centres its text vertically and the two verdicts start on
   different lines, which reads as a layout accident rather than a
   deliberate blank. Top-align both. */
.vl-review .vl-versus .vl-review__verdict {
  align-content: start;
}


/* --- 15b. A product that has not been scored yet ----------------
   A dial whose --vl-score is 0 does NOT render as an empty ring.
   stroke-linecap round draws a rounded cap on each end of a
   zero-length dash, and the two caps land on top of each other at
   twelve o'clock, so the reader sees a small gold dot. On a page
   comparing two named companies that dot reads as a sliver of a
   score, which is the one thing this card exists to avoid saying.

   Add is-unscored alongside vl-score and the arc goes entirely.
   The track stays, so the ring is still a ring, just empty. */
.vl-review .vl-score.is-unscored .vl-score__arc {
  display: none;
}

/* The em dash sits high and small at dial-number size. Drop it to
   the weight of a placeholder rather than the weight of a 4.6. */
.vl-review .vl-score.is-unscored .vl-score__num {
  color: var(--vlr-muted, #5C6472);
  font-weight: 600;
}


/* --- 16. Sticky bar with more than one product -----------------
   A comparison carries a button per product. The bar clones them
   all, drops the single score, and lets the buttons share the
   room. Below 640px the name goes onto its own line so the
   buttons stay side by side and both remain tappable.

   The old behaviour cloned only the FIRST affiliate button, so a
   two-product page followed every reader down with product A's
   link no matter which one they were leaning toward. */
.vl-stickybar.has-many {
  gap: .5rem .6rem;
}

.vl-stickybar.has-many .vl-btn-affiliate {
  flex: 1 1 auto;
  min-width: 0;
  padding: .55rem .9rem;
  font-size: .84rem;
  text-align: center;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* On a phone the name goes entirely. Two products means two
   buttons, and 152px of a 700px screen is a quarter of the
   viewport spent on a title the reader already scrolled past.
   The buttons are the reason the bar exists. */
.vl-stickybar.has-many .vl-stickybar__name {
  display: none;
}

@media (min-width: 641px) {
  .vl-stickybar.has-many .vl-stickybar__name {
    flex: 1 1 12rem;
    white-space: nowrap;
  }
  .vl-stickybar.has-many .vl-btn-affiliate {
    flex: 0 1 auto;
  }
}


/* --- 17. The cloned buttons, again ------------------------------
   THE SAME TRAP AS THE BAR'S BACKGROUND. .vl-btn-affiliate is
   styled as .vl-review .vl-btn-affiliate, and the sticky bar is
   attached to the body, outside .vl-review. So a cloned button
   keeps its colours from the .vl-stickybar rule but loses the
   radius, the weight and text-decoration none, and renders as an
   underlined gold rectangle.

   Anything the cloned button needs has to be declared here, in a
   rule that does not depend on being inside the article. */
.vl-stickybar .vl-btn-affiliate {
  border-radius: 6px;
  font-family: inherit;
  font-weight: 700;
  line-height: 1.4;
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.vl-stickybar .vl-btn-affiliate:hover {
  background: #C9A63B;
}

.vl-stickybar .vl-btn-affiliate:focus-visible {
  outline: 2px solid var(--vl-linen, #E6E2DA);
  outline-offset: 2px;
}