/* LUCID — THE SCALE. The sizes, the corners, the shadows and the air, written
 * down once so that the rest of the app can stop guessing.
 *
 * The SPEED is not here, and the MOTION section below explains at length why
 * not: app.css has had --ease-out and --dur-fast at :root for a long time and
 * two thirds of the file already uses them. Defining a second set would have
 * been this exercise making its own disease worse.
 *
 * 11 September 2026. Matt, back after a week away: "Overall, it feels like
 * something that was assembled with cheap parts."
 *
 * That turned out to be a measurable statement rather than a mood. app/app.css
 * was counted — the counter is tools/count-the-scale.js and it still runs — and
 * on the day this file was written the shipping stylesheet held 49 distinct
 * font sizes, 24 distinct corner radii written as 36 distinct radius shapes,
 * 189 distinct box-shadows, 268 distinct padding shapes and 55 distinct
 * durations. Six of the type sizes — 11.5, 12.5, 12, 11, 13 and 10.5 — sit
 * inside a two-and-a-half pixel range. Nobody can see the difference between
 * 11.5px and 12px, so that difference communicates nothing at all. It is not a
 * scale. It is nine hundred decisions that were made separately and never met.
 *
 * That is the cheapness. It is not one ugly screen; it is that no two screens
 * were built to the same rules, and an eye reads that instantly without being
 * able to name it — which is exactly what happened.
 *
 * WHY THIS IS A SEPARATE FILE AND NOT A BLOCK AT THE TOP OF app.css. Two
 * reasons and both are practical. First, app.css is sixteen thousand lines and
 * several people are usually inside it; a file nobody else edits is a file
 * nobody else conflicts with. Second, index.html loads this one line BEFORE
 * app.css, so app.css both uses these tokens and, where a rule genuinely has to
 * differ, wins over them. The order is deliberate. Do not swap it.
 *
 * THESE ARE NOT COLOURS. Every value here is a measurement — a size, a corner,
 * a distance, a duration — and a measurement does not change between themes.
 * So they sit on :root once rather than being restated thirteen times. The
 * colour system is settled and lives in app.css, and nothing in this file
 * defines a hue. The ONE exception is written up where it happens, under
 * ELEVATION, because a shadow cannot be separated from its ink and pretending
 * otherwise would ship three shadows that are wrong on half the themes.
 *
 * NO BUILD STEP. Plain custom properties a browser reads as it stands. Matt
 * deploys by dragging a folder into Netlify.
 *
 * The mapping from every old value to its token is docs/THE-SWEEP-INVENTORY.md.
 * tools/the-scale.html is the page the scale was approved from.
 * tests/lint-the-scale.js counts what is still raw.
 */

