/* ==========================================================================
   legibility-floor.css — NOTHING IN THIS APP IS SMALLER THAN A PERSON CAN READ

   ⭐ ACCESSIBILITY_AND_PERFORMANCE_v8 §1.1: "Text size — default LARGE, not
   OS-follow, because older users have not found the iOS setting." And §1.2:
   "Tap targets — 56pt minimum."

   ⚠️ WHY THIS FILE EXISTS AS A FLOOR RATHER THAN AS EDITS IN PLACE. A device
   sweep across fourteen pages and the ten commonest phones and tablets found
   280 pieces of text under 12px and 52 tap targets under 40px — spread across
   the felt, the Academy and the roadmap, each set in its own stylesheet with
   its own scale. Editing thirty rules in five files would fix today's list and
   drift again the first time somebody adds a caption.

   ⭐ SO THE FLOOR IS DECLARED ONCE, LAST, AND APPLIES TO EVERYTHING. A rule
   that wants smaller text has to say so louder than this file, which is a
   deliberate speed bump.

   ⛔ IT DOES NOT MAKE ANYTHING BIGGER THAN IT WAS — every rule here is a
   minimum, expressed with max()/min-height, so a comfortable size is untouched
   and only the unreadable ones move.
   ========================================================================== */

/* ---- 1 · THE TEXT FLOOR --------------------------------------------------
   ⚠️ 12px is the floor, not the target. The felt labels sat at 9-11px, which
   is legible to a thirty-year-old on a desk monitor and genuinely is not to
   the reader this product is for. */
.dealer-tag,
.pot-tag,
.seat .tag,
.seat .badge,
.trole,
.tname {
  font-size: max(12px, 1em) !important;
}

/* the Academy's small print */
.phase-hd,
.dnum,
.tag,
.tag.free,
.lk {
  font-size: max(12px, 0.82rem) !important;
}

/* ---- 2 · THE ROADMAP, WHICH IS THE FIRST PAGE MANY PEOPLE SEE -------------
   ⚠️ THE PATH is browsed by somebody deciding whether this app is for them.
   Half its labels were under 12px and its "what is this" links were under
   40px — on the one screen whose whole job is to be read. */
.rh-eyebrow,
.rh-slimk,
.rh-ztag,
.rh-what,
.rh-char,
.rh-lvl-sub {
  font-size: max(12.5px, 0.8rem) !important;
}

/* ---- 3 · THE TAP FLOOR ---------------------------------------------------
   ⭐ §1.2 asks for 56pt. ⚠️ THAT IS APPLIED AS A MINIMUM HEIGHT AND A
   PADDING, never as a fixed height: a link inside a paragraph that suddenly
   becomes 56px tall breaks the line box it sits in. Inline targets get the
   padding; block ones get the height. */
a.rh-what,
a.rh-char,
.rh-card a,
.rh-lvl a {
  display: inline-block;
  min-height: 44px;
  padding-top: 12px;
  padding-bottom: 12px;
  /* ⚠️ 12pt between targets — "a tremor turns adjacent buttons into a coin
     flip" — and this is the cheapest place to guarantee it. */
  margin-right: 12px;
}

/* ⚠️ .fs-nav-opener JOINS THE EXCLUSIONS, for the same reason .fs-nav-item is
   here: the nav sets its OWN, HIGHER floor (56px, UNIVERSAL_LAYOUT_v8 §9.4)
   and this 44px rule is (0,2,1), which beat the opener's (0,2,0) and quietly
   pulled the drawer button back UNDER the floor -- measured 56x44 on every
   landscape phone. Excluding it here keeps ONE place deciding the nav's
   height, rather than starting a specificity race in the other file. */
/* ⛔⛔ `.fs-btn` AND `.abtn.deal` ARE EXCLUDED HERE, NOT ESCALATED ELSEWHERE.
   This chain is (0,3,1) -- each `:not()` carries its argument's weight -- so it
   beats `.fs-btn` (0,1,0) AND `.fs-deal-row .abtn` (0,2,0), and both action-area
   floors resolved to 44px while their own stylesheet said 64 and 52-64.
   ⚠️ CLAUDE.md records this trap and its remedy in the same breath: the nav
   opener hit it, and the fix was "adding the opener to THAT file's exclusion
   list, so one file still decides the nav's height". Same call here -- ONE file
   decides a control's floor, and these two are raising it, never lowering it:
   the action buttons claim 64px (§22, "Do NOT solve narrow layouts by making the
   buttons tiny") and DEAL claims 52-64. ⛔ Never `!important`, never a reorder. */
