/* ===========================================================================================
   THE DARK THEME (#374)

   THIS FILE IS THE DARK THEME AND NOTHING ELSE. Every rule below is scoped to `html.dark`, so
   with the light theme selected - which is the default, and what every user gets until they
   switch - this file matches nothing at all and the app renders exactly as it did before it
   existed.

   THE LIGHT THEME IS NOT HERE. It is the design tokens in `tailwind-input.css` plus Tailwind's
   own palette, unchanged; the app-wide rules Tailwind cannot express are in
   `common/config/CssConfig.kt`, served from `/styles.css`. Neither of those two files needs a
   dark counterpart - this one is it.

   HOW IT WORKS. Tailwind v4 compiles every colour utility down to a variable rather than to a
   literal:

       .bg-white      { background-color: var(--color-white); }
       .text-gray-700 { color: var(--color-gray-700); }

   so re-pointing the variables under `html.dark` re-themes the whole app without touching a
   single class name in the Kotlin sources. It reaches the `hover:`, `focus:`, `divide-` and
   `placeholder:` variants for free, and the `/40`-style opacity modifiers too, because those
   are `color-mix()` over the very same variable.

   THE RULE, in two parts:

     1. Each ramp is MIRRORED, and the mid-tones are TONED DOWN. What was the lightest tint
        becomes the darkest and vice versa, so `text-gray-700` (dark ink on a white card) turns
        into light ink on a dark one, and `border-gray-200` (a pale hairline) turns into a dark
        one. The mid-tones (400-600) are the accent and the borders; they carry no light/dark
        role and are not mirrored, but they are pulled back in chroma, because a colour tuned to
        be an accent against white is the loudest thing on a dark page.

        The whole palette is very nearly monochrome as a result, with the blue accent as the one
        colour on screen that is allowed to be one.

        Contrast is carried by the GROUND, not by the ink. The ground is a soft charcoal rather
        than a true black, and against it the hierarchy is a matter of how far up the ramp each
        role is allowed to go: headings highest, body and data a step under, labels a long way
        under that, and everything that is not text - the fills, the hairlines, the ground itself -
        within a narrow band at the bottom.

        BOTH ENDS OF THE RAMP ARE PULLED IN, which is what keeps the theme from feeling harsh. A
        near-black page under near-white ink is a 16:1 screen, and that much separation reads as
        glare rather than as clarity; here the ground is lifted and the ink stops well short of
        white, so a heading lands around 13:1 and body text around 10:1 on a card - still far clear
        of any contrast floor, and far easier to sit in front of. It fixes a rendering problem at
        the same time: a near-white bold glyph on a near-black ground blooms outwards, and the halo
        it throws is read as a frayed edge.

     2. Section 2 lists the EXCEPTIONS, the handful of places where one variable is ink in some
        utilities and a surface in others and a mirror cannot satisfy both. Each is overridden
        at utility level with the reason it is there. If a new screen ever looks wrong in dark,
        this is where the one-line fix goes.

   Nothing here is in an `@layer`, and unlayered rules win over layered ones no matter what the
   specificity is - which is why none of this needs `!important` against Tailwind's own
   `@layer theme` variables or `@layer utilities` rules.

   Adding a colour to the app? Check it in both themes. See the "Themes" section of docs/DESIGN.md.
   =========================================================================================== */

/* -------------------------------------------------------------------------------------------
   1. THE MIRRORED RAMPS
   ------------------------------------------------------------------------------------------- */

