/* =====================================================================
   PILLAR PAGE STYLES — shared by the builder review articles
   (/blog/elementor-review/ and /blog/breakdance-review/)

   Loaded AFTER style.css, so it inherits every design token defined there
   (--accent, --stroke, spacing scale, --ease, etc.) and only adds the
   components these long-form review pages need.

   Contents
     1) LAYOUT       — reading column, reading-progress bar
     2) BYLINE       — author credit under the H1
     3) DISCLOSURE   — FTC affiliate notice
     4) VERDICT BOX  — score, bars, sticky buy panel
     5) TOC          — jump links
     6) PROSE        — article typography
     7) TABLES       — feature / pricing comparison
     8) CALLOUTS     — key takeaway + warning boxes
     9) AFF BAND     — mid-article affiliate CTA
    10) ALT GRID     — "alternatives" cards
    11) FAQ          — accordion
   ===================================================================== */


/* ---------------------------------------------------------------------
   1) LAYOUT
   --------------------------------------------------------------------- */

/* Wider than the 860px default so the verdict box and tables can breathe,
   but the prose itself is capped separately (see §6) for readability. */
.main { max-width: 1060px; }

/* Let the intro fill the wider column instead of stranding whitespace */
.page-head .page-intro { max-width: 68ch; }

/* Small "← Blog · Review" crumb above the H1 */
.post-crumb {
  display: inline-flex; align-items: center; gap: var(--xs);
  font-size: 12.5px; font-weight: 600; color: var(--subtext);
  margin-bottom: var(--sm);
}
.post-crumb a { color: var(--accent); }

/* Reading-progress bar — pinned to the top of the content well.
   Purely decorative feedback; width is driven by JS in main.js fallback below. */
.read-bar {
  position: sticky; top: 0; z-index: 30;
  height: 3px; width: 100%;
  background: transparent;
  pointer-events: none;
}
.read-bar span {
  display: block; height: 100%; width: 0%;
  background: var(--accent);
  transition: width .1s linear;
}


/* ---------------------------------------------------------------------
   2) BYLINE — the E-E-A-T signal. A real person, with real experience.
   --------------------------------------------------------------------- */
.byline {
  display: flex; align-items: center; gap: var(--sm);
  margin-top: var(--lg);
}
.byline-avatar {
  flex: none;
  width: 40px; height: 40px; border-radius: 50%;
  display: grid; place-items: center;
  background: var(--grad); color: #fff;
  font-family: var(--display); font-size: 13px; font-weight: 700;
  letter-spacing: .02em;
}
.byline-text { font-size: 13.5px; line-height: 1.4; }
.byline-text b { color: var(--text); font-weight: 600; }
.byline-meta {
  display: block;
  font-size: 12.5px; color: var(--subtext); margin-top: 2px;
}


/* ---------------------------------------------------------------------
   3) AFFILIATE DISCLOSURE
   Must sit ABOVE the first affiliate link and be visually distinct —
   that's what "clear and conspicuous" means to the FTC.
   --------------------------------------------------------------------- */
.aff-disclosure {
  display: flex; align-items: flex-start; gap: var(--sm);
  margin: var(--lg) 0 0;
  padding: var(--sm) var(--md);
  background: var(--bg-2);
  border: 1px solid var(--stroke);
  border-left: 3px solid var(--accent);     /* accent edge draws the eye */
  border-radius: var(--radius);
}
.aff-disclosure svg {
  flex: none; width: 17px; height: 17px;
  color: var(--accent); margin-top: 2px;
}
.aff-disclosure p {
  margin: 0;
  font-size: 13px; line-height: 1.55; color: var(--subtext);
  max-width: 78ch;
}
.aff-disclosure b { color: var(--text); font-weight: 600; }


/* ---------------------------------------------------------------------
   4) VERDICT BOX
   Two columns: the assessment on the left, the buy panel on the right.
   Collapses to one column on tablet and below.
   --------------------------------------------------------------------- */
.verdict {
  display: grid;
  grid-template-columns: 1fr 300px;
  gap: 0;

  /* This is a <section>, and style.css gives every section 48px of vertical
     padding plus a bottom border. Both are wrong here: the padding would sit
     INSIDE this box's border and stop the grey buy panel reaching the top and
     bottom edges. Reset them so the two columns fill the card edge to edge. */
  padding: 0;
  border-bottom: 0;

  margin: var(--xl) 0 var(--lg);
  background: var(--bg-1);
  border: 1px solid var(--stroke);
  border-radius: var(--r-md);
  overflow: hidden;                          /* clips the buy panel's fill */
}

.verdict-main { padding: var(--lg); }

.verdict-head {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: var(--md);
  /* On a 320px phone the product name and the score can't sit side by side —
     without this they overflow their box and the "/5" gets clipped. */
  flex-wrap: wrap;
  padding-bottom: var(--sm);
  border-bottom: 1px solid var(--stroke);
}
.verdict-kicker {
  display: block;
  font-family: var(--display); font-size: 11px; font-weight: 700;
  letter-spacing: .06em; text-transform: uppercase;
  color: var(--accent);
}
.verdict-title { font-size: 26px; margin-top: 2px; }

/* The score — big, confident, immediately scannable */
.verdict-score {
  flex: none;
  display: flex; align-items: baseline; gap: 2px;
  font-family: var(--display);
}
.score-num {
  font-size: 34px; font-weight: 700; letter-spacing: -.03em;
  color: var(--accent); line-height: 1;
}
.score-out { font-size: 14px; font-weight: 600; color: var(--subtext); }

.verdict-line {
  margin: var(--sm) 0 var(--md);
  font-size: 14.5px; line-height: 1.6; color: var(--text);
  max-width: 60ch;
}