button:not(.fs-nav-item):not(.fs-nav-opener):not([data-fs-dev]):not(.fs-btn):not(.deal),
.btn,
.abtn:not(.deal) {
  min-height: 44px;
}

/* ⭐ AND THE ONES THAT COST SOMETHING GET 72. §1.2 singles out anything that
   spends money or folds a hand, because those are the two irreversible taps in
   the product. */
#btnFold,
.abtn.fold,
.abtn.raise,
.abtn.allin {
  min-height: 56px;
}

/* ⛔ WHAT I TRIED HERE AND REMOVED, BECAUSE IT WAS THE WRONG TOOL.

   §1.1 bans thin fonts and italics, so this file briefly carried a blanket
   `font-weight: inherit` and `font-style: normal` on every element. Both are
   real rules and both were enforced in the wrong place:

     ⚠️ `font-weight: inherit` flattens every deliberate bold in the product —
       headings, the accent words in Tiger's lines, the rank on a card. It is
       not a floor, it is a restyle.
     ⚠️ And it changed rendered text width enough to alter button heights,
       which broke an onboarding assertion that "Not sure yet" is the same size
       as Continue. That test was right and this file was wrong.

   ⭐ A FLOOR RAISES MINIMUMS. It does not have opinions about weight. Thin
   fonts and italics belong in the typography spec and its own audit. */


/* ==========================================================================
   TIER TWO — the selectors the first pass did not name.

   ⚠️ FOUND THE SAME WAY: re-running the device sweep after the first floor
   dropped the count from 280 to 164, and the remainder were simply different
   components with their own scales. Every one of these measured between 10.5px
   and 11.9px on a real phone.
   ========================================================================== */
.se-q,
.se-q.se-key,
.se-what,
.kick,
.hand,
.val.soon,
.fwho,
.tc-chip,
#log,
#step {
  font-size: max(12.5px, 0.8rem) !important;
}

/* ⭐ THE NAV RAIL IS A TAP TARGET LIKE ANY OTHER. It was excluded from the
   first pass because it has its own sizing system — and it measured under 40px
   on every phone, which is the one control a student uses on every screen.
   ⚠️ min-height only: the rail's own layout still decides width and spacing. */
.fs-nav-item {
  min-height: 44px;
}

.se-what {
  min-height: 44px;
  padding-top: 10px;
  padding-bottom: 10px;
}


/* ==========================================================================
   ⚠️ WHAT THIS FLOOR CANNOT FIX, AND WHY IT IS LEFT ALONE

   The nav rail measures under 44px per item on a landscape phone, and
   app-nav.css already asks for 44 — it simply cannot have it. Nine items at
   44px is 396px of height, and a landscape iPhone is 390-430px tall including
   the address bar. Forcing the floor would make the rail taller than the
   screen, which trades a slightly small target for an unreachable one.

   ⭐ The honest options are a scrolling rail, fewer items on short screens, or
   accepting ~40px there. That is a design decision, not a stylesheet one, so
   it is reported rather than papered over.
   ========================================================================== */

/* ---- 5 · THE PLACEHOLDER FLOOR — register item 53 ------------------------
   "Placeholder contrast >= 4.5:1, and a placeholder is NOT a label. Both,
   always."

   ⛔⛔ THE APP WROTE NO ::placeholder RULE AT ALL, so every placeholder in
   the product rendered at Chrome's default rgb(117,117,117) against felt-dark
   fields. MEASURED by _placeholder-contrast.html across 43 pages and 25
   fields: 3.87:1 to 4.35:1, and TWENTY-THREE of the twenty-five were below
   the floor. Not one was styled wrong -- none of them was styled at all.

   ⚠️ IT COULD NOT BE FOUND BY READING SOURCE. There is no wrong value to
   grep for; the fault is an ABSENT rule, and a browser filling the gap with
   a plausible grey is invisible until something computes the ratio. Same
   shape as the stale dealer constant: every number looked fine beside the
   others.

   ⭐ AND IT LIVES HERE RATHER THAN IN main.css BECAUSE main.css IS NOT ON
   THE PAGES THAT NEED IT. forgot-password, reset-password, welcome and
   settings load legibility-floor.css and typography.css and nothing else --
   and forgot-password is a page you only reach when you are already locked
   out, which is exactly how the api-config bug survived on those same three
   files. This stylesheet is on all eleven.

   ⚠️ opacity:1 IS NOT DECORATION. Firefox applies a default opacity to
   placeholder text, so setting only `color` there yields a lower ratio than
   the one measured here -- a contrast fix that silently does not hold in the
   browser it was not measured in. */
