/* Headings inside an editor-authored Stream B article body.
   THE ONE PLACE THEY ARE STYLED -- review_notes 141.

   Sudeep, on /feature, against the `h2` "Technology vs Commodity vs
   Property": "This H2 should not be BOLD at all. Medium / Regular at best.
   Not thin or light."

   Two facts made that H2 the heaviest type on the page:

   1. /feature had NO body-heading rule at all, so the H2 fell through to
      base.html's sitewide `h2{font-size:28px;font-weight:600}` -- a page-
      title scale applied to a mid-article subhead.
   2. It inherited `font-family` from `.body`, which is IBM Plex Serif.
      base.html loads Plex Serif at weights 300/400/500 ONLY. There is no
      600, so the browser SYNTHESISED one -- smeared faux-bold, which is
      exactly the "BOLD" the note is describing and is not a weight IBM
      ever drew.

   /article/{slug} renders the same editor field through the same `.body`
   wrapper and had its own hand-written copy of this rule. Two surfaces,
   one kind of content, two independently maintained declarations, one of
   which was simply missing -- the same failure note 102 named: "Make
   changes to TEMPLATE and do not hand code this." So the declaration lives
   here, in a file both pages link, rather than in either page.

   500 is Medium: a real drawn weight in both families the site loads, and
   the heavier end of what the note allows. Never 600 (Plex Serif cannot
   render it), and never 300/400 -- the note rules out thin and light just
   as explicitly as it rules out bold.

   Scoped to `.body` on purpose. This is the wrapper around
   `article.body | safe` on both pages; the H2 comes from the editor's own
   "H2" toolbar button in the Stream B workspace, which is the only heading
   level that workspace can emit. Section headings that the TEMPLATES own
   (`h2.row`, `h2.sec`, `.pol-body h2`) are a different thing with a
   different job and keep their own rules.

   WHY THIS RULE STILL NAMES A FAMILY, when the type scale (2026-08-16)
   forbids every other page stylesheet from doing so. Naming it IS the fix
   note 141 asked for: this heading sits inside `.body`, whose family is
   IBM Plex Serif, so a rule that does not say `sans` INHERITS serif -- and
   that inheritance, combined with weight 600, is exactly the synthesised
   faux-bold the note was filed about. Stating it here is the difference
   between a heading that cannot regress and one that regresses the moment
   anybody touches `.body`. test_stream_b_typography.py asserts the string
   is present, and tests/test_type_scale_is_single_source.py registers this
   rule by name in _FAMILY_EXCEPTIONS with this reason.

   The SIZE is now the lead role -- 19px on a desktop, unchanged, and 17px
   on a phone where it used to be 19px there too. */
/* THE READING SURFACE -- ONE RULE FOR BOTH ROUTES (cp-478, 2026-09-19).

   Until today `.body` was declared twice, and the same Feature article was
   set at two sizes depending on the URL it was read at:

     /article/{slug}  font-size:17px fixed, 16px below 640   (article.html)
     /feature         var(--fs-body) clamp, 15px below 640   (feature.css)

   Nobody reported it because the two AGREE where a tablet sits and diverge
   where a desktop does. An editor signs copy off on one surface and it
   ships at another size on the other.

   IT LIVES HERE FOR THE REASON THE FILE ALREADY GIVES for `.body h2` below:
   two surfaces, one kind of content, two independently maintained
   declarations. This is that rule one level up -- `.body h2` sits inside
   this very wrapper, so having the child shared and the parent duplicated
   was always the odd arrangement.

   THE SIZE IS THE `prose` ROLE, new on 2026-09-19 (cp-477). Sudeep, testing
   on his own iPad: "increased size to 115% for daily and 125% for Ctrl+Shift
   and feature article and it was very comfortable to read on the device."
   Then, on the two calls put to him: "21px, global". 125% of 17px is 21.25;
   21 is the primitive that exists, and the 1.2% shortfall is recorded rather
   than met with a thirteenth size in tokens.json.

   THE MEASURE IS 59ch, AND IT WAS 62ch FOR TWO DAYS BECAUSE ONE CONSTANT
   WAS GUESSED. A `ch` cap is proportional to the font size, so while it
   binds the character count does not move with the size -- which means the
   cap is either right at every width or wrong at every width.

   BOTH NUMBERS ARE MEASURED NOW, read from the font file Google Fonts
   actually serves this site (the IBM Plex Serif 400 TTF linked from
   base.html:101), not from a rendering of it:

       advance of "0"            0.6000 em/ch   <- the CSS `ch` unit itself
       average over real prose   0.4727 em/char <- was ASSUMED to be 0.50

   Plex Serif's prose is NARROWER than 0.50 assumed, so more characters fit
   per line than the arithmetic predicted, and every cap derived from it was
   too loose:

       65ch -> 82.5 characters   (believed to be 78.0)
       62ch -> 78.7 characters   (believed to be 74.4, and over the ceiling
                                  at 796 of the 960 widths from 805px up)
       59ch -> 74.9 characters   (inside 45-75 at EVERY width 641-1600)

   So: 75 chars x 0.4727 em/char = 35.45 em ; 35.45 / 0.6000 em/ch = 59.09ch.

   THE LESSON IS NOT "MEASURE FONTS". It is that the 62ch change shipped
   with its soft constant NAMED as inferred, three times, in the commit, in
   the tests and in the desk entry -- and shipped anyway. Naming an unproven
   number is not the same as proving it, and a caveat carried alongside a
   deploy does not make the deploy correct. The measurement took twenty
   minutes and could have been done before.

   A LITERAL AND NOT A TOKEN, deliberately. After this consolidation the cap
   has exactly ONE reader, and `ch` is a CSS unit that means nothing on iOS or
   Android -- so a token would be a name in a platform-neutral file for a
   web-only value nothing else asks for. If a second surface ever needs the
   same cap, that is when it earns a name.

   WHAT IS NOT HERE. `blockquote` stays in both files: /article's is a 20px
   italic with an accent rule, /feature's an inverted navy block. Those are
   different DESIGNS, not drift, and feature.css says reconciling them is a
   design call. `.standfirst` / `.dek` are already identical at 18px serif
   300 -- nothing to converge -- and feature.css carries a written ruling
   that the pair moves to the lead role together, in a commit that moves
   both and says so. */
.body{font:var(--type-prose);color:#3a3a38;max-width:59ch;margin:24px 0}
/* 24px rather than article.html's 26px: feature.css already carried a
   written argument for 24 on this surface, so the REASON travels with the
   value. The difference a reader sees is 2px of leading above a paragraph. */
.body p{margin-bottom:var(--space-xl)}

.body h2{font-family:var(--sans);font-size:var(--fs-lead);font-weight:500;color:var(--navy);margin:30px 0 12px}