/* --- Score bars --- */
.bars { display: flex; flex-direction: column; gap: var(--xs); }
.bar-row {
  display: grid;
  grid-template-columns: 96px 1fr 30px;      /* label | track | value */
  align-items: center; gap: var(--sm);
}
.bar-label { font-size: 12.5px; font-weight: 600; color: var(--subtext); }
.bar-track {
  height: 6px; border-radius: 999px;
  background: var(--bg-3);
  overflow: hidden;
}
/* Width comes from the inline --v custom property on each bar, e.g. style="--v:60%" */
.bar-fill {
  display: block; height: 100%; width: var(--v, 0%);
  border-radius: 999px;
  background: var(--grad);
}
.bar-val {
  font-family: var(--display); font-size: 12px; font-weight: 700;
  color: var(--text); text-align: right;
}

/* --- Buy panel (the money column) --- */
.verdict-buy {
  padding: var(--lg);
  background: var(--bg-2);
  border-left: 1px solid var(--stroke);
  display: flex; flex-direction: column;
}
.buy-label {
  font-family: var(--display); font-size: 10.5px; font-weight: 700;
  letter-spacing: .06em; text-transform: uppercase;
  color: var(--subtext);
}
.buy-for {
  margin: var(--xxs) 0 var(--md);
  font-size: 13px; line-height: 1.5; color: var(--text);
}
.buy-price {
  margin: var(--xxs) 0 var(--lg);
  font-size: 13px; line-height: 1.6; color: var(--text);
}
.buy-price b { font-weight: 700; }
.buy-price-pro {
  font-family: var(--display); font-size: 16px; font-weight: 700;
  color: var(--accent);
}
/* Push the button to the bottom so both columns end flush */
.buy-btn {
  margin-top: auto;
  justify-content: center; width: 100%;
}
.buy-alt {
  margin-top: var(--xs);
  text-align: center; font-size: 12.5px; color: var(--subtext);
  text-decoration: underline; text-underline-offset: 2px;
}
.buy-alt:hover { color: var(--accent); }

/* Stack the verdict box on narrow screens */
@media (max-width: 860px) {
  .verdict { grid-template-columns: 1fr; }
  .verdict-buy {
    border-left: 0;
    border-top: 1px solid var(--stroke);
  }
  .buy-btn { margin-top: var(--sm); }
}


/* ---------------------------------------------------------------------
   5) ARTICLE LAYOUT + STICKY TOC RAIL

   Two columns: the article on the left, a sticky table of contents in the
   right rail. This is what earns the width of the container — previously the
   prose was capped at 72ch and the leftover space on the right was dead.

   The reading measure is now produced by the grid itself: at .main's 1060px,
   the text column lands at roughly 780px (~72ch), which is exactly where we
   want it — so no element needs its own max-width cap any more.

   DOM order is TOC-then-article, so on mobile (where the grid collapses) the
   contents list naturally appears above the article. On desktop we place it
   into column 2 explicitly.
   --------------------------------------------------------------------- */
.article-wrap {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 216px;   /* article | rail */
  gap: var(--xxl);
  align-items: start;                            /* lets the rail stick */
  padding-top: var(--xl);
}
.article-wrap .prose { grid-column: 1; grid-row: 1; }
.article-wrap .toc   { grid-column: 2; grid-row: 1; }

/* --- The rail itself --- */
.toc {
  position: sticky;
  top: var(--lg);                                /* sticks below the top edge */
  padding: var(--md);
  border: 1px solid var(--stroke);
  border-radius: var(--r-md);
  background: var(--bg-2);
  /* Never taller than the viewport — scroll inside the rail if the list is long */
  max-height: calc(100vh - var(--xl));
  overflow-y: auto;
  /* Quiet scrollbar — the chunky default looks like a bug inside a small card */
  scrollbar-width: thin;
  scrollbar-color: var(--stroke) transparent;
}

/* --- Head: label + reading-progress ring --- */
.toc-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--xs);
  padding-bottom: var(--xs);
  margin-bottom: var(--xs);
  border-bottom: 1px solid var(--stroke);
}
.toc-label {
  font-family: var(--display); font-size: 10.5px; font-weight: 700;
  letter-spacing: .07em; text-transform: uppercase;
  color: var(--subtext);
}

/* The ring is a second read of the same information as the top progress bar,
   but it's the one that's always in view next to the contents — it answers
   "how much of this is left?" at the moment you're deciding whether to bail. */
.toc-progress {
  position: relative;
  flex: none;
  width: 30px; height: 30px;
  display: grid; place-items: center;
}
.toc-progress svg {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  transform: rotate(-90deg);                     /* start the arc at 12 o'clock */
}
.toc-progress circle {
  fill: none;
  stroke-width: 2.5;
  cx: 15px; cy: 15px; r: 12px;
}
.ring-bg { stroke: var(--stroke); }
.ring-fg {
  stroke: var(--accent);
  stroke-linecap: round;
  /* 2πr for r=12 ≈ 75.4. JS writes stroke-dashoffset to draw the arc. */
  stroke-dasharray: 75.4;
  stroke-dashoffset: 75.4;                       /* 0% until JS says otherwise */
  transition: stroke-dashoffset .15s linear;
}
.toc-pct {
  font-family: var(--display);
  font-size: 9px; font-weight: 700;
  color: var(--subtext);
  font-variant-numeric: tabular-nums;            /* stops the digits jittering */
}

/* --- The list, with a sliding active marker --- */
.toc-track {
  position: relative;
  padding-left: 2px;
}
/* The spine every item sits against */
.toc-track::before {
  content: "";
  position: absolute;
  left: 0; top: 0; bottom: 0;
  width: 2px;
  background: var(--stroke);
  border-radius: 2px;
}
/* The marker itself. JS positions and sizes it to match the active item, so it
   SLIDES between sections instead of blinking on and off — which is what makes
   the rail feel like it's tracking you rather than just reacting. */
.toc-marker {
  position: absolute;
  left: 0;
  top: 0;
  width: 2px;
  height: 0;                                     /* JS sets height + translateY */
  background: var(--accent);
  border-radius: 2px;
  transition: transform .32s var(--ease), height .32s var(--ease);
  will-change: transform;
}