::placeholder {
  /* rgb(138,148,144) -- 5.8:1 on the lightest field background in the app
     (rgb(15,21,18)) and 6.6:1 on the darkest (rgb(3,9,11)). It is --grey
     from main.css, repeated here as a literal ONLY because main.css does
     not load on four of these pages; _placeholder-contrast.html measures
     the RESULT, so the two cannot drift without the harness saying so. */
  color: #8A9490;
  opacity: 1;
}

/* ---- 6 · THE READING FLOOR — register item 60 ----------------------------
   "18px body · 1.6 line height · max-width: 68ch · ⛔ never justified."

   ⛔⛔ IT WAS SET IN typography.css AND LOST EVERYWHERE ELSE. Measured by
   _typography-audit across all 44 pages: 251 reading paragraphs rendered
   BELOW 18px — many at 15.2px, which is `font-size:0.95rem` typed into a
   page's own <style> block — and 207 ran past 68ch, some at ~84ch.
   ⚠️ The register said "18px/1.6 set; 68ch and never-justified not audited
   page by page." It was set, and it was losing: a per-page rule declared
   later beats a shared one, which is #19 and #21 in this file's own history.

   ⭐ SO IT IS A FLOOR, DECLARED ONCE AND LAST, like every other rule here.
   ⚠️ max() and a min-height, never a fixed size: a paragraph already at 19px
   is untouched, and only the unreadable ones move.

   ⛔ SCOPED TO READING, NOT TO EVERY <p>. A tag, a caption, a byline and a
   legal footer are deliberately smaller and failing those would be failing
   correct design. This selects the containers that hold prose and exempts
   the classes that are labels — which is why 15.2px body copy moves and the
   0.8rem "Last updated" line does not. */
/* ⚠️⚠️ TWO MISTAKES IN THE FIRST CUT OF THIS BLOCK, both silent.
   1. `line-height: max(1.6, 1.6em)` mixes a UNITLESS number with a LENGTH.
      max() requires comparable types, so the whole declaration was dropped
      and the 26 tight paragraphs did not move at all. ⭐ The spec says 1.6,
      so 1.6 it is — a floor is not expressible here and pretending to
      write one produced no rule whatsoever.
   2. The selectors were CHILD combinators (`.wrap > p`), and most of these
      pages nest their prose a div or two deeper, so the rule reached about
      a third of what it should. Descendant, not child. */
/* ⚠⚠⚠ AND THE THIRD MISTAKE: MOST PAGES HAVE NEITHER `.wrap` NOR
   `<main>`. field-guide, sharksense and roadmap have no such container at
   all, so scoping to them reached about half the app while reporting a
   confident-looking improvement. ⭐ `body p` is (0,0,2) and beats a page's
   own bare `p { font-size:0.95rem }` at (0,0,1) without depending on which
   stylesheet was linked last — which is #19 and #21's lesson: when two
   rules disagree, RAISE SPECIFICITY, do not reorder and hope.
   ⛔ The deliberately-small classes are exempted by name further down. */
/* ⚠️ `body p` AND `body li` ARE GONE FROM HERE TOO, and `.lede` with them.
   Same reason: `max(18px, 1em)` cannot exceed the inherited size, so on any
   element whose design asks for more it is a cap wearing a floor's name. The
   base above does this job without competing.
   ⭐ THE LINE-HEIGHT STAYS, on the same selectors, because that was never the
   problem and prose still needs it. */
body p,
body li,
.lede,
.say,
.leak-note,
.leak-invite {
  line-height: 1.6;
}
/* ⭐ THE MEASURE. 68ch is about 12 words a line; past that the eye loses the
   start of the next one, which is the single biggest readability lever
   available to a page of prose. ⚠️ `ch` is the width of "0" in the element's
   OWN font, so this stays right when the font-size floor above moves. */
