/* ============================================================================
   Ramos design system — tokens
   ----------------------------------------------------------------------------
   One file, one definition per decision. Nothing downstream may introduce a
   colour, a type size, a spacing value, a radius, a control height, a duration
   or an easing curve that is not declared here.

   Direction: an operate surface for one professional who lives in it daily.
   Precision-instrument, not card deck. Identity comes from the hairline grid,
   the three surface layers, mono column labels and tabular figures — not from
   decoration.

   Colour discipline, because financial UI punishes ambiguity:
     accent    interaction only — focus, current selection, primary action, links
     positive  value direction up, and "healthy"
     negative  value direction down, and "critical"
     warning   needs a human, not yet broken
     info      neutral annotation
   A hue never does two jobs. Classification values (price-only / total-return)
   are NOT coloured: green on "price-only" would imply a quality judgement the
   data does not carry.
   ========================================================================== */

:root {
  color-scheme: light dark;

  /* --- surfaces ---------------------------------------------------------
     Three layers, not two. --chrome is the frame (sidebar, top bar) and sits
     cooler and further back than the content it holds; --surface is a panel;
     --raised is the step used by table header bands, row hover and quiet
     chips. The separation between --surface and --raised is load-bearing, so
     the dark palette is tuned to the same perceptual ratio as light rather
     than to a similar hex. */
  --ground:      #EEF1F4;
  --chrome:      #E4E9EE;
  --chrome-deep: #D9E0E7;
  --surface:     #FFFFFF;
  --raised:      #E9EDF1;
  --sunken:      #E3E8ED;

  /* --- ink -------------------------------------------------------------
     Four steps. --faint is still text and still clears 4.5:1; when a value
     must recede further than that, the job belongs to size or position, not
     to an unreadable fifth grey. */
  --ink:    #10161C;
  --ink-2:  #2A3540;
  /* --muted and --faint were measured, not eyeballed, against the DARKEST
     surface each actually appears on rather than against white. The earlier
     values cleared 4.5:1 on --surface and then failed at 4.06-4.41 on --sunken
     and --chrome, which is where most of this text lives: the figure band's
     lead cell, the rail's group labels, the table toolbar. Both were darkened
     until the worst pairing clears, and --muted was taken a step further so the
     two remain visibly distinct rather than collapsing into one grey. */
  --muted:  #4E5B67;
  --faint:  #576470;

  /* --- rules -----------------------------------------------------------
     Two weights only. --rule divides rows inside one thing; --rule-2 closes
     the edge of a thing. A third weight is how a grid stops reading as a grid. */
  --rule:   #DCE2E8;
  --rule-2: #C3CDD6;

  /* --- accent: interaction only ---------------------------------------- */
  --accent:      #15598F;
  --accent-ink:  #0F4570;
  --accent-soft: #DCE8F2;
  --accent-line: #4A8DC4;
  /* Text ON the accent. A token rather than a hardcoded #fff, because the dark
     theme's accent is a LIGHT blue: white on it measured 2.39:1, which made the
     primary button — the one control on the screen that must be unmissable —
     the least readable text in the product. It flips with the theme; the accent
     does not care which, but the label does. */
  --accent-on:   #FFFFFF;

  /* --- value direction + health ---------------------------------------- */
  --pos:       #0F6B44;
  --pos-soft:  #DCEBE3;
  --neg:       #A82F22;
  --neg-soft:  #F4E0DD;
  --warn:      #7E5300;
  --warn-soft: #F2E9D6;
  --info:      #3A5480;
  --info-soft: #E1E6F0;

  /* The categorical series palette. EIGHT slots, assigned to a security by its
     identity rather than by its position in a chart, so the same holding is the
     same colour in the provided ring and the optimised one beside it.

     Not derived from --accent and not a ramp: these encode IDENTITY, which is a
     different job from magnitude, and a sequential ramp says the fourth holding
     is somehow more than the fifth. Deliberately not --pos/--neg/--warn either,
     which are reserved for state -- a security drawn in the red reserved for a
     breach reads as a judgement its weight does not carry.

     Validated as a set against this file's own surfaces, in both themes:
     adjacent-pair CVD separation and the normal-vision floor both clear, and
     every dark step clears 3:1 against the dark surface. THREE LIGHT STEPS SIT
     BELOW 3:1 (aqua, yellow, magenta), which is permitted only because every
     slice is also a labelled legend row carrying its own percentage, and the
     allocation table repeats all of it. Remove either and the palette stops
     being compliant. A ninth series folds into one recessive "Other" slice --
     a ninth hue would have to be invented rather than chosen. */
  --series-1: #2a78d6;
  --series-2: #eb6834;
  --series-3: #1baf7a;
  --series-4: #eda100;
  --series-5: #e87ba4;
  --series-6: #008300;
  --series-7: #4a3aa7;
  --series-8: #e34948;
  
  /* A fund's composition, by constituent. A SEPARATE SET FROM --series-*,
     because these answer a different question: --series-* names a holding
     across two portfolios being compared, and eight slots is the whole of
     that job. A fund publishes its top ten, so this one needs ten.
  
     Tokens rather than the color-mix ramp they replace, and that is the
     reason the ramp existed: a mix of one accent is derivable, so it needed
     no declaration. These are ten chosen hues whose dark steps are SELECTED
     rather than flipped -- six differ from their light value -- so there is
     nothing to derive them from. DECLARED IN ALL THREE SCOPES, and the one
     that is easy to miss is this one: an OS-dark reader is served by the
     @media block and a reader who used the in-app toggle is served by
     the explicit data-theme rule below. Shipped once with the block
     pasted twice into @media and absent from that rule, where every
     arc then resolved
     to nothing at all. test_both_themes_define_every_token does NOT cover
     this family -- its token list is a literal that names no --fund-*,
     --series-* or --corr-* -- so test_fund_chart.py slices this file into
     its three scopes and checks each by name.
  
     Validated with the dataviz validator against this application's own
     surfaces. On the adjacent pairlist both themes pass every check
     (worst CVD dE 19.2 light, 18.6 dark; normal-vision 29.2 and 27.5).
     ACROSS ALL PAIRS the worst CVD dE is 6.5 light and 6.1 dark, which is
     the 6-8 floor band. And three steps per theme sit below 3:1 against
     the chart surface -- light #14c466 2.30, #13b3ef 2.41, #ee8710 2.59;
     dark #980bcf 2.79, #b50a87 2.86, #2f48fd 2.95. BOTH of those are
     permitted only because every arc is also a labelled legend row
     carrying its own percentage and the holdings table below repeats all
     of it with no colour at all.
  
     THAT FLOOR IS A SEARCH RESULT AND NOT A PROOF. Simulated annealing
     over an OKLCH pool -- hue every 4 to 6 degrees, five or six lightness
     steps inside each theme's own band, chroma just inside gamut and above
     the 0.1 floor -- scored on ALL pairs under the Machado protan and
     deutan transforms in OKLab, against these two surfaces. The best
     separation FOUND was 9.6 at eight categories, 8.9 at nine and 7.4 at
     ten. One anneal per size, not an exhaustive enumeration: a wider pool,
     a different metric or a longer search may do better. What is claimed is
     that ten hues clearing 8 were not found here, not that none exist.
  
     And the palette that MAXIMISED that score was deliberately not
     shipped. It came back muted and blue-heavy -- the opposite of the
     bright, distinctive set this was asked for, and worse for the readers
     who are the large majority. These ten maximise normal-vision
     separation subject to a 6.0 CVD floor instead, which is the tradeoff
     being made rather than a limit being hidden. Remove the legend or the
     table and this palette stops being compliant. */
  --fund-1: #980bcf;
  --fund-2: #14c466;
  --fund-3: #aa70fd;
  --fund-4: #5a7406;
  --fund-5: #fe16be;
  --fund-6: #ee8710;
  --fund-7: #13b3ef;
  --fund-8: #dd0d2c;
  --fund-9: #2f48fd;
  --fund-10: #b50a87;

  /* The correlation ramp. Diverging, and deliberately not --pos/--neg: a
     negative correlation is not "bad", and this codebase already reverted an
     attempt to say otherwise. Blue is the negative end, orange the positive,
     with the page's own surface as the neutral midpoint. */
  --corr-neg: #2C6E9B;
  --corr-pos: #B4661E;
  --corr-mid: #8A8F96;

  /* --- type ------------------------------------------------------------
     System sans, deliberately: operate surfaces want earned familiarity, and
     a webfont here buys character the user never asked for at the cost of a
     render-blocking request on a tool opened dozens of times a day.
     Mono is for data and measurement — tickers, figures, dates, identifiers,
     column labels — never for prose. */
  --sans: "Segoe UI Variable Text", "Segoe UI", system-ui, -apple-system,
          "Helvetica Neue", sans-serif;
  --mono: ui-monospace, "Cascadia Mono", "Cascadia Code", "SF Mono", Consolas,
          "Liberation Mono", monospace;

  /* Fixed steps, not fluid: an operator views at one consistent DPI, and a
     heading that shrinks inside a narrow panel looks worse, not better.
     Ratio ~1.15 through the working range because this surface carries far
     more type roles than a brand page and exaggerated contrast reads as noise.
     --t-display exists once per screen, for the single figure that leads. */
  --t-micro:   11px;
  --t-data:    12px;
  --t-body:    13px;
  --t-lead:    15px;
  --t-title:   18px;
  --t-figure:  22px;
  --t-display: 30px;

  --lh-tight: 1.2;
  --lh:       1.5;

  --tr-label: 0.07em;  /* mono column labels, uppercase */
  --tr-tight: -0.015em;
  --tr-figure: -0.025em;

  /* --- space -----------------------------------------------------------
     Seven steps. Anything not on this scale is a mistake, including the
     optical nudges: those get their own token below. */
  --s-1: 4px;
  --s-2: 6px;
  --s-3: 10px;
  --s-4: 14px;
  --s-5: 20px;
  --s-6: 28px;
  --s-7: 40px;
  --hair: 1px;

  /* --- radii -----------------------------------------------------------
     Sharp. This world is an instrument, so the frame is a rule, not a pill.
     Pills exist only on status chips, where the shape carries meaning. */
  --r-sm: 2px;
  --r:    3px;
  --r-lg: 5px;
  --r-pill: 999px;

  /* --- control heights -------------------------------------------------
     Declared, so a button, an input and a select in one row cannot disagree
     by a pixel. --ctl is the default; --ctl-sm is for in-table controls where
     the row height governs.

     --ctl-sm was 22px, which is under the 24px minimum WCAG 2.2 SC 2.5.8 sets
     for a pointer target. Two pixels is not a visual decision worth making
     against a person who cannot reliably hit a 22px button, and Phase 7 adds
     more in-table controls than any earlier phase — so it is fixed here, at the
     token, rather than patched per screen. It still clears --row (28px), so no
     row grows and no existing layout moves. */
  --ctl:    28px;
  --ctl-sm: 24px;
  --ctl-lg: 34px;

  /* --- table density ---------------------------------------------------
     --row is the target row height. Density is bought with padding rather
     than with type size: 11px is the floor for functional text. */
  --row:     28px;
  --row-pad: 5px;
  --cell-x:  var(--s-4);
  --edge-x:  var(--s-5);

  /* --- shell metrics --------------------------------------------------- */
  --rail:          228px;
  --rail-collapsed: 52px;
  --topbar:        46px;

  /* --- elevation -------------------------------------------------------
     Declared once. A panel is a hairline border and no shadow; a shadow is
     reserved for things that genuinely float above the page (menus, drawers,
     the mobile nav), and it carries a real offset with a soft blur. A 1px
     border under a wide soft shadow is the ghost-card tell, so overlays get
     the shadow and drop the border. */
  --lift:      0 6px 16px -4px rgb(16 22 28 / 0.18), 0 2px 4px -2px rgb(16 22 28 / 0.12);
  --lift-high: 0 18px 40px -12px rgb(16 22 28 / 0.28), 0 4px 8px -4px rgb(16 22 28 / 0.14);

  /* --- motion ----------------------------------------------------------
     Durations are short because every one of these is seen many times a day.
     Nothing in the shell exceeds 240ms. The curves are the strong variants:
     the CSS built-ins are too weak to read as intentional, and ease-in is
     never used on UI because it delays the first frame — the exact moment the
     user is watching. */
  --d-press:  120ms;   /* button / row press feedback */
  --d-fast:   140ms;   /* colour, hover, indicator */
  --d:        180ms;   /* menu, dropdown, disclosure */
  --d-panel:  240ms;   /* drawer, sidebar collapse */

  --ease-out:    cubic-bezier(0.23, 1, 0.32, 1);
  --ease-in-out: cubic-bezier(0.77, 0, 0.175, 1);
  --ease-drawer: cubic-bezier(0.32, 0.72, 0, 1);

  /* --- layering --------------------------------------------------------
     Named, so a new overlay cannot guess a number and land under a table
     header it was meant to cover. */
  --z-sticky:  10;
  --z-topbar:  30;
  --z-rail:    40;
  --z-menu:    60;
  --z-overlay: 70;
  --z-drawer:  80;
}

