/* Thai Broker Review v2 — clean/clear/professional design system (2026-07-02)
   ink navy + paper + emerald/amber/red semantics · Inter + Noto Sans Thai */
:root{
  --ink:#12291D; --ink2:#2A4A3A; --paper:#F8FCFA; --card:#FFFFFF; --line:#DFEDE5;
  --slate:#5F7A6C; --slate2:#93AC9E;
  --pos:#0E9F6E; --pos-bg:#E6F6EF; --warn:#B45309; --warn-bg:#FDF3E7;
  --neg:#B42318; --neg-bg:#FBEAE8; --info:#0A7350; --info-bg:#E9F7F0;
  /* --pos stays the brand green for large numerals, bars and borders (3.39:1 on white — fine
     for 24px+ display type). It FAILS AA as small text: #0E9F6E on --pos-bg is 3.03:1. --pos-text
     is the same hue darkened for small green text on a green tint: 5.25:1 on --pos-bg, 5.86:1 on
     white. --info (every link, incl. .rev) was 4.37:1 on white — also under 4.5 — and is now the
     same #0A7350 at 5.86:1. Measured with the WCAG relative-luminance formula, not eyeballed. */
  --pos-text:#0A7350;
  --gold:#B7791F; --radius:14px; --shadow:0 1px 2px rgba(18,41,29,.05),0 8px 24px -12px rgba(18,41,29,.12);
  /* ===== motion token scale (2026-08-27, dev_scratch/motion_spec_2026-08-27.md §1) =====
     --m-1..5 = reusable interactive durations; --e-out/io/in/pop = the 4 named eases;
     --m-stag = per-sibling stagger unit (cap index 4); --m-lift-sm/lg = reveal shift distances.
     --m-amb is a reference pitch only — NOT used for marquee scroll speed (those durations are
     content-width-tuned, see §1c). --e-pop is reserved/declared, unused by the 6 shipped moves. */
  --m-1:80ms; --m-2:160ms; --m-3:240ms; --m-4:320ms; --m-5:480ms; --m-amb:6000ms;
  --e-out:cubic-bezier(.16,1,.3,1); --e-io:cubic-bezier(.65,0,.35,1);
  --e-in:cubic-bezier(.4,0,1,1); --e-pop:cubic-bezier(.34,1.56,.64,1);
  --m-stag:60ms; --m-lift-sm:12px; --m-lift-lg:18px;
  /* §1b bespoke one-off (not part of the 5-step scale) */
  --m-shine:700ms;
  /* §7 badge visual spec — --gold already existed (was orphaned, see §4 note); --gold-bg/
     --silver/--silver-bg are new. */
  --gold-bg:#FDF6E7; --silver:#64748B; --silver-bg:#F1F5F9;
}
/* Perf gate V2 fix F2 (2026-08-27): mobile CLS 0.0201-0.0246 vs the 0.02 budget, root cause =
   Google-Fonts display=swap FOUT reflow on the body face (Inter) -- ~21px shift + hero CTA
   width change at ~220ms, measured at 375px. Inter loads via <link> in generate.py's FONTS
   constant (untouched here -- this is a style.css-only fix, no font-display change). Standard
   published Arial-to-Inter override metrics (the same values next/font/Google-Fonts tooling
   ships) size the LOCAL fallback so its box model matches Inter closely enough that the later
   swap causes ~zero layout shift, instead of eliminating the swap. Named "Inter Fallback" (not
   "Inter") so it can sit as its own step in the stack below without colliding with the real
   webfont's family name once it loads. Thai text is unaffected: local("Arial") lacks Thai
   glyphs on essentially every platform, so the browser's own per-character font-fallback still
   reaches "Noto Sans Thai" for Thai runs regardless of this family's position in the stack. */
@font-face{
  font-family:"Inter Fallback";
  src:local("Arial");
  size-adjust:107%;
  ascent-override:90%;
  descent-override:22.5%;
  line-gap-override:0%;
}
*{box-sizing:border-box}
body{margin:0;background:var(--paper);color:var(--ink);
  font:400 16.5px/1.65 Inter,"Inter Fallback","Noto Sans Thai",-apple-system,"Segoe UI",sans-serif;
  -webkit-font-smoothing:antialiased;
  /* LONG-TOKEN OVERFLOW GUARD (2026-08-06). Editorial copy contains slash-runs of source
     names ("ThaiBrokerForex/ForexInThai/ThaiForexBroker/dojipedia") and bare domains. Chrome
     offers no line-break opportunity at "/" between letters, so one bullet blew the page out
     by 210px at 360 / 180px at 390 on /broker/xm.html and 6px on /broker/fxpro.html.
     `break-word` (not `anywhere`) is deliberate: it only breaks a word that cannot fit a line
     on its own, and it does NOT change min-content contribution — so no table, flex or grid
     track re-sizes because of it. Inherited, so it covers every text node on the site. */
  overflow-wrap:break-word}
html[lang=th] body{font-family:"Noto Sans Thai",Inter,sans-serif}
a{color:var(--info);text-decoration:none} a:hover{text-decoration:underline}
h1,h2,h3{line-height:1.25;font-weight:800;letter-spacing:-.015em}
main{max-width:1120px;margin:0 auto;padding:0 20px 72px}

/* nav */
.nav{position:sticky;top:0;z-index:50;display:flex;flex-wrap:wrap;align-items:center;gap:10px 22px;
  padding:14px 24px;background:rgba(255,255,255,.92);backdrop-filter:blur(8px);
  border-bottom:1px solid var(--line)}
.nav .brand{font-weight:800;font-size:19px;color:var(--ink);letter-spacing:-.02em;position:relative}
.nav .brand .wt{font-weight:400;color:var(--slate);font-size:12.5px}
.nav nav{margin-left:auto;display:flex;gap:18px;align-items:center;flex-wrap:wrap}
.nav nav a{color:var(--ink2);font-weight:600;font-size:14.5px;position:relative}
.nav .lang{border:1.5px solid var(--line);border-radius:999px;padding:3px 13px;color:var(--ink);background:var(--card);position:relative}
/* D02 (P1, 2026-08-06): nav links measured 24px tall at 375px (lang chip 32px, breadcrumb
   links 17px) -- under the 44px tap-target floor. .side's rendered height (53.2px) is
   hard-baked into the scroll-margin-top formulas a few rules down (176px/214px = .side top +
   height + 11px breathing room) and .nav's own total height is the OTHER load-bearing input to
   those same formulas -- growing any real box here (padding, font-size) would drift both.
   Instead this expands the TAPPABLE area via an absolutely-positioned invisible ::before
   (standard WCAG SC 2.5.5 technique) -- the link's own rendered box, and therefore .nav's total
   height, is untouched (measured identical 144px before/after at 375px real render). */
.nav nav a::before,.nav .lang::before,.nav .brand::before{content:'';position:absolute;top:50%;left:50%;
  transform:translate(-50%,-50%);width:max(100%,44px);height:44px}
.preview-flag{background:var(--warn-bg);color:var(--warn);text-align:center;
  font-size:13px;padding:7px 16px;border-bottom:1px solid #F3E2C7}

/* hero */
.hero{padding:64px 0 22px;max-width:820px}
.hero h1{font-size:clamp(34px,5vw,52px);margin:0 0 18px}
.hero .lead{font-size:19px;color:var(--ink2);margin:0}
.counters{display:flex;gap:14px;flex-wrap:wrap;margin:30px 0 8px}
.counters .c{background:var(--card);border:1px solid var(--line);border-radius:var(--radius);
  box-shadow:var(--shadow);padding:16px 26px;min-width:150px}
/* Display numerals opt out of the body-level break-word guard: the box is min-width:150px and
   "1,384+" measures ~155px at 30px/800, so break-word split it onto two lines. It only ever
   overhangs into the .c padding, never the page. */
.counters .n{font-size:30px;font-weight:800;color:var(--ink);overflow-wrap:normal}
.counters .l{font-size:13px;color:var(--slate);font-weight:600}

/* top-10 medal cards */
.rankgrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:14px;margin:20px 0 8px}
.rankcard{position:relative;min-width:0;background:var(--card);border:1px solid var(--line);border-radius:var(--radius);
  box-shadow:var(--shadow);padding:18px 16px 14px;display:block;color:var(--ink)}
.rankcard:hover{text-decoration:none;border-color:var(--slate2);transform:translateY(-2px);transition:.15s}
.rankcard .medal{position:absolute;top:-10px;left:-8px;width:30px;height:30px;border-radius:999px;
  background:var(--ink);color:#fff;font-weight:800;font-size:14px;display:flex;align-items:center;justify-content:center;
  box-shadow:var(--shadow)}