body p,
.lede,
.say,
.ss-a,
.leak-note,
.leak-invite {
  max-width: min(68ch, 100%);
}
/* ⛔ NEVER JUSTIFIED — the spec's never list. Justified text opens rivers of
   white space between words, and the ragged right edge is what a struggling
   reader uses to find the next line. */
p, li, .lede, blockquote { text-align: left; }
[dir="rtl"] p, [dir="rtl"] li { text-align: right; }

/* ⚠️ AND THE CLASSES THAT ARE DELIBERATELY SMALL keep their size. Listed
   explicitly rather than left to specificity luck, because "it happens to
   win today" is how the nav width came out two different numbers. */
body p.updated,
body p.tagline,
body p.meta,
body p.caption,
body p.legal,
body p.fine,
.fs-nav p,
/* ⚠️ `.sub` AND `.fg-sub` WERE ON THIS LIST FOR ONE RUN AND THAT WAS
   WRONG: they are the paragraph UNDER a heading — prose a student reads,
   not a caption — so exempting them put 14 paragraphs straight back below
   the floor and the count went UP. An exemption list is a place to hide,
   and every name on it has to earn its place. */
.tag, .meta, .updated, .caption, .byline, .who {
  font-size: revert;
  line-height: revert;
  max-width: none;
}

/* ---- 6b · AND A SINGLE CLASS BEATS `body p` ------------------------------
   ⚠️⚠️ THE FLOOR ABOVE REACHED THE BARE `p { }` RULES AND NOTHING ELSE.
   `academy.css` has `.sub { font-size:.92rem; line-height:1.55 }` — that is
   (0,1,0), and `body p` is (0,0,2). A class beats any number of elements, so
   the floor lost on every page that names its prose with a class.

   ⭐ #21's LESSON, VERBATIM: "When two rules disagree, RAISE SPECIFICITY; do
   not reorder and hope." `body .sub` is (0,1,1) and wins wherever it is
   linked, which is what a floor has to do.

   ⚠️ THESE ARE THE CLASSES _typography-audit NAMED, not a guess. The harness
   prints the class beside every failure precisely so this list can be
   derived rather than imagined — and it will name any new one the day a page
   adds it, which is the only reason a list like this is safe to keep. */
body .say,
body .sub,
body .fg-sub,
body .gate-note,
body .note,
/* ⚠️ `.lede` STAYS HERE and not in the !important block: it is a MULTI-USE
   class -- 21px on the homepage hero, 14.7px inside an Academy card -- so a
   rule that clamps it clamps the large one too. Without !important the
   design wins where the design is bigger, and the 18px base catches it
   where the design is silent. */
body .lede,
body .first,
body .gr-var,
body .ss-a,
body .rh-ladlede,
body .leak-note,
body .leak-invite {
  font-size: max(18px, 1em);
  line-height: 1.6;
  max-width: min(68ch, 100%);
}

/* ==========================================================================
   ⛔⛔ 18px IS A FLOOR AND IT WAS LOSING ON SPECIFICITY — Eric, 2026-09-05:
   "18 px minimum must be met on all home page and app pages. Period. Fix
   them all."

   `body p` is (0,0,2). A page's own `.about-card p { font-size:.85rem }` is
   (0,1,1) and beats it, so the rule above this one was correct and inert:
   a sweep measured 42 body-text nodes between 12.5px and 17.0px across
   sixteen shipping pages, every one of them on a page that links this file.

   ⭐ The lower floors in this file have carried `!important` from the start,
   for exactly this reason. A floor is not a design opinion competing on
   specificity — it is the number nothing goes under. #19 and #21 say raise
   specificity rather than reorder; a floor goes one further and refuses to
   compete at all.

   ⚠️ THE CLASSES ARE MEASURED, NOT GUESSED. Each was rendering under 18px on
   a real page. Some were deliberately small — captions, the legal line, the
   lawyer notice — and they are not exempt any more. Our reader is
   seventy-eight; a 14px legal line is one they cannot read.
   ========================================================================== */
