/* ============================================================================
   ShieldLabs Console - theme.css
   Token layer + base layer.

   RULE: no hex may appear outside this file. That is now true, not aspirational:
   a scan of the twelve prototypes finds zero hex literals and zero references to
   the old token names. (design/support.js holds #b00020 and #fff in the
   dc-runtime error banner it mirrors from upstream; that is harness code, not
   console style, and outside this rule.)

   TWO themes, both in the brand palette: dark (default) and light. There is no
   third. The warm brown-black dark and warm cream light that this file used to
   carry were a different design system, and [data-theme="brand"] was the product
   palette sitting alongside them as an opt-in curiosity. The product has one
   palette, so the brand palette IS the console, in both directions of light.

   Every ratio in a comment below was computed with a WCAG 2.x relative-luminance
   implementation, compositing color-mix() against its real ground as floats
   without rounding the composite, and IS THE MINIMUM across every ground that
   token can land on, with the ground named beside it. There are four grounds,
   not three: --card, --bg, --hover and --sunken. Which one is worst flips with
   the theme, and that is the single most common way a figure in a file like this
   turns out to be a friendly number wearing a minimum's clothes:

       in LIGHT the worst ground is --sunken (the darkest surface)
       in DARK  the worst ground is --hover  (the lightest surface)

   For a CHROMATIC token the ground set is eight, not four: those four surfaces,
   plus that tone's own chip fill composited over each of them. That holds for
   the -ink tokens, which carry text at 4.5, and equally for the marks, which
   carry dots and strokes at 3.0 and are just as capable of landing on their own
   tint. It was written here for the inks alone until the marks were measured;
   see "A MARK ALSO LANDS ON ITS OWN TINT" below for what that cost.

   ---- Corrections made to the supplied palette, with the measurement ---------

   The supplied light column was measured on --bg, and the supplied dark inks on
   their own fill over --card or --bg. Re-measured across all four grounds, nine
   values did not hold and were walked along their own hue until they did. Hue
   and saturation are preserved in every case; only lightness moved.

     LIGHT  --text-2   #6B7280 -> #686F7D   was 4.39 on --hover, 4.32 on --sunken
            green-2     #16A34A -> #16A049   was 2.99 on --hover
            yellow mark #A88E09 -> #A28909   was 2.92 on --hover
            orange mark #D87606 -> #D17206   was 2.93 on --hover
            all five inks darkened one notch: the supplied set bottomed at
            4.24-4.41 on its own fill over --sunken, which is what a chip in a
            table head actually sits on
     DARK   green ink   #00A36D -> #00A770   was 4.38 on its fill over --hover
            green-2 ink #17A74C -> #17A84C   was 4.49 there
            red ink     #E45A5A -> #E76C6C   was 4.07 there

   Everything else in the supplied palette reproduced exactly and is used as
   given. The full dark column, in particular, was already a true four-ground
   minimum and is untouched.

   THAT TABLE IS NOT THE WHOLE LIST. Two more light values moved later, on a
   measurement the round above did not make: the band MARKS on their own chip
   fill. Light's green went #009966 -> #009161 and light's orange went the
   further step #D17206 -> #C06906. Both are recorded with their numbers under
   "A MARK ALSO LANDS ON ITS OWN TINT" below, which is also where the rule that
   found them is written down.

   ---- The ink / mark split. Read this before changing any colour. -----------

   THIS IS THE LOAD-BEARING IDEA IN THIS FILE. A tone has TWO values and they
   are not interchangeable:

     MARK  the tone as a dot, a bar, a chart stroke, a chip border, a gauge
           arc. No character is drawn in it. Floor 3:1 (WCAG 1.4.11, a
           graphical object carrying information), measured on the bare
           surfaces AND on the tone's own chip fill, because a chip puts a
           tone's dot on that same tone's tint. Same eight grounds as the ink.
     INK   the tone as TEXT: a band word, a chip label, a gauge figure.
           Floor 4.5:1 (WCAG 1.4.3), measured on the bare surfaces AND on the
           tone's own chip fill, because a chip puts a tone's text on that same
           tone's tint.

   On a DARK ground the brand hexes carry both roles at mark weight and need
   only a small lift to carry text, so --trusted / --suspicious / --dangerous
   are the brand hexes exactly. On a LIGHT ground they cannot: green reads 3.26,
   and the brand yellow and orange read 1.50 and 2.21, which fails even the 3.0
   non-text floor. So light's yellow and orange take a derived MARK value too,
   and so, once the fill grounds were measured, does light's green. The next
   block is where that happened.

   A MARK ALSO LANDS ON ITS OWN TINT, AND THAT IS ITS BINDING GROUND.
   The inks were measured on their own chip fill from the beginning and this file
   said so. The marks were not, and they had to be: Analytics paints a selected
   band filter with background:<tone>-fill and puts the 5px <tone> dot inside it,
   so that dot's ground is its own 14% tint. A dot is a graphical object carrying
   information, so the floor is 3.0. Minimum across all four surfaces, bare
   ground first, then the tone's own fill over each of them:

                    LIGHT  bare / own fill      DARK  bare / own fill
     trusted        3.59 / 3.04                 4.67 / 3.96
     suspicious     3.56 / 3.04                 7.38 / 5.82
     dangerous      4.28 / 3.48                 3.55 / 3.22
     accent         4.06 / 3.41                 3.74 / 3.27

   Dark needed nothing; its worst is --dangerous at 3.22. LIGHT FAILED ON TWO
   TONES, not one. The brand green read 2.78 on its own fill and the previous
   orange #D17206 read 2.65, both under 3.0. Note how nearly this hides: on
   --card alone the green reads 3.09 and clears. It is the four-ground minimum
   that catches it, exactly as it was the four-ground minimum that caught the
   nine values in the corrections table above.
   Both were walked along their own hue, hue and saturation preserved and only
   lightness moved, the same treatment light's yellow and orange already had:

     LIGHT  --trusted     #009966 -> #009161   was 2.78 on its own fill
            --suspicious  #D17206 -> #C06906   was 2.65 on its own fill

   THE RULE THIS PRODUCES: a mark is measured on the four surfaces AND on that
   tone's own fill over each of them, floor 3.0. That is eight grounds, the same
   eight the ink is measured on at 4.5. A mark that clears only the bare surfaces
   has not cleared. Darkening a mark darkens the fill derived from it, so both
   inks were re-measured on their new ground and both still clear: light
   --trusted-ink 6.26, light --suspicious-ink 6.13. (Recomputed 2026-09-06:
   they read 4.71 / 4.71 before the inks were darkened at section 2, and this
   paragraph was not re-derived with them.)

   The brand hexes remain the identity in the logo, in illustration and in
   marketing. The interface uses the derived value where a ratio demands it, and
   this file is where that translation happens once. On a light ground that now
   covers two of the three band marks, green and orange; only red survives there
   as its brand hex. On a dark ground all three still do.

   THE COROLLARY, and it is the one this file is most often asked to break:
   THE NEUTRAL TEXT RAMP IS MEASURED ON THE FOUR SURFACES, NOT ON TINTS. Put
   --text-2 or --text-3 on an --accent-soft or a band fill and it is out of its
   measured range: it reads 3.56 to 4.03 there in both themes. Text sitting on a
   tint takes that tone's INK, which is exactly what --on-accent-soft and the
   three -ink tokens are for, each measured on the fill it belongs to.
   Making the neutral ramp survive every tint instead would mean darkening
   light's --text-2 to #5A606C and lifting dark's to #8E97A6, a visible change to
   369 references (308 --text-2 plus 61 --text-3, counted at this revision; a
   reference count drifts with every component edit, so treat it as a magnitude
   and not a constant) to serve a handful of chips. The ramp stays; chips use
   ink.
   Live scan, 12 screens x 2 themes x 3 viewports (1440x900, 1024x768, 390x844):
   72 of 72 combinations, 10,246 text nodes, ZERO AA failures. Every node was
   measured against its real composited ground, walking ancestors until the stack
   went opaque, so a caption on a tint was measured on the tint and not on the
   card behind it. There is no AA failure left in the package.
   An earlier revision of these lines reported one, in Analytics in light, where
   the selected entity chip and the selected chart-scale chip were said to put a
   --text-3 caption on --accent-soft at 4.03. That control has changed and the
   reading is gone: Analytics in light now measures 375 nodes and zero failures
   at all three viewports. Do not go hunting for it.
   The last site of this class to be fixed was the rail's quota figure, and it is
   worth recording because a scan like the one above cannot find it: it is an
   11px figure on the --accent-soft of the ACTIVE nav item, so it only exists
   when Usage is the active route, and every screen's default route is something
   else. Driven into that state and measured: --text-3 there reads 4.24 dark and
   4.19 light, both under AA, and the shipped --on-accent-soft reads 5.17 in both
   themes. Same defect as the Analytics one and the same one-token substitution
   fixed it, --text-3 -> --on-accent-soft. Neither was a change to this file.
   The lesson for the next person to run this scan: a default-state sweep proves
   only the default states. A control that appears only in an interaction state
   has to be driven into that state before its ground is real.

   The previous build merged the two roles and failed on 91 references: --green
   was one token doing button fill, dot and band word at the same time, so the
   band words shipped at 3.61-4.06 as text. Never re-merge them. If you find
   yourself writing color:var(--trusted) you want var(--trusted-ink).

   ---- Card versus page --------------------------------------------------

   "The card is duller than the page" is a rule from a different palette and it
   is not restated here. What is true in both themes: THE CARD SEPARATES FROM
   THE PAGE BY ONE SMALL LUMINANCE STEP, AND THE DIRECTION IS THE THEME'S OWN.
   In light the page is #F9FAFB and the card is #FFFFFF, so the card is
   BRIGHTER than the page: that is the brand's own model, cards floating on a
   tinted ground, and it is correct. In dark the page is #0C0E10 and the card
   #131518, so the card is brighter there too, this time away from the extreme.
   Measured step, card against page: 1.045 light / 1.057 dark.
   Never elevation: cards carry no shadow in either theme.

   ---- The four decisions this file encodes -------------------------------

   D1  The console is responsive down to 360px. Breakpoint tokens live here;
       the @media blocks live in the screens, because a media query cannot read
       a custom property. See "breakpoints" in section 5.

   D2  Entity-card counters are LIFETIME, its identifications table is
       period-scoped, and the card labels which is which. No token consequence;
       recorded here so this file is a complete record of the decisions. The
       label it needs is a --text-2 line next to the figure, per the rule that
       anything a reader needs in order to read a figure correctly is visible
       copy and not a title attribute.

   D3  Dark is what a page gets with no attribute. Light is the explicit opt-in.
       Ten of twelve screens stamp nothing, so bare :root carries the DEFAULT,
       and the owner\'s default is LIGHT. [data-theme="dark"] is the opt-in now;
       the shell toggle still swaps both ways and stamps either value.
       [data-theme="dark"] is kept working alongside :root because the shell
       writes document.documentElement.dataset.theme explicitly.
       Reverse by: swapping the contents of the two theme blocks.

   D4  The Settings Team / Alerts / Domains / Activity feature does NOT exist.
       This line used to record the opposite, that the feature exists and gets
       bound. The owner reversed that and the feature was removed from the
       package; the reversal is written down here so that nobody restores the
       feature on the authority of the old line. No token consequence in either
       direction: it used tokens that were already here, and removing it
       retired none of them.

   ---- What is deliberately NOT in this file ------------------------------

   Per-screen @media blocks, component CSS, the 65ch prose cap, the icon set,
   and the type scale beyond the base size. Those belong to the screens and to
   the component layer; putting them here would make this file the place people
   edit layout, which is how a token file rots.
   ============================================================================ */