.rankcard.m1 .medal{background:var(--gold)} .rankcard.m2 .medal{background:#7C8A9C} .rankcard.m3 .medal{background:#A16B3F}
.rankcard .bn{font-weight:800;font-size:16.5px;margin-right:6px}
.rankcard .sc{font-size:26px;font-weight:800;color:var(--pos)}
.rankcard .sc small{font-size:13px;color:var(--slate);font-weight:600}
.rankcard .meta{font-size:12.5px;color:var(--slate);margin-top:4px;line-height:1.5}
.scale-note{font-size:13.5px;color:var(--slate);margin:2px 0 0}
.scale-note a{font-weight:600}

/* findings */
.findings{display:grid;grid-template-columns:repeat(auto-fit,minmax(230px,1fr));gap:14px;margin:34px 0}
.findings .f{background:var(--card);border:1px solid var(--line);border-left:4px solid var(--ink);
  border-radius:10px;padding:16px 18px;box-shadow:var(--shadow)}
.findings h3{margin:0 0 6px;font-size:16px}
.findings p{margin:0;font-size:14px;color:var(--ink2)}

/* category cards */
.cats{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:14px;margin:8px 0 34px}
.cats a{background:var(--card);border:1px solid var(--line);border-radius:var(--radius);box-shadow:var(--shadow);
  padding:16px 18px;color:var(--ink);font-weight:700;font-size:15px;display:block}
.cats a:hover{text-decoration:none;border-color:var(--slate2)}
.cats .sub{display:block;font-weight:400;font-size:13px;color:var(--slate);margin-top:4px}

/* section heads */
.sh{margin:40px 0 6px;font-size:24px}
/* D06 (P1, 2026-08-06): measured 102-104 REAL characters/line on index -- past the ~45-75ch
   readable band. CSS `ch` is the "0"-glyph width, which in Inter is wider than the font's real
   average character width, so a naive max-width:75ch under-corrects (measured: 75ch here still
   rendered 72-76 real chars, i.e. it was already close to correct -- see the .card>p/.card>ul
   rule below, where 75ch measured 93-99 real chars, nearly a no-op). Verified per-line real
   character counts with Range.getClientRects() at the live render, not by eye or by the ch
   number alone; 55ch landed both this and the .card rule inside the band. */
.shsub{margin:0 0 16px;color:var(--slate);font-size:14.5px;max-width:55ch}

/* comparison table */
.tablewrap{background:var(--card);border:1px solid var(--line);border-radius:var(--radius);
  box-shadow:var(--shadow);padding:18px;overflow-x:auto}
.controls{display:flex;gap:18px;align-items:center;flex-wrap:wrap;margin-bottom:12px}
.controls input{border:1.5px solid var(--line);border-radius:9px;
  padding:9px 14px;font:inherit;font-size:14.5px;min-width:220px}
.controls input[type=checkbox]{min-width:0}
.controls label{font-size:14px;color:var(--ink2);display:flex;gap:6px;align-items:center}
table.cmp{width:100%;border-collapse:collapse;font-size:14.5px;min-width:760px}
.cmp th{font-size:12px;text-transform:uppercase;letter-spacing:.06em;color:var(--slate);
  text-align:left;padding:10px 12px;border-bottom:2px solid var(--line);cursor:pointer;user-select:none;white-space:nowrap}
.cmp th:hover{color:var(--ink)}
.cmp td{padding:12px;border-bottom:1px solid var(--line);vertical-align:middle}
.cmp tr:hover td{background:#F4FBF7}
.cmp .scnum{font-weight:800;font-size:16px}
.rev{font-weight:700;white-space:nowrap}

/* badges + chips */
.badge{display:inline-block;border-radius:999px;padding:3px 11px;font-size:12.5px;font-weight:700;
  background:#EDF5F0;color:var(--ink2);margin:2px 4px 2px 0;white-space:nowrap}
.badge.t1{background:var(--pos-bg);color:var(--pos-text)}
.badge.t2{background:#EAF0FD;color:#1D4ED8}
.badge.t3{background:#EDF5F0;color:var(--ink2)}
/* Fix 5 (§3b/§5): licence-badge classes were binary t1/t3 — Tier-2 (FSCA/DFSA) shared a class
   with genuine Tier-3/offshore entries, and unregulated/registry-only/revoked entities (e.g. the
   St. Vincent shell entity rendering as "SV") shared t3 with real offshore regulators too. .t2 is
   new (blue, between t1's green and t3's neutral gray); .tno is the not-a-real-regulator class
   (Revoked/Unregulated/Not a regulator/Registry only) — red, visually distinct from every
   regulator tier, matching the amber/red honesty-signal language the site already uses. */
.badge.tno{background:var(--neg-bg);color:var(--neg)}
.badge.pos{background:var(--pos-bg);color:var(--pos-text)}
.badge.mix{background:var(--warn-bg);color:var(--warn)}
.badge.neg{background:var(--neg-bg);color:var(--neg)}
.badge.thin{background:#EDF5F0;color:var(--slate)}
.chips{display:flex;gap:8px;flex-wrap:wrap}
.chip{border-radius:999px;padding:4px 13px;font-size:13.5px;font-weight:600;border:1.5px solid var(--line);color:var(--slate)}
.chip.on{background:var(--pos-bg);border-color:transparent;color:var(--pos-text)}
.chip.off{background:#F0F5F2;color:var(--slate2);text-decoration:line-through}
.chip.unk{background:#F8FAFC;color:var(--slate2)}
.ver,.ind{font-size:11.5px;font-weight:700;border-radius:6px;padding:1.5px 7px;vertical-align:middle}
.ver{background:var(--pos-bg);color:var(--pos-text)} .ind{background:var(--warn-bg);color:var(--warn)}

/* broker page */
.crumb{font-size:13px;color:var(--slate);margin:22px 0 8px}
/* D02: crumb links measured 17px tall -- same invisible-hit-zone technique as .nav above,
   scoped to the <a> only so the surrounding " › " separator text's own layout is untouched. */
.crumb a{position:relative}
.crumb a::before{content:'';position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);
  width:max(100%,44px);height:44px}
.crumb a{color:var(--slate)}
.broker .bhead{display:flex;gap:26px;align-items:flex-start;flex-wrap:wrap;margin:6px 0 18px}
/* The hero is a flex chain: .bhead > div > .bhl > (img.blogo-lg + h1). Flex items default to
   min-width:auto, so the h1 could never shrink below its longest unbreakable run — next to a
   118px-wide logo that pushed /th/broker/thinkmarkets.html 9px past the viewport at 360.
   min-width:0 lets it shrink. flex-wrap lets the name drop to its own full-width line instead
   of being squeezed beside a wide logo: without it, break-word rescued the overflow but split
   the brand ("ThinkMarke/ts"). Wrapping only engages when the row is genuinely too tight —
   XM, CMC Markets and every desktop width are byte-for-byte unchanged. */
.broker .bhead>div,.broker .bhl,.broker .bhl>h1{min-width:0}
.broker .bhl{flex-wrap:wrap}
.broker h1{font-size:clamp(28px,4vw,40px);margin:0}
.broker h1 .pop{display:block;font-size:14px;color:var(--slate);font-weight:600;letter-spacing:0;margin-top:2px}
.scorebox{margin-left:auto;background:var(--card);border:1px solid var(--line);border-radius:var(--radius);
  box-shadow:var(--shadow);padding:16px 22px;display:flex;gap:22px;align-items:center;min-width:300px}
.scorebox .big{font-size:44px;font-weight:800;color:var(--pos);line-height:1}
.scorebox .big small{font-size:15px;color:var(--slate);font-weight:600}
.scorebox .big.low{color:var(--neg)} .scorebox .big.mid{color:var(--warn)}
.subs{flex:1;min-width:170px}
.subs .s{display:grid;grid-template-columns:1fr auto;gap:8px;font-size:12px;color:var(--slate);
  font-weight:600;align-items:center;margin:3px 0}
.subs .bar{grid-column:1/3;height:5px;border-radius:99px;background:#E7F1EB;overflow:hidden}
.subs .bar i{display:block;height:100%;border-radius:99px;background:var(--pos)}
.subs .bar i.mid{background:var(--warn)} .subs .bar i.low{background:var(--neg)}
.badges{margin:10px 0 0}

.card{background:var(--card);border:1px solid var(--line);border-radius:var(--radius);
  box-shadow:var(--shadow);padding:22px 24px;margin:16px 0}
.card h2{margin:0 0 12px;font-size:19px}
.card.verdict{border-left:5px solid var(--pos);background:linear-gradient(180deg,#F4FBF8,#FFF)}
.card.danger{border-left:5px solid var(--neg)}
.card.danger h2{color:var(--neg)}
.card.good{border-left:5px solid var(--pos)}
.card.warn{border-left:5px solid var(--warn)}
/* D06 (P1, 2026-08-06): Strengths/Weaknesses <ul><li> bullets measured 109-113 REAL
   characters/line on broker pages -- well past the ~45-75ch readable band. Direct-child
   selector only (.card>p/.card>ul), so it never reaches into a card's fact grids/tables/badges
   (.qfgrid, .kv, .reg, .licgrid, .tprow review cards) that live one level deeper -- those stay
   full width by design. First pass used max-width:75ch and measured 93-99 real chars/line at
   the card's 19px font -- Inter's "0"-glyph (the ch unit) runs wider than its real average
   character, so 75ch under-corrects here. Verified per-line real character counts with
   Range.getClientRects() at the live render (not by eye, not by the ch number alone); 55ch
   measured 61-76 real chars/line -- inside the band. */
.card>p,.card>ul{max-width:55ch}
.cols{display:grid;grid-template-columns:1fr 1fr;gap:16px}
@media(max-width:760px){.cols{grid-template-columns:1fr}.scorebox{margin-left:0;width:100%}}
ul{margin:8px 0;padding-left:22px} li{margin:5px 0}

/* licence card grid */
.licgrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(215px,1fr));gap:12px}
.lic{border:1px solid var(--line);border-radius:10px;padding:12px 14px;background:#FBFDFC}
.lic .auth{font-weight:700;font-size:14.5px}
.lic .jur{font-size:12.5px;color:var(--slate);margin:2px 0 6px}
.lic .no{font-family:ui-monospace,Menlo,monospace;font-size:12.5px;color:var(--ink2)}
.lic .tier{float:right}

/* kv + reg tables */
table.kv,table.reg{width:100%;border-collapse:collapse;font-size:14.5px}
.kv th{width:220px;text-align:left;color:var(--slate);font-weight:600;padding:9px 10px 9px 0;vertical-align:top}
.kv td{padding:9px 0;border-bottom:1px solid var(--line)}
.kv tr:last-child td{border-bottom:0}
.reg th{font-size:12px;text-transform:uppercase;letter-spacing:.05em;color:var(--slate);text-align:left;padding:8px 10px;border-bottom:2px solid var(--line)}
.reg td{padding:9px 10px;border-bottom:1px solid var(--line);vertical-align:top}

/* red flags severity */
ul.rf{list-style:none;padding-left:0}
.rf-severe{background:var(--neg-bg);border-radius:8px;padding:8px 12px;margin:6px 0}
.rf-severe::before{content:"⛔ ";font-weight:700}
.rf-minor{margin:6px 0;padding:2px 12px}
.rf-minor::before{content:"⚠ ";color:var(--warn)}

/* CTA */
.cta{display:block;text-align:center;background:var(--ink);color:#fff;font-weight:800;font-size:17px;
  border-radius:12px;padding:17px 20px;margin:26px 0 6px;box-shadow:var(--shadow)}
.cta:hover{text-decoration:none;background:var(--ink2)}
.cta .disc{display:block;font-weight:400;font-size:12.5px;color:#BFD0E2;margin-top:4px}
.stamp{font-size:12.5px;color:var(--slate);margin:10px 0 0}
.caveat{font-size:13.5px;color:var(--slate);background:#F0F7F3;border-radius:10px;padding:12px 16px}
.back{font-size:14px;font-weight:600}
.muted{color:var(--slate);font-size:14px}

/* category ranking pages — 2026-08-07 founder order: NO ordinal rank numbers. The old
   .rk CSS counter (counter-reset/increment + ::before content) painted 1,2,3… medals on
   every category/learn list row; removed with the score engine. Tier badges only. */
.catrow{display:flex;gap:18px;align-items:center;background:var(--card);border:1px solid var(--line);
  border-radius:var(--radius);box-shadow:var(--shadow);padding:16px 20px;margin:12px 0;color:var(--ink)}
.catrow:hover{text-decoration:none;border-color:var(--slate2)}
.catrow .bn{font-weight:800;font-size:17px}
.catrow .why{font-size:13.5px;color:var(--slate);margin-top:2px}

/* docs + faq */
/* D06: .doc only ever renders on non-has-side pages (methodology, learn articles, category
   pages) -- main now centers itself there via the base main{margin:0 auto}, but .doc itself had
   no margin, so it sat flush-left inside main's (wider, uncapped) box. margin:0 auto centers the
   readable column in the freed width instead of leaving it lopsided. */
.doc{max-width:780px;margin:0 auto}
.doc h1{font-size:clamp(28px,4vw,38px);margin:26px 0 10px}
.doc h2{font-size:20px;margin:28px 0 8px}
.doc .lead{font-size:18px;color:var(--ink2)}
details.faq{background:var(--card);border:1px solid var(--line);border-radius:10px;
  padding:14px 18px;margin:10px 0;box-shadow:var(--shadow)}
details.faq summary{font-weight:700;cursor:pointer;font-size:15.5px}
details.faq[open] summary{margin-bottom:8px}
table.wts{border-collapse:collapse;font-size:14.5px;margin:10px 0}
.wts th,.wts td{border:1px solid var(--line);padding:8px 14px;text-align:left}
.wts th{background:#F0F7F3;font-size:12.5px;text-transform:uppercase;letter-spacing:.05em;color:var(--slate)}

/* anti-solicitation strip + footer */
.antisolicit{margin:44px 0 0;text-align:center;font-size:13px;color:var(--slate);
  background:var(--card);border:1px dashed var(--line);border-radius:10px;padding:12px 18px}
footer{max-width:1120px;margin:26px auto 0;padding:26px 20px 60px;border-top:1px solid var(--line);
  color:var(--ink2);font-size:14px}
footer .fine{color:var(--slate);font-size:12.5px}

/* ===== v2.1 — motion + color + friendliness (2026-07-02) ===== */
html{scroll-behavior:smooth}
@keyframes fadeup{from{opacity:0;transform:translateY(18px)}to{opacity:1;transform:none}}
/* move 2 (2026-08-27): retokenized, -120ms from the old 600ms hardcode; distance (--m-lift-lg
   18px) is a zero-delta bind to what was already hardcoded here. */
.reveal{opacity:0;transform:translateY(var(--m-lift-lg,18px));
  transition:opacity var(--m-5) var(--e-out),transform var(--m-5) var(--e-out)}
.reveal.in{opacity:1;transform:none}
.card,.rankcard,.catrow,.cats a,.findings .f,.counters .c{transition:transform .18s ease,box-shadow .18s ease,border-color .18s ease}
.card:hover{box-shadow:0 2px 4px rgba(14,27,44,.06),0 14px 34px -14px rgba(14,27,44,.18)}
.rankcard:hover,.cats a:hover,.catrow:hover{transform:translateY(-3px);box-shadow:0 4px 8px rgba(14,27,44,.06),0 18px 38px -14px rgba(14,27,44,.22)}

/* hero wash + buttons */
.hero{position:relative}
.hero::before{content:"";position:absolute;inset:-40px -50vw 0 -50vw;z-index:-1;
  background:radial-gradient(700px 340px at 18% 8%,#E9F7F0 0%,transparent 62%),
             radial-gradient(600px 300px at 85% 20%,#F0FAF3 0%,transparent 60%)}
.hero h1 em{font-style:normal;background:linear-gradient(92deg,var(--pos),var(--info));
  -webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent}
.herobtns{display:flex;gap:12px;margin-top:26px;flex-wrap:wrap}
.btn{display:inline-block;font-weight:800;font-size:15.5px;border-radius:11px;padding:13px 24px;transition:transform .15s ease,box-shadow .15s ease}
.btn:hover{text-decoration:none;transform:translateY(-2px)}
.btn.pri{background:linear-gradient(120deg,var(--pos) 0%,#0B7A56 100%);color:#fff;box-shadow:0 8px 20px -8px rgba(14,159,110,.55)}
.btn.sec{background:var(--card);border:1.5px solid var(--line);color:var(--ink)}

/* colored findings */
.findings .f:nth-child(1){border-left-color:var(--pos);background:linear-gradient(180deg,#FAFEFC,#FFF)}
.findings .f:nth-child(2){border-left-color:#7BC9A4;background:linear-gradient(180deg,#FAFEFC,#FFF)}
.findings .f:nth-child(3){border-left-color:var(--warn);background:linear-gradient(180deg,#FFFDF9,#FFF)}
.findings .f:nth-child(4){border-left-color:var(--pos);background:linear-gradient(180deg,#FAFEFC,#FFF)}

/* friendly category picker */
.cats a{display:flex;gap:14px;align-items:center}
.cats .ic{flex:0 0 44px;height:44px;border-radius:12px;display:flex;align-items:center;justify-content:center;font-size:22px}
.cats a:nth-child(1) .ic{background:#E9F7F0} .cats a:nth-child(2) .ic{background:#DFF3E9}
.cats a:nth-child(3) .ic{background:#F0FAF3} .cats a:nth-child(4) .ic{background:#E6F6EF}
.cats .q{display:block;font-size:12px;font-weight:600;color:var(--slate);text-transform:uppercase;letter-spacing:.05em}

/* top-3 medal tint */
.rankcard.m1{background:linear-gradient(180deg,#FFFBF2,#FFF);border-color:#EAD9AE}
.rankcard.m2{background:linear-gradient(180deg,#F6FAF8,#FFF)}
.rankcard.m3{background:linear-gradient(180deg,#FDF8F4,#FFF)}

/* animated score bars + counters — move 3 (2026-08-27): transform-based fill (was `width`,
   a banned property). Base rule carries the CORRECT resting fraction itself, not just the
   keyframe's `to` state, so a disabled/dropped animation still lands right, never a false-full
   bar. Dropped border-radius on the <i> itself (scaling a rounded rect horizontally ellipsizes
   the end-caps) — the parent .subs .bar already clips it into a pill via overflow:hidden. */
.subs .bar i{width:100%;border-radius:0;transform-origin:left;transform:scaleX(var(--wf,0));
  animation:barfill calc(var(--m-5) * 2) var(--e-out) 1 both}
@keyframes barfill{from{transform:scaleX(0)}to{transform:scaleX(var(--wf,0))}}
.counters .n{color:var(--pos)}

/* gradient CTA */
.cta{background:linear-gradient(120deg,#0E9F6E 0%,#0B7A56 100%);background-size:160% 100%;transition:background-position .35s ease,transform .15s ease}
.cta:hover{background-position:90% 0;transform:translateY(-2px)}

/* broker-page jump chips */
.jumps{display:flex;gap:8px;flex-wrap:wrap;margin:14px 0 4px}
.jumps a{border:1.5px solid var(--line);background:var(--card);border-radius:999px;padding:5px 14px;
  font-size:13px;font-weight:700;color:var(--ink2);transition:all .15s ease}
.jumps a:hover{text-decoration:none;border-color:var(--pos);color:var(--pos);transform:translateY(-1px)}
section[id],h2[id],h3[id],#cta{scroll-margin-top:86px}

/* ===== v3 — white/green + friendliness (2026-07-02) ===== */
.swd{display:inline-block;border-radius:999px;padding:2px 12px;font-size:12.5px;font-weight:800;margin-top:4px}
.swd.pos{background:var(--pos-bg);color:var(--pos-text)} .swd.mid{background:var(--warn-bg);color:var(--warn)} .swd.low{background:var(--neg-bg);color:var(--neg)}
.rankcard .swd{margin-top:6px}
details.morex summary{cursor:pointer;list-style:none;color:var(--ink2)}
details.morex summary::-webkit-details-marker{display:none}
details.morex .rm{color:var(--pos-text);font-weight:800;font-size:13.5px;white-space:nowrap}
details.morex[open] summary .tr{display:none}
details.morex[open] summary .rm .open-label{display:inline}
details.morex p{margin:8px 0 0}
.nav nav a:hover{color:var(--pos);text-decoration:none}
h2.sh{position:relative;padding-left:16px}
h2.sh::before{content:"";position:absolute;left:0;top:6px;bottom:6px;width:5px;border-radius:99px;background:linear-gradient(180deg,var(--pos),#7BC9A4)}

/* ===== v4 — compare + filters + learn (2026-07-02) ===== */
.controls select{border:1.5px solid var(--line);border-radius:9px;padding:9px 12px;font:inherit;font-size:14px;background:var(--card);color:var(--ink2)}
.cmpk{font-size:13px;font-weight:700;color:var(--slate);white-space:nowrap;cursor:pointer}
.cmpk input{accent-color:var(--pos)}
.cmpbar{position:fixed;left:50%;transform:translateX(-50%);bottom:18px;z-index:60;display:flex;gap:16px;align-items:center;
  background:var(--ink);color:#fff;border-radius:999px;padding:10px 12px 10px 22px;box-shadow:0 12px 30px -8px rgba(18,41,29,.45);
  font-size:14px;font-weight:700;max-width:92vw}
.cmpbar .btn{padding:9px 18px;font-size:14px}
.cmptab th{white-space:nowrap}
.cmptab td{vertical-align:top}
/* Same opt-out as .xl below: this side-by-side table is column-constrained by design, and the
   body-level break-word guard was splitting single cell words ("Unverified") mid-word. */
.cmptab th,.cmptab td{overflow-wrap:normal}

/* ===== v5 — broker logos + per-broker brand theming (2026-07-02) ===== */
.blogo-sm{width:22px;height:22px;border-radius:6px;object-fit:contain;vertical-align:middle;margin-right:8px;background:#fff;border:1px solid var(--line);padding:2px}
.blogo-lg{width:52px;height:52px;border-radius:13px;object-fit:contain;background:#fff;border:1px solid var(--line);padding:7px;box-shadow:var(--shadow);flex:0 0 52px}
/* Wide wordmarks (ratio > 1.6:1 — ThinkMarkets 4.45, Vantage 3.48, LiteFinance 3.37, FxPro 3.02,
   HFM 1.78) get a wider box instead of being squeezed into the square one. object-fit:contain
   already preserved the aspect ratio, so the squeeze was really a HEIGHT collapse: a 4.45:1
   wordmark inside 28x28 renders ~6px tall, i.e. an illegible smudge. Letting the box grow
   horizontally lets the same image render at full row height. The .wide class is emitted by
   blogo() from the PNG's measured IHDR dimensions, so it tracks the real assets. */
.blogo-sm.wide{width:auto;max-width:74px;min-width:44px;border-radius:5px;padding:2px 4px}
/* In TABLE rows the logo is the first thing in the first column, so a 74px wordmark next to a
   28px square left every broker name at a different x and the column read as ragged. Giving every
   table logo the SAME 74px box (object-fit:contain keeps each mark's own aspect ratio, squares
   simply centre in it) aligns all 35 names on one edge while wide wordmarks stay full height. */
.cmp td .blogo-sm,.cmp td .blogo-sm.wide{width:74px;max-width:74px;min-width:74px;height:28px;
  object-fit:contain;padding:2px 4px;margin-right:10px}
.blogo-lg.wide{width:auto;max-width:118px;min-width:70px;flex:0 0 auto}
.bhl{display:flex;gap:12px;align-items:center}
.rankcard .bhl{gap:9px;margin-bottom:2px}
.rankcard .blogo-sm{margin-right:0}
.brandstrip{height:5px;border-radius:99px;margin:2px 0 14px;max-width:340px;
  background:linear-gradient(90deg,var(--brand,#0E9F6E) 0%,color-mix(in srgb,var(--brand,#0E9F6E) 25%,transparent) 70%,transparent 100%)}
.broker .card.verdict{border-left-color:var(--brand,#0E9F6E)}
.broker .scorebox{border-top:4px solid var(--brand,#0E9F6E)}
.broker .jumps a:hover{border-color:var(--brand,#0E9F6E);color:var(--brand,#0E9F6E)}
.broker .cta{background:linear-gradient(120deg,color-mix(in srgb,var(--brand,#0E9F6E) 80%,#083) 0%,#0B7A56 130%)}
.cmptab th img{margin-right:6px}

/* ===== v6 — simplest look: left jump bar + regulator chips (2026-07-02) ===== */
.side{display:none}
@media(min-width:1280px){
  .side{display:flex;flex-direction:column;gap:2px;position:fixed;left:18px;top:130px;width:168px;z-index:40;
    background:var(--card);border:1px solid var(--line);border-radius:var(--radius);box-shadow:var(--shadow);padding:12px}
  .side a{color:var(--ink2);font-size:13.5px;font-weight:600;padding:7px 10px;border-radius:8px}
  .side a:hover{background:var(--info-bg);color:var(--pos);text-decoration:none}
  .side .sidehead{display:flex;gap:8px;align-items:center;font-size:14px;padding:2px 10px 10px;border-bottom:1px solid var(--line);margin-bottom:6px}
  .side .sidehead .blogo-sm{margin-right:0}
  .side .sideback{margin-top:6px;border-top:1px solid var(--line);padding-top:10px;color:var(--slate);font-size:12.5px}
}
@media(min-width:1500px){ main{max-width:1120px} }
/* D06: scoped to body.has-side (see the other 4 main-margin-left rules at style.css ~646/688/
   690/750 for the full explanation) -- pages with no .side aside (methodology, learn, compare,
   category pages) must not get this pushed right with nothing to clear. */
@media(min-width:1280px) and (max-width:1499px){ body.has-side main{max-width:1000px;margin-left:210px} }
.licchips .badge{font-size:13.5px;padding:6px 15px}
.kv details.morex p{font-size:13.5px;color:var(--ink2)}
.caveat details.morex{display:inline}

/* ===== v7 — plug&play cards, Trustpilot carousel, blog Learn, whole-site type pass (2026-07-02) ===== */
/* typography normalization */
body{font-size:16px}
.card{padding:20px 22px}
.card h2{font-size:17.5px;margin:0 0 10px}
.sh{font-size:22px}
.hero h1{font-size:clamp(32px,4.6vw,48px)}
.hero .lead{font-size:18px}
html[lang=th] h1,html[lang=th] h2,html[lang=th] h3{letter-spacing:0}
html[lang=th] body{line-height:1.75}
.badge{vertical-align:middle}
.counters .c{flex:1 1 150px;text-align:center}
.rankgrid{grid-auto-rows:1fr}
.footer,footer{text-align:center}

/* plug&play hero card */
.hero-card{border-top:5px solid var(--brand,#0E9F6E);padding:24px 26px}
.hero-card .badges{margin:8px 0 6px}
.hero-card .licchips{margin-top:2px}
.hero-card .licchips .badge{font-size:12px;padding:4px 12px}
.qfgrid{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:10px;margin:18px 0 4px}
.qf{background:var(--paper);border:1px solid var(--line);border-radius:11px;padding:10px 14px}
.qf .qfl{display:block;font-size:11.5px;font-weight:700;color:var(--slate);text-transform:uppercase;letter-spacing:.04em}
.qf b{font-size:15.5px}
.hero-card .cta{margin:16px 0 0}

/* sticky mobile CTA */
.stickycta{display:none}
@media(max-width:760px){
  .stickycta{display:block;position:fixed;left:10px;right:10px;bottom:10px;z-index:70}
  .stickycta .cta{margin:0;padding:14px;font-size:15.5px;box-shadow:0 10px 30px -6px rgba(18,41,29,.4)}
  .stickycta .cta .disc{display:none}
  main{padding-bottom:110px}
}

/* Trustpilot carousel (static fallback — brokers with no fresh _tp_reviews.json data) */
.tprate{color:#00B67A;font-weight:800;font-size:15px;margin-left:8px}
.tprow{display:flex;gap:12px;overflow-x:auto;scroll-snap-type:x mandatory;padding:4px 2px 8px;-webkit-overflow-scrolling:touch}
.tpcard{min-width:270px;max-width:300px;flex:0 0 auto;scroll-snap-align:start;background:var(--paper);
  border:1px solid var(--line);border-radius:12px;padding:14px 16px}
.tpcard p{margin:6px 0;font-size:14px;line-height:1.55;color:var(--ink2)}
.tpstars{color:#00B67A;letter-spacing:2px;font-size:14px}
.tpwho{font-size:12px;color:var(--slate);font-weight:600}

/* Trustpilot LOOP MARQUEE (2026-07-03) — 30-50 real reviews, continuous scroll left→right loop */
.tpmarquee{overflow:hidden;position:relative;margin:4px 0 8px;
  -webkit-mask-image:linear-gradient(90deg,transparent,#000 28px,#000 calc(100% - 28px),transparent);
  mask-image:linear-gradient(90deg,transparent,#000 28px,#000 calc(100% - 28px),transparent)}
.tpmtrack{display:flex;width:max-content;padding:4px 0 8px;animation:tpscroll var(--tpdur,60s) linear infinite}
.tpmarquee:hover .tpmtrack,.tpmarquee:focus-within .tpmtrack{animation-play-state:paused}
.tpmset{display:contents}
@keyframes tpscroll{from{transform:translateX(0)}to{transform:translateX(-50%)}}
.tpmcard{min-width:270px;max-width:300px;flex:0 0 auto;margin-right:12px;background:var(--paper);
  border:1px solid var(--line);border-radius:12px;padding:14px 16px}
.tpmcard p{margin:6px 0;font-size:14px;line-height:1.55;color:var(--ink2);
  display:-webkit-box;-webkit-line-clamp:5;-webkit-box-orient:vertical;overflow:hidden}
/* Learn blog grid */
.ltabs{display:flex;gap:8px;flex-wrap:wrap;margin:6px 0 18px}
.ltab{border:1.5px solid var(--line);background:var(--card);border-radius:999px;padding:7px 18px;
  font:600 14px Inter,"Noto Sans Thai",sans-serif;color:var(--ink2);cursor:pointer;transition:all .15s ease}
.ltab.on,.ltab:hover{background:var(--pos-text);border-color:var(--pos-text);color:#fff}
.lgrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:16px;margin-bottom:40px}
.lcard{background:var(--card);border:1px solid var(--line);border-radius:var(--radius);box-shadow:var(--shadow);
  overflow:hidden;color:var(--ink);display:flex;flex-direction:column;transition:transform .15s ease,box-shadow .15s ease}
.lcard:hover{text-decoration:none;transform:translateY(-3px);box-shadow:0 14px 34px -12px rgba(18,41,29,.22)}
.lthumb{width:100%;height:150px;object-fit:cover;display:block}
.lbody{padding:14px 16px 16px}
.lbody b{font-size:15.5px;line-height:1.4;display:block;margin:6px 0 4px}
.lbody p{margin:0;font-size:13px;color:var(--slate)}
.lcat{font-size:11px;font-weight:800;text-transform:uppercase;letter-spacing:.06em;color:var(--pos-text)}
.lsoon{opacity:.65;border-style:dashed;justify-content:center;min-height:120px}

/* ===== v8 — readability + positive-only (2026-07-02) ===== */
/* uniform readable CTA — per-broker brand gradients made light-on-light text (Exness yellow) */
.broker .cta{background:linear-gradient(120deg,#0E9F6E 0%,#0B7A56 100%)}
.tpwho{color:var(--slate);font-size:12.5px}
.qf .qfl{font-size:12px}
.muted{font-size:14.5px}
footer .fine{font-size:13px}
.lbody p{font-size:13.5px}
.cmp td{line-height:1.5}
.side a{line-height:1.4}
.cta .disc{color:#D7EFE4}

/* ===== v9 — heroic hero, marquee, steps, one-line rows, mobile pass (2026-07-02) ===== */
/* one-line broker names + compact chips in the table */
.cmp td:first-child{white-space:nowrap}
.cmp .licchips{flex-wrap:nowrap}
.cmp .licchips .badge{font-size:11.5px;padding:3px 10px}
.cmp tbody tr:nth-child(even) td{background:#FAFCFB}

/* heroic dark hero */
.hero.herodark{position:relative;max-width:none;margin:26px -20px 0;padding:74px 40px 66px;border-radius:22px;
  background:linear-gradient(135deg,#0B2E20 0%,#0E3A2A 55%,#0A1F33 130%);overflow:hidden;color:#fff}
.hero.herodark::before{display:none}
.hero.herodark::after{content:"";position:absolute;inset:0;pointer-events:none;
  background:radial-gradient(700px 400px at 82% 30%,rgba(14,159,110,.28),transparent 65%)}
.heroin{position:relative;z-index:2;max-width:640px}
.herodark h1{color:#fff;font-size:clamp(34px,5vw,56px);margin:0 0 14px}
.herodark h1 em{background:linear-gradient(92deg,#5EEAB4,#A7F3D0);-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent}
.herodark .lead{color:#C9E7D9;font-size:18px}
.herodark .counters .c{background:rgba(255,255,255,.07);border-color:rgba(255,255,255,.14);backdrop-filter:blur(6px)}
.herodark .counters .n{color:#5EEAB4}
.herodark .counters .l{color:#A9CBBB}
.btn.hsec{background:rgba(255,255,255,.1);border:1.5px solid rgba(255,255,255,.3);color:#fff}
.herochart{position:absolute;right:-30px;bottom:-12px;width:56%;max-width:640px;z-index:1;opacity:.9}
.hgrid{stroke:rgba(255,255,255,.08);stroke-width:1}
.hline{stroke:#5EEAB4;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;
  stroke-dasharray:1;stroke-dashoffset:1;animation:hdraw 2.4s cubic-bezier(.3,.7,.3,1) .3s forwards;
  filter:drop-shadow(0 0 8px rgba(94,234,180,.55))}
@keyframes hdraw{to{stroke-dashoffset:0}}
/* hpulse (2026-08-27): rewritten from an `r`-attribute animation (not compositor-only) to
   transform:scale — also converts an infinite loop to bounded (3 iterations), freeing a slot in
   the hero's infinite-loop budget. 1.43x approximates the original r:7->r:10 ratio (10/7). */
.hdot{fill:#5EEAB4;opacity:0;transform-box:fill-box;transform-origin:center;
  animation:hpop .5s ease 2.6s forwards,hpulse 2s ease 3.1s 3 forwards}
@keyframes hpop{to{opacity:1}}
@keyframes hpulse{0%,100%{transform:scale(1)}50%{transform:scale(1.43)}}

/* broker logo marquee */
.mq{overflow:hidden;margin:18px 0 6px;padding:14px 0;border-bottom:1px solid var(--line);
  -webkit-mask-image:linear-gradient(90deg,transparent,#000 8%,#000 92%,transparent);
  mask-image:linear-gradient(90deg,transparent,#000 8%,#000 92%,transparent)}
.mq-in{display:flex;gap:42px;align-items:center;width:max-content;animation:mq 38s linear infinite}
.mq-in img{width:30px;height:30px;object-fit:contain;border-radius:8px;background:#fff;border:1px solid var(--line);padding:3px}
@keyframes mq{to{transform:translateX(-50%)}}
.mq:hover .mq-in{animation-play-state:paused}

/* 3 steps */
.steps3{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:14px;margin:14px 0 8px}
.step{background:var(--card);border:1px solid var(--line);border-radius:var(--radius);box-shadow:var(--shadow);padding:18px 20px;position:relative}
.stepn{display:inline-flex;width:30px;height:30px;border-radius:999px;background:var(--pos);color:#fff;font-weight:800;
  align-items:center;justify-content:center;margin-bottom:8px}
.step b{display:block;font-size:16px}
.step p{margin:4px 0 0;font-size:13.5px;color:var(--slate)}

/* mobile / tablet pass */
@media(max-width:900px){
  .herochart{opacity:.35;width:80%}
}
@media(max-width:700px){
  .hero.herodark{margin:14px -12px 0;padding:46px 22px 44px;border-radius:16px}
  .herodark h1{font-size:31px}
  .herodark .lead{font-size:15.5px}
  .herobtns .btn{flex:1;text-align:center;padding:13px 10px}
  .counters .c{padding:12px 14px;min-width:0}
  .counters .n{font-size:22px}
  .cmp th:nth-child(3),.cmp td:nth-child(3),.cmp th:nth-child(4),.cmp td:nth-child(4){display:none}
  table.cmp{min-width:540px}
  .qfgrid{grid-template-columns:1fr 1fr}
  .cmpk input{width:22px;height:22px}
  .jumps{overflow-x:auto;flex-wrap:nowrap;padding-bottom:4px}
  .jumps a{white-space:nowrap}
  .tablewrap{padding:12px}
  .lgrid{grid-template-columns:1fr}
  main{padding:0 14px 72px}
}
/* ===== v10 — bigger cards, dropdown filters, update clock (2026-07-02) ===== */
.findings .f{padding:26px 28px}
.findings h3{font-size:19px;margin-bottom:8px}
.findings p{font-size:15.5px;line-height:1.6}
.cats a{padding:24px 26px;font-size:17px}
.cats .ic{flex:0 0 54px;height:54px;font-size:26px;border-radius:14px}
.cats .q{font-size:13px}
.cats .sub{font-size:14.5px;margin-top:6px}
.step{padding:24px 26px}
.step b{font-size:18px}
.step p{font-size:15px}
.counters .n{font-size:34px}
.counters .l{font-size:13.5px}
details.faq{padding:18px 22px}
details.faq summary{font-size:16.5px}
.preview-flag .updclock{font-weight:700}

/* ===== v11 — table-row hover expansion, all 35 rows (founder-approved mockup v3) ===== */
.cmp tbody.rowgrp{border-bottom:1px solid var(--line)}
.cmp tbody.rowgrp:nth-of-type(even) tr.base td{background:#FAFCFB}
.cmp tbody.rowgrp tr.base td{border-bottom:0;transition:background .2s}
.cmp tbody.rowgrp:hover tr.base td{background:#F4FBF7}
.cmp tr.exptr td{padding:0;border-bottom:0;background:#F7FCF9}
/* v11's max-height/opacity/overflow/transition here were dead landmine code — harmless only
   because v16.1 (!important, below) neutralizes them; deleted 2026-08-27 per motion spec §4
   item 6 so a future v16.1 edit can't silently resurrect the 23.6ms-jank interpolated-height
   mechanic. border-left is the one live property (nothing else sets it) — kept, standalone. */
.expwrap{border-left:4px solid var(--bc,#0E9F6E)}
.expin{display:grid;grid-template-columns:280px minmax(0,1fr) 185px;gap:22px;align-items:center;padding:8px 16px 18px}
.expin .mbar{font-size:11.5px;color:var(--slate);font-weight:600;margin:5px 0}
.expin .mbar em{display:block;height:6px;border-radius:99px;background:#E7F1EB;margin-top:2px}
.expin .mbar i{display:block;height:100%;border-radius:99px;background:var(--bc,#0E9F6E);min-width:6px;filter:saturate(.85) brightness(.82)}
.tfacts{display:flex;gap:8px 18px;font-size:13px;font-weight:700;flex-wrap:wrap;margin-bottom:8px}
.trate{color:#00B67A}
.tquote{font-size:12.5px;color:var(--ink2);background:#EFF9F3;border-radius:10px;padding:9px 12px;margin:0}
.tcta{display:block;text-align:center;background:linear-gradient(120deg,#0E9F6E,#0B7A56);color:#fff;font-weight:800;
  border-radius:10px;padding:13px 10px;text-decoration:none;font-size:14px}
.tcta:hover{text-decoration:none;transform:translateY(-1px)}
@media(max-width:900px){
  .expin{grid-template-columns:1fr;gap:10px;padding:6px 14px 16px}
}

/* ===== v12 — overlap fix, REAL font bump, bigger sidebar, more color (2026-07-02) ===== */
/* 1 · expanded-row overlap fix: columns can no longer bleed under the CTA */
@media(min-width:901px){
  .expin{grid-template-columns:270px minmax(0,1fr) 190px}
}
.expin .tcol{min-width:0}
.tquote{overflow-wrap:anywhere;white-space:normal}

/* 2 · site-wide font scale bump */
body{font-size:17px}
table.cmp{font-size:16px}
.cmp td{padding:15px 14px}
.cmp th{font-size:13px}
.cmp .scnum{font-size:19px}
.badge{font-size:13.5px;padding:4px 13px}
.licchips .badge{font-size:13px;padding:5px 14px}
.cmp .licchips .badge{font-size:12.5px;padding:4px 12px}
.card h2{font-size:20px}
.sh{font-size:26px}
.shsub{font-size:16px}
.hero .lead,.herodark .lead{font-size:20px}
.findings h3{font-size:20px}
.findings p{font-size:16px}
.cats a{font-size:18px}
.cats .sub{font-size:15.5px}
.cats .q{font-size:13.5px}
.step b{font-size:19px}
.step p{font-size:16px}
.counters .n{font-size:38px}
.counters .l{font-size:14.5px}
.rankcard .bn{font-size:18px}
.rankcard .sc{font-size:30px}
.rankcard .swd,.swd{font-size:13.5px}
.rankcard .meta{font-size:14px}
.controls input,.controls select{font-size:15.5px;padding:11px 15px}
details.faq summary{font-size:17.5px}
details.faq p{font-size:16px}
.jumps a{font-size:14.5px;padding:7px 16px}
.qf b{font-size:17.5px}
.qf .qfl{font-size:12.5px}
.scorebox .big{font-size:48px}
.subs .s{font-size:13px}
.muted{font-size:15.5px}
.expin .mbar{font-size:13px}
.tfacts{font-size:15px}
.tquote{font-size:14px}
.tcta{font-size:15.5px;padding:15px 12px}
.lbody b{font-size:17px}
.lbody p{font-size:14.5px}
footer{font-size:15px}

/* 4 · bigger left sidebar */
@media(min-width:1280px){
  .side{width:216px;padding:16px;gap:4px}
  .side a{font-size:15.5px;padding:10px 12px}
  .side .sidehead{font-size:16.5px;padding:4px 12px 12px}
}
@media(min-width:1280px) and (max-width:1499px){ body.has-side main{margin-left:250px} }

/* 5 · more color — kill the "too white" */
body{background:linear-gradient(180deg,#EAF7EF 0%,#F5FAFF 340px,#F8FCFA 700px)}
.preview-flag{background:#0B2E20;color:#CDEBDC;border-bottom:0;font-weight:600}
.preview-flag .updclock{color:#5EEAB4}
.cmp thead th{background:#0B2E20;color:#EDF7F1;padding:13px 14px}
.cmp thead th:hover{color:#5EEAB4}
.tablewrap{border-top:5px solid #0E9F6E}
.steps3{background:linear-gradient(120deg,#E9F7F0,#E8F1FD);border-radius:18px;padding:20px}
.step{border:0;box-shadow:0 6px 18px -8px rgba(18,41,29,.15)}
.step:nth-child(1) .stepn{background:#0E9F6E}
.step:nth-child(2) .stepn{background:#1D4ED8}
.step:nth-child(3) .stepn{background:#B45309}
.cats a{border-top:4px solid #0E9F6E}
.cats a:nth-child(2){border-top-color:#1D4ED8}
.cats a:nth-child(3){border-top-color:#B45309}
.cats a:nth-child(4){border-top-color:#7C3AED}
.findings .f{box-shadow:0 6px 18px -8px rgba(18,41,29,.12)}
.findings .f:nth-child(1){background:linear-gradient(180deg,#EAF7F0,#FFF)}
.findings .f:nth-child(2){background:linear-gradient(180deg,#EAF0FD,#FFF);border-left-color:#1D4ED8}
.findings .f:nth-child(3){background:linear-gradient(180deg,#FDF3E7,#FFF);border-left-color:#B45309}
.findings .f:nth-child(4){background:linear-gradient(180deg,#F3EDFD,#FFF);border-left-color:#7C3AED}
.counters .c:nth-child(1) .n{color:#0E9F6E}
.counters .c:nth-child(2) .n{color:#5EEAB4}
.counters .c:nth-child(3) .n{color:#FBBF24}
footer{background:#0B2E20;color:#C9E7D9;border-top:0;max-width:none;padding:34px 20px 64px}
footer p{max-width:1080px;margin:0 auto 10px}
footer .fine{color:#7FA795}
.antisolicit{background:#EDF5F0;border-color:#CDE5D6}
.mq{background:#fff;border-radius:14px;border:1px solid var(--line);margin:20px 0 10px;
  -webkit-mask-image:none;mask-image:none}
/* v12.1 — keep expanded panel inside the visible area (td spans the full scrollable table width) */
.expwrap{position:sticky;left:0;max-width:1062px}
@media(max-width:1140px){.expwrap{max-width:calc(100vw - 76px)}}
/* v12.2 — panel width follows the actual visible table container (container units) */
.tablewrap{container-type:inline-size}
.expwrap{max-width:calc(100cqw - 38px)}

/* ===== v13 — BIG fonts for real + nothing out of frame (2026-07-02) ===== */
/* layout: use the whole screen, sidebar + content never collide, table never clips */
@media(min-width:1280px){
  body.has-side main{margin-left:266px;max-width:min(1420px,calc(100vw - 306px))}
}
@media(min-width:1800px){ body.has-side main{margin-left:calc((100vw - 1420px)/2 + 120px)} }
table.cmp{min-width:0;width:100%;table-layout:auto}
@media(max-width:900px){ table.cmp{min-width:680px} }
.cmp td.act{white-space:nowrap}
.cmp td.act .cmpk{margin-left:12px}
.nav{padding-right:34px}

/* BIG type */
body{font-size:19px}
table.cmp{font-size:18px}
.cmp td{padding:18px 16px}
.cmp th{font-size:14.5px;padding:15px 16px}
.cmp td a strong{font-size:19px}
.cmp .scnum{font-size:23px}
.badge{font-size:15px;padding:5px 15px}
.cmp .licchips .badge{font-size:14px;padding:5px 14px}
.blogo-sm{width:28px;height:28px}
.rev{font-size:17px}
.card h2{font-size:23px}
.sh{font-size:31px}
.shsub{font-size:17.5px}
.hero .lead,.herodark .lead{font-size:22px}
.herodark h1{font-size:clamp(40px,5.4vw,64px)}
.findings h3{font-size:22px}
.findings p{font-size:17px}
.cats a{font-size:20px}
.cats .sub{font-size:16.5px}
.cats .q{font-size:14px}
.step b{font-size:21px}
.step p{font-size:17px}
.counters .n{font-size:44px}
.counters .l{font-size:15.5px}
.rankcard .bn{font-size:19px}
.rankcard .sc{font-size:34px}
.rankcard .meta{font-size:15px}
.swd{font-size:14.5px;padding:3px 14px}
.controls input,.controls select{font-size:17px;padding:13px 17px}
details.faq summary{font-size:19px}
details.faq p{font-size:17px}
.qf b{font-size:19px}
.qf .qfl{font-size:13.5px}
.scorebox .big{font-size:54px}
.subs .s{font-size:14px}
.expin .mbar{font-size:14.5px}
.tfacts{font-size:16.5px}
.tquote{font-size:15.5px}
.tcta{font-size:17px;padding:17px 14px}
.side a{font-size:16.5px;padding:11px 13px}
.side .sidehead{font-size:17.5px}
.lbody b{font-size:18px}
.lbody p{font-size:15.5px}
.ltab{font-size:15.5px;padding:9px 20px}
.btn{font-size:17px;padding:15px 28px}
.cta{font-size:19px;padding:19px 22px}
.crumb{font-size:14.5px}
.stamp{font-size:14px}
.antisolicit{font-size:14.5px}
footer{font-size:16px}
footer .fine{font-size:14px}
/* v13.1 — reclaim horizontal room so the actions column never clips */
/* D06 (P1, 2026-08-06): this is the LAST-declared main{margin-left/max-width} rule in the
   cascade -- equal specificity + no upper media bound means it silently overrode every other
   main{margin-left} rule above (377/649/691/693) at every width >=1280, regardless of THEIR
   own narrower media ranges. That made it, in practice, the only one of the five that mattered
   -- and the one most responsible for D06's dead gutter on pages with no .side to clear. Scoped
   here like the other four so a non-has-side page (methodology, learn, compare, category pages)
   gets none of them and just centers via the base main{margin:0 auto} (style.css:31). */
@media(min-width:1280px){ body.has-side main{margin-left:252px;max-width:min(1460px,calc(100vw - 286px))} }
.cmp td{padding:18px 11px}
.cmp th{padding:15px 11px}
.cmp .licchips .badge{font-size:13px;padding:4px 11px;margin-right:3px}
.cmp td.act{padding-right:6px}
.cmp td.act .cmpk{margin-left:8px}
.rev{font-size:16px}
.tablewrap{padding:18px 14px}

/* ===== v15 — Top-10 strict 5×2 + living hero (2026-07-02) ===== */
/* 1 · exactly 5 per row, 2 rows */
.rankgrid{grid-template-columns:repeat(5,minmax(0,1fr))}
@media(max-width:1100px){.rankgrid{grid-template-columns:repeat(3,minmax(0,1fr))}}
@media(max-width:700px){.rankgrid{grid-template-columns:repeat(2,minmax(0,1fr))}}
.rankcard .bn{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;display:block;max-width:100%}
.rankcard .bhl{min-width:0}

/* 2 · hero in motion (all reduced-motion safe) */
/* heroglow deleted 2026-08-27 (task deletion list: pagein/floaty/heroglow) — was already dead
   cascade-wise (v15.3's .herodark::after{animation:none;...} below overrides it), same shape as
   the lineglow orphan the spec itself caught; deleting the source, not leaving an orphan. */
/* shimmer deleted 2026-08-27 (motion spec §4 item 4, cut not converted): the hero already runs
   3 concurrent one-shot arrivals on load (line-draw, dot-pop, counter-rise) + 1 ambient loop
   (glowfade) — a 4th competing moment is the "overdesigned" failure mode. Gradient-clipped
   headline text stays, just static — no motion lost visually, the gradient is still there. */
.herodark h1 em{background:linear-gradient(92deg,#5EEAB4,#A7F3D0,#5EEAB4);background-size:220% 100%;
  -webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent}
.hline{animation:hdraw 2.4s cubic-bezier(.3,.7,.3,1) .3s forwards,lineglow 3.2s ease-in-out 3s infinite alternate}
/* floaty deleted 2026-08-27 (motion spec §4 item 2) — superseded by move 2's .reveal one-shot
   rise; .herodark .counters .c is already in the .reveal querySelectorAll list, no JS change
   needed. */
.herochart{animation:chartdrift 12s ease-in-out infinite alternate}
@keyframes chartdrift{from{transform:translateY(0)}to{transform:translateY(-14px)}}

/* ===== v15.3 — REALLY smooth: eliminate paint storms + hover intent (2026-07-02) ===== */
/* hero glow: was animating `background` (full-panel repaint every frame) → two static layers crossfading opacity (compositor-only) */
.herodark::after{animation:none;background:radial-gradient(700px 400px at 82% 30%,rgba(14,159,110,.28),transparent 65%)}
.hero.herodark::before{content:"";display:block;position:absolute;inset:0;z-index:0;pointer-events:none;
  background:radial-gradient(760px 430px at 70% 55%,rgba(94,234,180,.20),transparent 65%),
             radial-gradient(500px 300px at 30% 80%,rgba(29,78,216,.15),transparent 60%);
  opacity:0;animation:glowfade 8s ease-in-out infinite alternate}
@keyframes glowfade{to{opacity:1}}
/* chart glow: was animating drop-shadow filter (expensive repaint) → static glow, pulse only the dot */
.hline{animation:hdraw 2.4s cubic-bezier(.3,.7,.3,1) .3s forwards;filter:drop-shadow(0 0 10px rgba(94,234,180,.6))}
/* row expansion: faster ease-out + hover-intent delay so skimming never triggers, GPU-pinned */
.cmp tbody.rowgrp .base td{transition:background .15s ease}
/* don't let card hover-shadows repaint during table interaction */
.tablewrap:hover{box-shadow:var(--shadow)}


/* ===== v16 — HeadOfCX ruling: CLICK accordion, snap-height + compositor fade (2026-07-02) =====
   DO NOT re-add an interpolated height/max-height transition here: the rAF probe measured
   per-frame table reflow at 23.6ms mean / 80 jank frames. Any change to this mechanic must
   re-run site/_probe.html in a real browser before merging. */
.cmp tbody.rowgrp .base{cursor:pointer}
.cmp tbody.rowgrp:hover .base td{background:#F4FBF7}
.rowtoggle{background:none;border:0;padding:4px 6px;cursor:pointer;color:var(--slate);vertical-align:middle;
  transform:rotate(0deg);transition:transform .18s ease}
.cmp tbody.rowgrp.open .rowtoggle{transform:rotate(180deg);color:var(--pos)}
.rowtoggle:focus-visible{outline:2px solid var(--bc,#0E9F6E);outline-offset:2px;border-radius:4px}
.expwrap{display:grid;grid-template-rows:0fr;overflow:hidden;transition:none;position:sticky;left:0;max-width:calc(100cqw - 38px)}
.cmp tbody.rowgrp.open .expwrap{grid-template-rows:1fr}
.expwrap>.expin{min-height:0;overflow:hidden;opacity:0;transform:translateY(-6px);
  transition:opacity .18s ease,transform .18s ease}
.cmp tbody.rowgrp.open .expwrap>.expin{opacity:1;transform:none}
.cmp tr.exptr td{background:transparent}
.cmp tbody.rowgrp.open tr.exptr td{background:#F7FCF9}
.cmp td.act .rowcta{display:inline-block;margin-left:8px;padding:7px 14px;font-size:14px;font-weight:800;
  border-radius:8px;background:linear-gradient(120deg,#0E9F6E,#0B7A56);color:#fff;text-decoration:none}
.cmp td.act .rowcta:hover{text-decoration:none;transform:translateY(-1px)}
/* v16.1 — neutralize v11 base clamps (max-height/opacity) superseded by grid-rows mechanism */
.expwrap{max-height:none !important;opacity:1}
/* v16.2 — collapsing element must carry ZERO vertical padding (it can't shrink below it) */
.expwrap>.expin{padding-top:0 !important;padding-bottom:0 !important}

/* ===== v17 — HOVER expands (founder's true intent), keeping the jank-free snap+fade mechanics =====
   Desktop hover-capable devices: mouseover opens the push-down panel (snap height, fade content).
   Click/chevron still pins open (and is the path for touch devices). */
@media (hover:hover){
  .cmp tbody.rowgrp:hover .expwrap{grid-template-rows:1fr}
  .cmp tbody.rowgrp:hover .expwrap>.expin{opacity:1;transform:none;transition-delay:.06s,.06s}
  .cmp tbody.rowgrp:hover tr.exptr td{background:#F7FCF9}
}

/* ===== v18 — Excel-style master comparison table on compare page ===== */
/* v19: fits the screen — no sideways scroll box (founder order); page-scoped
   main override in compare.html removes the index sidebar gutter */
.xlwrap{overflow:auto;max-height:none;background:var(--card);border:1px solid var(--line);border-radius:var(--radius);
  box-shadow:var(--shadow);border-top:5px solid #0E9F6E;width:100%}
table.xl{table-layout:fixed;width:100%;min-width:1100px;border-collapse:separate;border-spacing:0}
.xl td,.xl thead th{padding:12px 8px;font-size:15px}
.xl thead th{font-size:11.5px;white-space:normal;letter-spacing:.02em;padding:12px 6px}
/* .xl opts OUT of the body-level break-word guard. Its columns are deliberately fixed-percent
   and deliberately tight, so a word wider than its column is the normal case here, not an
   accident: with break-word the headers split mid-word ("POPULARIT/Y", "MIN DEPOSI/T") and
   cells split "unknown"/"Unverified" at 1440. Overhang is already handled per column below
   (ellipsis / line-clamp / overflow:hidden) and the table scrolls inside .tablewrap, so it
   never reaches the page scroll box. The explicit `overflow-wrap:anywhere` rules further down
   are more specific and still win where they were intentionally applied. */
.xl thead th,.xl td{overflow-wrap:normal}
/* D1 fix (2026-07-05, my v5-scoring regression — see FIX_QUEUE): columns were sized for
   pre-v5 short scores ("8.4") + short verdict words. v5's 2-decimal scores ("8.85") and the
   real verdict-word set (up to "Read closely first" / TH "ควรศึกษาเพิ่มเติม") measured wider
   than the Score/Verdict columns (real getBoundingClientRect: score content ~57px + Read-
   closely-first ~140px, vs old 53.5px/77.3px column boxes) — silently clipped by the blanket
   `.xl td{overflow:hidden}` below with no ellipsis (unlike every other column, which already
   degrades gracefully via ellipsis/line-clamp/wrap). Score 4.5%->7%, Verdict 6.5%->11% (fits
   "Good pick"/"Worth a look" on one line at every tested breakpoint: 901/1024/1280 EN+TH); the
   rarer long words fall back to wrap (below), never a hard clip. Difference reclaimed from
   columns that already have a graceful-degradation path (ellipsis/line-clamp/wrap), so nothing
   that was previously clipping-free becomes clipped: Broker 13.5%->12% (already ellipsis-
   truncates its one over-long name), Regulation 13%->12% (chips already flex-wrap), Leverage
   8%->7.5% (short_v() already self-truncates with its own "…"), Min deposit 5%->4.5% (already
   wraps, wave2c item 6), Spreads 12.5%->10.5% / Platforms 11%->9.5% (both already 2-line-clamp
   with an auto-ellipsis). Popularity/Support/Trustpilot/Action untouched (no slack — verified
   via matching real content-width to column-width; they were already at zero margin). Total
   still sums to exactly 100%. table min-width 1040->1100 gives every column a little more
   absolute room at the 901-1039px floor (where .xlwrap already scrolls sideways by design). */
/* D08 (P2, 2026-08-06): the previous wave's note above says Broker "already ellipsis-truncates
   its ONE over-long name" -- measured fresh at a real 1280px render (canvas measureText against
   the live cell's actual computed font, 700 19px Inter, not assumed), 29 of 33 names truncate,
   not one: the fixed-width logo (74px, style.css .cmp td .blogo-sm) + its 8px margin + 16px cell
   padding already consume 98px of the old 142.8px (12% of 1190px table) column, leaving ~45px
   for text -- less than even "Exness" (68px). Widened Broker 12%->20% (238px -> fits Pepperstone/
   ThinkMarkets/CMC Markets/IUX Markets and everything shorter; "EBC Financial Group" at 189px
   text + 98px chrome = 287px still won't fit and stays ellipsized -- accepted per spec).
   First pass took the 8% evenly from Regulation/Leverage/Spreads/Platforms (-3/-1/-2/-2) --
   re-rendered and checked every cell's scrollHeight vs clientHeight (not just eyeballed) and
   Leverage came back TRUE: "1:Unlimited conditional" was hard-clipping mid-character with no
   ellipsis ("1:Unlimite" / "conditiona", missing text) because .xl td{overflow:hidden} clips
   multi-line overflow with no ellipsis affordance, unlike .cl2's -webkit-line-clamp -- an actual
   regression, not a cosmetic one. Leverage restored to its original 7.5% (0 contribution).
   Regulation absorbs the rest (12%->6%, still zero data-loss -- chips just stack to 3 rows
   instead of wrapping across 2, verified clipping-free the same way). Spreads/Platforms trimmed
   -1% each (not -2%) -- Platforms' mid-word "MetaTrader" break moved to a clean word boundary
   ("MetaTrader" / "4 (MT4),..." ) as a result; Spreads' "Standard/Standard" still breaks
   mid-word for the one broker with that unusually long compound value, but confirmed against
   the ORIGINAL (unmodified) screenshot that this exact break already existed before this fix at
   the old 10.5% width -- not a regression, not chased further. */
.xl th:nth-child(1){width:20%}.xl th:nth-child(2){width:7%}.xl th:nth-child(3){width:11%}
.xl th:nth-child(4){width:6.5%}.xl th:nth-child(5){width:6%}.xl th:nth-child(6){width:7.5%}
.xl th:nth-child(7){width:4.5%}.xl th:nth-child(8){width:9.5%}.xl th:nth-child(9){width:8.5%}
.xl th:nth-child(10){width:5.5%}.xl th:nth-child(11){width:7%}.xl th:nth-child(12){width:7%}
.xl .rowcta{padding:8px 10px;font-size:13.5px}
.xl .cl2{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;line-height:1.45}
.xl td .scnum{font-size:24px;font-variant-numeric:tabular-nums}
.xl .rowcta{white-space:nowrap;padding:8px 12px}
.xl td:first-child{overflow:hidden;text-overflow:ellipsis}
.xl .licchips{flex-wrap:wrap;gap:4px}
/* D1: verdict badge no longer force-nowrap — the rare long word (EN "Read closely first",
   TH "ควรศึกษาเพิ่มเติม") wraps to 2 lines inside the pill instead of clipping; the common
   words ("Good pick"/"Worth a look" and their TH equivalents) fit on one line at the new
   11% column width so this essentially never fires in practice. */
.xl .swd{font-size:13px}
.xl td:nth-child(2){overflow:visible}
.xl td:nth-child(3){overflow:visible;white-space:normal;text-align:left}
.xl td:nth-child(3) .swd{white-space:normal;display:inline-block;line-height:1.25}
.xl thead th{position:sticky;top:0;z-index:3;background:#0B2E20;color:#EDF7F1;cursor:pointer}
.xl thead th:first-child{left:0;z-index:4}
.xl td{border-bottom:1px solid var(--line);vertical-align:middle;background:#fff}
.xl tbody tr:nth-child(even) td{background:#FAFCFB}
.xl td:first-child,.xl thead th:first-child{position:sticky;left:0;z-index:2;box-shadow:2px 0 6px -3px rgba(18,41,29,.15)}
.xl td:first-child{white-space:nowrap}
.xl tbody tr:hover td{background:#F4FBF7}
.xl td{overflow:hidden}
/* wave2c item 6: Min deposit column (7th) — DOM string is already short_v()-truncated fine;
   the 5%-wide column + the general .xl td{overflow:hidden} above was clipping unverified/long
   values (e.g. SuperForex's "?") on desktop. Let this one column wrap instead of clip. */
.xl td:nth-child(7){overflow:visible;overflow-wrap:anywhere;white-space:normal}
.byline{color:var(--dim,#8a8f98);font-size:13px;margin:6px 0 14px}
.fundsum{font-size:13.5px;color:var(--dim,#6b7280);margin:2px 0 12px;line-height:1.45}

/* ===== v20 — WAVE-2 (2026-07-04): mobile_spec.md H1+H6+M13+M15 consolidated append,
   applied in the exact relative order the spec requires (H6 tablet/mobile pair
   is cascade-order-dependent; nothing else has an order dependency). ===== */
.xl .clbl{display:none} /* real DOM text, hidden on desktop where the <th> already labels the column */

@media(max-width:900px){
  /* wave2c item 1: 899->900 fix — card layout must cover <=900px (900=card, 901=table),
     matching the SAME 900/901 boundary convention the C1 .expin fix already uses. The
     original 899 cutoff was a genuine off-by-one: at exactly 900px the table fell through
     to the unscoped cramped desktop styling instead of the card layout. .herochart's own
     unrelated max-width:900px rule (different selector, no shared property) is not a real tie. */
  .xlwrap{overflow:visible;background:none;border:0;box-shadow:none;padding:0}
  table.xl{display:block;width:100%;min-width:0;table-layout:auto}
  table.xl thead{display:none}
  table.xl tbody{display:block}
  .xl tr{display:flex;flex-wrap:wrap;align-items:flex-start;
    background:var(--card);border:1px solid var(--line);border-radius:var(--radius);
    box-shadow:var(--shadow);padding:14px 16px 16px;margin:0 0 14px}
  .xl tbody tr:nth-child(even) td,.xl tr:hover td{background:transparent}
  .xl td{display:block;padding:0;border-bottom:0;background:transparent;overflow:visible;font-size:15px}
  .xl td:first-child{flex:1 1 auto;min-width:0;white-space:normal;overflow:visible;
    text-overflow:clip;position:static;box-shadow:none;font-size:16px}
  .xl td:nth-child(2){flex:0 0 auto;padding-left:8px}
  .xl td:nth-child(3){flex:0 0 100%;margin:6px 0 4px}
  .xl td:nth-child(n+4):nth-child(-n+11){flex:0 0 100%;display:flex;justify-content:space-between;
    align-items:baseline;gap:14px;padding:8px 0;border-top:1px solid var(--line)}
  .xl .clbl{display:block;flex:0 0 42%;font-size:11.5px;font-weight:700;color:var(--slate);
    text-transform:uppercase;letter-spacing:.03em}
  .xl .cval{flex:1 1 58%;text-align:right}
  /* D04-CSS-half (P1, 2026-08-06): Spreads/Platforms are the only two columns using .cl2 (both
     multi-line prose, generate.py's spreads_v/platforms_v) -- right-aligning THEIR wrapped text
     made a ragged-left edge, hard to read. Every other stacked-card value (leverage, min
     deposit, support, Trustpilot) is a short single-line value where right-align reads fine and
     stays untouched via .cval above -- .cl2 is a strict subset, so this is scoped by existing
     markup, no new generate.py class needed. */
  .xl .cl2{-webkit-line-clamp:unset;overflow:visible;text-align:left}
  .xl .licchips{justify-content:flex-end;flex-wrap:wrap}
  .xl td.act{order:99;flex:0 0 100%;margin-top:10px;padding:0;border-top:0}
  .xl td.act .rowcta{display:block;width:100%;text-align:center;padding:14px 12px;
    font-size:15px;border-radius:10px}
}

@media(max-width:1279px){
  .side{
    display:flex;flex-direction:row;flex-wrap:nowrap;overflow-x:auto;-webkit-overflow-scrolling:touch;
    position:sticky;top:70px;z-index:45;
    width:auto;left:auto;right:auto;
    gap:8px;background:var(--card);border:0;border-bottom:1px solid var(--line);border-radius:0;
    box-shadow:none;padding:10px 14px;margin:0;
    /* F7 fix (2026-08-06): this jump-nav bar overflows horizontally (scrollWidth 629 vs
       375 viewport at mobile widths) with zero visual affordance that there's more to scroll —
       a reader could easily miss the Weaknesses/Reviews chips sitting off-screen. Right-edge
       fade signals "more tabs" without JS or a scroll-position listener. */
    -webkit-mask-image:linear-gradient(to right,#000 0,#000 calc(100% - 32px),transparent 100%);
    mask-image:linear-gradient(to right,#000 0,#000 calc(100% - 32px),transparent 100%)
  }
  .side .sidehead,.side .sideback{display:none}
  .side a{flex:0 0 auto;white-space:nowrap;border:1.5px solid var(--line);background:var(--paper);
    border-radius:999px;padding:6px 14px;font-size:13px;font-weight:700;color:var(--ink2)}
  .side a:hover{border-color:var(--pos);color:var(--pos);text-decoration:none}
}
@media(max-width:760px){
  #cta .cta{display:none}
}
@media(max-width:700px){
  /* 2026-08-06 anchor-occlusion fix. scroll-margin-top was set equal to .side's top offset,
     which forgot that .side is itself ~53px tall: the real bottom edge of the sticky stack is
     top + height, so every jump-chip target landed with ~53px of its heading tucked underneath.
     Measured with getBoundingClientRect at a real viewport, not estimated: at 390px the stack
     bottom is 203.2px against a 150px scroll-margin, hiding 53.2px of a 77.5px heading.
     Formula below is .side top + .side height + ~11px breathing room, per breakpoint. */
  .side{top:112px}
  section[id],h2[id],h3[id],#cta{scroll-margin-top:176px}  /* 112 + 53 + 11 */
}
@media(max-width:480px){
  /* wave2d item 1: at narrow phone widths (375-390px) the nav wraps to 3 lines
     (brand row + the <nav> element's own 4 items — Brokers/Learn/Compare/lang —
     wrapping onto 2 sub-rows), not 2. Box-model check: .nav padding is 14px top/
     bottom + 24px/34px left/right (style.css:638 override) -> 375-24-34=317px
     available; brand "The FX Verdict" (~160px, 19px/800 Inter) fits its own row,
     but the <nav> row's own content (4 items + 3x18px gaps, ~300px estimated)
     does not comfortably fit 317px and wraps again -> 3 lines, ~127-144px
     (estimate vs. real-render measurement, same order of magnitude). 112px was
     sized for the 2-line case and left the .side.jumps chip row's top edge
     sliced under the taller wrapped nav on scroll. Bump to clear it, same
     language on both EN+TH since the brand text/row-count doesn't differ by lang. */
  .side{top:150px}
  /* 2026-08-06: same off-by-a-whole-chip-row bug as the 700px block above. Measured at a real
     390px viewport (EN nav 102.3px, TH nav 105.6px, .side stuck at top:150px, .side height
     53.2px) the sticky stack ends at 203.2px, so a 150px scroll-margin buried 53.2px of the
     77.5px "All 35 brokers" heading behind the chip row on every #top10/#cats/#all jump. */
  section[id],h2[id],h3[id],#cta{scroll-margin-top:214px}  /* 150 + 53 + 11 */
}
@media(max-width:480px){
  /* Fix 4 (§3e/§5, RED visual bug): the score-metrics widget clipped off-screen at 375px, both
     languages. Root-caused with Playwright computed-style measurement on broker/exness.html at
     a real 375px viewport (not guessed): .scorebox is flex/nowrap with its first child (score
     number block) at an intrinsic ~144px, a 22px gap, and .subs (the 4 metric bars) floored at
     min-width:170px (line 120) — 144+22+170 = 336px of content plus 44px of L/R padding (16px
     22px, line 116) = 380px needed inside a 375px viewport. Measured overflow: .subs right edge
     landed at 400px, ~25px past the viewport edge, consistent with the math. The existing
     @media(max-width:760px) rule (line 137) only widens the outer box — it never touches
     flex-wrap or the two children's floors, so the squeeze survives untouched down to 375px.
     Fix: wrap the two children onto separate rows and drop .subs's floor so it takes the full
     row width instead of squeezing beside the score number. Verified after the fix: all 4
     metric numbers (Withdrawals/Regulation/Funding options/Local support) visible with zero
     horizontal overflow at 375px, EN+TH — see _exec/screens/. */
  .scorebox{flex-wrap:wrap;min-width:0}
  .scorebox>div:first-child{flex:0 0 auto}
  .subs{min-width:0;flex:1 1 100%}
}

/* ===== v20b — WAVE-2 bonus fixes bundled with the touched code paths above:
   H4 keyboard-operable sort headers (mirrors .rowtoggle's focus-visible pattern),
   M9 marquee duplicate-copy aria-hidden wrapper, footer methodology nav link. ===== */
.thsort{all:unset;cursor:pointer;display:block;width:100%;color:inherit;font:inherit;text-align:inherit}
.thsort:hover{color:var(--pos)}
.thsort:focus-visible{outline:2px solid var(--bc,#0E9F6E);outline-offset:2px;border-radius:4px}
.mq-set{display:contents} /* same technique as .tpmset — keeps .mq-in's flex gap/sizing on the real <img> children */
footer .fnav{margin-top:4px}
/* D01 (P0, 2026-08-06): was color:var(--ink2) (#2A4A3A), a LIGHT-bg token used on the dark
   footer (#0B2E20 bg) -> 1.5:1, effectively invisible. Footer's own text color (#C9E7D9,
   style.css:666) measures 11.16:1 on this bg -- reuse it so the link is actually legible. */
footer .fnav a{color:#C9E7D9;font-weight:700}

/* 2026-08-06: the "Partner Pick" chip no longer REPLACES the score, it sits above it, so the
   card discloses the paid placement AND keeps the "always the broker's real score" promise
   literally true. Column layout (not a row) so the extra element can't overflow the card at
   390px, where the rank grid is a single narrow column. */
.rankcard .scwrap{display:flex;flex-direction:column;align-items:flex-end;gap:3px}
.rankcard .ppick{font-size:11.5px;font-weight:800;letter-spacing:.03em;text-transform:uppercase;
  color:var(--pos-text);background:var(--pos-bg);border-radius:999px;padding:3px 9px;text-align:center}
/* D03 chip-slot placeholder (generate.py pairs this with .ppick.ph) -- box stays, pixel stays
   invisible, so cards without the "Partner Pick" chip reserve the exact same vertical slot. */
.rankcard .ppick.ph{visibility:hidden}
/* TH-legibility fix (2026-08-27, motion spec §7 note): uppercase is a no-op for Thai script and
   letter-spacing is a real risk — vowel/tone combining marks can visually detach from their base
   consonant on some text-shaping engines (incl. older Android WebView). Color-only tier signal
   survives; only the EN-only-assumption typography drops for Thai. */
html[lang=th] .rankcard .ppick{letter-spacing:0;text-transform:none}

/* P1 empty state — filtering to zero used to leave a blank void. */
.emptystate{text-align:center;padding:46px 20px;border:1.5px dashed var(--line);border-radius:var(--radius);
  background:var(--card);margin:6px 0 4px}
.emptystate .emptyic{font-size:34px;line-height:1;margin-bottom:10px;opacity:.65}
.emptystate h3{margin:0 0 6px;font-size:19px}
.emptystate p{margin:0 auto 16px;max-width:430px;color:var(--slate);font-size:15px}

/* P2 compare picker cap message (was a silent un-check). */
.cmpwarn{margin:0 0 10px;padding:8px 14px;border-radius:10px;background:var(--warn-bg);color:var(--warn);
  font-size:14px;font-weight:700;border:1px solid #F2DFC2}

/* P0-1 partner-block disclosure, adjacent to the cards it describes. */
.pdisc{max-width:900px;margin:16px 0 6px;padding:14px 18px;border-radius:12px;background:var(--pos-bg);
  border:1px solid #CBE9DC;color:var(--ink2);font-size:14.5px;line-height:1.65}
.pdisc strong{color:var(--ink)}

/* Accessible-name-only text for the unlabelled actions column header. */
.visually-hidden{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;
  clip:rect(0 0 0 0);white-space:nowrap;border:0}

/* P2 compare page: platform/spread cells clipped mid-word with an ellipsis and no way to read
   the rest. Allow the two lines to actually wrap instead of truncating inside the line box. */
.xl .cl2{overflow-wrap:anywhere}

/* ===== 2026-08-07 community ranking — popularity tier badges replace the 0-10 score ===== */
/* Small badge: table cells, category rows, compare picker. Tier classes come from the real
   thaiPopularity field (p-very-high … p-unknown); green = popular tiers, amber = medium,
   neutral grey = low/niche/unknown. */
.popbadge{display:inline-block;border-radius:999px;padding:3px 12px;font-size:12.5px;font-weight:800;
  white-space:nowrap;background:#EEF2F0;color:var(--slate)}
.popbadge.p-very-high{background:var(--pos-bg);color:var(--pos-text)}
.popbadge.p-high{background:var(--info-bg);color:var(--info)}
.popbadge.p-medium{background:var(--warn-bg);color:var(--warn)}
/* Large badge: the broker-hero primary metric slot + partner cards. */
.popbig{display:inline-block;border-radius:14px;padding:9px 16px;font-size:16px;font-weight:800;
  line-height:1.35;background:#EEF2F0;color:var(--slate)}
.popbig.p-very-high{background:var(--pos-bg);color:var(--pos-text)}
.popbig.p-high{background:var(--info-bg);color:var(--info)}
.popbig.p-medium{background:var(--warn-bg);color:var(--warn)}
.popbox{max-width:300px}
.rankcard .popbig{font-size:13px;padding:6px 11px;text-align:right;line-height:1.3}
.rankcard .scwrap .popbig{max-width:190px}
.catrow .popbadge{margin-left:auto;flex:0 0 auto}
.cmptab .popbadge{white-space:normal}
/* Expanded index-table panel without the old sub-score bars: facts column + CTA column. */
.expin.expin2{grid-template-columns:minmax(0,1fr) 185px}
@media(max-width:900px){.expin.expin2{grid-template-columns:1fr}}
@media(min-width:901px){.expin.expin2{grid-template-columns:minmax(0,1fr) 190px}}

/* ── AD ZONES (2026-08-11, founder order) — built hidden, revealed per-zone
   via generate.py AD_ZONES. Fixed heights = a revealed slot shifts layout by
   one known block and can never overlap neighbours. */
.adslot{position:relative;background:#111;color:#9aa5a0;display:flex;align-items:center;justify-content:center;
  gap:8px;margin:14px auto;border-radius:8px;font-size:11px;letter-spacing:.12em;text-transform:uppercase}
.adslot[hidden]{display:none!important}
.ad-970x90{width:970px;max-width:calc(100vw - 32px);height:90px}
.ad-970x250{width:970px;max-width:calc(100vw - 32px);height:250px}
.ad-728x90{width:728px;max-width:calc(100vw - 32px);height:90px}
.ad-320x100{width:320px;max-width:calc(100vw - 32px);height:100px}
.ad-160x600{width:160px;height:600px}
/* desktop vs mobile/tablet split at 1024 */
.ad-desktop{display:none}
@media(min-width:1024px){.ad-desktop:not([hidden]){display:flex}}
.ad-mobile{display:none}
@media(max-width:1023.9px){.ad-mobile:not([hidden]){display:flex}}
/* right-rail skyscraper: only where the gutter actually fits it */
.ad-siderail{display:none}
@media(min-width:1560px){.ad-siderail:not([hidden]){display:flex;position:fixed;right:8px;top:130px;margin:0;z-index:40}}
/* dismissable sticky bottom bar, desktop only */
.ad-sticky{display:none}
@media(min-width:1024px){.ad-sticky:not([hidden]){display:flex;position:fixed;bottom:0;left:50%;transform:translateX(-50%);margin:0 0 4px;z-index:60}}
.ad-close{position:absolute;top:-10px;right:-6px;width:22px;height:22px;border-radius:50%;border:1px solid #2a3a33;
  background:#fff;color:#333;font-size:11px;line-height:1;cursor:pointer}

/* ===================================================================
   E3 MOTION WAVE (2026-08-27) — implements dev_scratch/motion_spec_2026-08-27.md
   §1 token table (in :root above), §3 the 6 named moves, §7 badge visual spec.
   @DesignSystemEngineer scope: style.css only — generate.py markup hooks (blogo() vt= kwarg,
   scorebox()'s --w->--wf change, the REVEAL_JS gold-shine observer) are @Frontend's to ship.
   =================================================================== */

/* ---- move 1 (logo view-transition morph, brk-<slug>) + move 4 (root cross-fade) ----
   One @view-transition enabling rule powers both. Progressive enhancement by spec design —
   browsers without cross-document view-transition support (Safari/Firefox/older Android
   WebView) simply ignore this and navigate exactly as before, zero risk of breakage. */
@view-transition{navigation:auto}
::view-transition-old(root),::view-transition-new(root){
  animation-duration:var(--m-4);animation-timing-function:var(--e-io)
}

/* ---- move 5 (row tint sweep on hover) — layers on top of the existing instant background
   swap (.cmp tbody.rowgrp:hover .base td, above — left untouched, it's the reduced-motion
   fallback for free). Brand green (--pos), not a foreign color.
   Byte-identical law: zero sponsor-tier class in this selector chain — every row, featured or
   editorial, gets the exact same sweep. */
.cmp tbody.rowgrp .base td:first-child{position:relative;overflow:hidden}
.cmp tbody.rowgrp .base td:first-child::after{
  content:"";position:absolute;inset:0;pointer-events:none;
  background:linear-gradient(100deg,transparent 20%,rgba(14,159,110,.10) 50%,transparent 80%);
  transform:translateX(-120%);opacity:0
}
.cmp tbody.rowgrp:hover .base td:first-child::after{
  transform:translateX(120%);opacity:1;
  transition:transform var(--m-3) var(--e-out),opacity var(--m-2) var(--e-out)
}

/* ---- §7 sponsor badge system + move 6 (one-shot Gold shine, ~700ms, Gold ONLY, never
   infinite) ---- distinct from the existing regulation-tier .badge.t1/t2/t3 system (regulator
   credibility, not sponsor placement). TH-legibility: no uppercase/letter-spacing dependence
   anywhere below — follows the .popbadge/.popbig precedent (tier differentiated by color alone),
   not .ppick's (a real Thai combining-mark risk on some text-shaping engines). Font-size 13px,
   ≥13px per the TH-legibility floor.
   NAMING (2026-08-27 correction): the motion spec (§7) authored `.spbadge`/`.spbadge.gold` etc.
   The parallel task brief names the actual markup contract @Frontend emits: `badge-gold`,
   `badge-silver`, `badge-partner`, `featured-tag` (standalone classes, not `.spbadge`
   compounds) — confirmed generate.py doesn't emit either yet (parallel, unlanded), so both are
   grouped below rather than guessed at: whichever lands, it's already styled identically,
   self-contained so it's correct whether markup combines it with the existing .badge base or
   uses it standalone. */
.spbadge,.badge-gold,.badge-silver,.badge-partner{
  display:inline-block;border-radius:999px;padding:3px 12px;font-size:13px;font-weight:700}
.spbadge.gold,.badge-gold{background:var(--gold-bg);color:var(--gold);border:1px solid rgba(183,121,31,.35)}
.spbadge.silver,.badge-silver{background:var(--silver-bg);color:var(--silver);border:1px solid rgba(100,116,139,.30)}
.spbadge.partner,.badge-partner{background:#EEF2F0;color:var(--slate);border:1px solid var(--line)}
.feattag,.featured-tag{font-size:12px;font-weight:600;color:var(--slate)}

.spbadge.gold,.badge-gold{position:relative;overflow:hidden}
.spbadge.gold::after,.badge-gold::after{
  content:"";position:absolute;inset:0;
  background:linear-gradient(120deg,transparent 30%,rgba(255,255,255,.55) 50%,transparent 70%);
  transform:translateX(-150%);opacity:0
}
.spbadge.gold.shine-play::after,.badge-gold.shine-play::after{
  animation:goldshine var(--m-shine,700ms) var(--e-out) 1 forwards
}
@keyframes goldshine{
  0%{transform:translateX(-150%);opacity:0}
  15%{opacity:1}
  100%{transform:translateX(150%);opacity:0}
}

/* ---- .sort-toggle (task contract, not spec-anchored — new class, no prior art to preserve).
   Chevron rotation using the reusable --m-2 token (160ms, "hover/press feedback, chip/chevron
   transitions" per §1a) — mirrors the existing .rowtoggle pattern. ASSUMPTION flagged in the
   handoff: modifier class guessed as `.on` (site convention: .chip.on/.ltab.on/.rowgrp.open) —
   confirm actual active-state class with @Frontend and adjust if different. Needs no explicit
   reduced-motion entry: var(--m-2) already collapses to 0ms via the canonical block's token
   reset, so the rotation goes instant automatically. */
.sort-toggle{background:none;border:0;padding:0 2px;cursor:pointer;color:var(--slate);
  display:inline-flex;vertical-align:middle;transform:rotate(0deg);
  transition:transform var(--m-2) var(--e-out)}
.sort-toggle:hover{color:var(--pos)}
.sort-toggle.on{transform:rotate(180deg);color:var(--pos)}

/* CONFIRMED by @Frontend (2026-08-27): the shipped markup is two labelled buttons ("Featured
   first" / "By score"), not the single rotating chevron this file guessed at above -- a bare
   icon can't carry two named states without a label, and the task brief asked for the literal
   two-word toggle. Markup: <button class="btn sec sortbtn"> ×2, wrapped in .sorttoggle, with the
   active one additionally carrying `.on` -- so the `.on` guess above WAS right, just applied to
   a different element shape. Do NOT put `.sort-toggle`'s reset (background:none;border:0) on
   these buttons -- it would strip the .btn.sec pill they render with today (confirmed via
   cascade order: .sort-toggle is declared after .btn.sec, so it would win on any element
   carrying both classes). V1 visual gate fix: `.sortbtn.on` previously had NO rule at all, so
   both buttons were visually identical in every state -- filled/ink treatment now makes the
   active one unmistakable; aria-pressed (already set in generate.py's markup/JS) covers
   assistive tech. No dark/light theme exists on this site (single palette) -- "survives both
   locales" verified by inspection: TH labels are the same length class, no wrap risk at this
   padding. */
.sortbtn{transition:background var(--m-2) var(--e-out),color var(--m-2) var(--e-out),border-color var(--m-2) var(--e-out)}
.sortbtn.on{background:var(--ink);border-color:var(--ink);color:#fff}
.sortbtn.on:hover{background:var(--ink);color:#fff}

/* Compliance gate V4 fixwave FIX C (2026-08-27): .featnote (the paid-placement disclosure line
   co-located with the sponsor grid + both full-list tables) was rendering as `<p class="muted
   featnote">` -- .muted's color (--slate, #5F7A6C) is only 4.52:1 on --paper (#F8FCFA), which
   PASSES WCAG AA's 4.5:1 floor by a margin so thin it is not safe for a compliance-required
   disclosure (any of: sub-pixel rendering, a slightly different white point, or a future --paper
   tweak could tip it under). --ink2 (#2A4A3A) measures 9.49:1 on --paper -- more than double the
   floor -- while still reading as secondary/caption weight, not full heading-strength --ink. */
.featnote{color:var(--ink2)}

/* ---- @supports fallback: background-clip:text + text-fill-color:transparent renders fully
   invisible text in any engine that doesn't paint the clip. Falls back to a solid, legible
   color so the H1 headline can never render blank. Covers both hero variants. ---- */
@supports not ((background-clip:text) or (-webkit-background-clip:text)){
  .hero h1 em{background:none;-webkit-text-fill-color:currentColor;color:var(--pos)}
  .herodark h1 em{background:none;-webkit-text-fill-color:currentColor;color:#5EEAB4}
}

/* ===== CANONICAL REDUCED-MOTION — replaces v2.1/v7/v9/v11/v15/v15.3/v16's 7 scattered blocks
   (2026-08-27). Invariant: every override below ends at the element's normal CSS default —
   fully visible, untransformed, unclipped. Nothing is left invisible or offset. Token reset
   handles every TOKENIZED consumer (moves 1-6 above); explicit selectors handle every
   UNTOKENIZED animation still in the file, because a --m-* reset alone can't reach a hardcoded
   value. Placed last in the file so cascade order can't undo it. ===== */
@media (prefers-reduced-motion:reduce){
  :root{
    --m-1:0ms; --m-2:0ms; --m-3:0ms; --m-4:0ms; --m-5:0ms; --m-amb:0ms; --m-shine:0ms;
    --m-stag:0ms; --m-lift-sm:0px; --m-lift-lg:0px;
  }
  html{scroll-behavior:auto}

  /* legacy untokenized animations still in the file after the §4 deletions. .herochart added
     2026-08-27 (spec gap: the deleted v15 RM block covered it — chartdrift's 12s is hardcoded,
     not tokenized, so a --m-* reset alone can't reach it; without this line a reduced-motion
     user regresses from "static chart" to "perpetual 12s drift"). */
  .tpmtrack,.mq-in,.hdot,.hline,.herochart{animation:none!important}
  .hline{stroke-dashoffset:0}
  .hdot{opacity:1;transform:scale(1)}
  .tpmarquee{overflow-x:auto;-webkit-overflow-scrolling:touch;-webkit-mask-image:none;mask-image:none}
  .tpmset[aria-hidden]{display:none}
  .expwrap>.expin{transition:none}
  .hero.herodark::before{animation:none}

  /* the 6 moves */
  .reveal{opacity:1;transform:none;transition:none}                 /* move 2 + generic reveal */
  .subs .bar i{animation:none;transform:scaleX(var(--wf,0))}         /* move 3 — belt: correct resting value even if something upstream drops the animation */
  ::view-transition-group(*),::view-transition-old(*),::view-transition-new(*){animation:none!important}  /* moves 1 + 4 */
  .cmp tbody.rowgrp .base td:first-child::after{transition:none;opacity:0} /* move 5 — sweep never renders; instant bg-swap hover still fires */
  .spbadge.gold::after,.badge-gold::after{content:none}              /* move 6 — shine never renders; plain gold pill remains fully legible */
}