/* ⭐⭐ THE FLOOR IS THE INHERITED BASE, NOT A RULE THAT FIGHTS EVERY OTHER ONE.
   With `body` at 18px, an element that sets no size of its own IS 18px, and
   an element the design wants larger simply is larger -- no `!important`
   involved, nothing to lose a specificity fight to.
   ⛔ This replaces `body p` / `body li` in the important block below, which is
   what was capping the page: `max(18px, 1em)` reads the INHERITED size, and
   once that is 18px the expression can never return anything else.
   MEASURED before the change: all 63 <p> on the homepage at exactly 18px,
   with .band-price designed 28, .who-pull 30 and .standfirst 21. */
body { font-size: 18px; }

/* ⭐⭐ `body p` AND `body li` ARE BACK, AND THE EXEMPTION GOES THE OTHER WAY.
   Taking them off restored the headline hierarchy and dropped THIRTY-SIX
   body-text paragraphs under 18px across twenty pages -- most of them bare
   <p> with no class to name an exemption after, coming from rem-based page
   rules that resolve against the ROOT, so moving the body base never reached
   them either.
   ⭐ The six elements the design deliberately sets ABOVE 18 now carry
   `!important` in their OWN rule, where the number already lives. Nothing is
   restated here, and the floor is absolute again. */
body p, body li,
body .say, body .fsay, body .as-step-say,
body .lede, body .first, body .note, body .gate-note,
body .gr-locked, body .gr-var,
body .rm-sub, body .rm-note, body .rh-ladlede,
body .sl-sub,
body .lkart-free, body .lkr-close,
body .vils-foot, body .sb-web-cap, body .sb-cap,
body .price-what, body .legal, body .lawyer-notice,
body .key, body .nobody,
body .ss-a, body .leak-note, body .leak-invite {
  font-size: max(18px, 1em) !important;
  line-height: 1.6;
  /* ⭐ AND THE FLOOR OWNS THE WRAPPING IT CHANGED. Raising type re-wraps
     every line, and _orphan-audit immediately found two: the homepage footer
     at 390px stranded "info@feltshark.com" alone once .legal went 14px -> 18px.
     `text-wrap: pretty` is the mechanism for exactly this -- it pulls a word
     down from the line above. ⛔ NOT `balance`, which CLAUDE.md records as
     not reaching a long paragraph (Chrome stops balancing past a few lines),
     and ⛔ not a non-breaking space, which that same entry proves does not
     stop a line break at all. */
  text-wrap: pretty;
}

/* ==========================================================================
   ⭐ TABULAR FIGURES WHERE NUMBERS LINE UP — register row 61.

   Measured before this: `font-variant-numeric: tabular-nums` was in six
   stylesheets and two pages, and in NONE of the files every page loads. So a
   stat panel or a session summary rendered proportional figures, where a 1 is
   narrower than a 7 — a column of numbers that should line up does not, and a
   figure that ticks over shifts everything beside it.

   ⛔ DELIBERATELY NOT `body`. Tabular figures are wider and read colder; they
   belong where digits are COMPARED and not in running prose. This list is the
   places numbers actually line up.
   ========================================================================== */
table, th, td,
.stat, .stats, .stat-value, .stat-num, .num, .figure, .count, .score,
.money, .chips, .pot, .bb, .timer, .clock, .pct, .percent,
[class*="-num"], [class*="-stat"], [class*="-score"], [class*="-count"],
output, meter, progress, time, kbd, code, pre {
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}

/* ==========================================================================
   ⭐ A TERM A STUDENT HAS MET FOUR TIMES — VOCABULARY_v8 §4.3's last rung.
   The word keeps its sentence and gains a dotted underline; tapping opens the
   full definition.
   ⛔ It is a real control, not a styled span: `term-glossary.js` gives it
   `role="button"` and a tab stop, because a definition nobody can reach from
   a keyboard is one the large-print reader cannot reach either.
   ⚠️ Underline, not colour. Brass already means LEAK MARKER and this project
   records that as load-bearing; a fifth meaning for a fourth colour is how a
   palette stops carrying information.
   ========================================================================== */
.fs-term {
  border: 0;
  padding: 0;
  background: none;
  font: inherit;
  color: inherit;
  cursor: help;
  text-decoration: underline dotted;
  text-underline-offset: 3px;
}
.fs-term:focus-visible {
  outline: 2px solid currentColor;
  outline-offset: 2px;
}