/* ============================================================================
   1. THEME: DARK. The default (D3), and the explicit attribute.
      Every token is restated in both themes. Tokens that appear only in
      section 3 or 5 are invariant by decision, and each says why.
   ============================================================================ */
/* Dark is declared FIRST and light second, so the default has to exclude an
   explicit dark stamp: :root and [data-theme="dark"] carry the same
   specificity, and the later block would otherwise win over the theme the
   reader actually chose. Caught by the toggle turning nothing dark.     */
[data-theme="dark"] {

  /* ---- surfaces ---------------------------------------------------------
     Derived from the brand ink #1D2025, which is hue 218 at saturation 0.121,
     so the dark theme is the same colour family as the light one rather than
     a foreign neutral. The card is LIGHTER than the page here, one 1.057 step
     away from the dark extreme.
     --sunken is the fourth surface. It must be a real token and never
     color-mix(in srgb, var(--text) 6%, transparent), which is what the package
     shipped four times: that mix carries the LIGHT --text into the ground, so
     in a dark theme it resolves LIGHTER than --card and lighter than --hover,
     giving a recessed surface that outshines a hovered row.                */
  /* Native UI follows the theme or it does not follow anything: without this
     a dark console gets a light scrollbar, a light select, a light date picker
     and a white autofill over its own dark field. One line, and it is the
     only line in this block that paints nothing itself.                   */
  color-scheme: dark;

  --bg:            #0C0E10;
  --card:          #131518;
  --hover:         #1A1C21;
  --sunken:        #08090A;   /* code blocks, table head, quota trough,
                                 disabled and readonly fills              */

  /* ---- text ramp --------------------------------------------------------
     --text-2 is the DIMMEST LEGIBLE TEXT IN THIS PALETTE: it is the first
     step down from --text that still clears 4.5 on all four grounds, so there
     is no third text level below it. --text-3 is therefore the same value,
     kept as a name because 61 references carry the role "smallest text, code
     comments, gauge end labels, input prefixes" and that role is real even
     when the value cannot differ. If the palette ever gains headroom, this is
     the one line to change. Anything dimmer than --text-2 must be --graphic,
     which never renders a character.
     These three are measured on the four SURFACES. On a tint they are out of
     range and the tone's ink takes over; see the corollary in the header.  */
  --text:          #EEEFF2;   /* 14.83 min, on --hover; 15.91 card, 16.82
                                        bg, 17.33 sunken                   */
  --text-2:        #798496;   /*  4.51 min, on --hover: labels, captions,
                                        table headers                      */
  --text-3:        var(--text-2);  /* same value by measurement, not by
                                      laziness. See the note above.        */
  --graphic:       #5F6979;   /*  3.07 min, on --hover. NON-TEXT ONLY: axis
                                        strokes, gauge track, gridlines.
                                        Nothing that draws a character.    */

  /* ---- lines ------------------------------------------------------------
     WCAG 1.4.11 covers a boundary needed to identify a component. A row
     divider is not one; an input's border is the only thing that makes it an
     input. So the hairlines stay quiet BY DECISION and a control a user has
     to find gets --line-control instead. Raising the hairlines to 3:1 would
     turn a quiet console into a wireframe and delete the gutter grid, which
     is this design's structural device.
     --line-control is measured on four grounds because --sunken is the
     readonly fill, and 1.4.11 exempts a disabled control but not a readonly
     one, so 3:1 has to hold there too.                                    */
  --line:          #262A31;   /* 1.18 min, on --hover: decorative hairline
                                       and gutter fill; 1.38 on --sunken   */
  --line-2:        #363B45;   /* 1.52 min, on --hover: emphasised structure,
                                       card edge; 1.77 on --sunken         */
  --line-control:  #5F6878;   /* 3.03 min, on --hover; 3.55 on --sunken.
                                       MANDATORY on input / select /
                                       checkbox / radio / switch /
                                       secondary-button boundaries         */

  /* ---- accent: the green you are allowed to put white text on -----------
     The brand green #009966 gives white 3.65, and the console puts white on
     it at 33 button sites. #00875A is the nearest hue-preserving green where
     white clears 4.5, and it still passes 3:1 as a mark on every ground in
     both themes. The logo keeps #009966; the button fill does not.
     This is the same token in both themes, which is why it is the one 4.55 in
     this file that does not depend on a ground.                           */
  --accent:        #00875A;   /* 3.74 bare as a mark, on --hover; 4.38 sunken.
                                 3.27 on its own --accent-soft, which is what
                                 a mark inside an active nav item sits on.
                                 White on it 4.55, ground-independent      */
  --on-accent:     #FFFFFF;   /* 4.55 on --accent                          */
  --on-accent-soft: var(--trusted-ink);
                              /* 4.79 min, on the --accent-soft tint over
                                 --hover: active nav item, selected tab.
                                 The green ink already clears it, so this is
                                 an alias and cannot drift from the band.  */

  /* ---- band MARKS: dots, bars, chart strokes, chip borders. Non-text. ---
     On this ground the brand hexes survive exactly as given, on the bare
     surfaces AND on their own 14% fill. Two figures per tone: bare minimum,
     then the minimum on the tone's own fill, which is where a band dot sits
     inside a selected band chip. Both bottom out on --hover. Floor 3.0.   */
  --trusted:       #009966;   /* 4.67 bare / 3.96 own fill  (brand green)  */
  --suspicious:    #F9911A;   /* 7.38 bare / 5.82 own fill  (brand orange) */
  --dangerous:     #DC2828;   /* 3.55 bare / 3.22 own fill  (brand red)    */

  /* ---- band INKS: band words, chip labels, gauge figure. Text. ----------
     Measured on the four bare surfaces AND on this tone's own 14% chip fill
     composited over each of them, which is eight grounds. The binding one is
     always the fill over --hover.                                         */
  --trusted-ink:    #00A770;  /* 4.63 min, on its fill over --hover        */
  --suspicious-ink: #E17B06;  /* 4.53 min, on its fill over --hover. This is
                                 the TIGHTEST ink in the file, in either
                                 theme. Treat it as immovable.             */
  --dangerous-ink:  #E76C6C;  /* 4.97 min, on its fill over --hover.
                                 #DC2828 as text bottoms at 3.55           */

  /* ---- chart series: laddered in luminance, not only in hue -------------
     Deliberately NOT the band tones: a green chart line in a console whose
     green means Trusted is a false signal. Greyscale separation between
     neighbours 1.60 / 1.64 / 1.67. Hue alone cannot separate four series for
     every reader, so series must also differ in dash.                     */
  --s1: #7D53B9;              /*  3.09 min, on --hover */
  --s2: #508ECC;              /*  4.94 */
  --s3: #DAA890;              /*  8.10 */
  --s4: #D6EADC;              /* 13.52 */

  /* ---- code: the snippet palette ---------------------------------------
     Snippets are the one place in this console where a WORD carries colour,
     so the rule the chart series live under applies here twice over: these
     are NOT the band tones. Green, orange and red mean Trusted, Suspicious
     and Dangerous in every other pixel, and a keyword wearing one would read
     as a verdict on the code. These borrow the series' hue family, which is
     the palette's only non-semantic one, and they ladder in luminance as
     well as in hue, so the three still separate for a reader who cannot
     separate violet from blue. There is no fourth: comments take --text-2,
     plain code takes --text, and that is the whole scheme.
     Measured on the four surfaces, bottoming out on --hover. The blocks
     themselves sit on --bg, which is the second-kindest of the four.      */
  --code-str: #8FC0F0;        /*  8.90 min, on --hover: strings, URLs,
                                        numbers                            */
  --code-key: #B99BF0;        /*  7.31 min: keywords, tag names            */
  --code-fn:  #D179B5;        /*  5.77 min: called functions, attribute
                                        names, config directives           */

  /* ---- focus -----------------------------------------------------------
     The package had 14 outline:none declarations and zero focus rules, so the
     browser default was the only ring: rgb(0,95,204), a blue in a system that
     contains no blue. Green, clearing 3:1 on every ground it lands on. The
     ground moved when the dark neutrals did, so this was re-measured.     */
  --focus:      #00875A;      /* 3.74 min, on --hover; 4.25 on --bg, which
                                 is what the halo below actually sits on   */

  /* ---- elevation -------------------------------------------------------
     Two values, floating panels only. There is no --elev-card: cards never
     carry a shadow, in either theme.                                      */
  --elev-popover: 0 8px 24px -6px rgba(0,0,0,0.55);
  --elev-dialog:  0 24px 64px -16px rgba(0,0,0,0.70);
}


