/*
 * Design tokens. The single source of colour, spacing and layout scalars for
 * both /admin and the public site.
 *
 * Two tiers on purpose:
 *
 *   Tier 1 (primitives) holds the brand's actual colours, including the ones
 *   that are not safe as text. Nothing outside this file should reference a
 *   primitive directly.
 *
 *   Tier 2 (semantic) decides which primitive a given role gets. Components
 *   only ever touch these, so re-pointing --color-brand re-skins the app.
 *
 * Contrast ratios below are measured against white and are the reason the tiers
 * exist: two of the three brand colours fail WCAG AA as text.
 */
:root {
  /* --- Tier 1: primitives ------------------------------------------------ */

  /* Brand green (billfors.se header). 3.94:1 on white -- large text only, so
     it is a surface colour. --green-700 is a darkened derivative that clears
     AA for body text and button labels; it is what --color-brand points at. */
  --green-700: #1f7a2b; /*  5.42:1  safe as text        */
  --green-600: #2a9337; /*  3.94:1  brand, surfaces     */
  --green-100: #e3f0e5;

  /* Brand orange wash. The saturated orange formerly paired with it had no
     consumers and was not accessible as text. */
  --orange-100: #fbeedd;

  /* Brand purple (front-page "Lediga objekt"). The only brand colour that is
     safe anywhere, which is why the admin leans on it. */
  --purple-700: #541877; /* 11.84:1 safe anywhere       */
  --purple-500: #7b2ca8; /* chart fills, see below      */
  --purple-100: #f0e6f6;

  /* Neutrals, biased very slightly green so they sit with the brand rather
     than against it. */
  --grey-50: #f7f8f6;
  --grey-100: #eef0ed;
  --grey-200: #dfe3dd;
  --grey-300: #c9cfc6;
  --grey-500: #7c877f;
  --grey-600: #55605a;
  --grey-800: #2b332e;
  --grey-900: #171e19;

  --red-600: #a32020;
  --red-100: #f7e4e4;
  --blue-600: #1c5fa8;
  --blue-100: #e3edf8;
  --amber-700: #9a5a00;
  --amber-100: #fbf0dc;

  /* --- Tier 1: dark steps -------------------------------------------------
   *
   * The same hues re-stepped for a dark ground, and not by formula: a colour
   * that clears AA on white is usually too dark to read on near-black.
   * --purple-700 is the clearest case -- 11.84:1 on white, 1.6:1 on
   * --grey-950, i.e. gone. Every step below was measured against --grey-950
   * the way the light ramp was measured against white.
   *
   * The neutrals keep the same slight green bias, so the dark admin is a
   * dimmed version of the same room rather than a different one. */
  --grey-950: #10150f; /* page ground                  */
  --grey-875: #1c231e; /* cards, app bar               */
  --grey-700: #3a443d; /* hairlines                    */
  --grey-400: #98a49b; /*  7.15:1 faint text           */

  --purple-400: #a86fd0; /* 5.15:1 chart fills         */
  --purple-300: #c9a1e6; /* 8.56:1 safe anywhere       */
  --purple-900: #2a1636; /* washes                     */

  --green-400: #57c169; /*  8.13:1                     */
  --green-900: #14301a;
  --red-400: #f08a8a; /*    7.66:1                     */
  --red-900: #3a1616;
  --blue-400: #74b0ea; /*   8.05:1                     */
  --blue-900: #142a40;
  --amber-400: #e0a445; /*  8.42:1                     */
  --amber-900: #38280d;

  /* Gold, for the primary-image star. Two steps because no single yellow is
     both legible on white and still yellow: #f0ad4e, the Bootstrap gold this
     replaces, is 1.95:1 on white. --gold-600 is deepened only as far as the
     3:1 a non-text graphic needs and still reads gold rather than brown;
     --gold-300 is the bright one, which near-black can afford. */
  --gold-600: #b8860b; /*  3.25:1 on white            */
  --gold-300: #f5c542; /* 11.40:1 on --grey-950       */

  /* Post-it yellow, for the notes on a tenant. A paper colour rather than a
     status, so it is neither the amber of "expiring" nor the star's gold. */
  --note-100: #fff4b8;
  --note-300: #ead77c;
  --note-800: #5b5022;
  --note-900: #3a3416;

  /* --- Tier 2: semantic -------------------------------------------------- */

  /* Both schemes ship, and light-dark() is the whole mechanism: it resolves
     against the *used* colour scheme of the element, so every pair below
     switches together the moment `color-scheme` changes on <html>. That is one
     property for the toggle to write instead of a parallel set of overrides,
     and it is also the only way to re-skin what CSS cannot reach -- native
     form controls, scrollbars, and the canvas behind a page shorter than the
     viewport. See the bottom of this file for the two override rules. */
  color-scheme: light dark;

  --color-bg: light-dark(#fff, var(--grey-950));
  --color-surface: light-dark(var(--grey-50), var(--grey-875));
  --color-surface-sunk: light-dark(var(--grey-100), var(--grey-800));

  /* A band that groups the rows under it -- the house captions on
     /admin/lagenheter. It has to clear both the page ground and the zebra
     stripe (--color-surface) to read as a heading rather than another row,
     which --color-surface-sunk does not: it is one step from the stripe in
     light mode. This is that step further out, in both schemes. */
  --color-surface-band: light-dark(var(--grey-200), var(--grey-700));
  --color-text: light-dark(var(--grey-900), var(--grey-100));
  --color-text-muted: light-dark(var(--grey-600), var(--grey-300));
  --color-text-faint: light-dark(var(--grey-500), var(--grey-400));
  --color-border: light-dark(var(--grey-200), var(--grey-700));
  --color-border-strong: light-dark(var(--grey-300), var(--grey-600));

  /* Admin primary is purple: it is the accessible brand colour and it reads as
     "the tool" rather than "the company". The public site re-points this to
     --green-700 in site.css, which is the whole point of the indirection. */
  --color-brand: light-dark(var(--purple-700), var(--purple-300));
  --color-brand-ink: light-dark(#fff, var(--grey-950));
  --color-brand-wash: light-dark(var(--purple-100), var(--purple-900));

  /* Actions are a separate role from links and brand decoration. The public
     site can therefore keep green buttons without a higher-specificity
     component override. */
  --color-action: light-dark(var(--purple-700), var(--purple-300));
  --color-action-ink: light-dark(#fff, var(--grey-950));
  --action-hover-brightness: 1.15;

  --color-danger: light-dark(var(--red-600), var(--red-400));
  --color-danger-ink: light-dark(#fff, var(--grey-950));
  --color-danger-wash: light-dark(var(--red-100), var(--red-900));
  --color-focus: light-dark(var(--purple-700), var(--purple-300));
  --color-link: light-dark(var(--purple-700), var(--purple-300));

  /* The app bar and the caretaker bar are their own ground. They are the one
     surface in the admin that is dark in both schemes, which is why every
     overlay on them is a hardcoded white alpha rather than a token -- those
     rules stay correct only as long as the bar stays dark, and this token is
     what promises it.
     In light the bar is the brand. In dark it is not: a bright purple band
     across the top of a dark page glows, and --color-brand has to step light
     anyway to survive as a link colour, which would make the bar lighter than
     the page it sits on. So the bar steps back to a raised neutral and the
     brand goes back to being only the link and button colour.
     --color-appbar-edge does the job the drop shadow stops doing on a dark
     ground: separate the bar from the page. */
  --color-appbar: light-dark(var(--purple-700), var(--grey-875));
  --color-appbar-ink: light-dark(#fff, var(--grey-100));
  --color-appbar-edge: light-dark(transparent, var(--grey-700));

  /* An inverted surface: the sticky table head and the "inverse" chip, both of
     which are deliberately the opposite of the page around them. They read as
     --color-text on --color-bg and mostly could have kept naming those, but
     there is no token for "one shade off the inverted ink" -- and the two
     places that needed it reached for a primitive instead, which is a fixed
     colour and the one thing on the page that would not have inverted.
     Measured both ways: 16.99:1 / 10.7:1 in light, 16.13:1 / 5.71:1 in dark. */
  --color-inverted: light-dark(var(--grey-900), var(--grey-100));
  --color-inverted-ink: light-dark(#fff, var(--grey-950));
  --color-inverted-ink-muted: light-dark(var(--grey-300), var(--grey-600));

  /* The star on the gallery card that is the presentation's main image. Its own
     token, like --chart-bar, because it is one mark with one job and it is the
     one thing in the admin that is deliberately gold: a star is gold or it is
     not a star. That does not reopen the orange question above -- this is a
     glyph on a 34px button, never a surface, and the state it reports is
     carried by the filled-vs-outline star and the card's brand border before
     colour gets a say. */
  --color-star: light-dark(var(--gold-600), var(--gold-300));
  /* A note is yellow in both schemes, the way a star is gold: dimmed paper
     and pale ink when dark, not an inverted grey card. */
  --color-note: light-dark(var(--note-100), var(--note-900));
  --color-note-edge: light-dark(var(--note-300), var(--note-800));
  --color-note-ink: light-dark(var(--amber-900), var(--note-100));

  /* --- Status ramp ------------------------------------------------------- */
  /* Contract states. Kept off the brand hues on purpose so a contract state is
     never confused with branding, and vice versa. Fill alone is not the signal
     -- see the left border in _status.css -- because the old 50%-alpha
     green/blue/red fills were indistinguishable to a red-green colourblind
     user. */
  --status-active: light-dark(var(--green-700), var(--green-400));
  --status-active-wash: light-dark(var(--green-100), var(--green-900));
  --status-upcoming: light-dark(var(--blue-600), var(--blue-400));
  --status-upcoming-wash: light-dark(var(--blue-100), var(--blue-900));
  --status-expiring: light-dark(var(--amber-700), var(--amber-400));
  --status-expiring-wash: light-dark(var(--amber-100), var(--amber-900));
  --status-expired: light-dark(var(--red-600), var(--red-400));
  --status-expired-wash: light-dark(var(--red-100), var(--red-900));
  --status-canceled: light-dark(var(--grey-500), var(--grey-400));
  --status-canceled-wash: light-dark(var(--grey-100), var(--grey-800));

  /* --- Chart fills --------------------------------------------------------
   * The income bars carry one series: rent that matched a contract. Payments
   * with no payer we recognise are left out of the chart entirely.
   *
   * --purple-700 is the brand and is right for text and buttons, but it sits
   * below the lightness band a categorical fill wants -- as a mark it reads as
   * near-black rather than as purple. --purple-500 is the same hue re-stepped
   * into the band, and is used only inside charts.
   *
   * One series, so there is no categorical pair to check: the only contrast
   * that has to hold is bar against the card surface, which this clears at
   * 3:1. */
  --chart-bar: light-dark(var(--purple-500), var(--purple-400));

  /* --- Spacing ----------------------------------------------------------- */
  --space-1: 0.25rem;
  --space-2: 0.5rem;
  --space-3: 0.75rem;
  --space-4: 1rem;
  --space-5: 1.25rem;
  --space-6: 1.5rem;
  --space-8: 2rem;
  --space-12: 3rem;

  /* --- Type ---------------------------------------------------------------
   *
   * The ramp was already here; what was missing was anyone using the top of
   * it. --text-xl was referenced zero times and --text-2xl once, so every
   * admin page lived inside 12/14/16/18 -- a 1.5x total range with the page's
   * own subject only 1.125x larger than its body text. That is the whole
   * reason the pages read as flat: there is no entry point for the eye, so a
   * section title and the sentence under it compete on equal terms.
   *
   * Each step now has one job, and base.css binds the headings to them:
   *
   *   --text-xs    12  overlines, table headers, badges. Uppercase + tracked.
   *   --text-sm    14  dense UI: table cells, chips, buttons, breadcrumbs.
   *   --text-base  16  body, and the value half of a label/value pair.
   *   --text-lg    18  h3 -- a section inside a page. legend, .card__title.
   *   --text-xl    22  h2 -- a major division of a page.
   *   --text-2xl   28  h1 -- the page subject. One per page.
   *
   * The gaps are deliberately uneven. 12->14->16 are one family (dense UI at
   * three densities) and want small steps; 18->22->28 are headings and want
   * large ones, because a heading has to win against the text it sits above
   * before it can do anything else. */
  --font-sans: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue",
    Arial, sans-serif;
  --font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  --text-xs: 0.75rem;
  --text-sm: 0.875rem;
  --text-base: 1rem;
  --text-lg: 1.125rem;
  --text-xl: 1.375rem;
  --text-2xl: 1.75rem;

  /* Four leadings, because "line-height" is four different problems.
   *
   * Type sets tighter as it grows: at 28px the 1.25 that used to be the only
   * heading value left a visible trough between the two lines of a wrapped
   * title, so the block stopped reading as one object. --leading-tight is for
   * 22px and up only.
   *
   * --leading-snug is the one that was missing. Data rows ("Lägenheter: 8"
   * over "Hyresgäster: 7") are a list, not prose: at --leading-normal the two
   * halves of a pair drift apart until they read as unrelated lines. Prose
   * leading on tabular data is most of why the dense blocks feel loose.
   *
   * --leading-normal drops 1.55 -> 1.5. 1.55 is a reading value and the admin
   * is scanned, not read; the 5% back is roughly one extra table row per
   * screen. --leading-relaxed keeps 1.65 for the two places that really are
   * read start to finish -- .doc and .measure, i.e. the tenant-facing contract
   * and invoice. */
  --leading-tight: 1.2;
  --leading-snug: 1.35;
  --leading-normal: 1.5;
  --leading-relaxed: 1.65;

  /* system-ui is drawn for text, so it tracks slightly loose once it is used
     at display sizes; the large headings pull back. The wide value is the
     uppercase overline -- caps have no ascender/descender rhythm to separate
     them and need the space. Both existed already as five different ad-hoc
     values (0.02/0.03/0.04/0.06em) on rules doing the same job. */
  --tracking-tight: -0.012em;
  --tracking-wide: 0.04em;

  /* --- Layout scalars ---------------------------------------------------- */
  /* Header height. `thead.sticky th` used to hardcode `top: 48px`, which meant
     any change to the navbar silently misaligned every sticky table header in
     the admin. Both now read this token. */
  --header-h: 3.25rem;

  /* Minimum touch target. Bootstrap's .btn lands around 34px, which is under
     every published guideline and is most of why the admin feels bad on a
     phone. Buttons, nav links and table row actions all clear this. */
  --tap: 44px;
  --control-sm: 2.25rem;

  --measure: 68ch;
  --radius: 4px;
  --radius-lg: 8px;
  --radius-pill: 999px;

  --duration-fast: 150ms;
  --duration-normal: 200ms;
  /* For movement that is meant to be watched rather than merely not jump --
     a picture growing under the pointer, a lightbox arriving. At 200ms those
     read as a repaint; this is long enough to see the thing travel. */
  --duration-slow: 400ms;
  --ease-standard: cubic-bezier(0.2, 0, 0, 1);
  /* Fast out of the gate and settling at the end: for entrances, where
     --ease-standard's slow start makes the arrival feel late. */
  --ease-out: cubic-bezier(0.2, 0.7, 0.3, 1);
  --shadow-sm: 0 1px 2px light-dark(rgb(23 30 25 / 0.08), rgb(0 0 0 / 0.6));
  --shadow-md: 0 4px 12px light-dark(rgb(23 30 25 / 0.12), rgb(0 0 0 / 0.7));
  --shadow-lg: 0 8px 32px light-dark(rgb(23 30 25 / 0.18), rgb(0 0 0 / 0.8));

  /* Environment badges deliberately keep deployment colours stable across
     themes; their job is warning, not page decoration. */
  --environment-shadow: 0 -2px 7px rgb(0 0 0 / 0.3);
  --environment-database-bg: #f59e0b;
  --environment-database-ink: #241700;
  --environment-default-bg: #1f2937;
  --environment-default-ink: #f9fafb;
  --environment-test-bg: #2563eb;
  --environment-test-ink: #fff;
  --environment-dev-bg: #16a34a;
  --environment-dev-ink: #052e16;

  /* The app bar outranks the drawer: the drawer opens beneath it so the button
     that opened it stays visible and can close it again. */
  --z-sticky: 10;
  --z-drawer: 30;
  --z-header: 40;
  --z-popover: 50;
  /* Above the popover: a toast reports the result of the action that closed
     whatever was open, so it has to be readable over the top of it. */
  --z-toast: 60;
  --z-drag: 70;
  --z-environment: 80;
}

/* --- The toggle -----------------------------------------------------------
 *
 * No [data-theme] means "follow the OS", which is the default and what a first
 * visit gets. These two rules are the only thing the toggle changes:
 * javascripts/theme.js writes the attribute, and shared/_theme_boot
 * restores the saved choice before first paint so a dark admin never flashes
 * white on navigation. */
:root[data-theme="light"] {
  color-scheme: light;
}
:root[data-theme="dark"] {
  color-scheme: dark;
}

/* Paper is light. Browsers drop background fills when printing but keep text
   colours, so without this a dark-mode admin prints --color-text -- which is
   near-white -- onto a white page, and a printed contract comes out blank. */
@media print {
  :root,
  :root[data-theme="dark"] {
    color-scheme: light;
  }
}