:root{

  /* =======================================================================
     TYPE — seven sizes, and every step visibly a step.
     =======================================================================

     11 / 13 / 16 / 20 / 26 / 32 / 40, which is a ratio of about 1.25 all the
     way up: 1.18, 1.23, 1.25, 1.30, 1.23, 1.25.

     WHY THESE AND NOT THE 11/13/15/18/22/28/40 THAT WAS PROPOSED. The proposal
     was a good instinct and the inventory argued with it in two places.

     One: 13 to 15 is a ratio of 1.15, which at reading size is not a step. It
     is another 11.5-versus-12 — a difference too small to mean anything, which
     is the disease this file exists to cure. 16 makes it a step.

     Two: 28 to 40 is a ratio of 1.43 while everything under it is about 1.2, so
     the top of the scale reads as detached from the rest of it. Worse, the
     file's real cluster up there is 30/32/34 — eleven declarations, every one
     of them a large NUMBER on a stat tile — and a scale with no step between 28
     and 40 would have inflated all of them by a fifth. 32 is where they already
     live.

     WHAT EACH STEP IS FOR. This is the half that matters, because the app's
     problem was never that 11.5px was the wrong number. It was that 11.5px was
     being asked to mean four different things.

       --type-label   11px   Labels, meta, timestamps, counts, column heads,
                             badge text, the uppercase eyebrow over a section.
                             Anything you read to orient yourself rather than to
                             read.
       --type-body    13px   Everything you actually read: list rows, mail
                             subjects, field values, buttons, inputs, the title
                             of a row. If you are unsure, it is this one.
       --type-lead    16px   A lead line, the heading inside a panel, the first
                             sentence of something that wants to be read first.
       --type-title   20px   The title of a section or a panel.
       --type-page    26px   The title of a page. The largest piece of TEXT in
                             the app — see the note on figures below.
       --type-figure  32px   A number that is the point of the tile it sits in.
       --type-display 40px   The largest static size. Rare and deliberate.
       --type-hero    52px   The same figure again on a wide window. Two rules
                             wear it and both are single serif numbers on the
                             home tiles — see the note at the token itself.

     THE 11px STEP IS A DECISION AND IT COSTS SOMETHING. Today the app runs 9,
     9.5, 10, 10.5 and 11 at the bottom — 278 declarations across five sizes in
     a two-pixel range. All of them become 11px, so a handful of the smallest
     labels grow by up to a fifth. That is on purpose: app.css already carries a
     measured note beside .home-date saying ten-pixel uppercase did not have
     enough ink to clear 4.5:1, and 9px uppercase is under the floor for reading
     at all. The label still reads as a label — through its tracking, its weight
     and its ink — and not through being too small to see. That is the whole
     argument of row 1826 in one token.

     AND THE 13px STEP COSTS MORE. 11.5px is Lucid's body size today, in 150
     declarations, and it becomes 13px. The dense bottom of the app grows by
     about an eighth. This is the single largest visual change in the sweep and
     it is the one that must be done view by view with a screenshot each time,
     because text that grows an eighth is text that can wrap where it did not
     wrap before. 13px is still a dense body size — it is what Linear and a
     Bloomberg terminal run — and it buys the thing 11.5px could never buy: a
     label at 11 that is visibly not the body. */
  --type-label: 11px;
  --type-body: 13px;
  --type-lead: 16px;
  --type-title: 20px;
  --type-page: 26px;
  --type-figure: 32px;
  --type-display: 40px;

  /* AN EIGHTH STEP, ADDED BECAUSE THE ALTERNATIVE WAS TO DELETE A RESPONSIVE
     PAIR. Row 1827 counted seventeen raw values left in app.css and two of them
     were the same number: 52px, on .ht-count-n and on .ht-big, which are single
     serif figures on the home tiles at a wide window. .ht-big is already
     --type-display at 40px both in its base rule and again under 760px, so the
     52 IS the wide half of a 52/40 pair. Mapping it onto --type-display would
     not have shrunk a number, it would have made the tile the same size at
     every width and called that consistency.
     It is the eighth and last step. A ninth would mean the scale is being
     asked to hold a layout decision, and there are three fluid sizes below for
     type that is genuinely meant to grow with the window. */
  --type-hero: 52px;

  /* THE THREE FLUID SIZES, WHICH ARE NOT AN EIGHTH AND NINTH STEP.
     Five rules in app.css set a clamp() instead of a size — the home greeting,
     the weather temperature, the year on the timeline ring. Those are hero type
     that is meant to scale with the window, and freezing them to a static step
     would be a worse page, not a more consistent one. So they are promoted to
     tokens as they are rather than swept away, and the three values below are
     the file's own, unchanged. A sixth clamp anywhere should be one of these. */
  --type-fluid-1: clamp(19px, 2.1vw, 27px);
  --type-fluid-2: clamp(26px, 3vw, 38px);
  --type-fluid-3: clamp(46px, 5.6vw, 78px);

  /* =======================================================================
     WEIGHT — four, because four is what the app downloads.
     =======================================================================

     This is here because of row 1826, which says hierarchy in a dense app is
     carried by weight and colour rather than by size. That is only true if the
     weights are a ladder rather than a pile, and app.css currently sets seven:
     400, 500, 550, 600, 650, 700 and 800.

     THREE OF THOSE SEVEN WERE NEVER DOWNLOADED. index.html asks Google for
     Public Sans at 400, 500, 600 and 700 and Fraunces at 400, 500, 600 and 700.
     Nothing in the app has ever loaded a 550, a 650 or an 800. Fifty
     declarations — 42 of them at 650 — are asking for a weight that does not
     exist in the file the browser fetched, and the browser quietly resolves
     each one to something else. Half the "boldness" decisions in Lucid are
     therefore not decisions; they are roundings nobody chose.
     (Since 14 October the two faces are Manrope and Outfit, still fetched at
     400, 500, 600 and 700. Titles in Outfit use 600, and 500 for an accent.) */
  --weight-body: 400;
  --weight-medium: 500;
  --weight-strong: 600;
  --weight-loud: 700;

  /* =======================================================================
     FAMILY — three roles, and the picker moves the roles, not the rules.
     =======================================================================

     Row 2170: "need a font picker in settings". The picker is the easy half.
     The hard half is that app.css spelled its faces out by hand in 202 places
     — 'IBM Plex Mono' 76 times across four different spellings, 'Fraunces' 51
     times across four more — so a font change had exactly two shapes available
     to it and both were wrong:

       · paste a font-family onto `body` and let it inherit, which does not work
         at all here because those 202 rules override it; or
       · rewrite 202 declarations from JavaScript, which is row 1827's decay
         arriving by a faster route.

     So the faces become three tokens, the declarations reach for them, and the
     picker assigns ONE of two variables on :root. Nothing else moves. That is
     the argument the sizes won in row 1826, applied to the other half of type.

     THE THREE ROLES, and why there are three rather than one:

       --font-body     Everything you read and every control you press. This is
                       what "the font" means to a person, and it is the one the
                       picker changes most.
       --font-display  Page titles, the brand, section and panel titles, the big
                       serif figures on the home tiles. A second voice, used
                       sparingly.
       --font-mono     Every time, count, duration, confirmation code and column
                       of figures in the app: .agenda-time, .cal-hour,
                       .mail-time, .tb-count, .attn-mini-days, .tv-conf-value.

     --font-mono IS NOT IN THE PICKER, and that is the decision the row asked
     for rather than an omission. It is what makes a column of times line up:
     every glyph the same width by construction, in every weight, with no
     feature to switch on and nothing to get wrong. The Work band and the agenda
     are built on that and there is no preference worth making them ragged for.
     It is a token because the other two are, so a rule reaching for a face
     never has to know which of the three is choosable.

     AND EVERY FACE THE PICKER OFFERS HAS LINING FIGURES. A face whose numerals
     are OLDSTYLE — 3, 4, 5, 7 and 9 hanging below the baseline while 0, 1 and 2
     sit at x-height — turns a column of dates into a ragged hedge whatever else
     it does well, and no CSS can undo it unless the face carries an `lnum`
     feature. That was measured from the actual font binaries rather than
     assumed; the list, the method, and the one face it disqualified are at
     UI_FACES in app/app.js.

     THE FALLBACKS ARE REAL FALLBACKS. Each token ends in a generic family, so a
     blocked Google Fonts request degrades to the right KIND of face rather than
     to whatever the browser calls default. That already matters: app.css names
     'Newsreader' and 'JetBrains Mono' in two rules and index.html has never
     downloaded either, so both have always quietly rendered as something
     else. */
  /* 14 OCTOBER — THE FACES MATT CONFIRMED, after two rounds of demos:
       "Outfit for headlines. Manrope for everyday. and I like ledger for lucid
        text and folio for user text." … "I just confirmed it"
     Manrope replaces Public Sans as the face everything is read and pressed
     in; Outfit replaces Fraunces for titles, the brand and the big figures. He
     hated the old-style "tails", so there is no serif left in this pair.
     OUTFIT HAS NO ITALIC. Where a title used an italic accent (the brass
     "Everything you own."), app.css sets it upright at 500 in the same colour
     instead — the browser's fake slant is never asked for. Both fallbacks are
     sans now, so a blocked font server degrades to the right kind of face. */
  /* 15 Oct, Matt: "i want to switch to instument sans instead of mantrope (too wide)". */
  --font-body: 'Instrument Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  --font-display: 'Outfit', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  --font-mono: 'IBM Plex Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
  /* --mono is referenced in ten rules of app.css as `var(--mono, …)` and was
     never defined anywhere, so all ten have always fallen back. Defined here as
     the same thing --font-mono is, so those ten start working and follow the
     same token as the other sixty-six. */
  --mono: var(--font-mono);

  /* =======================================================================
     RADII — two on the scale, three shapes beside it.
     =======================================================================

     app.css sets 24 distinct radius values in 36 distinct shapes. Thirty-six
     radii is the visual equivalent of thirty-six fonts.

     Two of these are a SCALE — a control and a card — and they are concentric,
     which is the thing that makes nested corners look drawn rather than
     stacked: a control inset 6px inside a card takes 14 - 6 = 8. That is not a
     coincidence, it is why these two numbers and not two others.

       --radius-control  8px   Buttons, inputs, chips, menu rows, small tiles.
                               Absorbs today's 5, 6, 7, 8 and 9 — 123 uses.
       --radius-card    14px   Cards, tiles, panels, sheets, dialogs. Absorbs
                               today's 10 through 22 — 196 uses. 14 is already
                               the single most-used card radius in the file and
                               it is what the newest tiles wear.

     The other three are not steps on a scale, they are SHAPES, and collapsing
     them into the two above would be wrong rather than tidy:

       --radius-pill    999px  A capsule. Its radius is "however tall this is,
                               halved", which is not a number on any ramp. 28
                               rules say 999px and another 34 say 20px on a
                               24px-tall element, which is the same intention
                               spelled differently.
       --radius-round     50%  A circle. Avatars, dots, the 92 places that mean
                               "this is round", not "this is quite rounded".
       --radius-tight     4px  The exception, and the one to argue about. It is
                               for marks smaller than a control — a legend
                               swatch, a progress bar, a 2px status pip — where
                               8px on a 10px-tall object is not a rounded
                               rectangle, it is a lozenge. 56 uses today at 2,
                               3 and 4px. If Matt wants strictly two radii this
                               is the one to cut, and those 56 marks become
                               square. */
  --radius-control: 8px;
  --radius-card: 14px;
  --radius-pill: 999px;
  --radius-round: 50%;
  --radius-tight: 4px;

  /* =======================================================================
     ELEVATION — three, and the one place this file touches ink.
     =======================================================================

     THE COUNT SAID 189 SHADOWS AND THE COUNT WAS MISLEADING. Sorted by what
     they are actually DOING, only 71 of the 244 shadow declarations in app.css
     are drop shadows. The rest are focus rings (0 0 0 2px, 25 uses), coloured
     glows (0 0 14px, 69 uses), ring-and-glow pairs (23 uses) and insets (24
     uses). So "three elevations" was never going to cover this file: two
     conventions had been written two hundred separate times and neither of them
     is elevation. The ring geometry is below; the glow is a colour decision and
     stays with the colour system.

     And 145 of those 244 declarations already resolve through a var(), most of
     them --glass-shadow, which app.css tunes per theme across thirteen blocks.
     That is a good tokenised elevation already and nothing here replaces it. A
     glass surface keeps var(--glass-shadow). These three are for the surfaces
     that are NOT glass — the matte finish, plain cards, menus, the 44 raw
     shadows that were written by hand.

     WHY THIS BLOCK CARRIES A COLOUR WHEN THE REST OF THE FILE DOES NOT. A
     shadow is a colour. rgba(0,0,0,.5) under a card is right on Obsidian and is
     a grey bruise on Alabaster, and app.css already knows this — its light
     themes shadow in a warm brown, rgba(92,72,48,...), rather than in black.
     Three elevation tokens written in black would be wrong on every light
     theme in the app, which is exactly the failure the house rule about
     hardcoded hexes exists to prevent. So the ink is a channel: a triplet and
     two alphas, restated once for light mode using the same [data-mode="light"]
     guard app.css uses. It defines no hue that the colour system owns and it
     overrides nothing that already exists.

       --elev-flat      The page. A thing at rest is not floating, and most of
                        the app should be here.
       --elev-raised    A card, a tile, a row under the cursor. Close to the
                        surface.
       --elev-floating  A menu, a dialog, a sheet. Off the page and casting.

     AND THE THIRD LAYER, --elev-lip, WHICH WAS ADDED AFTER LOOKING AT IT.
     The first version of these tokens was two black layers each, and on the
     dark themes it did not work — tools/the-scale.html was rendered on a
     #0B0D16 ground and "raised" and "floating" came back indistinguishable from
     "flat", because a black shadow on a near-black page is nothing at all. That
     is not a theory; it is what the screenshot showed, and it is the exact
     failure this page exists to catch before Matt does.
     What makes a dark card read as lifted is the LIT TOP EDGE — the one-pixel
     line where the light catches the front of the object — and app.css has
     known this the whole time: every one of its thirteen --glass-shadow
     definitions ends in `inset 0 1px 0 rgba(255,255,255,…)`. So each elevation
     carries that line too, brighter on light grounds than on dark, in the same
     proportions app.css already uses — and brighter on floating than on raised,
     which is the second cue that separates the two. On a dark ground the
     shadows alone did not separate them, because a shadow on a near-black page
     has almost nowhere to go. The lit edge does most of the work there and the
     shadow does most of it on cream. The honest statement of this system is
     therefore: on light grounds elevation is a shadow, and on dark grounds it
     is an edge with a shadow underneath it. */
  --elev-ink: 0, 0, 0;
  --elev-raised-near: 0.34;
  --elev-raised-far: 0.60;
  --elev-float-near: 0.42;
  --elev-float-far: 0.78;
  --elev-lip: inset 0 1px 0 rgba(255, 255, 255, 0.06);
  --elev-lip-hi: inset 0 1px 0 rgba(255, 255, 255, 0.13);

  --elev-flat: none;
  --elev-raised:
    0 1px 2px rgba(var(--elev-ink), var(--elev-raised-near)),
    0 8px 20px -8px rgba(var(--elev-ink), var(--elev-raised-far)),
    var(--elev-lip);
  --elev-floating:
    0 2px 6px rgba(var(--elev-ink), var(--elev-float-near)),
    0 24px 56px -12px rgba(var(--elev-ink), var(--elev-float-far)),
    var(--elev-lip-hi);

  /* THE RING, WHICH IS GEOMETRY HERE AND COLOUR IN app.css.
     A focus ring is `box-shadow: 0 0 0 var(--ring-width) var(--accent)`, and
     the two halves of that belong to two different owners. The width and the
     gap are a measurement and live here; which colour rings which control is a
     decision the colour system makes and this file does not touch. 25 rules
     currently pick a ring width of their own, between 1.5px and 7px. */
  --ring-width: 2px;
  --ring-gap: 1px;

  /* =======================================================================
     SPACE — one unit of 4px, used only in multiples.
     =======================================================================

     268 distinct padding shapes and 39 distinct gap shapes, with 6, 7, 8, 9,
     10, 12 and 14 all in heavy use. The name of each step is its multiple, so
     --space-3 is three units is 12px and there is never a question about what a
     number means.

     THE HONEST WARNING, BECAUSE THIS IS THE STEP THAT WILL ARGUE BACK. 6px is
     used 74 times as a gap and 77 times as a padding, and on a 4px grid it has
     nowhere to go but 8px. That loosens the inline gap between an icon and its
     label by a third, in about a hundred and fifty places, in the densest part
     of a deliberately dense app. It is the change in this whole sweep most
     likely to be WRONG, and it is the one to look at on a screen rather than in
     a diff. If the chip rows read loose after it, the answer is a named 6px
     half-step here — not a return to 5, 6 and 7 scattered through sixteen
     thousand lines.

     --space-hair is 2px and exists for the same reason --radius-tight does:
     the distance between a 6px status dot and its label is not a spacing
     decision on the same ramp as the padding of a dialog. 16 gaps and 76
     paddings use it today. */
  --space-hair: 2px;
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 20px;
  --space-6: 24px;
  --space-8: 32px;
  --space-10: 40px;
  --space-12: 48px;
  --space-16: 64px;

  /* =======================================================================
     MOTION — ALREADY SOLVED, AND NOT BY THIS FILE.
     =======================================================================

     THIS SECTION DEFINES ALMOST NOTHING, AND THAT IS THE FINDING.

     Row 1822 asked for one curve and three durations, on the measurement that
     app.css holds eight easing curves and durations from 120ms to 1.6s. Both
     halves of that measurement turned out to be counting the wrong thing, and
     the second half changed what this file should do.

     FIRST, MOST OF THOSE DURATIONS ARE LOOPS. 55 distinct durations sounds like
     chaos; 44 of them belong to keyframe ANIMATIONS — spinners, marquees, the
     orbit on the login page, a 290-second drift — and a loop that runs for five
     minutes is not a state change that should take 120ms. Sorted properly there
     are 19 durations across 63 real transitions, and 45 of those 63 already
     land between 120ms and 180ms.

     SECOND, AND THIS IS THE ONE. app.css ALREADY HAS A MOTION SYSTEM, at :root
     around line 30, and it is the majority of the file:

         --ease-out      cubic-bezier(0.22, 1, 0.36, 1)      194 uses
         --dur-fast      130ms                               133 uses
         --dur-base      220ms                                47 uses
         --ease-in-out   cubic-bezier(0.65, 0, 0.35, 1)        6 uses
         --ease-spring   cubic-bezier(0.34, 1.4, 0.64, 1)      1 use
         --dur-slow      320ms                                 1 use
         --dur-view      150ms                                 1 use

     Of the 277 transition and animation declarations in app.css, 129 already
     reach for one of those and 65 still write a literal. The comment above them
     says why they were introduced, in 2026's own words: "before this there were
     five ad-hoc durations and easing was whatever `ease` happens to be — which
     is why nothing felt related to anything else". Somebody already fought this
     fight and won two thirds of it.

     SO THIS FILE DOES NOT DEFINE A SECOND MOTION VOCABULARY. Adding --ease and
     --dur-state beside --ease-out and --dur-fast would put two names on one
     decision, in a codebase whose entire problem is that one decision has too
     many names. It would make the cheapness worse while claiming to fix it.
     The existing names win on the only argument that matters: 374 declarations
     already use them and none use ours.

     WHICH MEANS ROW 1822 IS SMALLER THAN IT READS, and the sweep's motion job
     is not to invent a voice. It is:

       state change   →  var(--dur-fast)     130ms
       appearing      →  var(--dur-base)     220ms
       crossing       →  var(--dur-slow)     320ms
       the curve      →  var(--ease-out)

     ...applied to the 65 declarations that still write a literal, and then a
     decision from Matt about whether --ease-spring (one use), --ease-in-out
     (six) and --dur-view (one) survive as deliberate exceptions or go.

     ONE CORRECTION TO THE BRIEF, because it matters and it is checkable. Row
     1822 says cubic-bezier(.2,.8,.3,1) "is already the most used in the file".
     That is true only of curves written out by hand, where it leads with 7.
     Counting what the browser actually resolves, the most-used curve in Lucid
     is --ease-out's cubic-bezier(0.22,1,0.36,1) at 194 uses — twenty-eight
     times more. It is the same kind of curve: a fast start that decelerates
     into its landing. So the recommendation stands and the name changes.

     THE TWO THINGS THAT GENUINELY WERE MISSING are below, and they are here
     rather than in app.css only because this file is the one nobody else is
     inside this wave. Both belong beside the six above and should move there
     the first time somebody is editing that block anyway. */

  /* `linear` is used 30 times and almost all of them are loops, where linear is
     not laziness but correct — a spinner that eases is a spinner that limps.
     Naming it lets a sweep say "this one is deliberate" out loud, instead of
     leaving a bare keyword that reads like an oversight. */
  --ease-linear: linear;

  /* The prefers-reduced-motion value. app.css writes the literal 0.01ms in two
     places; nothing named it. 0.01ms rather than 0 because a real zero makes
     some browsers skip the transitionend event the app listens for. */
  --dur-none: 0.01ms;
}