/* ============================================================================
   2. THEME: LIGHT. Opt-in (D3). Same brand palette, other direction.
      The supplied light column was measured on --bg only; every value here was
      re-measured on --sunken and --hover and nine of them moved. See the
      corrections table in the header.
   ============================================================================ */
:root:not([data-theme="dark"]),
[data-theme="light"] {

  /* The brand's own model: a #FFFFFF card floating on a #F9FAFB tinted ground.
     The card is BRIGHTER than the page and that is correct, see the header.
     --sunken continues the same hue-218 family one step below --hover, and it
     is the worst ground in this theme: every minimum below lands on it.    */
  color-scheme: light;

  --bg:            #F9FAFB;
  --card:          #FFFFFF;
  --hover:         #F3F4F6;
  --sunken:        #F1F2F4;   /* 1.120 against --card                      */

  --text:          #1D2025;   /* 14.58 min, on --sunken; 15.63 bg, 16.34
                                        card. The brand Text Primary       */
  --text-2:        #686F7D;   /*  4.51 min, on --sunken. The brand Text
                                        Secondary #6B7280 reads 4.39 on
                                        --hover and 4.32 on --sunken, so the
                                        neutral gets the same ink treatment
                                        the chromatic tones get            */
  --text-3:        var(--text-2);  /* no third legible step, as in dark    */
  --graphic:       #838B9A;   /*  3.06 min, on --sunken. NON-TEXT ONLY     */

  --line:          #E5E7EB;   /* 1.11 min, on --sunken; 1.18 on --bg. The
                                       brand Line, quiet by decision       */
  --line-2:        #D1D5DB;   /* 1.32 min, on --sunken                     */
  --line-control:  #818C9D;   /* 3.04 min, on --sunken                     */

  --accent:        #00875A;   /* 4.06 bare as a mark, on --sunken; 4.36 bg,
                                 4.55 card. 3.41 on its own --accent-soft.
                                 White on it 4.55                          */
  --on-accent:     #FFFFFF;   /* 4.55 on --accent                          */
  --on-accent-soft: var(--trusted-ink);
                              /* 6.21 min, on the --accent-soft tint over
                                 --sunken. Was 4.66 before the light inks
                                 were darkened.                            */

  /* Marks. Only RED survives as a brand hex here. Orange never did, at 2.21 on
     --bg and 2.10 on --hover. Green cleared the bare surfaces at 3.26 and was
     the brand hex until the marks were measured on their own chip fill, where
     it read 2.78, so it is derived now too. This is the asymmetry the header
     describes: the same tone needs a translation on a light ground and none on
     a dark one. Two figures per tone: bare minimum, then the minimum on the
     tone's own 14% fill. Both bottom out on --sunken. Floor 3.0.          */
  --trusted:       #009161;   /* 3.59 bare / 3.04 own fill. Brand green
                                       #009966 is 3.26 bare but 2.78 on fill*/
  --suspicious:    #C06906;   /* 3.56 bare / 3.04 own fill. Brand orange
                                       #F9911A reads 2.10 on --hover; the
                                       previous #D17206 read 2.65 on fill   */
  --dangerous:     #DC2828;   /* 4.28 bare / 3.48 own fill  (brand red)    */

  /* Inks, one notch darker than supplied: the supplied set bottomed at
     4.24-4.41 on its own fill over --sunken, which is what a chip in a table
     head sits on. Darkening the ink is the fix; restricting where a chip may
     be placed is not.                                                     */
  /* ONE MORE NOTCH DARKER, and the reason is legibility rather than the floor.
     The previous set cleared 4.5 by a hair — 5.56 / 5.53 / 5.69 on the bare
     surfaces — and these inks do most of their work at 11 and 12px: a band
     word, a signal count, a period delta. Text that small at the WCAG minimum
     is legible in a test and weak on a white card, which is what the owner
     reported. Measured on the four surfaces, bottoming out on --sunken as
     everything in this theme does; the hues are unchanged.                */
  --trusted-ink:    #005A3C;  /* 7.42 min, on --sunken; was 5.56           */
  --suspicious-ink: #7A4203;  /* 7.19 min, on --sunken; was 5.53           */
  /* RED HAS TO READ AS RED. This was raised from 5.69 to 7.19 to answer
     "hard to see"; the reading was wrong. At 7.19 it is not faint, it is DARK,
     and a dark low-chroma red on white reads as brown - the colour was lost as
     a signal while passing every contrast check. Chroma is the axis that
     complaint was about, not luminance. The first attempt at this, #C81E1E,
     held 5.12 across the four bare surfaces and then failed on the ground this
     file insists a mark is ALSO measured on: its own 14% fill, where it fell to
     4.16. #BC0808 is the most saturated red that clears the 4.5 floor on all
     EIGHT grounds - the four surfaces and the four fills - at 4.80, and its
     red-dominance is 0.706 against the old brick's 0.522.
     Green and amber stay where they are: danger is the one that has to carry
     furthest, and it is the one the eye should reach first. */
  --dangerous-ink:  #BC0808;  /* 4.80 min, on its own fill over --sunken    */

  --s1: #3B255B;              /* 11.70 min, on --sunken; ladder 1.43 /
                                 1.57 / 1.61                               */
  --s2: #224A73;              /*  8.17 */
  --s3: #965432;              /*  5.19 */
  --s4: #4D9565;              /*  3.23 */

  /* The same three roles, walked down the same three hues. Light bottoms out
     on --sunken, as everything in this theme does, and the ladder inverts:
     the keyword is now the DARKEST of the three, because on a white ground
     luminance separation runs the other way.                              */
  --code-key: #5C34AB;        /*  7.40 min, on --sunken                    */
  --code-fn:  #9B2F73;        /*  6.15 */
  --code-str: #17669F;        /*  5.45 */

  --focus:      #00875A;      /* 4.06 min, on --sunken; 4.36 on --bg       */

  --elev-popover: 0 8px 24px -6px rgba(29,32,37,0.14);
  --elev-dialog:  0 24px 64px -16px rgba(29,32,37,0.22);
}


