//! The webview renderer for [`makeover_layout`].
//!
//!
//!
//! # The renderer that needs no palette
//!
//! `makeover-immediate` and `makeover-tui` both take a `Palette`, because egui
//! and a terminal need an actual colour before they can put anything on
//! screen. A webview does not: `var(--surface-raised)` *is* the late binding,
//! and the browser resolves it against whatever `themes.js` last wrote onto
//! `:root`.
//!
//! So this crate emits text naming intents, and never learns a colour. It is
//! the deferral rule with no adapter in the way, and it is why the webview was
//! always the wrong renderer to derive a vocabulary from: it can express
//! anything, so it never pushes back.
//!
//! # Phase A: the stylesheet
//!
//! This module emits component CSS and no markup, deliberately. GoingsOn has
//! 145 `innerHTML` sites and Balanced Breakfast 175 `createElement` sites, so
//! moving markup is a migration where adopting a generated stylesheet is not.
//! The apps keep every line of their markup and gain the classes.
//!
//! It is not a deletion either, which this header claimed until the measurement
//! came in. Adoption across goingson removed 49 declarations net and *added* 25
//! lines: a rule loses its depth declarations and gains a variant selector next
//! to it, so the file stays the same size. What phase A moves is where depth is
//! defined, not how much CSS exists. Numbers and method in the wiki note under
//! "The deletion test, run".
//!
//! The bevel properties are byte-identical to what both apps already
//! hand-write, which is asserted below.
//!
//! Some of what phase A emits is not a look but the withdrawal of one. A
//! renderer that picks its element from the description inherits that element's
//! user-agent chrome, and [`reset`] is where a primitive says which parts of it
//! were never asked for.
//!
//! # Phase B: the markup, one description at a time
//!
//! [`form`] renders [`makeover_layout::Field`], which is the half of phase B
//! whose description is settled. It emits strings, because both apps
//! interpolate their fields into larger string-built forms and returning nodes
//! would rewrite those too. It owns its own escaping, on the reasoning in that
//! module: a Rust encoder can cover element text and attribute values with one
//! function, where the apps need four and have to choose correctly at every
//! call site.
//!
//! [`facet`] renders `makeover_layout::Facet`: a dimension a set is narrowed by,
//! and the one phase-B emitter whose markup an app is not keeping, because the
//! markup it replaces was two mechanisms rather than one. A tag's selection and
//! a tag's browse position were separate state on MNW's discover page, which is
//! why every filter row there carries a tick box *and* a chevron; one gesture
//! doing both is what lets the second one go.
//!
//! [`list`] is the other half: column tracks, the narrowing rules, and the cell
//! containers a row is made of. It stops at the cell boundary and does not
//! render what goes inside one, on the reasoning in that module. So phase B is
//! now the frame around content in both directions, and what an app still owns
//! is the content itself.
//!
//! # What phase A settled, and what it costs
//!
//! Measured against goingson's `styles.css` rather than against a component
//! list: `.btn`, `.card` and `.tag, .badge` each hand-write the same
//! composition, so three quarters of phase A is one rule with several names.
//!
//! Two of the four decisions change how goingson looks, and adoption should
//! not be described as a pure deletion:
//!
//! - **Pressed carries its fill.** [`interactive_rules`] emits
//! [`Depth::pressed`] whole. goingson presses to `--surface-sunken` today and
//! will press to `--surface-well`, and hovers to `--surface-overlay` today
//! and will hover to `--hover-surface`. Since `surface-well` inverts by theme
//! where `surface-sunken` does not, a dark theme presses *lighter* than it
//! hovers. That falls out of `makeover`'s own derivation, which says outright
//! that `surface-sunken` cannot serve as a well, so if it reads wrong the
//! answer is there and not here.
//! - **Badges go flat.** See [`token_rules`].
//!
//! The other two: the progress trough is renderer-local and the scrollbar
//! track was dropped ([`component_rules`]), and no class prefix ships by
//! default, so adoption means deleting the app's hand-written rule in the same
//! commit that adds the generated one. `.card`, `.badge` and the tab classes
//! all already exist in goingson, and while both rules exist the cascade order
//! decides which wins. That is the one real risk in adopting this, and it is
//! why the migration lands per component rather than in one commit.
//!
//! # Interaction states
//!
//! [`interactive_rules`] emits four states, in emission order, and the order is
//! load-bearing: they are all specificity (0,2,0), so disabled beats hover by
//! coming last and by nothing else. Nothing here reaches for `:not(:disabled)`,
//! which would raise a selector this crate wraps in its own layer.
//!
//! Emitting the states here is what keeps an app from completing the primitive
//! from outside, by out-specifying a rule it does not own. Those overrides are
//! also what breaks under cascade layers: an app that declares `@layer` puts
//! its own rules in a named layer, and unlayered declarations outrank every
//! named layer regardless of specificity.
//!
//! Hover sits inside a capability query. `makeover-touch` answers whether a
//! fingertip has hover and `makeover-geometry` spells the condition; this crate
//! asks and does not decide, so no app has to take the hover state back on
//! touch.
//!
//! # The layer contract
//!
//! [`stylesheet`] emits into the `makeover` cascade layer ([`CSS_LAYER`], which
//! lives in `makeover-geometry` because that is the one crate every CSS emitter
//! in the family already depends on). `makeover-geometry` does the same for
//! `geometry.css`.
//!
//! The cascade resolves origin and importance, then layer, then specificity,
//! then source order, and **unlayered normal declarations outrank every named
//! layer**. An unlayered generated file therefore beats every rule an app owns,
//! regardless of specificity and regardless of loading last. Nothing errors when
//! that happens: the CSS is valid, the minifier is happy, and buttons and badges
//! look subtly wrong. The layer belongs here rather than in each app, because an
//! app cannot fix it from its own stylesheet: the fix is to layer the file it
//! does not own.
//!
//! An app should declare the order once, or the layer's position is decided by
//! whichever generated file the browser happens to see first:
//!
//! ```css
//! @layer makeover, base, components, responsive;
//! ```
//!
//! [`in_css_layer`] is re-exported for an app that assembles its own stylesheet
//! from this crate's pieces. Rules an app generates from
//! [`list::narrowing_css`] and [`list::grid_template_columns`] are as generated
//! as the ones here, so they belong in the same layer and this crate cannot put
//! them there on the app's behalf.
//!
//! # Suggestions
//!
//! `Outcome::Suggestions` carries `Candidate` rather than `Choice`, and a
//! candidate has no `unavailable`: a suggestion that cannot be picked is a row
//! a route should not have offered. What it has instead is a `detail`, the line
//! that tells it from a row reading the same, and it is drawn in
//! `--content-muted` rather than in the disabled token. A detail orients rather
//! than refuses, and every other secondary line in this crate reads the same
//! way. The class is `.form-suggestion-detail`.
//!
//! # An interval is one question with two ends
//!
//! [`makeover_layout::FieldKind::Interval`] emits a `role="group"` named by the
//! field's label, holding one `` per end.
//!
//! - **The group carries the error and the descriptions**, on the split
//! [`makeover_layout::FieldKind::Radio`] already uses here: what is wrong is
//! the answer, and a crossed interval is not the fault of either end.
//! - **Both boxes take the whole extent.** `min`, `max` and `step` describe the
//! axis, so they are written twice. The crossing rule is not emitted, because
//! HTML has no attribute for it and the description does not carry it: it
//! comes back as an error on the group, like every other refusal.
//! - **Which end is which is `aria-label` and nothing more.** The description
//! states direction structurally, by which member holds which name, and never
//! in words. Visible Min and Max captions are a page's own and reach the
//! group through [`form::Filling::trailing`].
//!
//! [`form::Value::Between`] is the second value. A separator inside one string
//! would make this crate the owner of a delimiter that either end could contain.
//!
//! # A number's unit is adjacent text
//!
//! HTML has no unit attribute and inventing one would be markup nothing reads,
//! so `Field::unit` is a `` after the control. It is named in
//! `aria-describedby` rather than left as decoration, because a number and what
//! it is measured in are one fact and reading the first without the second is
//! reading it wrong. What that buys is a unit a consumer can read back rather
//! than a suffix on a label it would have to parse.
//!
//! # A curve this renderer can carry, and one it declines
//!
//! A range takes its granularity from the curve (`Field::curve.step()`), every
//! other kind keeps `Field::step`, and `Curve::Linear` emits a plain range.
//!
//! **A constant-ratio curve emits a linear track, and that is the answer, not a
//! debt.** HTML has no logarithmic range input, so a described screen asking
//! for one is asking the browser for something it does not have, the same class
//! of request as [`makeover_layout::FieldKind::Date`] on a host with no
//! calendar. The renderer answers with the nearest control the host really
//! offers and keeps every fact that survives the translation: the extent, the
//! granularity, and the value's own units. What does not survive is resolution
//! at the small end. The value submitted is still a value in the field's own
//! units, which is what every handler on this path reads.
//!
//! The alternatives are worse in the specific way this stack exists to avoid.
//! Shipping JS that maps thumb position to value puts app code back in the
//! renderer. Changing what the control submits from a value to a fraction moves
//! the mapping to whoever reads the form, and a server reading these forms with
//! its own handlers would take a fraction where a value is expected, silently.
//!
//! When this reopens: the day a described screen on the webview path asks for a
//! non-linear range. The answer then is mapping in `quasi-router`, where one
//! implementation serves every host, not JS here.
//!
//! # A markdown field gets a preview
//!
//! A [`makeover_layout::FieldKind::Rich`] field is marked
//! `data-format="markdown"`, and [`form::editor_rules`] is what spends that
//! mark. The Write/Preview pair is a segmented control, so it takes the depth,
//! the focus ring and the chosen state from rules that already exist; the
//! preview pane is a well, because it stands where the control stood. Both are
//! gated on the attribute rather than on a class, which is what the attribute is
//! for. A permission taken and not spent turns every conversion into a
//! regression.
//!
//! **This crate renders no markdown.** The pane arrives empty and is filled by
//! whatever binds the editor, which is where the host's sanitiser already is. A
//! converter here would move that guarantee into a crate with no view of the
//! host's content-security posture.
//!
//! # Ranges, ghost text, and an option that cannot be picked
//!
//! - `FieldKind::Range` emits ``, and `Field::step` emits
//! `step`. The step is emitted only when the description carries one: the
//! browser's own default is `step="1"`, which is what a description means by
//! saying nothing, and is also what turns a 0-to-1 threshold into a
//! two-position control.
//! - A select with nothing chosen emits a disabled, selected, valueless first
//! option carrying `Field::placeholder`. HTML has no placeholder attribute on
//! `