.toc-list {
  display: flex; flex-direction: column;
  margin: 0; padding: 0; list-style: none;
  counter-reset: toc;                            /* drives the 01, 02, 03… */
}
.toc-list a {
  display: flex; align-items: baseline; gap: var(--xs);
  padding: 7px var(--xs) 7px var(--sm);
  border-radius: 0 6px 6px 0;
  font-size: 12.5px; font-weight: 500; line-height: 1.4;
  color: var(--subtext);
  transition: color .2s, background-color .2s;
}
/* Numbering. Section numbers give the reader a sense of scale — "I'm on 3 of 9"
   is a different feeling from an undifferentiated list of links. */
.toc-list a::before {
  counter-increment: toc;
  content: counter(toc, decimal-leading-zero);
  flex: none;
  font-family: var(--display);
  font-size: 9.5px; font-weight: 700;
  color: var(--stroke);
  transition: color .2s;
}
.toc-list a:hover {
  color: var(--text);
  background: rgba(0,0,0,.03);
}
.toc-list a:hover::before { color: var(--subtext); }

/* Active section — set by initTocHighlight() in main.js as you scroll */
.toc-list a.is-active {
  color: var(--accent);
  font-weight: 700;
  background: var(--bg-accent);
}
.toc-list a.is-active::before { color: var(--accent); }

/* --- Rail footer: the affiliate CTA + back to top --- */
.toc-foot {
  margin-top: var(--sm);
  padding-top: var(--sm);
  border-top: 1px solid var(--stroke);
}
/* The rail is sticky, so this button is on screen for the entire article.
   It's the highest-exposure CTA on the page — but it's deliberately the quiet
   version: small, ghosted, no shine. The loud one lives in the verdict box. */
.toc-cta {
  display: flex; align-items: center; justify-content: center; gap: var(--xxs);
  width: 100%;
  padding: 9px var(--xs);
  border-radius: var(--radius);
  background: var(--accent);
  color: #fff;
  font-size: 12.5px; font-weight: 600;
  transition: transform .25s var(--ease), box-shadow .25s var(--ease);
}
.toc-cta:hover {
  transform: translateY(-1px);
  box-shadow: 0 6px 16px rgba(84,115,78,.35);
}
.toc-cta .arrow { transition: transform .25s var(--ease); }
.toc-cta:hover .arrow { transform: translateX(2px); }

.toc-top {
  display: flex; align-items: center; justify-content: center; gap: var(--xxs);
  width: 100%;
  margin-top: var(--xs);
  padding: 5px;
  border: 0; background: transparent;
  font-family: var(--sans);
  font-size: 11.5px; font-weight: 600;
  color: var(--subtext);
  cursor: pointer;
  transition: color .2s;
}
.toc-top:hover { color: var(--accent); }
.toc-top svg { width: 12px; height: 12px; }

/* --- Below 1024px there isn't room for a rail: fall back to the original
       horizontal pill row, sitting above the article. --- */
@media (max-width: 1024px) {
  .article-wrap {
    /* minmax(0, 1fr), NOT 1fr. A bare `1fr` is minmax(auto, 1fr), and that
       `auto` minimum resolves to the column's min-content — which the 620px
       comparison tables blow straight past, forcing the whole page wider than
       the screen. Pinning the minimum to 0 lets the column shrink and makes
       the tables scroll inside .table-wrap, which is the whole point of it. */
    grid-template-columns: minmax(0, 1fr);
    gap: 0;
    padding-top: 0;
  }
  .article-wrap .prose,
  .article-wrap .toc { grid-column: 1; }
  .article-wrap .toc { grid-row: 1; }            /* contents first */
  .article-wrap .prose { grid-row: 2; }

  .toc {
    position: static;                            /* no sticking on mobile */
    max-height: none; overflow: visible;
    display: block;
    padding: var(--md) 0 var(--lg);
    background: transparent;
    border: 0; border-bottom: 1px solid var(--stroke);
    border-radius: 0;
  }

  /* The rail's furniture is all about a sticky, always-visible companion.
     None of that is true on mobile — it's a one-shot jump list at the top of
     the article — so the ring, the sliding marker, the spine, the sticky CTA
     and the back-to-top all go. Keeping them would be decoration with no job. */
  .toc-progress,
  .toc-marker,
  .toc-foot { display: none; }

  .toc-head {
    padding-bottom: 0;
    margin-bottom: var(--xs);
    border-bottom: 0;
  }

  .toc-track { padding-left: 0; }
  .toc-track::before { display: none; }          /* no spine */

  /* Back to a wrapping row of pills */
  .toc-list {
    flex-direction: row; flex-wrap: wrap; gap: var(--xs);
  }
  .toc-list a {
    padding: 5px var(--sm);
    border: 1px solid var(--stroke); border-radius: 999px;
    background: var(--bg-1);
    font-weight: 600;
  }
  .toc-list a::before { display: none; }         /* no numbers on the pills */
  .toc-list a:hover { border-color: var(--accent); color: var(--accent); background: var(--bg-accent); }
  .toc-list a.is-active {
    border-color: var(--accent);
    background: var(--bg-accent);
    color: var(--accent);
  }
  .prose { padding-top: var(--xl); }
}


/* ---------------------------------------------------------------------
   6) PROSE — the article body
   No max-width caps here: the grid column above already produces the right
   reading measure, so tables, cards and the affiliate band all get to use
   the full width of the text column.
   --------------------------------------------------------------------- */
.prose {
  padding: 0;
  /* Grid and flex items default to a min-content floor, which lets a wide
     child (a table, a long code string) push the whole layout sideways.
     Zeroing it is what allows .table-wrap's overflow-x to actually engage. */
  min-width: 0;
}

/* --- Section headings ---
   Each H2 opens a new section of a long article, so it gets a hairline rule
   with a short accent tick sitting on it. In a 3,000-word page that a reader
   is scanning rather than reading, these dividers are what make the structure
   legible at a glance. */
