/* ============================================================
   DariBook — Verwaltung
   Token-driven stylesheet. The colour system is carried over from the
   earlier HostBoard investigation, where the categorical slots were
   validated against the dataviz checks in both light and dark mode:
   --series-1 means Booking.com and --series-2 Airbnb throughout, so a
   platform keeps its colour in every chart and badge on every page.
   ============================================================ */

@layer reset, tokens, layout, components, utilities;

@layer reset {
  *, *::before, *::after { box-sizing: border-box; }
  * { margin: 0; }
  html { -webkit-text-size-adjust: 100%; }
  body { min-height: 100svh; font-synthesis-weight: none; }
  img, svg { display: block; max-width: 100%; }
  button, input, select, textarea { font: inherit; color: inherit; }
  button { cursor: pointer; border: 0; background: none; }
  table { border-collapse: collapse; }
  a { color: inherit; }
  :focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; }
  @media (prefers-reduced-motion: reduce) {
    *, *::before, *::after { animation-duration: .01ms !important; transition-duration: .01ms !important; }
  }
}

@layer tokens {
  :root {
    color-scheme: light;

    --surface-0: #f4f4f1;
    --surface-1: #fcfcfb;
    --surface-2: #ffffff;
    --surface-3: #f0efec;
    --line:      #e2e1dc;
    --line-soft: #ededea;

    --text-primary:   #0b0b0b;
    --text-secondary: #52514e;
    --text-muted:     #86857e;

    /* validated categorical slots */
    --series-1: #2a78d6;   /* Booking.com */
    --series-2: #eb6834;   /* Airbnb      */
    --series-3: #1baf7a;   /* direkt      */
    --series-4: #eda100;   /* sonstige    */

    /* "Not counted yet": a deliberately grey, non-categorical encoding -- it marks a state,
       not an identity, so the chroma floor for categorical hues does not apply.
       The step IS chosen so the pair green/grey clears the separation checks, which do:
       vs --series-3 the validator reports normal-vision ΔE 17.6 and CVD ΔE 11.4 (deutan).
       Contrast against the light surface stays under 3:1, which obliges relief -- provided
       by the legend and the data table every chart carries. */
    --neutral-bar: #74746e;

    /* --- DariBook brand ---------------------------------------------------------
       Navy and gold are taken from the logo itself: the wordmark is #0D1E4D on white
       with a gold dot, and the app icon is the same navy with gold windows. The two
       tokens below are the owner's specified values, which the asset matches to within
       a few units.

       The navy is a *surface*, not an accent: it carries the sidebars, where it sits
       behind the light wordmark. Gold is the accent -- the thing that marks what is
       current or actionable -- and is used sparingly for that reason.
    */
    --brand-navy:  #101F50;
    --brand-gold:  #E5A326;

    /* Measured, not assumed. Against the light surface (#fcfcfb):

         brand gold        2.13:1  -- fails even the 3:1 non-text minimum
         #c0881f           3.02:1  -- clears non-text: borders, rules, icon strokes
         #976b19           4.61:1  -- clears text: links, figures, labels

       So the brand gold is never load-bearing on a light background: not for text, and
       not for a border either. It stays for decorative fills, and for the sidebars, where
       it sits on navy at 7.20:1 and is comfortable for both text and marks.

       This is the one place the brand palette and legibility disagree, and the disagreement
       is resolved in favour of legibility -- the hue is kept, the lightness is not. */
    --accent:        var(--brand-gold);
    --accent-line:   #c0881f;
    --accent-text:   #976b19;
    --accent-ink:    #101F50;
    --accent-soft: color-mix(in oklab, var(--accent) 16%, var(--surface-1));

    --good: #008300;
    --warning: #eda100;
    --critical: #e34948;
    --good-soft:     color-mix(in oklab, var(--good) 12%, var(--surface-1));
    --critical-soft: color-mix(in oklab, var(--critical) 12%, var(--surface-1));
    --warning-soft:  color-mix(in oklab, var(--warning) 16%, var(--surface-1));

    --grid: color-mix(in oklab, var(--text-primary) 9%, transparent);
    --axis: color-mix(in oklab, var(--text-primary) 22%, transparent);

    --r-sm: 6px; --r-md: 10px; --r-lg: 14px; --r-xl: 20px;
    --shadow-1: 0 1px 2px rgb(0 0 0 / .05), 0 1px 3px rgb(0 0 0 / .04);
    --shadow-2: 0 4px 12px rgb(0 0 0 / .07), 0 1px 3px rgb(0 0 0 / .05);
    --shadow-3: 0 12px 32px rgb(0 0 0 / .13), 0 2px 8px rgb(0 0 0 / .07);

    /* Sidebar surfaces: navy, with the second level a step lighter so the two read as
       levels rather than as one block. */
    --nav-bg:       var(--brand-navy);
    --nav-bg-2:     color-mix(in oklab, var(--brand-navy) 88%, #ffffff);
    --nav-line:     color-mix(in oklab, #ffffff 14%, transparent);
    --nav-text:     color-mix(in oklab, #ffffff 86%, var(--brand-navy));
    --nav-text-dim: color-mix(in oklab, #ffffff 58%, var(--brand-navy));

    --nav-w: 248px;
    --nav-w-2: 214px;
    --font: "Inter", ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
    --mono: ui-monospace, "SF Mono", "JetBrains Mono", Menlo, monospace;
  }

  /*
     There is one theme.

     The dark scheme used to be offered alongside it and switched with a toggle. With the
     DariBook palette the application already *is* two-toned -- navy sidebars against a light
     working surface -- and a second, fully dark variant would have meant maintaining a
     second set of brand steps, including a navy that no longer reads as navy against a dark
     background. The toggle, its stored preference and the dark token block all came out
     together.
  */
}

@layer layout {
  body {
    background: var(--surface-0);
    color: var(--text-primary);
    font-family: var(--font);
    font-size: 15px;
    line-height: 1.5;
  }

  .shell { display: grid; grid-template-columns: var(--nav-w) 1fr; min-height: 100svh; }

  /*
     With an apartment selected there are two sidebars: the first for the apartments and the
     cross-apartment pages, the second for the apartment's own pages. The second is narrower
     -- it holds short labels, and two full-width columns would leave the content cramped on
     a 1280px screen.
  */
  .shell--nested { grid-template-columns: var(--nav-w) var(--nav-w-2) 1fr; }

  .sidebar {
    background: var(--nav-bg);
    color: var(--nav-text);
    border-right: 1px solid var(--nav-line);
    padding: 20px 14px;
    display: flex; flex-direction: column; gap: 22px;
    position: sticky; top: 0; height: 100svh; overflow-y: auto;
  }

  .main { min-width: 0; display: flex; flex-direction: column; }

  .topbar {
    background: color-mix(in oklab, var(--surface-1) 85%, transparent);
    backdrop-filter: blur(8px);
    border-bottom: 1px solid var(--line);
    padding: 12px 24px;
    display: flex; align-items: center; gap: 16px; flex-wrap: wrap;
    position: sticky; top: 0; z-index: 20;
  }

  /* The working surface is white, against the navy sidebars and the page's off-white
     shell. flex:1 keeps it filling the viewport so the footer sits at the bottom on a
     short page instead of floating under the last card. */
  .content {
    padding: 24px; display: flex; flex-direction: column; gap: 20px;
    background: #ffffff; flex: 1 0 auto;
  }

  .site-foot {
    display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
    padding: 16px 24px;
    background: #ffffff;
    border-top: 1px solid var(--line);
    color: var(--text-muted); font-size: 13px;
  }
  .site-foot strong { color: var(--brand-navy); font-weight: 650; }

  .grid { display: grid; gap: 16px; }
  .grid--kpi { grid-template-columns: repeat(auto-fit, minmax(205px, 1fr)); }
  .grid--two { grid-template-columns: repeat(auto-fit, minmax(340px, 1fr)); }
}

@layer components {
  /* --- brand --- */
  .brand { display: flex; align-items: center; gap: 8px; padding: 0 8px; }
  .brand__name { font-size: 17px; font-weight: 650; letter-spacing: -.01em; color: var(--nav-text); }
  .brand__sub { font-size: 12px; color: var(--nav-text-dim); }

  /* The wordmark. The light variant carries a transparent background, so it sits on the
     navy without the white box the original asset would have brought with it. */
  .brand__logo { display: block; width: 136px; height: auto; }

  /* --- the apartment sidebar (second level) --- */
  .sidebar--apartment { background: var(--nav-bg-2); }
  .brand--apartment .brand__name { font-size: 15px; font-weight: 600; }
  .nav__link--apartment[aria-current="true"] {
    background: var(--accent-soft); color: var(--accent); font-weight: 600;
  }
  .sidebar__foot { margin-top: auto; padding: 0 8px; color: var(--nav-text-dim); }
  .sidebar__foot strong { color: var(--nav-text); }
  .sidebar__foot a { color: var(--nav-text-dim); }
  .sidebar__foot a:hover { color: var(--brand-gold); }

  /* --- navigation --- */
  .nav { display: flex; flex-direction: column; gap: 2px; }
  .nav__label {
    font-size: 11px; text-transform: uppercase; letter-spacing: .06em;
    color: var(--nav-text-dim); padding: 10px 8px 4px;
  }
  .nav__link {
    display: flex; align-items: center; gap: 10px;
    padding: 8px 10px; border-radius: var(--r-sm);
    color: var(--nav-text); text-decoration: none;
    font-size: 14px; font-weight: 500;
  }
  .nav__link:hover { background: color-mix(in oklab, #ffffff 10%, transparent); color: #fff; }

  /* Gold marks the current page. On navy it is 7.2:1, so it carries both the text and the
     left rule -- and the rule means the state is not colour alone. */
  .nav__link[aria-current="page"],
  .nav__link--apartment[aria-current="true"] {
    background: color-mix(in oklab, var(--brand-gold) 18%, transparent);
    color: var(--brand-gold);
    font-weight: 600;
    box-shadow: inset 3px 0 0 var(--brand-gold);
  }
  .nav__link--muted { color: var(--nav-text-dim); cursor: not-allowed; }
  .nav__link--muted:hover { background: none; color: var(--text-muted); }
  .nav__icon { width: 18px; text-align: center; font-size: 15px; color: var(--brand-gold); }

  /* --- year scope --- */
  .scope { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
  .scope__label { font-size: 12px; color: var(--text-muted); margin-right: 2px; }
  .scope__item {
    padding: 5px 11px; border-radius: 999px; font-size: 13px; font-weight: 550;
    text-decoration: none; color: var(--text-secondary);
    border: 1px solid var(--line); background: var(--surface-2);
  }
  .scope__item:hover { border-color: var(--accent); color: var(--accent); }
  .scope__item[aria-current="true"] {
    background: var(--accent); border-color: var(--accent); color: var(--accent-ink);
  }

  /* --- cards --- */
  .card {
    background: var(--surface-2); border: 1px solid var(--line);
    border-radius: var(--r-lg); padding: 16px 18px; box-shadow: var(--shadow-1);
  }
  .card__title { font-size: 13px; font-weight: 600; color: var(--text-secondary); }
  .card__head {
    display: flex; align-items: baseline; justify-content: space-between;
    gap: 12px; margin-bottom: 12px; flex-wrap: wrap;
  }
  .card__hint { font-size: 12px; color: var(--text-muted); }

  /* --- KPI tiles --- */
  .kpi__label { font-size: 12px; color: var(--text-muted); text-transform: uppercase; letter-spacing: .05em; }
  /* A money figure must not break across lines: "31.867,33 €" split after the number reads
     as two separate values for a moment. The tile grows instead. */
  .kpi__value {
    font-size: 26px; font-weight: 650; letter-spacing: -.02em; margin-top: 6px;
    font-variant-numeric: tabular-nums; white-space: nowrap;
  }
  .kpi__meta { font-size: 12px; color: var(--text-secondary); margin-top: 4px; }
  .kpi--good .kpi__value { color: var(--good); }
  .kpi--critical .kpi__value { color: var(--critical); }

  /* --- summary boxes (Amazon / Sonstige) --- */
  .boxes { display: grid; gap: 14px; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); }
  .box { background: var(--surface-2); border: 1px solid var(--line); border-radius: var(--r-lg); padding: 14px 16px; }
  .box--accent { border-left: 3px solid var(--series-4); }
  .box--other  { border-left: 3px solid var(--series-1); }
  .box--total  { border-left: 3px solid var(--text-secondary); background: var(--surface-3); }
  .box__label { font-size: 12px; color: var(--text-muted); display: flex; justify-content: space-between; gap: 8px; }
  .box__value { font-size: 22px; font-weight: 650; font-variant-numeric: tabular-nums; margin-top: 4px; }
  .box__meta { font-size: 12px; color: var(--text-secondary); margin-top: 2px; }

  /* --- tables --- */
  .table-wrap { overflow-x: auto; border: 1px solid var(--line); border-radius: var(--r-lg); background: var(--surface-2); }
  .table { width: 100%; font-size: 14px; }
  .table th, .table td { padding: 9px 12px; text-align: left; border-bottom: 1px solid var(--line-soft); white-space: nowrap; }
  .table thead th {
    position: sticky; top: 0; background: var(--surface-3); z-index: 1;
    font-size: 12px; font-weight: 600; color: var(--text-secondary);
    text-transform: uppercase; letter-spacing: .04em;
  }
  .table tbody tr:hover { background: var(--surface-1); }
  .table tbody tr:last-child td { border-bottom: 0; }
  .table td.num, .table th.num { text-align: right; font-variant-numeric: tabular-nums; }
  .table td.wrap { white-space: normal; min-width: 220px; }
  .table tfoot td { font-weight: 650; background: var(--surface-3); border-top: 2px solid var(--line); }
  .table__sort { color: inherit; text-decoration: none; display: inline-flex; gap: 4px; align-items: center; }
  .table__sort:hover { color: var(--accent); }
  .table tr.is-flagged { background: var(--warning-soft); }

  .money { font-variant-numeric: tabular-nums; }
  .money--neg { color: var(--critical); }
  .money--pos { color: var(--good); }

  /* --- badges --- */
  .badge {
    display: inline-block; padding: 2px 8px; border-radius: 999px;
    font-size: 12px; font-weight: 550; border: 1px solid var(--line);
    background: var(--surface-3); color: var(--text-secondary);
  }
  /* .badge--booking / --airbnb / --direct used to live here, keyed on the platform name.
     They are gone: the colour is data now and arrives through .badge--dyn. */
  .badge--amazon  { background: color-mix(in oklab, var(--series-4) 16%, var(--surface-2)); color: color-mix(in oklab, var(--series-4) 80%, var(--text-primary)); border-color: color-mix(in oklab, var(--series-4) 34%, transparent); }
  /* The data-driven badge. Its three steps arrive as inline custom properties from
     platform_style(); the class only supplies the shape. A platform with no stored colour
     still gets one -- PlatformColors::suggestFor() derives it from the name -- so nothing
     renders unstyled while the owner has not chosen. */
  .badge--dyn {
    background: var(--badge-fill, var(--surface-3));
    color: var(--badge-ink, var(--text-secondary));
    border-color: var(--badge-line, var(--line));
  }

  .badge--cancelled { background: var(--critical-soft); color: var(--critical); border-color: color-mix(in oklab, var(--critical) 30%, transparent); }
  .badge--open { background: var(--warning-soft); color: color-mix(in oklab, var(--warning) 75%, var(--text-primary)); border-color: color-mix(in oklab, var(--warning) 34%, transparent); }

  /* --- buttons --- */
  .btn {
    display: inline-flex; align-items: center; gap: 7px;
    padding: 7px 14px; border-radius: var(--r-sm);
    border: 1px solid var(--line); background: var(--surface-2);
    font-size: 14px; font-weight: 550; color: var(--text-primary); text-decoration: none;
  }
  .btn:hover { border-color: var(--accent); color: var(--accent); }
  .btn--primary { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }
  .btn--primary:hover { background: color-mix(in oklab, var(--accent) 85%, black); color: var(--accent-ink); }
  .btn--danger { color: var(--critical); border-color: color-mix(in oklab, var(--critical) 40%, transparent); }
  .btn--danger:hover { background: var(--critical); border-color: var(--critical); color: #fff; }
  .btn--sm { padding: 4px 9px; font-size: 13px; }
  .btn--ghost { border-color: transparent; background: none; }


  /* --- forms --- */
  .form { display: flex; flex-direction: column; gap: 16px; max-width: 760px; }
  /* ---------- colour swatches ---------- */
  .swatches { display: flex; flex-wrap: wrap; gap: 8px; }

  .swatch {
    display: inline-flex; align-items: center; gap: 8px;
    padding: 6px 12px 6px 8px;
    border: 1px solid var(--line); border-radius: 999px;
    background: var(--surface-2);
    cursor: pointer; font-size: 13px; line-height: 1;
    transition: border-color var(--dur-fast) var(--ease-out), background var(--dur-fast) var(--ease-out);
  }
  .swatch:hover { background: var(--surface-3); }

  /* The radio stays in the DOM -- it is what carries the value, the keyboard focus and the
     announced state -- and is made invisible rather than removed. display:none would take
     it out of the tab order and out of the accessibility tree. */
  .swatch__input {
    position: absolute; width: 1px; height: 1px;
    opacity: 0; pointer-events: none; margin: 0;
  }

  .swatch__dot {
    width: 18px; height: 18px; border-radius: 50%;
    background: var(--swatch, var(--neutral-bar));
    box-shadow: inset 0 0 0 1px rgb(0 0 0 / .14);
    flex: none;
  }

  /* Selected and focused states are a ring plus a weight change, never colour alone --
     the control is a row of colours, so colour cannot also be the thing that marks which
     one is chosen. */
  .swatch:has(.swatch__input:checked) {
    border-color: var(--swatch);
    background: color-mix(in oklab, var(--swatch) 12%, var(--surface-2));
    box-shadow: inset 0 0 0 1px var(--swatch);
  }
  .swatch:has(.swatch__input:checked) .swatch__name { font-weight: 650; }
  .swatch:has(.swatch__input:focus-visible) { outline: 2px solid var(--accent-line); outline-offset: 2px; }

  /*
     A text input with a datalist gets a browser-drawn dropdown caret in Chrome, which makes
     a free-text field look like a <select> that will not open. The list is a suggestion,
     not a closed set -- typing a new platform is the point -- so the indicator is removed
     and the suggestions stay available on focus and typing.
  */
  input[list]::-webkit-calendar-picker-indicator { display: none !important; }
  input[list]::-webkit-list-button { display: none !important; }

  .form__row { display: grid; gap: 14px; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); }
  .field { display: flex; flex-direction: column; gap: 5px; }
  .field > label { font-size: 13px; font-weight: 550; color: var(--text-secondary); }
  .field input, .field select, .field textarea {
    padding: 8px 10px; border-radius: var(--r-sm);
    border: 1px solid var(--line); background: var(--surface-2); color: var(--text-primary);
    width: 100%;
  }
  .field input:focus, .field select:focus, .field textarea:focus { border-color: var(--accent); outline: none; box-shadow: 0 0 0 3px var(--accent-soft); }

  /* Computed, not typed. A field that looks editable but refuses input is worse than one
     that plainly says it is filled in for you. */
  .field input[readonly] {
    background: var(--surface-3);
    color: var(--text-secondary);
    border-style: dashed;
    cursor: default;
  }
  .field input[readonly]:focus { box-shadow: none; border-color: var(--line); }
  .field__help { font-size: 12px; color: var(--text-muted); }
  .field__error { font-size: 12px; color: var(--critical); }
  .field--error input, .field--error select { border-color: var(--critical); }

  /* The eye on a password field. The wrapper is built by assets/modules/password.js and is
     absent without JavaScript, so the padding that reserves room for the button comes with
     it rather than sitting on every password input. `display: block` keeps the input's
     `width: 100%` measured against the field, which it would not be in a flex wrapper.
     The selectors above reach through it: both are descendant selectors on .field. */
  .password { position: relative; display: block; }
  .password input { padding-right: 38px; }
  .password__toggle {
    position: absolute; top: 1px; right: 1px; bottom: 1px; width: 34px;
    display: flex; align-items: center; justify-content: center;
    border-radius: 0 var(--r-sm) var(--r-sm) 0;
    color: var(--text-muted);
  }
  .password__toggle:hover { color: var(--accent); }
  .password__toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

  .form__actions { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }

  /* a read-only value computed from what is being typed */
  .derived {
    font-family: var(--mono); font-size: 13px;
    background: var(--surface-3); border: 1px dashed var(--line);
    border-radius: var(--r-sm); padding: 7px 10px; color: var(--text-secondary);
  }
  .derived strong { color: var(--text-primary); }
  .derived--warn { border-color: var(--warning); background: var(--warning-soft); }

  /* --- filters --- */
  .filters { display: flex; flex-direction: column; gap: 12px; }
  .filters__row { display: grid; gap: 12px; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); align-items: end; }
  .chips { display: flex; gap: 7px; flex-wrap: wrap; align-items: center; }
  .chip {
    display: inline-flex; align-items: center; gap: 6px;
    padding: 3px 7px 3px 10px; border-radius: 999px; font-size: 13px;
    background: var(--accent-soft); color: var(--accent);
    border: 1px solid color-mix(in oklab, var(--accent) 28%, transparent);
    text-decoration: none;
  }
  .chip__x { font-weight: 700; opacity: .75; }
  .chip:hover .chip__x { opacity: 1; }

  /* --- pagination --- */
  .pager { display: flex; align-items: center; justify-content: space-between; gap: 14px; flex-wrap: wrap; }
  .pager__pages { display: flex; gap: 4px; align-items: center; flex-wrap: wrap; }
  .pager__link {
    min-width: 32px; padding: 5px 9px; text-align: center;
    border: 1px solid var(--line); border-radius: var(--r-sm);
    font-size: 13px; text-decoration: none; color: var(--text-secondary); background: var(--surface-2);
  }
  .pager__link:hover { border-color: var(--accent); color: var(--accent); }
  .pager__link[aria-current="page"] { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); font-weight: 600; }
  .pager__link--disabled { opacity: .4; pointer-events: none; }
  .pager__meta { font-size: 13px; color: var(--text-muted); }
  .pager__size { display: flex; align-items: center; gap: 6px; font-size: 13px; color: var(--text-muted); }

  /* --- charts --- */
  .chart { position: relative; height: 300px; }
  .chart--tall { height: 360px; }
  .legend { display: flex; gap: 14px; flex-wrap: wrap; font-size: 13px; color: var(--text-secondary); }
  .legend__item { display: inline-flex; align-items: center; gap: 6px; }
  .legend__swatch { width: 10px; height: 10px; border-radius: 3px; }
  .legend__swatch--hatch {
    background-image: repeating-linear-gradient(
      -45deg,
      var(--surface-2) 0 2px,
      color-mix(in oklab, var(--text-muted) 45%, transparent) 2px 3px
    );
    border: 1px solid var(--line);
  }

  /* --- flash + notices --- */
  .flash { padding: 11px 14px; border-radius: var(--r-md); font-size: 14px; border: 1px solid var(--line); }
  .flash--success { background: var(--good-soft); border-color: color-mix(in oklab, var(--good) 34%, transparent); }
  .flash--error   { background: var(--critical-soft); border-color: color-mix(in oklab, var(--critical) 34%, transparent); }
  .flash--warning { background: var(--warning-soft); border-color: color-mix(in oklab, var(--warning) 34%, transparent); }

  .empty { text-align: center; padding: 36px 20px; color: var(--text-muted); font-size: 14px; }

  /* --- login --- */
  .login { min-height: 100svh; display: grid; place-items: center; padding: 24px; background: var(--surface-0); }
  .login__card {
    width: 100%; max-width: 380px; background: var(--surface-2);
    border: 1px solid var(--line); border-radius: var(--r-xl);
    padding: 30px 28px; box-shadow: var(--shadow-3);
  }
  /* The heading now holds the wordmark rather than text; it keeps its <h1> so the page
     still has one, and the alt text carries the name for a screen reader. */
  .login__title { margin: 0 0 4px; }
  .login__logo { display: block; width: 190px; height: auto; margin: 0 auto; }
  .login__sub { font-size: 13px; color: var(--text-muted); margin-top: 4px; margin-bottom: 22px; }

  .page-head { display: flex; align-items: baseline; justify-content: space-between; gap: 16px; flex-wrap: wrap; }
  .page-title { font-size: 21px; font-weight: 650; letter-spacing: -.02em; }
  .page-sub { font-size: 13px; color: var(--text-muted); margin-top: 3px; }

  .burger { display: none; }
}

@layer utilities {
  .muted { color: var(--text-muted); }
  .small { font-size: 12px; }
  .nowrap { white-space: nowrap; }
  .flex { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
  .between { justify-content: space-between; }
  .ml-auto { margin-left: auto; }
  .mono { font-family: var(--mono); }
  .sr-only {
    position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
    overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
  }
}

/* --- responsive: the sidebar folds into a disclosure at tablet width --- */
@media (max-width: 900px) {
  /*
     Both sidebars collapse to full-width disclosures, stacked above the content. Each keeps
     its own toggle -- a single one would mean opening the whole of both levels to reach one
     link, and on a phone the apartment's own menu is the one wanted most often.
  */
  .shell, .shell--nested { grid-template-columns: 1fr; }
  .sidebar {
    position: static; height: auto; border-right: 0; border-bottom: 1px solid var(--line);
    padding: 12px 14px; gap: 12px;
  }
  .sidebar .nav { display: none; }
  .sidebar:has(.burger__toggle:checked) .nav { display: flex; }

  /*
     The account footer folds away with the navigation. It sits outside .nav in the markup
     (it is pinned to the bottom of the sidebar on desktop), so without this it stayed
     visible next to the collapsed header and made the bar look like a half-open menu.
  */
  .sidebar__foot { margin-top: 0; display: none; }
  .sidebar:has(.burger__toggle:checked) .sidebar__foot { display: block; }
  .burger { display: block; margin-left: auto; }
  .burger__toggle { position: absolute; opacity: 0; pointer-events: none; }
  .burger__label { padding: 6px 10px; border: 1px solid var(--nav-line); border-radius: var(--r-sm); font-size: 14px; cursor: pointer; color: var(--nav-text); }
  .brand { display: flex; align-items: center; }
}

@media (max-width: 560px) {
  .content { padding: 16px; }
  .site-foot { padding: 14px 16px; font-size: 12px; }
  .topbar { padding: 10px 16px; }
  .kpi__value { font-size: 22px; }
  .page-title { font-size: 19px; }
}

/* ============================================================
   Interaction layer (spec D19)

   Rules that keep "modern" from becoming "fragile":
   every animation sits behind prefers-reduced-motion, and every
   reveal keyframe starts from a VISIBLE state, so content is never
   invisible when an animation does not run. The main DariBook site
   already shipped that bug once with its lazy sections; not twice.
   ============================================================ */

@layer tokens {
  :root {
    /* Fluid type scale: one clamp per step, so phone and desktop share a ratio
       instead of a media-query staircase. */
    --step--1: clamp(.78rem, .76rem + .1vw, .82rem);
    --step-0:  clamp(.9rem, .88rem + .12vw, .95rem);
    --step-1:  clamp(1.05rem, 1rem + .3vw, 1.25rem);
    --step-2:  clamp(1.3rem, 1.2rem + .6vw, 1.7rem);
    --step-3:  clamp(1.6rem, 1.4rem + 1.1vw, 2.3rem);

    --ease-out: cubic-bezier(.22, 1, .36, 1);
    --ease-spring: cubic-bezier(.34, 1.56, .64, 1);
    --dur-fast: 140ms;
    --dur: 240ms;
    --dur-slow: 420ms;
  }
}

@layer reset {
  html {
    /* Turbo + View Transitions: the cross-fade makes an instant swap legible. */
    view-transition-name: none;
    scroll-behavior: smooth;
  }
  @media (prefers-reduced-motion: reduce) {
    html { scroll-behavior: auto; }
  }

  ::selection { background: color-mix(in oklab, var(--accent) 28%, transparent); }

  /* Scrollbars that match the surface instead of the OS default. */
  * { scrollbar-width: thin; scrollbar-color: var(--line) transparent; }
  *::-webkit-scrollbar { width: 10px; height: 10px; }
  *::-webkit-scrollbar-thumb {
    background: var(--line); border-radius: 99px;
    border: 3px solid transparent; background-clip: content-box;
  }
  *::-webkit-scrollbar-thumb:hover { background: var(--text-muted); background-clip: content-box; }
}

@layer components {
  /* ---------- entrance reveals ----------
     Scroll-driven where supported: no observer, no JS, no main-thread cost.
     `.reveal` is visible by default; only a browser that can animate it
     hides it first, via the @supports guard. */
  .reveal { opacity: 1; transform: none; }

  @supports (animation-timeline: view()) {
    @media (prefers-reduced-motion: no-preference) {
      .reveal {
        animation: reveal-rise linear both;
        animation-timeline: view();
        animation-range: entry 0% entry 42%;
      }
    }
  }

  /* Firefox and older Safari: the JS observer adds .is-revealed. Until it does,
     .reveal-js is still visible unless JS confirmed it can take over by setting
     data-reveal="ready" on <html>. */
  @media (prefers-reduced-motion: no-preference) {
    html[data-reveal="ready"] .reveal-js { opacity: 0; transform: translateY(14px); }
    html[data-reveal="ready"] .reveal-js.is-revealed {
      opacity: 1; transform: none;
      transition: opacity var(--dur-slow) var(--ease-out), transform var(--dur-slow) var(--ease-out);
    }
  }

  @keyframes reveal-rise {
    from { opacity: 0; transform: translateY(16px); }
    to   { opacity: 1; transform: none; }
  }

  /* Staggered children, e.g. the KPI row. Capped so a long list does not
     take a second and a half to finish. */
  @media (prefers-reduced-motion: no-preference) {
    .stagger > * { animation: reveal-rise var(--dur-slow) var(--ease-out) both; }
    .stagger > :nth-child(1) { animation-delay: 0ms; }
    .stagger > :nth-child(2) { animation-delay: 55ms; }
    .stagger > :nth-child(3) { animation-delay: 110ms; }
    .stagger > :nth-child(4) { animation-delay: 165ms; }
    .stagger > :nth-child(5) { animation-delay: 210ms; }
    .stagger > :nth-child(n+6) { animation-delay: 250ms; }
  }

  /* ---------- micro-interactions ---------- */
  .card, .box, .kpi {
    transition: transform var(--dur) var(--ease-out), box-shadow var(--dur) var(--ease-out),
                border-color var(--dur) var(--ease-out);
  }
  @media (hover: hover) and (prefers-reduced-motion: no-preference) {
    .card--interactive:hover, .box:hover { transform: translateY(-2px); box-shadow: var(--shadow-2); }
  }

  .btn {
    transition: background var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out),
                color var(--dur-fast) var(--ease-out), transform var(--dur-fast) var(--ease-out);
  }
  .btn:active { transform: translateY(1px) scale(.985); }

  .nav__link { transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out); }

  /* The active nav item slides its marker between pages instead of blinking. */
  .nav__link[aria-current="page"] { position: relative; }
  .nav__link[aria-current="page"]::before {
    content: ""; position: absolute; inset-inline-start: 0; inset-block: 18%;
    width: 3px; border-radius: 99px; background: var(--accent);
    view-transition-name: nav-marker;
  }

  .scope__item { transition: background var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out); }
  .chip { transition: background var(--dur-fast) var(--ease-out); }
  .table tbody tr { transition: background var(--dur-fast) linear; }

  /* ---------- lazy rendering ----------
     The browser skips layout and paint for off-screen rows. contain-intrinsic-size
     keeps the scrollbar honest while they are skipped. */
  .table--long tbody tr {
    content-visibility: auto;
    contain-intrinsic-size: auto 38px;
  }

  /* NOT content-visibility: a canvas initialised while its container is layout-skipped
     measures a wrong width and Chart.js paints every bar crushed against the axis. The lazy
     IntersectionObserver init already defers the expensive work, which is the actual win. */
  .chart-host { contain: layout style; }

  /* ---------- skeletons ---------- */
  .skeleton {
    background: linear-gradient(90deg, var(--surface-3) 25%, var(--surface-1) 37%, var(--surface-3) 63%);
    background-size: 400% 100%;
    border-radius: var(--r-sm);
    color: transparent !important;
    user-select: none;
  }
  @media (prefers-reduced-motion: no-preference) {
    .skeleton { animation: skeleton-sweep 1.4s ease-in-out infinite; }
  }
  @keyframes skeleton-sweep {
    from { background-position: 100% 50%; }
    to   { background-position: 0 50%; }
  }
  .skeleton--text { height: 1em; width: 100%; }
  .skeleton--value { height: 26px; width: 60%; }

  /* Turbo marks the html element while a navigation is in flight. */
  html[aria-busy="true"] .is-pending { opacity: .55; transition: opacity var(--dur) var(--ease-out); }

  /* A thin progress bar for navigations slower than Turbo's own threshold. */
  .turbo-progress-bar {
    height: 2px;
    background: linear-gradient(90deg, var(--accent), var(--series-3));
  }

  /* ---------- count-up ---------- */
  .countup { font-variant-numeric: tabular-nums; }

  /* ---------- command palette ---------- */
  .palette {
    border: 0; padding: 0; background: transparent;
    width: min(560px, calc(100vw - 32px));
    margin: 0 auto; margin-block-start: 12vh;
  }
  .palette::backdrop {
    background: rgb(0 0 0 / .38);
    backdrop-filter: blur(3px);
  }
  .palette__card {
    background: var(--surface-2); border: 1px solid var(--line);
    border-radius: var(--r-xl); box-shadow: var(--shadow-3);
    overflow: hidden;
  }
  .palette__input {
    width: 100%; padding: 15px 18px; border: 0; border-bottom: 1px solid var(--line);
    background: transparent; font-size: var(--step-1); color: var(--text-primary);
  }
  .palette__input:focus { outline: none; }
  .palette__list { list-style: none; padding: 6px; margin: 0; max-height: 46vh; overflow-y: auto; }
  .palette__item {
    display: flex; align-items: center; gap: 10px;
    padding: 9px 12px; border-radius: var(--r-sm); cursor: pointer;
    font-size: var(--step-0); color: var(--text-secondary);
  }
  .palette__item[aria-selected="true"] { background: var(--accent-soft); color: var(--accent); }
  .palette__item mark { background: none; color: var(--accent); font-weight: 650; }
  .palette__hint { padding: 9px 14px; border-top: 1px solid var(--line); font-size: var(--step--1); color: var(--text-muted); display: flex; gap: 12px; }
  .palette__empty { padding: 22px; text-align: center; color: var(--text-muted); font-size: var(--step-0); }

  kbd {
    font-family: var(--mono); font-size: 11px;
    padding: 1px 5px; border: 1px solid var(--line); border-radius: 4px;
    background: var(--surface-3); color: var(--text-secondary);
  }

  @media (prefers-reduced-motion: no-preference) {
    dialog[open].palette { animation: palette-in var(--dur) var(--ease-spring) both; }
  }
  @keyframes palette-in {
    from { opacity: 0; transform: translateY(-10px) scale(.98); }
    to   { opacity: 1; transform: none; }
  }

  /* ---------- toast ---------- */
  .toasts {
    position: fixed; inset-block-end: 20px; inset-inline-end: 20px;
    display: flex; flex-direction: column; gap: 8px; z-index: 60;
    max-width: min(380px, calc(100vw - 32px));
  }
  @media (prefers-reduced-motion: no-preference) {
    .toast { animation: toast-in var(--dur) var(--ease-spring) both; }
  }
  @keyframes toast-in {
    from { opacity: 0; transform: translateY(10px) scale(.97); }
    to   { opacity: 1; transform: none; }
  }
}

@layer utilities {
  .page-title { font-size: var(--step-2); }
  /* Slightly tighter than --step-2 so a six-digit euro amount fits one line in a tile. */
  .kpi__value { font-size: clamp(1.2rem, 1.05rem + .55vw, 1.55rem); }
  .login__logo { display: block; width: 190px; height: auto; margin: 0 auto 4px; }
  .login__title { font-size: var(--step-2); }
}

/* View transitions: a short cross-fade, and the nav marker glides. */
@media (prefers-reduced-motion: no-preference) {
  ::view-transition-old(root) { animation: vt-out 110ms var(--ease-out) both; }
  ::view-transition-new(root) { animation: vt-in 170ms var(--ease-out) both; }
}
@keyframes vt-out { to { opacity: 0; } }
@keyframes vt-in { from { opacity: 0; } }

/* ============================================================
   Month calendar
   ============================================================ */

@layer components {
  .cal { display: flex; flex-direction: column; gap: 4px; }

  .cal__head, .cal__week {
    display: grid;
    grid-template-columns: repeat(7, minmax(0, 1fr));
    gap: 4px;
  }

  .cal__dow {
    font-size: 11px; font-weight: 600; letter-spacing: .06em; text-transform: uppercase;
    color: var(--text-muted); text-align: center; padding: 4px 0;
  }

  .cal__day {
    min-height: 92px;
    border: 1px solid var(--line);
    border-radius: var(--r-sm);
    background: var(--surface-2);
    padding: 5px 5px 6px;
    display: flex; flex-direction: column; gap: 3px;
    min-width: 0;
  }

  /* Blocked on the platform: a diagonal hatch, so it reads as "unavailable" without
     competing with the booking bars for attention or repeating a label 60 times. */
  .cal__day--blocked {
    background-image: repeating-linear-gradient(
      -45deg,
      transparent 0 5px,
      color-mix(in oklab, var(--text-muted) 18%, transparent) 5px 6px
    );
  }

  .cal__day--outside { background: var(--surface-0); border-color: var(--line-soft); }
  .cal__day--outside .cal__num { color: var(--text-muted); opacity: .6; }
  .cal__day--past { background: var(--surface-1); }

  .cal__day--today {
    border-color: var(--accent);
    box-shadow: inset 0 0 0 1px var(--accent);
  }
  .cal__day--today .cal__num { color: var(--accent); font-weight: 700; }

  .cal__num {
    font-size: 12px; font-weight: 600; color: var(--text-secondary);
    font-variant-numeric: tabular-nums; text-align: right; line-height: 1.2;
  }

  /* A booking bar. Truncated rather than wrapped: a calendar cell with wrapping text
     stretches the whole week row out of shape. The full detail is in the title attribute
     and in the table below the grid. */
  .cal__bar {
    display: block;
    font-size: 11px; line-height: 1.35; font-weight: 550;
    padding: 2px 5px; border-radius: 3px;
    text-decoration: none;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    border-left: 3px solid transparent;
    transition: filter var(--dur-fast) var(--ease-out);
  }
  .cal__bar:hover { filter: brightness(.95); }

  /* One rule for every platform. The three steps arrive inline from platform_style();
     the treatment is the one the per-platform rules used -- a tint, a darker label and the
     colour itself on the left edge -- so a bar stays readable whatever hue is chosen.
     Painting the bar in the solid identity colour, which an earlier version of this rule
     did, left white-ish text on a saturated fill. */
  .cal__bar--dyn {
    background: var(--badge-fill, var(--surface-3));
    color: var(--badge-ink, var(--text-secondary));
    border-left-color: var(--badge, var(--neutral-bar));
  }
  /* Short and unexplained -- this one is worth a look. */
  .cal__bar--missing {
    background: var(--critical-soft);
    color: color-mix(in oklab, var(--critical) 80%, var(--text-primary));
    border-left-color: var(--critical);
    cursor: help;
  }
}

/* Phone: a 7-column grid of 92px cells does not fit, so the grid becomes a day list. */
@media (max-width: 680px) {
  .cal__head { display: none; }
  .cal__week { grid-template-columns: 1fr; gap: 3px; }

  .cal__day {
    min-height: 0;
    flex-direction: row; align-items: flex-start; gap: 10px;
    padding: 7px 10px;
  }
  /* An empty day would otherwise be a row of pure padding. */
  .cal__day:not(:has(.cal__bar)) { display: none; }
  .cal__day--outside { display: none; }

  .cal__num {
    text-align: left; min-width: 2.2em; flex-shrink: 0;
    font-size: 13px;
  }
  .cal__num::after { content: "."; }

  .cal__bar { font-size: 12px; white-space: normal; }
  .cal__day > :not(.cal__num) { flex: 1; min-width: 0; }
}