/* ============================================================================
   2b. THE REST OF THE BRAND PALETTE, measured and NOT declared.

   The brand has five chromatic tones. The console binds three of them, as the
   three bands. Green Secondary and Yellow have no console role today, so they
   get no token: the previous build declared a --yellow on every theme that
   nothing referenced and nothing had measured, and it read 1.24 against
   --sunken, so the first component to reach for it would have failed on
   contact. A tone enters this file WITH its measurement, not before.

   These two are measured now, so that when a role appears (the High-Risk
   Events confidence axis is the obvious candidate) the value is not invented
   under deadline. Minimum across all four grounds, ink minimum across the
   eight-ground set:

                    LIGHT mark  (on --sunken)     DARK mark  (on --hover)
     green-2        #16A049  3.04                 #16A34A   5.17
     yellow         #A28909  3.06                 #F2CC0D  10.89

                    LIGHT ink   (fill/--sunken)   DARK ink   (fill/--hover)
     green-2        #0F7033  4.54                 #17A84C   4.53
     yellow         #726006  4.57                 #BA9D0A   5.15

   ONE WARNING ON THE TWO LIGHT MARKS ABOVE, added when the mark rule grew its
   second half. Those two figures are BARE-GROUND ONLY, and the mark floor is
   now eight grounds, not four. On its own 14% fill light's green-2 reads 2.62
   and light's yellow 2.67, both under 3.0, in exactly the way the band green
   and orange did before they were derived again. Dark is fine: 4.32 and 7.93.
   So these two light values are cleared to be a bar or a stroke on a plain
   surface and are NOT cleared to be a dot inside their own chip. Whoever gives
   one of them a role owes it the same walk down its own hue that --trusted and
   --suspicious took, and owes this table the new number. The point of section
   2b is that a tone arrives measured; a half-measured tone is the same trap
   with a friendlier face.

   Note what the light column costs: the brand Yellow #F2CC0D is 1.50 on --bg
   and 1.42 on --hover. It cannot be a mark on a light ground at all, let alone
   text. The derived value is a dark olive that no longer reads as the brand
   yellow. If a component needs "yellow" on light, the honest answer is usually
   the orange band, not a yellow nobody can see.
   ============================================================================ */


/* ============================================================================
   3. DERIVED TOKENS. The RECIPE is theme-invariant by decision; the colour it
      resolves to is not, because each recipe reads that theme's own tone token.
      One recipe in one place is the point: the package shipped five different
      fill mixes and two different scrim opacities.

      Why the selector list repeats both themes: a custom property whose value
      contains var() is substituted on the element where it is DECLARED.
      Declaring these on :root alone would be correct only while data-theme sits
      on the root element, which is where the shell puts it today. Restating
      them on each theme selector makes a subtree theme resolve against its own
      tones instead of inheriting an already substituted colour. Costs one extra
      selector, removes a silent footgun.
   ============================================================================ */
:root,
[data-theme="dark"],
[data-theme="light"] {

  /* ---- tints. 14% fill and 34% border are single values, not ranges. The
     README's "12-18%" and "32-38%" were drift, and five different mixes
     shipped under them.
     14% is not free-floating: it is the alpha at which every band ink still
     clears 4.5 on its own fill over the worst ground in both themes, the
     tightest being dark --suspicious-ink at 4.53.
     Recomputed 2026-09-06. The three LIGHT figures in this table were
     pre-darkening values and read as a much tighter case than the file
     actually has: the light inks were darkened afterwards (see "was 5.56" and
     "was 5.53" beside them) and this paragraph was not re-derived with them.
     At 18%, on its own fill over the worst of the four grounds:
         dark   suspicious 4.16   BELOW AA
         dark   trusted    4.43   BELOW AA
         dark   dangerous  4.80
         light  dangerous  4.51
         light  suspicious 5.83
         light  trusted    6.01
     So 18% costs TWO of the six inks, not five, and both are dark. The
     conclusion is unchanged - 14% is the alpha that keeps all six clear - but
     the margin is dark-side only, and a future change to the dark tones is
     what would move it. If this number is ever raised, all six ink values have
     to be re-derived, and so do the six mark-on-own-fill readings in the
     header, which get worse in the same direction.                        */
  --accent-soft:       color-mix(in srgb, var(--accent) 14%, transparent);
  --accent-soft-line:  color-mix(in srgb, var(--accent) 34%, transparent);

  --trusted-fill:      color-mix(in srgb, var(--trusted) 14%, transparent);
  --suspicious-fill:   color-mix(in srgb, var(--suspicious) 14%, transparent);
  --dangerous-fill:    color-mix(in srgb, var(--dangerous) 14%, transparent);
  --trusted-line:      color-mix(in srgb, var(--trusted) 34%, transparent);
  --suspicious-line:   color-mix(in srgb, var(--suspicious) 34%, transparent);
  --dangerous-line:    color-mix(in srgb, var(--dangerous) 34%, transparent);
  /* Chip = <tone>-ink on <tone>-fill inside a <tone>-line border. The ink
     minimum quoted per theme is the minimum over the fill composited on all
     four surfaces AND over the four bare surfaces. A chip on a hovered row and
     a chip in a table head are both inside that set, so neither needs a
     placement rule: the worst reading is 4.80 in light, which is
     --dangerous-ink on its own fill over --sunken, and 4.53 in dark, which is
     --suspicious-ink on its own fill over --hover. (Recomputed 2026-09-06;
     light read 4.62 before the inks were darkened.)
     A band DOT inside that same chip is measured too, at the 3.0 mark floor,
     on the same eight grounds; see the header. That measurement is what moved
     light's --trusted and --suspicious.
     The -line borders are 1.38 to 1.95 against their own card. They are chip
     EDGES, not the thing that identifies the chip, which is the fill plus the
     ink; they are quiet by the same decision as --line. A chip is never the
     sole carrier of a band anyway, because the band word is in the ink.

     A FILL IS A GROUND FOR ITS OWN INK AND FOR NOTHING ELSE. --text-2 and
     --text-3 are measured on the four surfaces only and drop to 3.56-4.03 on
     these tints. Text on a tint takes the matching ink: --on-accent-soft on
     --accent-soft, --trusted-ink on --trusted-fill, and so on. See the
     corollary in the header.                                              */

  /* ---- selection. Was the UA default, an off-palette blue.              */
  --select-bg:   color-mix(in srgb, var(--accent) 22%, transparent);
  --select-ink:  var(--text);   /* 11.00 min light (--sunken) /
                                   11.77 min dark (--hover)                */

  /* ---- scrim. Built from --bg on purpose so it recedes in both
     directions. The defect was two opacities inline in five places and no
     elevation under it, not the recipe: the veil alone gives a panel edge
     1.03 light / 1.04 dark, so a dialog MUST also carry --elev-dialog.    */
  --scrim: color-mix(in srgb, var(--bg) 72%, transparent);

  /* ---- focus ring. Two rings: the inner one is page-coloured so the green
     ring stays visible ON the green button, where ring and fill would
     otherwise be the same hue. --focus-ring is a box-shadow, so it paints
     OUTSIDE the box and an ancestor with overflow:hidden clips it; that is
     why a table row and a scroll container use the inset form.
     The outer ring lands on --focus-halo, which is --bg: 4.36 light,
     4.25 dark, both well clear of the 3:1 a focus indicator needs.        */
  --focus-halo:       var(--bg);
  --focus-ring:       0 0 0 2px var(--focus-halo), 0 0 0 4px var(--focus);
  --focus-ring-inset: inset 0 0 0 2px var(--focus);

  /* ---- the tour card floats at dialog height, so it takes the dialog
     shadow. Aliased rather than duplicated so the two cannot drift.       */
  --elev-tour: var(--elev-dialog);
}