/* ---------------------------------------------------------------------------
   Dark theme.

   Not an inversion. The ground goes to a deep cool graphite rather than black
   so the hairline grid still resolves; the chrome moves DARKER than the
   content it frames (in light it moves darker too — the frame recedes in both
   directions, it just does it from opposite sides of the surface); accents are
   lifted in lightness and pulled down in chroma so they carry on a dark ground
   without glowing. Every ink/surface pair below was checked for the same
   contrast ratio its light counterpart holds.

   The tokens are declared twice on purpose: once under prefers-color-scheme
   for the default "system" setting, guarded so an explicit light choice wins,
   and once under [data-theme="dark"] so the in-app toggle wins in both
   directions. A colour whose only definition lives inside a media query
   cannot be reached by the toggle.
   --------------------------------------------------------------------------- */

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --ground:      #0C1116;
    --chrome:      #080C10;
    --chrome-deep: #05080B;
    --surface:     #12181E;
    --raised:      #1C242B;
    --sunken:      #0E141A;

    --ink:    #E6ECF2;
    --ink-2:  #BFCBD6;
    --muted:  #8A98A5;
    /* Lifted from #7A8895, which measured 4.36:1 on --raised — the table header
       band and every hovered row. */
    --faint:  #808E9B;

    --rule:   #212A32;
    --rule-2: #313C46;

    --accent:      #63AEEA;
    --accent-ink:  #8CC6F5;
    --accent-soft: #10283C;
    --accent-line: #2F6D9E;
    --accent-on:   #06131F;

    --pos:       #4FBF87;
    --pos-soft:  #10261B;
    --neg:       #F0857A;
    --neg-soft:  #2B1614;
    --warn:      #E0A94A;
    --warn-soft: #2A2011;
    --info:      #8FA9D8;
    --info-soft: #161C29;

    /* The same eight hues, stepped for the dark surface -- a selected set, not
       an automatic flip of the light one. */
    --series-1: #3987e5;
    --series-2: #d95926;
    --series-3: #199e70;
    --series-4: #c98500;
    --series-5: #d55181;
    --series-6: #008300;
    --series-7: #9085e9;
    --series-8: #e66767;
    
    /* A fund's composition, by constituent. A SEPARATE SET FROM --series-*,
       because these answer a different question: --series-* names a holding
       across two portfolios being compared, and eight slots is the whole of
       that job. A fund publishes its top ten, so this one needs ten.
    
       Tokens rather than the color-mix ramp they replace, and that is the
       reason the ramp existed: a mix of one accent is derivable, so it needed
       no declaration. These are ten chosen hues whose dark steps are SELECTED
       rather than flipped -- six differ from their light value -- so there is
       nothing to derive them from. DECLARED IN ALL THREE SCOPES, and the one
       that is easy to miss is this one: an OS-dark reader is served by the
       @media block and a reader who used the in-app toggle is served by
       the explicit data-theme rule below. Shipped once with the block
       pasted twice into @media and absent from that rule, where every
       arc then resolved
       to nothing at all. test_both_themes_define_every_token does NOT cover
       this family -- its token list is a literal that names no --fund-*,
       --series-* or --corr-* -- so test_fund_chart.py slices this file into
       its three scopes and checks each by name.
    
       Validated with the dataviz validator against this application's own
       surfaces. On the adjacent pairlist both themes pass every check
       (worst CVD dE 19.2 light, 18.6 dark; normal-vision 29.2 and 27.5).
       ACROSS ALL PAIRS the worst CVD dE is 6.5 light and 6.1 dark, which is
       the 6-8 floor band. And three steps per theme sit below 3:1 against
       the chart surface -- light #14c466 2.30, #13b3ef 2.41, #ee8710 2.59;
       dark #980bcf 2.79, #b50a87 2.86, #2f48fd 2.95. BOTH of those are
       permitted only because every arc is also a labelled legend row
       carrying its own percentage and the holdings table below repeats all
       of it with no colour at all.
    
       THAT FLOOR IS A SEARCH RESULT AND NOT A PROOF. Simulated annealing
       over an OKLCH pool -- hue every 4 to 6 degrees, five or six lightness
       steps inside each theme's own band, chroma just inside gamut and above
       the 0.1 floor -- scored on ALL pairs under the Machado protan and
       deutan transforms in OKLab, against these two surfaces. The best
       separation FOUND was 9.6 at eight categories, 8.9 at nine and 7.4 at
       ten. One anneal per size, not an exhaustive enumeration: a wider pool,
       a different metric or a longer search may do better. What is claimed is
       that ten hues clearing 8 were not found here, not that none exist.
    
       And the palette that MAXIMISED that score was deliberately not
       shipped. It came back muted and blue-heavy -- the opposite of the
       bright, distinctive set this was asked for, and worse for the readers
       who are the large majority. These ten maximise normal-vision
       separation subject to a 6.0 CVD floor instead, which is the tradeoff
       being made rather than a limit being hidden. Remove the legend or the
       table and this palette stops being compliant. */
    --fund-1: #980bcf;
    --fund-2: #0fa957;
    --fund-3: #a368f5;
    --fund-4: #5a7406;
    --fund-5: #f211b5;
    --fund-6: #cd740c;
    --fund-7: #0f9ace;
    --fund-8: #dd0d2c;
    --fund-9: #2f48fd;
    --fund-10: #b50a87;

  --corr-neg: #6FB3DE;
  --corr-pos: #E39B54;
  --corr-mid: #7C8288;

    --lift:      0 6px 16px -4px rgb(0 0 0 / 0.55), 0 2px 4px -2px rgb(0 0 0 / 0.4);
    --lift-high: 0 18px 40px -12px rgb(0 0 0 / 0.7), 0 4px 8px -4px rgb(0 0 0 / 0.45);
  }
}