html.dark {
  /* The page ground behind the surfaces. A soft charcoal with the faintest cool cast - NOT a true
     near-black. The palette is still monochrome with the one blue accent, but the ground is lifted
     several points off #000, because a near-black page drives every piece of light ink on it to a
     15:1-and-up contrast, and that much separation on a screen is what makes a dark UI feel harsh
     and glary rather than dim. Raising the ground and backing the ink off the top of the ramp keeps
     the whole app comfortably above any contrast floor while taking the edge off it.

     It is DARKER than the cards, so the page recedes and the panels sit on top of it, which is the
     same figure/ground relationship as in the light theme. That step is what separates a panel
     here; the border below is only a hairline confirming the edge. */
  --color-canvas: #15171a;

  /* The neutral ramp, mirrored. 50 is the near-black end and 900 the near-white one.
     Read from the top down it is the app's greys turned inside out:
       50/100  - the pale fills (bg-gray-50, hover:bg-gray-50, bg-gray-100) become dark fills,
                 kept a step LIGHTER than the card surface below so that hovering still lifts a
                 row rather than sinking it
       200/300 - the hairlines (border-gray-200, border-gray-300 on inputs), barely above the
                 surface they sit on: a border that announces itself turns a dense table into a
                 grid of boxes
       400-600 - the muted ink (placeholders, hints, quiet uppercase labels)
       700-900 - the ink proper (labels, then body text and data, then headings)

     The bottom half of the ramp is nearly hueless and sits close to the ground; the top half
     carries a slight cool cast, which is what keeps a light ink from looking like a bare bulb.

     The ink STOPS WELL SHORT OF WHITE, and that is a rendering decision as much as a comfort one: a
     near-white bold glyph on a dark ground blooms outwards, and the halo it throws is read as a
     frayed edge. The top of the ramp is backed off far enough that a heading lands around 13:1 on
     a card rather than 16:1, and the muted steps are lifted with the ground so the quiet ink keeps
     the same relation to the surface it sits on as it always had. Nothing here is near a contrast
     floor; the ramp is simply narrower at both ends than the mirror on its own would make it. */
  --color-gray-50: #24262b;
  --color-gray-100: #292b30;
  --color-gray-200: #32353b;
  --color-gray-300: #3b3e45;
  --color-gray-400: #70757d;
  --color-gray-500: #90959e;
  --color-gray-600: #a7acb4;
  --color-gray-700: #c4c9d1;
  --color-gray-800: #d3d7dd;
  --color-gray-900: #e2e5ea;

  /* The semantic hues, mirrored the same way. The alert pattern is
     `bg-X-100 border-X-400 text-X-700`, so mirroring the two ends turns a pale panel with dark
     text into a dark tinted panel with light text.

     The mid-tones (400-600) are the accents and the borders. They are NOT mirrored - they carry
     no light/dark role - but they are pulled a long way back in chroma: a colour tuned to be an
     accent against white is the loudest thing in a room this dark. Everything here is desaturated
     until it reads as a tint of the neutral ramp with a hue, rather than as a colour.

     The tinted panels are a shade darker than the neutral fills at the same step, because they are
     tints of the CARD rather than of the page: a panel that is lighter than the card it lies on
     reads as raised, and an error message is not a raised surface. */
  --color-red-50: #2a1f22;
  --color-red-100: #33262a;
  --color-red-400: #a06a6e;
  --color-red-500: #b0777b;
  --color-red-600: #b98084;
  --color-red-700: #e2adaf;

  --color-green-100: #1e2a24;
  --color-green-200: #253429;
  --color-green-400: #5c9d81;
  --color-green-500: #58977a;
  --color-green-700: #96ccb1;
  --color-green-900: #b6dfc8;

  --color-emerald-100: #1e2a25;
  --color-emerald-400: #5ca189;
  --color-emerald-500: #56987f;
  --color-emerald-700: #92ceb4;

  --color-yellow-100: #2b291e;
  --color-yellow-400: #ab9351;
  --color-yellow-700: #d5c18e;
  --color-yellow-800: #e0cfa5;

  --color-amber-50: #27241c;
  --color-amber-100: #2e2921;
  /* The valuation tile draws each of its three methods with the 200-tint of its own hue. All
     three have to be equally quiet, so this one is matched in lightness to blue-200 and
     green-200 below rather than left at Tailwind's pale #fde68a, which on a dark card was the
     one border of the three that shouted. */
  --color-amber-200: #363128;
  --color-amber-500: #b29155;
  --color-amber-600: #c19f69;
  --color-amber-700: #d0b181;
  --color-amber-800: #dcc39c;
  --color-amber-900: #e5d2b5;

  --color-sky-100: #1d2932;
  --color-sky-500: #5b98b8;
  --color-sky-700: #8ec0d8;

  --color-indigo-400: #7880bd;

  /* Only the ADMIN role chip on the user list uses purple. */
  --color-purple-100: #33293f;
  --color-purple-700: #c4aee3;

  --color-rose-500: #b66d78;

  /* Blue is the one accent of the app and means *action* - and in a palette this quiet it is the
     only colour on the page that is allowed to be one. It is mirrored at the ends like every other
     hue: the pale fills that mark a selected dropdown option become dark blue, the dark blue text
     on them becomes light. In the middle, where it is the primary button, the toggle and the focus
     ring, it is cooled and desaturated - Tailwind's blue-600 is an electric colour built to carry a
     white page, and against this ground the same value is the brightest thing on screen.

     It is also pulled back towards a TRUE blue (around 210 degrees) rather than the indigo Tailwind
     sits at: desaturating a violet-leaning blue that far leaves it reading as purple rather than as
     a quieter blue, and the accent of this app is a blue. Lightness is unchanged from that indigo,
     so the accent is exactly as loud as it was - only its hue moved.

     The tint end stops being a ramp, because the app puts it to two jobs that pull in opposite
     directions on a dark ground. 50 and 100 are FILLS - the hovered and the selected option of a
     dropdown - and have to sit clearly above the panel they lie on (#1d1f23) or the selection
     disappears into it. 200 is a HAIRLINE, the valuation tile's DCF border, and has to stay as
     quiet as the amber and green ones beside it. So 200 is darker than 100 rather than lighter,
     and the one place that fills with it is corrected in section 2. */
  --color-blue-50: #26313d;
  --color-blue-100: #2c3f54;
  --color-blue-200: #27394a;
  --color-blue-300: #6396c8;
  --color-blue-400: #5287bd;
  --color-blue-500: #528ac1;
  /* The filled primary button. A step darker than the mid-tone the rest of the ramp would suggest,
     and the reason is the company it keeps: every other button on a dark screen here is a tinted
     one, sitting a few points above its card, and a mid blue next to those reads as lit rather than
     as primary. This is a small step - about a fifth of the luminance - so the button is still the
     brightest thing in a row of buttons, which is its job; it just stops being the brightest thing
     on the page. White on it is a little under 6:1. */
  --color-blue-600: #3a689f;
  --color-blue-700: #96bce0;
  --color-blue-800: #abcbe8;
  --color-blue-900: #c2d9ef;

  /* Elevation. On this ground a drop shadow is nearly invisible anyway, so the resting surfaces
     rely on the step between the canvas and the card instead, and only what genuinely floats
     above the page keeps any weight at all. */
  --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.2);
  --shadow-md: 0 1px 2px 0 rgba(0, 0, 0, 0.25), 0 2px 4px -2px rgba(0, 0, 0, 0.3);
  --shadow-lg: 0 1px 2px 0 rgba(0, 0, 0, 0.3), 0 6px 16px -6px rgba(0, 0, 0, 0.55);
  --shadow-xl: 0 2px 4px -1px rgba(0, 0, 0, 0.4), 0 12px 32px -8px rgba(0, 0, 0, 0.6);
  --shadow-2xl: 0 4px 8px -2px rgba(0, 0, 0, 0.45), 0 24px 48px -12px rgba(0, 0, 0, 0.65);

  /* The amber wash marking the forecast periods, thinned for the same reason every accent above
     is: 14% amber over a white card is a hint, over a dark one it is a solid olive block. Read by
     the legend swatches through FORECAST_BACKGROUND_COLOR; the canvas charts, which cannot use a
     variable, take the matching value from `window.aifChartTheme.forecastBand`. */
  --forecast-band: rgba(251, 191, 36, 0.06);

  /* The browser's own furniture: form control accents, the scrollbars and the default
     background it paints behind an unstyled element. */
  color-scheme: dark;
}