.prose h2 {
  position: relative;
  font-size: clamp(22px, 2.6vw, 30px);
  line-height: 1.2;
  margin: var(--xxl) 0 var(--md);
  padding-top: var(--lg);
  border-top: 1px solid var(--stroke);
  scroll-margin-top: var(--xl);              /* so TOC anchors don't hide under the top */
}
.prose h2::before {
  content: "";
  position: absolute;
  top: -1px; left: 0;                        /* sits ON the hairline */
  width: 44px; height: 2px;
  background: var(--accent);
}
/* The first heading follows the TOC, which already has a divider — a second
   rule immediately under it just looks like a mistake. */
.prose h2:first-child {
  margin-top: 0;
  padding-top: 0;
  border-top: 0;
}
.prose h2:first-child::before { display: none; }

.prose h3 {
  font-size: 18.5px;
  margin: var(--xl) 0 var(--xs);
  scroll-margin-top: var(--xl);
}

.prose p {
  margin: 0 0 var(--md);
  font-size: 15.5px; line-height: 1.7; color: var(--text);
}

.prose a {
  color: var(--accent); font-weight: 600;
  text-decoration: underline; text-underline-offset: 2px;
  text-decoration-color: rgba(84,115,78,.35);
  transition: text-decoration-color .2s;
}
.prose a:hover { text-decoration-color: var(--accent); }

/* The alternatives cards are whole-card links sitting inside .prose, so the
   inline-link styling above would paint the entire card accent-green and
   underline every line of it. Opt them back out — the card has its own hover
   affordance (border + lift), it doesn't need to look like a text link. */
.prose a.alt-card {
  color: inherit;
  font-weight: inherit;
  text-decoration: none;
}

/* --- Lists ---
   style.css sets `ul { list-style: none }` site-wide. That's correct for the
   nav and the card grids, but it also strips the markers from prose lists,
   which left these rendering as bare indented text with no bullets — the lists
   read as broken paragraphs and the indent looked arbitrary.

   Rather than fight that global rule, draw the marker ourselves. A pseudo-
   element gives a true hanging indent (wrapped lines align to the text, not
   under the bullet), lets the bullet carry the brand accent, and stays put
   regardless of what the theme does to list-style. */
.prose ul {
  margin: 0 0 var(--lg);
  padding: 0;
  list-style: none;
}
.prose li {
  position: relative;
  padding-left: 26px;                        /* the hanging indent */
  margin-bottom: var(--sm);
  font-size: 15.5px; line-height: 1.7;
}
.prose li:last-child { margin-bottom: 0; }

.prose li::before {
  content: "";
  position: absolute;
  left: 5px;
  top: .62em;                                /* optically centres on the first line */
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--accent);
}

/* Almost every list on these pages is a bold lead-in followed by an
   explanation. Giving the lead-in a little more weight makes the lists
   scannable — you can read just the bold parts and still get the argument. */
.prose li > b:first-child,
.prose li > strong:first-child {
  font-weight: 700;
  color: var(--text);
}

/* The "who should skip this" list argues against the product we're being paid
   to recommend, so it earns a visual identity of its own: a warning-coloured
   marker (the same red as a "No" in the comparison tables) and a tinted panel.
   It reads as a genuine caveat rather than more of the same sales copy — which
   is the whole point of having it on the page. */
.prose ul.list-warn {
  padding: var(--lg);
  border-radius: var(--r-md);
  background: #fdf6f6;
  border: 1px solid #f0dede;
}
.prose ul.list-warn li { padding-left: 26px; }
.prose ul.list-warn li::before {
  background: #a33;
  /* A square marker, not a dot — a different shape reads as a different KIND
     of point, which is exactly the signal we want here. */
  border-radius: 2px;
  width: 6px; height: 6px;
}

.prose code {
  padding: 2px 5px; border-radius: 4px;
  background: var(--bg-3);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: .88em;
}
.prose b { font-weight: 600; }
.prose em { font-style: italic; }

/* Visually-hidden — used for <caption> text that only screen readers need */
.sr-only {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}


/* ---------------------------------------------------------------------
   7) COMPARISON TABLES
   .table-wrap scrolls horizontally on mobile so a wide table never blows
   out the page width (the body must never scroll sideways).
   --------------------------------------------------------------------- */
/* The scroll container. The table inside is deliberately wider than a phone
   screen — squashing a four-column comparison into 360px makes it unreadable,
   so we let it keep its width and scroll it instead.

   The catch with that is discoverability: a table that scrolls but doesn't
   LOOK like it scrolls just reads as a broken, cut-off table. So the wrapper
   carries two affordances, both driven by initTableScroll() in main.js:
     • .can-scroll — the table is actually wider than its box (adds the hint)
     • .at-end     — you've scrolled to the right-hand edge (fade disappears)
   Neither is applied when the table fits, so on desktop this is all inert. */
.table-scroll {
  position: relative;
  margin: 0 0 var(--lg);
}