/* ============================================================================
   4. INVARIANT TOKENS. Neither theme overrides any of these, by decision: a
      radius, a rail width or a z-index that changed with the palette would be
      a second layout, not a second theme.
   ============================================================================ */
:root {
  /* ---- type. --font is here because the package had --mono but no --font,
     so the sans face was spelled four different ways across twelve files.  */
  --font: Geist, system-ui, -apple-system, "Segoe UI", sans-serif;
  --mono: "Geist Mono", ui-monospace, Menlo, monospace;

  /* ---- radius. Measured: 999 x160, 4 x95, 8 x30, 6 x21, 3 x14, 1 x4,
     5 x1, 2 x1. Four steps carry all of it. --r-tick earns its place
     because 4px on a 12px checkbox reads as a circle.                     */
  --r-pill:  999px;   /* buttons, chips, band pills, tabs, quota bar       */
  --r-float:   8px;   /* popovers, dialogs, tour card                      */
  --r-card:    4px;   /* cards, panels, rows, code blocks, inputs          */
  --r-tick:    2px;   /* 12 to 13px checkboxes and column-picker boxes     */

  /* ---- layout. Measured off the shell, not copied from the README,
     which states a stale 208px rail.                                      */
  --rail-w:            236px;
  --rail-w-collapsed:   56px;
  /* --topbar-h is gone: it had zero consumers and promised 46px for a bar that
     measures 50 (9px padding either side of its content plus a hairline). A
     documented token that is neither read nor true is worse than no token.
     --row-min-h is gone for the same two reasons: nothing read it, and no row
     in the package measures 34 - they are 37 on an Overview top list, 39 in
     both tables and 42 on the Analytics head.
     --card-head-pad now states the value that SHIPPED. It read 10px 16px 10px,
     and that string appeared exactly once in the package - in its own
     declaration. Every card head in the product is 11px 16px 10px. */
  --card-pad:      16px 16px 16px;
  --card-head-pad: 12px 16px 12px;
  --dialog-pad:    16px 20px 20px;
  --row-pad:        8px 16px;
  --hair:            1px;   /* the gutter-grid gap: display:grid;
                               gap:var(--hair); background:var(--line) with
                               --card cells. This is a structural device,
                               NOT a spacing step, and two audits mistook it
                               for drift. Never normalise it into the
                               spacing scale.                              */

  /* ---- layers. The package shipped eleven ad-hoc z-index values and put
     modals at 80 under a topbar at 120, so a modal's scrim left the topbar
     strip lit and clickable through the veil. The dialog sits above the
     topbar here by construction, not by luck.                             */
  --z-sticky:  100;   /* sticky table header                              */
  --z-rail:    200;
  --z-topbar:  300;
  --z-popover: 400;
  --z-scrim:   500;
  --z-dialog:  510;
  --z-tour:    600;
  /* --z-base and --z-toast are gone. Neither was ever read, and --z-toast
     named a layer for a component the package does not have. Same two
     reasons --topbar-h and --row-min-h were deleted: a documented token that
     is neither read nor true is worse than none. z-index:0 is the initial
     value and needs no name; a toast layer can be added when a toast is. */

  /* ---- motion. There were zero transitions in 8,969 lines against two
     infinite animations. Hover feedback is only a 1.05 to 1.10 lightness
     step, so the transition is what makes it legible on a table of twenty
     identical rows.                                                       */
  --dur-fill:   90ms;   /* background and border-colour                    */
  --dur-panel: 150ms;   /* popover and dialog entry                        */
  --ease: cubic-bezier(0.2, 0, 0.2, 1);
  --spin: 700ms;        /* the one spinner, on async controls              */

  /* ---- breakpoints (D1).
     CSS cannot read a custom property inside a media query condition:
     @media (max-width: var(--bp-md)) does not work and never has. So these
     numbers are restated by hand in every prototype's @media block and in
     tokens.ts. A number here that disagrees with a number there is a BUG,
     not a variant, and it is the one thing in this file that no stylesheet
     can enforce for you.

     The ladder these tokens name:
       >= 1200  rail expanded at --rail-w, two-column Overview
       900-1199 rail collapses to --rail-w-collapsed, icons plus tooltips
       640-899  rail becomes a drawer; dense data screens show a width gate
       < 640    Onboarding, Support and Copilot work fully, down to 360

     ABOVE --bp-xl the ladder said nothing, and the screens answered that
     silence with max-width:1400px. On a 4K display that is 1400px of product
     against 2400px of empty desk. There is no rung above xl because there is no
     largest monitor: past it the layout is fluid, and the card grids that would
     otherwise stretch use repeat(auto-fit, minmax(<floor>, 1fr)) so a wider
     display buys MORE COLUMNS rather than wider rows. A fixed column count and
     a fluid width cannot both be right.                                  */
  --bp-sm:    640px;
  --bp-md:    900px;
  --bp-lg:   1200px;
  --bp-xl:   1440px;
  /* ---- page gutter.
     The air down both sides of the content column. It is a clamp and not a
     number because the two fixed answers are both wrong: 20px reads as an
     oversight on a 4K display, and a max-width that centres the content leaves
     the rest of the desk empty, which is the complaint that produced this
     token. 20px at the floor, 3.5vw through the middle, 120px at the top -
     45px at 1280, 67 at 1920, 90 at 2560, and 120 from 3430 up.          */
  --gutter: clamp(20px, 3.5vw, 120px);

  --bp-floor: 360px;  /* from D1. The contract states four breakpoints and no
                         floor; the floor is the width the three phone
                         surfaces are built to.                            */

  /* ---- the spacing scale, stated rather than tokenised.
     Counted across the twelve screens: 399 gap declarations over 17 distinct
     values. Six of them carry 316, which is 79% of every gap in the console:

         6px   between an icon and the word it belongs to
         7px   the same pairing inside a control that has its own box
         8px   between siblings in a list or a row of cells
         9px   between a figure and its caption
        10px   between controls in a bar
        12px   between cards, and between a card's stacked blocks

     Below six: 1-3px is hairline work, 4-5px pulls a mark tight against its
     label. Above twelve: 14-20px is air a card sets around itself.

     This is a HALF-STEP scale and that is deliberate. A 4px grid would only
     admit 4, 8, 12 and 16 - the console is dense enough that the step between
     8 and 12 is a real design decision made 44 and 38 times, and rounding it
     away would move a quarter of the layout to buy a rounder number. 38% of
     gaps sit on a 4px multiple and that figure is not a defect.

     No --sp-* tokens: they would have no consumers today, and section 4 of
     this file deletes geometry tokens that have none. When a screen needs a
     step, take it from the six above rather than inventing a neighbour - the
     drift this list exists to prevent is 8px where the same shape elsewhere
     uses 7.                                                               */
}