/* SMOOTHING, and only here. Subpixel antialiasing lays a coloured fringe along every stem, which is
   invisible under dark ink on a white page and a rainbow edge under light ink on a black one. So
   the dark theme - and nothing else - asks for the grayscale kind. The light theme deliberately
   does not: see the note in `CssConfig.kt`. */
html.dark body {
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

/* -------------------------------------------------------------------------------------------
   2. WHAT THE MIRROR CANNOT EXPRESS

   One variable, two roles. Each of these is a colour the app uses as ink in one place and as a
   surface in another, so mirroring it fixes one and breaks the other. The utility that wanted
   the surface is re-pointed here; the one that wanted the ink keeps the mirrored variable.
   ------------------------------------------------------------------------------------------- */

/* WHITE. `--color-white` has to stay white, because `text-white` sits on the blue buttons and on
   the dark table rows below and is meant to be white in both themes. So the white *surfaces* are
   the ones that move: every card, every table body, every dropdown panel, the navigation rail. */
html.dark .bg-white {
  background-color: #1d1f23;
}

html.dark .border-white {
  border-color: #32353b;
}

/* FILLS ON THE SHADE END. These are the few `bg-` utilities at 700+, where the mirrored variable
   is the light ink meant for `text-*-700`. They are buttons and badges and have to stay fills. */
/* The primary button's hover, taken down with the fill above it so the lift stays the same size. */
html.dark .hover\:bg-blue-700:hover {
  background-color: #4576ab;
}

html.dark .bg-green-700 {
  background-color: #427a60;
}

html.dark .bg-indigo-700 {
  background-color: #4d5595;
}

html.dark .bg-amber-700 {
  background-color: #866c3b;
}

/* THE AI RECOMMENDATION TILE, the one full-width blue panel of the dashboard. `bg-blue-500`,
   `hover:bg-blue-600` and the `bg-blue-400` of its loading placeholder are used by nothing else in
   the app, so all three are the tile and can be tuned as one thing.

   The mirror left it at the mid-tone the light theme uses, and a mid blue spread across the whole
   width of a dark page is a lit panel: it pulls the eye before the data does, which is the wrong
   order. It is taken down to a deep blue that still reads as the accent - white on it is over 7:1,
   better than it was - and desaturated a little with it, so the tile sits ON the page rather than
   in front of it.

   Hover goes LIGHTER, not darker as it does on white. On this ground a fill that darkens on hover
   recedes into the page, and this tile is a link: the same reasoning as the `bg-gray-50` row hover
   at the top of this file. */
html.dark .bg-blue-500 {
  background-color: #35597d;
}

html.dark .hover\:bg-blue-600:hover {
  background-color: #40688c;
}

html.dark .bg-blue-400 {
  background-color: #3b6087;
}

/* THE DESTRUCTIVE BUTTON of the confirmation dialog is the one place in the app filled with red, and
   the mirror's toned-down mid-tone left it a dusty rose - a colour that reads as *disabled* on a
   control whose whole job is to give the user pause. It is saturated back to a real red instead.
   Note which way that moves the contrast: this red is no lighter than the one it replaces, so the
   white label on it is easier to read than before (about 5:1), not harder. `bg-red-600` and
   `hover:bg-red-700` are used by nothing else in the app, so the fill can be corrected here without
   dragging `text-red-600` - which is error *ink* - along with it. */
html.dark .bg-red-600 {
  background-color: #bd4b54;
}

html.dark .hover\:bg-red-700:hover {
  background-color: #a94249;
}

/* INK ON THE MID-TONES. The mirror leaves 400-600 alone because they are the accents and the
   borders - but blue at those steps is also the colour of a *link*, and a mid blue that reads as
   an accent on white is nearly the page itself on a dark ground. The indicator names on
   "Wskaźniki finansowe" are the worst of it: a whole table of `text-blue-600` sinking into the
   card. The fill keeps the mid-tone; the ink is lifted to the light end of the same hue, which is
   where `text-blue-700` and up already sit, so every link in the app is one colour again. */
html.dark .text-blue-500,
html.dark .text-blue-600 {
  color: #96bce0;
}

html.dark .hover\:text-blue-700:hover,
html.dark .hover\:text-blue-800:hover {
  color: #b0d0eb;
}

/* Same story for the one sky link and the emerald figures. */
html.dark .text-sky-500,
html.dark .text-sky-600 {
  color: #8ec0d8;
}

/* THE TWO PLACES THE BLUE TINTS ARE NOT A FILL. `--color-blue-100` is raised above the dropdown
   panel so a selected option reads as selected; the one place that uses the same step as *ink* -
   the retry link on the blue chat tile - would go dark with it, so it keeps a light blue of its
   own. And `--color-blue-200` stays down at the hairline value the valuation borders need, so the
   one control that fills with it gets the fill value instead. */
html.dark .hover\:text-blue-100:hover {
  color: #cbdff2;
}

html.dark .hover\:bg-blue-200:hover {
  background-color: #32495f;
}

/* THE SOFT-FILLED CONTROLS - the action buttons of user and data management ("link rejestracji",
   "blokuj", "usuń") and the status pills beside a user's name ("NIEAKTYWNY", "ZABLOKOWANY",
   "LINK WYGASŁ"). Each is a pale tint of its hue with ink of the same hue on top - `bg-red-50
   text-red-700`, `bg-amber-100 text-amber-800`, `bg-gray-100 text-gray-700` - and mirroring those
   tokens is what a warning, a destructive action or a status must NOT get: the tint end of any hue
   on a dark ground is a near-black, so all of them came out as dark smudges with muted ink. A
   control whose colour IS its meaning cannot be the least visible thing in the row.

   So they are lifted clear of the card and given ink with the chroma left in - a red that reads as
   red and a yellow that reads as yellow, at 8:1 or better on their own fill.

   THE FOUR FILLS ARE MATCHED IN LIGHTNESS, which is the whole point of writing them out here: the
   neutral, the red, the amber and the blue `bg-blue-50` of "resetuj hasło" (which the mirror
   happens to land in the right place on its own, and which is therefore left alone) sit within a
   few points of each other. A row of buttons in four colours should read as one row of buttons.
   The pill and the button of a colour are deliberately given the same values, so that the pill
   marking a blocked user and the button that unblocks them are visibly the same red - in the light
   theme they differ by one step of the ramp, which is right for a dense little chip on white but
   cannot be reproduced here without one of the two going muddy.

   Why marker classes rather than the `bg-red-50` / `bg-amber-*` utilities: those same tints are the
   forecast wash behind every predicted figure in the results, indicator and valuation tables, and
   the panel of an INFO alert. Brightening the tokens would repaint all of that. The class is what
   separates "this tint is a control" from "this tint is a wash". */
html.dark .soft-danger-button,
html.dark .soft-danger-chip {
  background-color: #452429;
  color: #f0abaf;
}

html.dark .soft-danger-button:hover {
  background-color: #522d32;
}

html.dark .soft-warning-button,
html.dark .soft-warning-chip {
  background-color: #453516;
  color: #f2cd92;
}

html.dark .soft-warning-button:hover {
  background-color: #52401c;
}

html.dark .soft-neutral-button,
html.dark .soft-neutral-chip {
  background-color: #2e3036;
  color: #c4c9d1;
}

html.dark .soft-neutral-button:hover {
  background-color: #393c42;
}

/* THE SERIES CHIP that names a selected parameter on "Trendy finansowe" is filled with that
   series' own colour, and its label is `text-white`. White does not survive that fill: in the dark
   palette every one of the eight is far too light for it. The fills carry near-black instead, at
   about 7:1 on all of them - so in this one theme the chip's ink flips rather than its background.
   The remove button's hover follows: a white veil over a light fill is invisible, a black one is
   not.

   ONE ink for eight fills only works because the dark palette is levelled in lightness - see
   PARAMETER_COLORS_DARK in DataGraphs.kt, which is where a chip that looks wrong is fixed. Adding a
   colour to that list that is darker than the rest puts black ink on a dark fill, which clears
   every contrast bar and still looks like a mistake. */
html.dark .series-chip {
  color: #16181a;
}

html.dark .series-chip .hover\:bg-white\/20:hover {
  background-color: rgb(0 0 0 / 0.18);
}

/* THE SELECTED SEGMENT of the language and theme switches is white on a `bg-gray-100` track, and
   the selection is that it is raised out of the track. Mirrored, the track (gray-100) comes out
   lighter than the segment (white) and the raised one reads as pressed in. It is lifted back above
   its track here. */
html.dark .segment-selected {
  background-color: #2e3137;
}

/* THE TOGGLE SWITCH PUCK is a white circle sliding along a gray or blue track, so it is the one
   white thing in the app that is a *mark* and not a surface. Turned dark with the cards it would
   vanish into the track it slides along. */
html.dark .toggle-knob {
  background-color: #d3d7dd;
}

/* THE LOADING VEIL (#screen-overlay) is the app's only `bg-gray-500` surface, drawn at 70% over the
   whole page while htmx is working. Mirrored it would be a LIGHT sheet, so every request in the dark
   theme would flash the screen white. It darkens instead - a couple of points under the page ground,
   so that at 70% it dims the app without blacking it out. */
html.dark .bg-gray-500 {
  background-color: #101215;
}

/* THE DASHBOARD TILE HAIRLINE is 5% black, chosen so that one border works on both the white
   tiles and the blue chat one. At 5% black on a dark tile there is nothing to see. */
html.dark .border-black\/5 {
  border-color: rgb(255 255 255 / 0.07);
}

/* ICONS. The 44 files under /static/icons are black-ink SVGs served as <img>, and an <img> cannot
   inherit `currentColor` from the text around it (docs/DESIGN.md) - which is why the app already
   normalises them with `brightness-0` instead. On a dark surface they have to come back out
   white. One rule covers all three cases: the plain black ones, the ones already forced black
   with `brightness-0`, and the two authored white for a coloured background (chat.svg, send.svg)
   - all of them want to be white here. The `opacity-60` that dims an inactive one is a separate
   property and still applies. */
html.dark img[src^="/static/icons/"] {
  filter: brightness(0) invert(1);
}

/* THE WORDMARK is black on transparent, so on the rail and on the login card it would be
   invisible. Inverted it becomes the same mark in white.

   Named file by file rather than by the /static/logo/ folder it sits in, because the square app
   icon next to it (192.png, which the collapsed rail falls back to) is finished colour artwork -
   white letters on its own black tile - and already reads on a dark rail. Inverting that one turns
   it into a negative of itself. Same reasoning as the language switch's flags, which are left
   alone for being artwork rather than ink. */
html.dark img[src="/static/logo/company-logo.png"] {
  filter: invert(1);
}

/* THE TILE SHIMMER of a loading dashboard tile is a white sweep, which is already the right
   direction on a dark placeholder - it is only far too bright at the 65% it is given on white. */
html.dark .tile-placeholder::after {
  background: linear-gradient(90deg, transparent, rgb(255 255 255 / 0.07), transparent);
}