/* THE LIGHT HALF OF THE ELEVATION INK, AND NOTHING ELSE.
   [data-mode="light"] rather than [data-theme="light"] for the reason app.css
   writes out beside .mail-unread-dot: there is more than one light theme, and
   naming one of them hard-codes a bug into the next one. setTheme stamps the
   mode alongside the name so "is this a light theme" has one answer in one
   place, and this uses that answer.

   Warm brown rather than black, and softer, because that is what app.css
   already does on its light themes — rgba(92,72,48,.10) under a card on
   Alabaster and Parchment. A black shadow on cream is a smudge. */
:root[data-mode="light"]{
  --elev-ink: 92, 72, 48;
  --elev-raised-near: 0.08;
  --elev-raised-far: 0.18;
  --elev-float-near: 0.10;
  --elev-float-far: 0.26;
  /* Brighter on cream than on black, because on a light ground the lit edge is
     most of what a reader sees: 0.75 is what app.css's own light theme uses. */
  --elev-lip: inset 0 1px 0 rgba(255, 255, 255, 0.75);
  --elev-lip-hi: inset 0 1px 0 rgba(255, 255, 255, 0.95);
}

/* WHAT THIS FILE CANNOT DO FOR prefers-reduced-motion, AND WHY IT SAYS SO
   RATHER THAN PRETENDING.

   The obvious next move is a @media (prefers-reduced-motion: reduce) block here
   that sets --dur-fast and --dur-base to --dur-none, and it would not work. A
   media query adds no specificity: both blocks target :root, app.css is the
   later stylesheet, and app.css's plain declarations would win over this one
   every time. Writing it anyway would be a rule that looks like it works and
   does not, which is the one thing this codebase does not do.

   So it belongs beside the durations themselves, in app.css, and it is three
   lines whenever somebody is next in that block:

       @media (prefers-reduced-motion: reduce){
         :root{ --dur-fast: var(--dur-none); --dur-base: var(--dur-none);
                --dur-slow: var(--dur-none); --dur-view: var(--dur-none); }
       }

   That is a promise the app cannot make today — its durations were written in
   55 separate places — and can make the moment the sweep lands. Noted here so
   the next wave finds it rather than rediscovering it. */