/* ============================================================================
   5. BASE LAYER. Rules every screen needs and no screen should re-declare.
   ============================================================================ */

*{box-sizing:border-box}
/* The face is set on the ROOT, not on body alone: anything the document paints
   outside body — and any surface a later shell mounts beside it — inherits the
   product's typeface instead of falling back to the UA serif.            */
html{font-family:var(--font)}
html,body{margin:0;padding:0}

body{
  background:var(--bg);
  color:var(--text);
  font-family:var(--font);
  /* 12px, not the 13px that shipped. Measured across the twelve screens:
     12px on 255 nodes against 13px on 52. The declared base was not the
     product's base. The 13px sites (body prose, dialog copy, snippet code,
     the rail) all declare 13px themselves, so they are unaffected. One line
     to revert if the owner disagrees.                                     */
  font-size:12px;
  line-height:1.55;
  font-weight:400;   /* 400 everywhere including headings. 500 exists only
                        on 11px uppercase captions. No bold, ever.         */
  -webkit-font-smoothing:antialiased;
  -webkit-text-size-adjust:100%;   /* D1: iOS inflates text in landscape,
                                      which silently breaks a layout that
                                      has just promised to work at 360px.  */
}

a{color:var(--text);text-decoration:none}
a:hover{color:var(--text-2)}   /* 4.51 min in both themes: the hover state
                                  must not land below the text floor       */
button{font-family:inherit;font-size:inherit;line-height:inherit}
table{border-collapse:collapse}

::selection{background:var(--select-bg);color:var(--select-ink)}

/* ---- focus -----------------------------------------------------------------
   One ring, one place. Nothing anywhere may set outline:none without this rule
   already covering it; the package had 14 such declarations and nothing
   restored a ring, so the Copilot composer, the Analytics search and the
   Onboarding domain field changed in no visible way when focused.
   The transparent outline is the forced-colors fallback: in forced-colors mode
   the box-shadow is dropped and the outline is repainted by the OS.        */
:focus-visible{
  outline:2px solid transparent;
  outline-offset:2px;
  box-shadow:var(--focus-ring);
}
/* Deviation from the contract's literal text, stated so it is a decision and
   not a slip: the contract pairs this with
   `:focus:not(:focus-visible){box-shadow:none}`. That rule would strip
   --elev-dialog from a panel the moment it takes the programmatic focus that
   the dialog contract requires on open, and it removes a ring this file never
   applied in that state anyway, since the ring only ever comes from
   :focus-visible. The outline half is kept; the box-shadow half is dropped.  */
:focus:not(:focus-visible){outline:none}

/* ---- fields ----------------------------------------------------------------
   An input's real boundary is the bordered container around it, so the ring
   goes on the container and the inner control keeps outline:none. The container
   must be a <label>: that is what makes the whole box the control's hit target
   and its accessible name at the same time. A placeholder is not a label.
   Mark it up as
     <label class="field tap"> <span>Domain</span> <input> </label>
   .tap ON A SINGLE-LINE FIELD ONLY, and the field must declare its own display.
   Measured at 390: the four single-line fields stand 30 to 36px and need the
   lift; the Support question box is 75 and the Copilot composer 127, already
   past the floor. And .tap sets display:inline-flex, which an inline style
   beats but a field with no display of its own does not - putting it on the
   composer collapsed it from 1440x113 to 286x75.                          */
.field:focus-within{
  box-shadow:var(--focus-ring);border-color:var(--line-control);
  /* The same forced-colors fallback the :focus-visible rule above carries, and
     for the same reason. It could not reach here: the ring on a field is keyed
     to :focus-within, because focus lands on the inner control while the ring
     belongs to the container. In forced-colors the box-shadow is dropped, and
     without this the five field containers were the one focus surface in the
     package with nothing left to repaint. Paints nothing otherwise. */
  outline:2px solid transparent;outline-offset:2px;
}
.field :is(input,textarea,select):focus-visible{outline:none;box-shadow:none}
.field :is(input,textarea){
  border:0;background:transparent;font-family:inherit;font-size:inherit;
  color:inherit;padding:0;min-width:0;
}

/* ---- disabled --------------------------------------------------------------
   The package had zero disabled, zero aria-disabled and zero aria-busy while
   modelling five async states, so every "Checking..." button stayed clickable
   and the same request could be fired repeatedly.
   :where() keeps this at zero specificity so a component can override it.
   Disabled controls are exempt from 1.4.3 and 1.4.11, but these values pass
   anyway: --text-3 on --sunken measures 4.51 light / 5.27 dark, because a
   disabled control actually sits on --sunken and not on --card.
   No pointer-events:none: an aria-disabled control must stay focusable, and
   killing pointer events also kills its tooltip.
   The filled variant, background:var(--sunken), belongs to the component: a
   ghost button must not grow a fill when it is disabled.                    */
:where([disabled],[aria-disabled="true"]){
  cursor:not-allowed;
  color:var(--text-3);
  border-color:var(--line-2);
}

/* ---- transitions -----------------------------------------------------------
   Fill and border only, on things a pointer acts on. Nothing else animates.
   Zero specificity so a component can opt out.
   box-shadow is deliberately NOT in this list: it is the focus ring, and a ring
   that fades in over 90ms is a ring that is not there when the key is pressed.
   Verified in the browser: with box-shadow transitioned, a focused button reads
   its ring as two fully transparent shadows on the first frame.             */
:where(a,button,[role="button"],[role="tab"],[role="option"],input,select,
       textarea,summary,tr,[role="row"]){
  transition:background-color var(--dur-fill) var(--ease),
             border-color var(--dur-fill) var(--ease);
}

@media (prefers-reduced-motion: reduce){
  *,*::before,*::after{
    animation-duration:1ms !important;
    animation-iteration-count:1 !important;
    transition-duration:1ms !important;
  }
}

/* ---- horizontal scrollers --------------------------------------------------
   Two of these exist, not one: the Analytics table shows 898px of 1910px, so
   53% of every row was unreachable without a mouse, and the entity card wraps
   29 focusable elements a keyboard user could tab into but never scroll to.
   CSS can give the scroller its ring; the markup still has to give it
   tabindex="0", role="region" and an aria-label, which no stylesheet can
   supply. The ring is the inset form because an outer ring on a scroll
   container is clipped by the container itself.                             */
/* BOTH AXES ARE STATED. Setting overflow-x alone does not leave the other axis
   visible: the spec computes a visible overflow-y to auto the moment its
   partner is not visible, so every horizontal strip in this package was also a
   vertical scroller. Integration's tab strip is 35px of content in a 27px box
   and drew a VERTICAL scrollbar down the right-hand end of a row of five tabs.
   A strip that scrolls sideways does not scroll down; saying so removes the
   thumb everywhere at once. */