.table-wrap {
  overflow-x: auto;
  border: 1px solid var(--stroke);
  border-radius: var(--r-md);
  -webkit-overflow-scrolling: touch;
  /* Firefox / modern scrollbar styling — a thin, quiet bar rather than the
     chunky default, which looks like a bug inside a bordered card. */
  scrollbar-width: thin;
  scrollbar-color: var(--stroke) transparent;
}
/* WebKit equivalent */
.table-wrap::-webkit-scrollbar { height: 6px; }
.table-wrap::-webkit-scrollbar-track { background: transparent; }
.table-wrap::-webkit-scrollbar-thumb {
  background: var(--stroke);
  border-radius: 999px;
}
.table-wrap:hover::-webkit-scrollbar-thumb { background: #cfcfcf; }

/* Right-edge fade — the visual cue that there's more table off-screen.
   Sits on the wrapper (not the scroller) so it stays pinned while you scroll. */
.table-scroll::after {
  content: "";
  position: absolute;
  top: 1px; right: 1px; bottom: 1px;      /* inset by the 1px border */
  width: 56px;
  border-radius: 0 var(--r-md) var(--r-md) 0;
  background: linear-gradient(90deg, rgba(250,250,250,0), rgba(250,250,250,.96));
  pointer-events: none;                    /* never blocks a swipe */
  opacity: 0;
  transition: opacity .25s ease;
}
/* Only show the fade when there IS more to see */
.table-scroll.can-scroll::after { opacity: 1; }
.table-scroll.can-scroll.at-end::after { opacity: 0; }

/* "Swipe to see more" hint above the table */
.table-hint {
  display: none;                           /* JS reveals it only when needed */
  align-items: center; gap: var(--xxs);
  margin-bottom: var(--xs);
  font-size: 12px; font-weight: 600;
  color: var(--subtext);
}
.table-scroll.can-scroll .table-hint { display: flex; }
.table-hint svg { width: 13px; height: 13px; }
/* The arrow nudges right, twice, then rests — enough to read as "swipe" */
@keyframes hint-nudge {
  0%, 60%, 100% { transform: translateX(0); }
  70%, 90%      { transform: translateX(3px); }
}
.has-motion .table-scroll.can-scroll .table-hint svg {
  animation: hint-nudge 2.4s ease-in-out infinite;
}

.cmp {
  width: 100%;
  min-width: 620px;                          /* forces the scroll rather than squashing */
  border-collapse: collapse;
  font-size: 13.5px;
}
.cmp thead th {
  position: sticky; top: 0;                  /* header stays put while the table scrolls */
  padding: var(--sm) var(--md);
  background: var(--bg-2);
  border-bottom: 1px solid var(--stroke);
  font-family: var(--display); font-size: 12px; font-weight: 700;
  letter-spacing: .03em; text-transform: uppercase;
  color: var(--subtext); text-align: left;
  white-space: nowrap;
}
.cmp tbody th {
  padding: var(--sm) var(--md);
  font-weight: 500; text-align: left; color: var(--text);
  border-bottom: 1px solid var(--stroke);
  vertical-align: top;
}
.cmp tbody td {
  padding: var(--sm) var(--md);
  border-bottom: 1px solid var(--stroke);
  color: var(--subtext);
  vertical-align: top;
}
.cmp tbody tr:last-child th,
.cmp tbody tr:last-child td { border-bottom: 0; }
.cmp tbody tr:hover { background: var(--bg-2); }

/* Yes / no / neutral cells — colour-coded, but never colour ALONE
   (the words "Yes" and "No" carry the meaning for accessibility). */
.cmp .yes  { color: #2c6e2a; font-weight: 600; }
.cmp .no   { color: #a33; font-weight: 600; }
.cmp .note { color: var(--subtext); }


/* ---------------------------------------------------------------------
   8) CALLOUTS — pull the reader's eye to the takeaway
   --------------------------------------------------------------------- */
.callout {
  margin: var(--lg) 0;
  padding: var(--md) var(--lg);
  border-radius: var(--r-md);
  font-size: 14.5px; line-height: 1.65;
}
.callout b { font-weight: 700; }

/* Key takeaway — the sentence you want quoted in an AI overview */
.callout-key {
  background: var(--bg-accent);
  border-left: 3px solid var(--accent);
  color: #21301d;
}
.callout-key b { color: #14200f; }

/* Warning / caveat — used for "verify the price before buying" */
.callout-warn {
  background: #fdf6e3;
  border-left: 3px solid #d9a441;
  color: #5c4813;
}
.callout-warn a {
  color: #8a6414; font-weight: 700;
  text-decoration: underline; text-underline-offset: 2px;
}


/* ---------------------------------------------------------------------
   9) AFFILIATE BAND — the mid-article CTA
   Sits right after the pricing section, which is peak buying intent.
   --------------------------------------------------------------------- */
.aff-band {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--lg); flex-wrap: wrap;
  margin: var(--xl) 0;
  padding: var(--lg) var(--xl);
  border-radius: var(--r-md);
  /* Dark treatment echoes the sitewide .cta so it reads as "this is an offer" */
  background:
    radial-gradient(ellipse 60% 90% at 90% 50%, #1e3d24 0%, transparent 70%),
    linear-gradient(160deg, #0d1e13 0%, #091315 100%);
  color: var(--bg-1);
}
.aff-band-text { flex: 1 1 300px; }
.aff-band h3 {
  font-size: 20px; color: var(--bg-1); margin: 0 0 var(--xxs);
  max-width: none;
}
.aff-band p {
  margin: 0;
  font-size: 14px; line-height: 1.6; color: #9aaa98;
  max-width: 52ch;
}
.aff-band .btn-primary {
  flex: none;
  background: var(--accent); color: #fff;
  transition: transform .3s var(--ease), box-shadow .3s var(--ease);
}
.aff-band .btn-primary:hover {
  transform: translateY(-2px);
  box-shadow: 0 8px 28px rgba(84,115,78,.45);
}


/* ---------------------------------------------------------------------
   10) ALTERNATIVES GRID
   The first card cross-links to the sibling review — that internal link is
   the backbone of the two-page cluster, so it gets the accent treatment.
   --------------------------------------------------------------------- */
.alt-grid {
  display: grid;
  /* minmax(min(260px, 100%), 1fr) — not minmax(260px, 1fr).
     A bare 260px floor is a HARD minimum: on a 320px phone the column is only
     ~255px wide, so the track refuses to shrink below 260 and pushes the whole
     page into horizontal scroll. Wrapping the floor in min(…, 100%) lets it
     collapse to the container width when the container is the smaller of the
     two, which is exactly what you want on a narrow screen. */
  /* 200px, not 260px: at 260 the three cards only fit two-across in the article
     column, leaving a lone orphan on a second row. 200 lets all three sit in a
     single row on desktop and still collapses cleanly on narrow screens. */
  grid-template-columns: repeat(auto-fit, minmax(min(200px, 100%), 1fr));
  gap: var(--md);
  margin: var(--lg) 0;
}
.alt-card {
  display: flex; flex-direction: column;
  padding: var(--lg);
  color: inherit;
}
.alt-tag {
  font-family: var(--display); font-size: 10.5px; font-weight: 700;
  letter-spacing: .06em; text-transform: uppercase;
  color: var(--accent);
}
.alt-card h3 {
  font-size: 18px; margin: var(--xs) 0 var(--xs);
  max-width: none;
  transition: color .2s ease;
}
.alt-card p {
  font-size: 13.5px; line-height: 1.6; color: var(--subtext);
  margin: 0; flex: 1;
  max-width: none;
}
/* The clickable card (the sibling review) lifts and greens on hover */
a.alt-card:hover { border-color: var(--accent); }
a.alt-card:hover h3 { color: var(--accent); }
.alt-link {
  margin-top: var(--md);
  display: inline-flex; align-items: center; gap: var(--xxs);
  font-size: 13px; font-weight: 600; color: var(--accent);
}
.alt-link .arrow { transition: transform .25s var(--ease); }
a.alt-card:hover .alt-link .arrow { transform: translateX(3px); }
/* Non-linked cards are informational only — no hover affordance */
.alt-card.is-plain { cursor: default; }


/* ---------------------------------------------------------------------
   11) FAQ — accordion, mirrors the FAQPage schema in each page's <head>
   --------------------------------------------------------------------- */
.faq { padding: var(--xxl) 0 var(--xl); }
.faq-title {
  font-size: clamp(20px, 2.4vw, 26px);
  margin-bottom: var(--md);
  scroll-margin-top: var(--xl);
}
.faq-item {
  border-bottom: 1px solid var(--stroke);
}
.faq-item summary {
  position: relative;
  padding: var(--md) var(--xl) var(--md) 0;
  cursor: pointer; list-style: none;
  font-family: var(--display); font-size: 15.5px; font-weight: 600;
  color: var(--text);
  transition: color .2s;
}
.faq-item summary:hover { color: var(--accent); }
.faq-item summary::-webkit-details-marker { display: none; }   /* hide default triangle */
.faq-item summary::after {                                     /* custom +/− toggle */
  content: "+";
  position: absolute; right: 0; top: 50%; transform: translateY(-50%);
  font-size: 20px; font-weight: 400; color: var(--accent);
}
.faq-item[open] summary::after { content: "−"; }
.faq-item p {
  margin: 0 0 var(--md);
  padding-right: var(--xl);
  font-size: 14.5px; line-height: 1.7; color: var(--subtext);
  max-width: 76ch;
}
.faq-item p code {
  padding: 2px 5px; border-radius: 4px;
  background: var(--bg-3);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: .88em;
}


/* ---------------------------------------------------------------------
   12) FIGURES & DIAGRAMS

   Everything visual on these pages is inline SVG rather than an image file:
   no extra requests, no layout shift, sharp on every display, and it inherits
   the site's palette via currentColor and the --accent tokens.

   The diagrams are argument-carrying, not decorative — each one makes a point
   the surrounding prose is already making, which is what earns it a place on
   a page people are scanning for a buying decision.
   --------------------------------------------------------------------- */
.figure {
  margin: var(--xl) 0;
  padding: 0;
}
.figure svg { display: block; width: 100%; height: auto; }

/* The framed panel a diagram sits in */
.figure-panel {
  padding: var(--lg);
  background: var(--bg-2);
  border: 1px solid var(--stroke);
  border-radius: var(--r-md);
}
/* Dotted-grid backdrop — same treatment as the blog cards, ties it to the site */
.figure-panel.is-grid {
  background:
    radial-gradient(circle at 1px 1px, rgba(9,19,21,.06) 1px, transparent 0) 0 0 / 16px 16px,
    var(--bg-2);
}

.figure figcaption {
  margin-top: var(--sm);
  font-size: 12.5px; line-height: 1.55; color: var(--subtext);
  text-align: center;
}
.figure figcaption b { color: var(--text); font-weight: 600; }

/* --- Shared SVG type styles. Set on the <svg> so every text node inherits. --- */
.viz { font-family: var(--sans); }
.viz .viz-title {
  font-family: var(--display); font-size: 11px; font-weight: 700;
  letter-spacing: .06em; text-transform: uppercase;
}
.viz .viz-label   { font-size: 11.5px; font-weight: 600; }
.viz .viz-sub     { font-size: 10.5px; }
.viz .viz-big     { font-family: var(--display); font-size: 15px; font-weight: 700; }

/* Colour roles inside the diagrams */
.viz .ink    { fill: var(--text); }
.viz .muted  { fill: var(--subtext); }
.viz .accent { fill: var(--accent); }
.viz .on-dark{ fill: #fff; }


/* ---------------------------------------------------------------------
   13) FEATURED CARD — the hero under each article's header

   This is a LIVE HTML version of the 1200x630 social card in assets/og/.
   Rendering it as markup rather than dropping in the PNG means it stays
   crisp at any size, reflows properly on a phone, weighs almost nothing,
   and can be edited without re-exporting an image.

   It deliberately omits the brand lockup that the PNG carries: on a shared
   link there's no context, so the social image needs the logo — but here the
   sidebar is right there, and repeating it is just noise.

   Every word in this card repeats the <h1> and intro directly above it, so
   the markup carries aria-hidden="true". Sighted readers get a hero; screen
   reader users don't get the headline read to them twice.
   --------------------------------------------------------------------- */
.featured {
  position: relative;
  overflow: hidden;
  margin: var(--lg) 0 0;
  padding: clamp(var(--lg), 4vw, var(--xxl));
  background: var(--bg-1);
  border: 1px solid var(--stroke);
  border-radius: var(--r-md);

  display: grid;
  grid-template-columns: 1fr minmax(200px, 38%);
  align-items: center;
  gap: clamp(var(--lg), 3vw, var(--xl));
}

/* Faint dotted grid, faded toward the centre — same motif as the social card */
.featured::before {
  content: "";
  position: absolute; inset: 0;
  background-image: radial-gradient(var(--stroke) 1.4px, transparent 1.4px);
  background-size: 26px 26px;
  -webkit-mask-image: radial-gradient(120% 120% at 100% 0%, #000 0%, transparent 55%);
          mask-image: radial-gradient(120% 120% at 100% 0%, #000 0%, transparent 55%);
  opacity: .6;
  pointer-events: none;
}
/* Soft green glow from the bottom-right */
.featured::after {
  content: "";
  position: absolute;
  right: -160px; bottom: -200px;
  width: 520px; height: 520px;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(84,115,78,.16), transparent 70%);
  pointer-events: none;
}

/* ---- Left: the copy ---- */
.f-copy { position: relative; z-index: 1; }

.f-eyebrow {
  display: inline-flex; align-items: center; gap: var(--xs);
  margin-bottom: var(--sm);
  font-size: 11px; font-weight: 700;
  letter-spacing: .12em; text-transform: uppercase;
  color: var(--accent);
}
.f-eyebrow .tag {
  padding: 3px var(--xs);
  background: var(--bg-accent);
  border: 1px solid #c8dcc2;
  border-radius: 999px;
  letter-spacing: .04em;
  color: #3c5436;
}

/* Not a real heading — the page already has its <h1>. A second one here would
   put a duplicate title in the document outline for no benefit. */
.f-title {
  font-family: var(--display);
  font-weight: 600;
  font-size: clamp(24px, 3vw, 36px);
  line-height: 1.05;
  letter-spacing: -.025em;
  color: var(--text);
  margin: 0;
  max-width: none;
}
.f-title em { font-style: normal; color: var(--accent); }

.f-lede {
  margin: var(--sm) 0 0;
  font-size: clamp(13.5px, 1.3vw, 15px);
  line-height: 1.5;
  color: var(--subtext);
  max-width: 42ch;
}

.f-chips {
  display: flex; flex-wrap: wrap; gap: var(--xs);
  margin-top: var(--md);
}
.f-chips span {
  padding: 5px var(--sm);
  background: var(--bg-2);
  border: 1px solid var(--stroke);
  border-radius: 999px;
  font-size: 12px; font-weight: 600;
  color: #4a4a4a;
}
.f-chips span.hero {
  background: var(--bg-accent);
  border-color: #c8dcc2;
  color: #3c5436;
}

/* ---- Right: the builder mockup ---- */
.f-art { position: relative; z-index: 1; }

.f-window {
  border-radius: 14px;
  background: #fff;
  border: 1px solid var(--stroke);
  box-shadow: 0 24px 48px -18px rgba(9,19,21,.26);
  overflow: hidden;
  transform: rotate(-2deg);
}
.f-bar {
  display: flex; align-items: center; gap: 6px;
  padding: 9px 11px;
  background: var(--bg-2);
  border-bottom: 1px solid var(--stroke);
}
.f-bar i {
  width: 8px; height: 8px; border-radius: 50%;
  background: var(--bg-3);
}
.f-bar u {
  flex: 1; margin-left: 5px;
  height: 16px; border-radius: 999px;
  background: #fff;
  border: 1px solid var(--stroke);
}

.f-body { display: flex; height: 168px; }

/* Widget / structure panel */
.f-panel {
  width: 34%; flex: none;
  padding: 9px 7px;
  background: var(--bg-2);
  border-right: 1px solid var(--stroke);
}
.f-row {
  display: flex; align-items: center; gap: 5px;
  margin-bottom: 7px;
}
.f-row i {
  width: 11px; height: 11px; flex: none;
  border-radius: 3px;
  background: var(--bg-3);
}
.f-row u { flex: 1; height: 4px; border-radius: 2px; background: var(--bg-3); }
/* "Selected" rows carry the accent */
.f-row.on i { background: var(--accent); }
.f-row.on u { background: #c8dcc2; }
/* Indent levels — used on the Breakdance card to show the structure tree */
.f-row.d1 { padding-left: 8px; }
.f-row.d2 { padding-left: 16px; }

/* Canvas */
.f-canvas { flex: 1; padding: 12px; }
.f-hero {
  height: 40px; border-radius: 7px;
  background: linear-gradient(120deg, var(--accent), var(--accent-light));
  margin-bottom: 10px;
}
.f-line { height: 7px; border-radius: 3.5px; background: var(--bg-3); margin-bottom: 6px; }
.f-line.w1 { width: 88%; }
.f-line.w2 { width: 62%; }
.f-cols { display: flex; gap: 6px; margin-top: 11px; }
.f-col {
  flex: 1; height: 36px;
  border-radius: 6px;
  background: var(--bg-2);
  border: 1px solid var(--stroke);
}
.f-col.sel {
  position: relative;
  background: #fff;
  border: 2px solid var(--accent);
}
/* Two corner handles on the selected element */
.f-col.sel::before,
.f-col.sel::after {
  content: "";
  position: absolute;
  width: 5px; height: 5px; border-radius: 1.5px;
  background: var(--accent);
}
.f-col.sel::before { top: -3.5px; left: -3.5px; }
.f-col.sel::after  { bottom: -3.5px; right: -3.5px; }

/* Score badge, clipped to the mockup's corner */
.f-score {
  position: absolute;
  right: -10px; bottom: -10px;
  width: 68px; height: 68px;
  border-radius: 18px;
  background: var(--button);
  display: grid; place-items: center;
  box-shadow: 0 14px 28px -8px rgba(9,19,21,.5);
  transform: rotate(-2deg);
  text-align: center;
  line-height: 1;
}
.f-score b {
  display: block;
  font-family: var(--display);
  font-size: 24px; font-weight: 800;
  letter-spacing: -.03em;
  color: #fff;
}
.f-score small {
  display: block; margin-top: 3px;
  font-size: 9px; font-weight: 700;
  color: var(--accent-light);
}

/* On a narrow column the mockup would be squeezed into illegibility, so the
   card becomes copy-only. The mockup is decoration; the words are the point. */
@media (max-width: 700px) {
  .featured { grid-template-columns: 1fr; }
  .f-art { display: none; }
}


/* ---------------------------------------------------------------------
   14) SCREENSHOT SLOT
   A placeholder for a real screenshot Glen still needs to capture. Styled to
   be obviously unfinished — it should never survive to production. Delete the
   whole <figure>, or swap in an <img>, before publishing.
   --------------------------------------------------------------------- */
.shot-slot {
  display: grid; place-items: center; gap: var(--xs);
  min-height: 200px; padding: var(--xl);
  border: 2px dashed var(--stroke);
  border-radius: var(--r-md);
  background: var(--bg-2);
  text-align: center;
}
.shot-slot svg { width: 26px; height: 26px; color: var(--subtext); }
.shot-slot b {
  font-family: var(--display); font-size: 13px; font-weight: 700;
  color: var(--text);
}
.shot-slot span {
  font-size: 12.5px; line-height: 1.5; color: var(--subtext);
  max-width: 46ch;
}


/* =====================================================================
   15) MOTION

   Everything here is gated behind `.has-motion`, a class that main.js puts
   on <html> ONLY when both are true:
     • JavaScript ran, and
     • the visitor hasn't asked their OS for reduced motion.

   That gate matters. The reveal effects start elements at opacity:0, so if
   they were applied unconditionally, a visitor with JS disabled — or a
   crawler that doesn't run it — would see a blank article. Gating on a
   JS-added class means the default, un-enhanced page is the fully visible
   one, and motion is purely additive.

   Note what is NOT animated: the featured image. It's the LCP element, and
   fading it in would delay Largest Contentful Paint for the sake of a flourish.
   ===================================================================== */

/* ---- Scroll reveal ----
   Applied by JS to structural blocks (headings, figures, tables, cards) —
   never to body paragraphs. Text that pops in as you read it is irritating;
   the point is to give the page a sense of assembly, not to gate the content. */
.has-motion .reveal {
  opacity: 0;
  transform: translateY(14px);
  will-change: opacity, transform;
}
.has-motion .reveal.is-in {
  opacity: 1;
  transform: none;
  transition: opacity .55s var(--ease), transform .55s var(--ease);
  will-change: auto;                      /* release the compositor layer */
}
/* Stagger siblings in a grid — JS sets --i to the item's index */
.has-motion .alt-card.reveal.is-in,
.has-motion .faq-item.reveal.is-in {
  transition-delay: calc(var(--i, 0) * 70ms);
}

/* ---- Verdict score bars ----
   Fill from zero when the verdict box scrolls into view. The bars are the
   most information-dense thing on the page, and watching them land is what
   makes a reader actually read them instead of skipping past. */
.has-motion .bar-fill { width: 0; }
.has-motion .verdict.is-in .bar-fill {
  width: var(--v);
  transition: width .9s var(--ease);
}
/* Each bar starts fractionally after the one above it */
.has-motion .verdict.is-in .bar-row:nth-child(1) .bar-fill { transition-delay: .10s; }
.has-motion .verdict.is-in .bar-row:nth-child(2) .bar-fill { transition-delay: .18s; }
.has-motion .verdict.is-in .bar-row:nth-child(3) .bar-fill { transition-delay: .26s; }
.has-motion .verdict.is-in .bar-row:nth-child(4) .bar-fill { transition-delay: .34s; }
.has-motion .verdict.is-in .bar-row:nth-child(5) .bar-fill { transition-delay: .42s; }

/* ---- Chart line draw ----
   JS measures each polyline with getTotalLength() and writes it to --len.
   Offsetting the dash by its own full length hides the line; animating the
   offset back to zero draws it. */
.has-motion .viz polyline[data-draw] {
  stroke-dasharray: var(--len);
  stroke-dashoffset: var(--len);
}
.has-motion .figure.is-in .viz polyline[data-draw] {
  stroke-dashoffset: 0;
  transition: stroke-dashoffset 1.1s var(--ease);
}
/* The data points and the shaded area fade in behind the line as it lands.
   Scoped to .viz--chart, not all .viz: the other diagrams have circles too
   (the tick badges), and hiding those would leave their checkmarks floating
   in mid-air until the fade caught up. */
.has-motion .viz--chart circle,
.has-motion .viz--chart path[fill-opacity] { opacity: 0; }
.has-motion .figure.is-in .viz--chart circle,
.has-motion .figure.is-in .viz--chart path[fill-opacity] {
  opacity: 1;
  transition: opacity .5s ease .7s;
}

/* ---- FAQ ----
   details/summary can't transition its own height, so animate the answer
   instead: it slides down as it appears. */
@keyframes faq-in {
  from { opacity: 0; transform: translateY(-5px); }
  to   { opacity: 1; transform: none; }
}
.has-motion .faq-item[open] p { animation: faq-in .28s var(--ease); }
/* Rotate the +/− marker rather than swapping the glyph abruptly */
.has-motion .faq-item summary::after { transition: transform .25s var(--ease); }
.has-motion .faq-item[open] summary::after { transform: translateY(-50%) rotate(180deg); }

/* ---- Buy button ----
   A slow, continuous shine that crosses the CTA every few seconds. It's the
   one thing on the page that's allowed to ask for attention. */
@keyframes buy-shine {
  0%        { transform: translateX(-120%) skewX(-20deg); }
  55%, 100% { transform: translateX(320%)  skewX(-20deg); }
}
.has-motion .buy-btn {
  position: relative;
  overflow: hidden;
}
.has-motion .buy-btn::after {
  content: "";
  position: absolute; top: 0; bottom: 0; left: 0;
  width: 40px;
  background: linear-gradient(90deg, transparent, rgba(255,255,255,.28), transparent);
  animation: buy-shine 4.5s var(--ease) infinite;
}


/* ---------------------------------------------------------------------
   RESPONSIVE — tighten the padding on small screens
   --------------------------------------------------------------------- */
@media (max-width: 560px) {
  .verdict-main,
  .verdict-buy { padding: var(--md); }
  .aff-band { padding: var(--lg) var(--md); }
  .bar-row { grid-template-columns: 84px 1fr 28px; }
  .prose p, .prose li { font-size: 15px; }
  .figure-panel { padding: var(--md); }
  .shot-slot { padding: var(--lg); min-height: 160px; }
}

/* Respect users who've asked the OS for less motion */
@media (prefers-reduced-motion: reduce) {
  .read-bar span,
  .alt-link .arrow,
  .aff-band .btn-primary { transition: none; }
}