:root[data-theme="dark"] {
  --ground:      #0C1116;
  --chrome:      #080C10;
  --chrome-deep: #05080B;
  --surface:     #12181E;
  --raised:      #1C242B;
  --sunken:      #0E141A;

  --ink:    #E6ECF2;
  --ink-2:  #BFCBD6;
  --muted:  #8A98A5;
  --faint:  #808E9B;

  --rule:   #212A32;
  --rule-2: #313C46;

  --accent:      #63AEEA;
  --accent-ink:  #8CC6F5;
  --accent-soft: #10283C;
  --accent-line: #2F6D9E;
  --accent-on:   #06131F;

  --pos:       #4FBF87;
  --pos-soft:  #10261B;
  --neg:       #F0857A;
  --neg-soft:  #2B1614;
  --warn:      #E0A94A;
  --warn-soft: #2A2011;
  --info:      #8FA9D8;
  --info-soft: #161C29;

  /* The same eight hues, stepped for the dark surface -- a selected set, not
     an automatic flip of the light one. */
  --series-1: #3987e5;
  --series-2: #d95926;
  --series-3: #199e70;
  --series-4: #c98500;
  --series-5: #d55181;
  --series-6: #008300;
  --series-7: #9085e9;
  --series-8: #e66767;
  
  /* A fund's composition, by constituent. A SEPARATE SET FROM --series-*,
     because these answer a different question: --series-* names a holding
     across two portfolios being compared, and eight slots is the whole of
     that job. A fund publishes its top ten, so this one needs ten.
  
     Tokens rather than the color-mix ramp they replace, and that is the
     reason the ramp existed: a mix of one accent is derivable, so it needed
     no declaration. These are ten chosen hues whose dark steps are SELECTED
     rather than flipped -- six differ from their light value -- so there is
     nothing to derive them from. DECLARED IN ALL THREE SCOPES, and the one
     that is easy to miss is this one: an OS-dark reader is served by the
     @media block and a reader who used the in-app toggle is served by
     the explicit data-theme rule below. Shipped once with the block
     pasted twice into @media and absent from that rule, where every
     arc then resolved
     to nothing at all. test_both_themes_define_every_token does NOT cover
     this family -- its token list is a literal that names no --fund-*,
     --series-* or --corr-* -- so test_fund_chart.py slices this file into
     its three scopes and checks each by name.
  
     Validated with the dataviz validator against this application's own
     surfaces. On the adjacent pairlist both themes pass every check
     (worst CVD dE 19.2 light, 18.6 dark; normal-vision 29.2 and 27.5).
     ACROSS ALL PAIRS the worst CVD dE is 6.5 light and 6.1 dark, which is
     the 6-8 floor band. And three steps per theme sit below 3:1 against
     the chart surface -- light #14c466 2.30, #13b3ef 2.41, #ee8710 2.59;
     dark #980bcf 2.79, #b50a87 2.86, #2f48fd 2.95. BOTH of those are
     permitted only because every arc is also a labelled legend row
     carrying its own percentage and the holdings table below repeats all
     of it with no colour at all.
  
     THAT FLOOR IS A SEARCH RESULT AND NOT A PROOF. Simulated annealing
     over an OKLCH pool -- hue every 4 to 6 degrees, five or six lightness
     steps inside each theme's own band, chroma just inside gamut and above
     the 0.1 floor -- scored on ALL pairs under the Machado protan and
     deutan transforms in OKLab, against these two surfaces. The best
     separation FOUND was 9.6 at eight categories, 8.9 at nine and 7.4 at
     ten. One anneal per size, not an exhaustive enumeration: a wider pool,
     a different metric or a longer search may do better. What is claimed is
     that ten hues clearing 8 were not found here, not that none exist.
  
     And the palette that MAXIMISED that score was deliberately not
     shipped. It came back muted and blue-heavy -- the opposite of the
     bright, distinctive set this was asked for, and worse for the readers
     who are the large majority. These ten maximise normal-vision
     separation subject to a 6.0 CVD floor instead, which is the tradeoff
     being made rather than a limit being hidden. Remove the legend or the
     table and this palette stops being compliant. */
  --fund-1: #980bcf;
  --fund-2: #0fa957;
  --fund-3: #a368f5;
  --fund-4: #5a7406;
  --fund-5: #f211b5;
  --fund-6: #cd740c;
  --fund-7: #0f9ace;
  --fund-8: #dd0d2c;
  --fund-9: #2f48fd;
  --fund-10: #b50a87;

  --corr-neg: #6FB3DE;
  --corr-pos: #E39B54;
  --corr-mid: #7C8288;

  --lift:      0 6px 16px -4px rgb(0 0 0 / 0.55), 0 2px 4px -2px rgb(0 0 0 / 0.4);
  --lift-high: 0 18px 40px -12px rgb(0 0 0 / 0.7), 0 4px 8px -4px rgb(0 0 0 / 0.45);
}