/* position:relative is not cosmetic here. An absolutely positioned child with
   no positioned ancestor takes the DOCUMENT as its containing block, so a
   visually-hidden span sitting mid-way through a 1,952px table stretched the
   page to 668px at a 375px viewport and the whole screen scrolled sideways
   because of text nobody can see. A scroll container has to be the containing
   block for what it scrolls. */
.scroll-x{position:relative;overflow-x:auto;overflow-y:hidden}
.scroll-x:focus-visible{box-shadow:var(--focus-ring-inset)}
/* A clipped table has to look clipped. The region is already reachable — role,
   tabindex and a label — but a keyboard affordance is invisible to the reader
   who is looking at a column that ends mid-word. The mask fades the last 24px
   of the scrollable edge and lifts as the reader reaches that end, so it is a
   statement about the content and not a permanent decoration. It rides on
   scroll-driven animation where that exists and simply stays on where it does
   not, which costs a reader nothing: a soft edge on content that does not
   scroll is quiet, a hard edge on content that does is a lie.            */
@supports (animation-timeline: scroll(self inline)) {
  /* The mask lives ONLY in the keyframes, never on the rule. A scroll timeline
     on an element with nothing to scroll is INACTIVE, and an animation on an
     inactive timeline does not apply, so a strip that fits keeps its hard edge
     and a strip that overflows gets the fade. Putting mask-image on .scroll-x
     itself, which is what the first pass did, faded the last 24px of every
     scroller in the package including the Settings sub-nav at 196 of 196px:
     a permanent decoration announcing content that does not exist. */
  .scroll-x{
    animation:scroll-x-edge linear both;
    animation-timeline:scroll(self inline);
  }
  /* FOUR STOPS AT BOTH ENDS, and the same four in both keyframes, because a
     gradient only interpolates cleanly into a gradient of the same shape.
     Animating a two-stop start into a two-stop end moved a stop's POSITION and
     its COLOUR at once, so half way through the scroll the first stop was a
     half-transparent black at the middle of the element and the whole table
     washed out. With the stop count fixed and only the offsets moving, the
     left edge fades in as the reader scrolls away from the start and the right
     edge fades out as they reach the end, and mid-scroll shows both. */
  /* PERCENTAGES, NOT calc(), and the same four stops in both keyframes. Two
     things had to be true for this to interpolate rather than flip. Same stop
     COUNT: animating a two-stop start into a two-stop end moved a stop's
     position and its colour at once, so mid-scroll the first stop was a
     half-transparent black in the middle of the element and the whole table
     washed out. Same stop UNIT: mixing calc(100% - 24px) with a plain 100% was
     the second reason it would not interpolate.
     MEASURED AFTERWARDS, and stated because the next reader will wonder: the
     timeline progress is a true 0.5 at half scroll and the easing is linear,
     and the engine STILL resolves mask-image discretely — at 0.5 it takes the
     end keyframe. So this is a two-state edge, not a continuous one: the right
     edge is faded while the reader is at the start and the left edge once they
     have moved. Both states are correct at the ends and neither washes the
     content, which was the defect. A continuous fade would have to animate
     mask-position on a fixed gradient instead, and is not worth the second
     mechanism. 3% is 24 to 30px on the scrollers in this package. */
  @keyframes scroll-x-edge{
    from{ mask-image:linear-gradient(to right,#000 0%,#000 0%,#000 97%,transparent 100%); }
    to  { mask-image:linear-gradient(to right,transparent 0%,#000 3%,#000 100%,#000 100%); }
  }
}

/* ---- hit targets -----------------------------------------------------------
   44x44 minimum, or 24x24 with 24px of clear space, which is the floor a
   console this dense can hold. 186 of 301 interactive elements measured under
   44x44 and 73 had a short side under 24px, including eight Overview list
   openers at 15x15, which is 11.6% of the required area.

   .tap expands the TARGET without touching the ink. Two things it cannot do,
   learned the hard way in the previous package, and this is why the media query
   below exists:
     1. the ::after expansion is clipped by overflow:hidden on a truncating
        chip, so on a chip that ellipsises, the extra area is not there;
     2. it generates nothing at all on a replaced element, so .tap on a bare
        <input> or <input type="checkbox"> does exactly nothing.
   So at touch widths it ALSO grows the painted box, and a bare field's real
   target is its bordered container, which is therefore a <label>, per the field
   rules above.                                                              */
.tap{position:relative;display:inline-flex;align-items:center;justify-content:center}
.tap::after{
  content:"";position:absolute;left:50%;top:50%;
  width:100%;height:100%;min-width:44px;min-height:44px;
  transform:translate(-50%,-50%);
}
@media (max-width: 639px), (pointer: coarse){
  /* 640 is --bp-sm restated by hand: see the breakpoint note in section 4. */
  .tap{min-width:44px;min-height:44px}

  /* Two shapes .tap cannot serve, which five screens had each solved on their
     own with a private class and the same copied media query. They are one
     rule now, so the floor and the breakpoint cannot drift apart per screen.
       .tap-row     a full-width row. .tap is an inline-flex centring helper:
                    it would take the row's width and centre the sentence.
       .tap-inline  a truncating link. The ::after IS clipped by the same
                    overflow:hidden that makes the ellipsis - but that is a
                    property of where the overflow sits, not of the link.
                    Move the truncation onto an inner span and the button
                    stops clipping its own overlay: Analytics.dc.html:626 and
                    EntityCard.dc.html:259 and :595 do exactly that, and carry
                    .tap AND .tap-inline. The overlay then reaches 44px at
                    every width with the painted link unchanged, so use both
                    on any link that truncates; .tap-inline alone leaves the
                    wide widths at the ink height.
     Both grow their own painted box, and only here. Desktop density is
     unchanged. */
  .tap-row{min-height:44px}
  .tap-inline{min-height:44px;display:inline-flex;align-items:center}
}

/* ---- figures ---------------------------------------------------------------
   tabular-nums is specified for every figure and resolved on zero elements in
   Integration, Settings, Support and Copilot, while Integration's Health tab is
   nothing but columns of numbers. Marking a figure is the component's job;
   rendering it in tabular figures is this file's.                           */
[data-figure]{font-variant-numeric:tabular-nums}

/* ---- scrollbar ------------------------------------------------------------ */
::-webkit-scrollbar{width:10px;height:10px}
::-webkit-scrollbar-thumb{
  background:var(--line-2);
  border-radius:var(--r-pill);
  border:3px solid transparent;
  background-clip:content-box;
}

/* The refresh control on the identifications table turns while it is fetching.
   Reduced motion is honoured by the block at the foot of this file, which stops
   every animation; the label carries the state either way, so nothing is lost
   when the turning is. */
@keyframes spin-360{ from{ transform:rotate(0deg) } to{ transform:rotate(360deg) } }
.an-refresh-icon[data-spin="true"]{ animation:spin-360 var(--spin) linear infinite }

/* LOADING CELLS, NOT AN EMPTY PAGE. Owner decision 2026-09-02: the states with
   no period and no identifications used to replace the whole screen with a
   centred sentence, so the reader who had just installed the snippet saw a
   different product from the one they were promised and had nothing to orient
   against. The dashboard stays built in those states and each figure is a bar
   in its place. It says the same thing - there is nothing here yet - while the
   shape of what is coming stays legible. Bars carry aria-hidden and their card
   carries aria-busy, so a screen reader is told the region is loading once
   rather than reading a fence of empty spans. */
.sk{display:inline-block;border-radius:var(--r-pill);background:linear-gradient(90deg,var(--sunken) 0%,var(--hover) 50%,var(--sunken) 100%);background-size:200% 100%;animation:sk-slide 1.4s ease-in-out infinite;vertical-align:middle}
@keyframes sk-slide{0%{background-position:200% 0}100%{background-position:-200% 0}}
@media (prefers-reduced-motion: reduce){.sk{animation:none;background:var(--sunken)}}

/* THE WHOLE SCREEN WAITING, in one rule. Every published number in this package
   carries data-figure - it is how the tabular-figures rule finds them - so the
   waiting state paints each one as a bar in its own place instead of teaching
   every tile, panel and row its own loading prop. The element keeps its box, so
   nothing reflows when the data lands. Charts go with them: a plotted line over
   figures that have not arrived contradicts the bars beside it. */
body .sk-wait [data-figure], body .sk-wait [data-cell]{color:transparent!important;min-width:38px;border-radius:var(--r-pill);
  background:linear-gradient(90deg,var(--sunken) 0%,var(--hover) 50%,var(--sunken) 100%);
  background-size:200% 100%;animation:sk-slide 1.4s ease-in-out infinite;display:inline-block}
body .sk-wait svg path[stroke]:not([stroke="none"]), body .sk-wait img{opacity:0}
@media (prefers-reduced-motion: reduce){body .sk-wait [data-figure], body .sk-wait [data-cell]{animation:none;background:var(--sunken)}}

/* ONE BADGE, ONE SIZE, EVERYWHERE. The three kinds of pill a row can hold were
   each sized by their own padding and font: a Risk score pill stood 23px at
   12px text, an event chip 23px at 12px with different padding, and a signal
   chip 19px at 11px - and the signal chip's padding then differed again
   between Analytics and the Overview. Three heights and two type sizes inside
   one table row read as three unrelated components rather than one family.
   Height is stated rather than left to padding, so a later padding tweak
   cannot pull them apart again; colour and border stay with each caller,
   because tone is the one thing these badges genuinely differ by. */
.badge{display:inline-flex;align-items:center;justify-content:center;height:22px;
  box-sizing:border-box;padding:0 8px;font-size:12px;line-height:1;
  border-radius:var(--r-pill);white-space:nowrap;max-width:100%}

/* A THRESHOLD IS ONE DIGIT, so its field is sized for one. The number input's
   native stepper forced a 44px box, which left the idle row reading "from   3
   accounts" with a hole either side of the figure; the arrows also gave a
   second way to change a value the field already takes by typing. Sized to the
   digit, the box is the same width in both states and nothing shifts when the
   pencil is taken. */
.an-thnum{-moz-appearance:textfield;appearance:textfield}
.an-thnum::-webkit-outer-spin-button,.an-thnum::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}

/* ONE HEIGHT FOR EVERY PILL CONTROL. A measurement pass across the twelve
   screens found them at 25, 27, 29, 30 and 31px: Analytics and Integration and
   the entity card had been unified at 30 one screen at a time, while the shell,
   the Analytics pager, Usage, Settings, Copilot and Support never were. Sized
   by padding, a control's height is whatever its author's padding happened to
   be, so the row it sits in reads as several kinds of control.
   Stated once, on the class every pill control carries. Padding still does the
   horizontal work; only the height is taken away from it, which is the axis
   that has to agree across a row. Buttons that are deliberately larger - the
   Onboarding hero action - carry their size in their own class and are not
   marked, so this rule never reaches them. */
.ctl-h{height:30px;box-sizing:border-box}

/* A FIELD IS AS TALL AS A CONTROL. The bordered container that holds an input
   was sized by its own padding on each screen: 26px on the Analytics and entity
   card search, 29 in Settings, so a field and the buttons beside it sat at
   three heights in one row. min-height, not height, because the onboarding hero
   field is deliberately larger and a floor leaves it alone while lifting the
   short ones onto the same line as .ctl-h. */
.field{min-height:30px;box-sizing:border-box}

/* THE PRIMARY BUTTON IS A ROLE, NOT TWENTY INLINE RESTATEMENTS. Twenty-two
   accent buttons each spelled ground, ink, border and radius inline, with nine
   different horizontal paddings and a hover that partitioned exactly by file:
   brightness in Integration and Usage, an outline in the shell, and nothing at
   all on fourteen of them. Fourteen unhoverable primaries is not a style
   question - the loudest control on a screen gave no sign it was live.
   Horizontal padding is 14px, the plurality of what shipped; vertical padding
   is zero because .ctl-h owns the height and two mechanisms setting one axis is
   how the heights drifted in the first place. Hover darkens rather than
   outlines: an outline on a filled control reads as focus, and focus already
   has --focus-ring. The onboarding hero pair is deliberately larger and keeps
   its own size - it carries no .btn-primary. */
.btn-primary{background:var(--accent);color:var(--on-accent);border:0;
  border-radius:var(--r-pill);padding:0 16px;font-size:12px;cursor:pointer}
.btn-primary:hover{filter:brightness(0.92)}

/* THE SECONDARY BUTTON, SAME TREATMENT. Thirty-eight text buttons carried the
   outline form with nine horizontal paddings between 9 and 14px. Every one of
   the thirty-eight already spelled the identical hover, so that moves here
   whole; the padding settles on 12px, the plurality.
   BACKGROUND STAYS WITH THE CALLER, deliberately. Thirty-three are transparent
   per canon, but five sit on a tinted ground - the topbar, a filled row - and
   read as a hole if made transparent. A role should own what is the same
   everywhere and leave what legitimately differs. */
.btn-secondary{border:1px solid var(--line-control);border-radius:var(--r-pill);
  padding:0 12px;font-size:12px;cursor:pointer}
.btn-secondary:hover{background:var(--hover)}

/* ONE VISUALLY-HIDDEN UTILITY. It existed six times under six names with three
   different bodies - .vh, .an-sr, .ic-sr, .se-sr, .su-sr, .cp-hidden - two of
   them still carrying the legacy clip:rect() and four not. EntityCard even
   carries a note about deleting a SEVENTH copy from inside itself; the lesson
   was applied within one file and never across them. clip-path alone is
   correct in every browser this ships to, so the legacy clip goes with the
   duplicates. */
.vh{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;
  clip-path:inset(50%);white-space:nowrap;border:0}

/* THE TILE CAPTION HAS A NAME NOW. A label over a figure is the most repeated
   unit in this console and it was spelled several ways: 11px uppercase on the
   metric cards, 13px sentence case on Traffic quality, 12px on the entity
   card's Details grid. On the Overview two of those collided - the word
   "Trusted" appeared twice within one viewport, 191px apart, over identically
   declared 20px figures, differing in size, case, weight, tracking and colour
   role at once. Five attributes changing while the figure does not is not a
   hierarchy step; a hierarchy step would move the figure too.
   Colour stays with the caller: most captions are --text-2, and the Traffic
   quality band legend is tinted because it labels a coloured rule directly
   above it. */
.cap{font-size:11px;font-weight:500;letter-spacing:0.04em;text-transform:uppercase;min-width:0}

/* ---- the card surface ------------------------------------------------------
   The panel every screen draws its content on. Three screens had defined it
   privately: .ec-card and .ic-card were byte-identical, and .se-card was the
   same four properties plus padding. One rule now, so the border, the radius
   and the fill cannot drift apart per screen.
   Padding is NOT here. A card that holds a table pads nothing (the rows own
   their gutters), a card that holds prose pads --card-pad, and a card with a
   head pads the head separately. The caller says which. */
.card{background:var(--card);border:1px solid var(--line);border-radius:var(--r-card);min-width:0}

/* ---- the floating surface --------------------------------------------------
   Nine copies of these six properties existed: five private classes (.an-pop,
   .se-pop, .rail-pop, .pop, .ob-pop) and four written inline. They had already
   drifted — the chart's hover readout was built from the same six but reached
   for --r-card, so one floating thing in the console had 4px corners where the
   other eight had 8px.
   Position, width and padding stay with the caller: a menu anchored under a
   rail item and a column picker pinned to the right edge of a table share a
   surface, not a geometry. */
.pop-surface{
  position:absolute;z-index:var(--z-popover);
  background:var(--card);border:1px solid var(--line-2);
  border-radius:var(--r-float);box-shadow:var(--elev-popover);
}
