/* common.css */
/* globals.css */
/* fonts.css */
/* -------------------------------------------------
   Amatic SC – self‑hosted, typographic logo font
   ------------------------------------------------- */

/* 400 – Regular */

@font-face {
    font-family: 'Amatic SC';
    src: url('/assets/fonts/Amatic_SC/AmaticSC-Regular.woff2') format('woff2'),
        url('/assets/fonts/Amatic_SC/AmaticSC-Regular.woff') format('woff');
    font-weight: normal;
    font-style: normal;
    font-display: swap;
}

/* 700 – Bold (Google calls it “700”) */

@font-face {
    font-family: 'Amatic SC';
    src: url('/assets/fonts/Amatic_SC/AmaticSC-Bold.woff2') format('woff2'),
        url('/assets/fonts/Amatic_SC/AmaticSC-Bold.woff') format('woff');
    font-weight: bold;
    font-style: normal;
    font-display: swap;
}

:root {
    --bg: #EBEBEB;
    --bg-cut: #F0EEE9;
    --bg-contrast: #595959;
    --bg-backdrop: rgba(0, 0, 0, 0.6);
    --bg-basic: #ffffff;
    --bg-cell: #f1f3f5; 

    --text-color: #495057;
    --text-muted: #555D62;
    --text-greyed: rgb(from var(--text-muted) r g b / 0.5);
     /* text to go on contrasting (dark) backgrounds - the header and the side menu */
     --text-color-contrast: #F0EEE9; /* warm off-white, the light triangle in the app icon */

    /* Fonts */
    --main-font: Arial, system-ui, sans-serif;
    --font-family-logo: "Amatic SC", sans-serif;


    --main-box-shadow: rgba(50, 50, 93, 0.25) 0px 2px 5px -1px, rgba(0, 0, 0, 0.3) 0px 1px 3px -1px;
    --shadow-pop: 0 1px 5px rgba(0, 0, 0, 0.3); /* a lit-up round button (open menu, account) */
    --shade: rgba(0, 0, 0, 0.3); /* a light dark wash: the spinner's veil, hover on the dark side menu */
    --soft-box-shadow: rgba(9, 30, 66, 0.25) 0px 1px 1px, rgba(9, 30, 66, 0.13) 0px 0px 1px 1px;
    
    --border-radius: 3px; /* main border radius for block containers */
    --input-border: 1px solid rgba(75, 83, 88, 0.3); /* a quiet line: under the search box, under a window's title */
    /* the edge of a form field: between the quiet line (0.3) and the 3:1 contrast guideline (0.65, WCAG 1.4.11).
       0.65 looked heavy; 0.45 is about 2.1:1 on white and clearer still on the grey windows */
    --control-border: 1px solid rgba(75, 83, 88, 0.45);
    /* Lemon Seed variables */
    --lemon: rgb(250, 250, 51); /* #fafa33 */
    --border: rgba(75, 83, 88, 0.3);
    
    --color-danger: #EC4E20;
    --bg-danger: #FFDCCC;
    --color-safe: #1C7C54;
    --color-info: #54428E;

    --color-ready: #FA7A02;
    --color-warning: rgb(from var(--color-ready) r g b / 0.35);

    --header-height: 30px;
    /* a table row that holds a 24px icon button (Set as Paid, Download) - what a dashboard row comes to.
       The detail tables use it for every row, so a row with a button is no taller than the rest */
    --row-height: 46px;
    --footer-height: 24px;

    /* Action button */
    --color-action-button: #39739d;
    --color-hover-action-button: #2c5777;
    --bg-action-button: #e1ecf4;
    --hover-action-button: #CFE3F2;
    --border-action-button: #7aa7c7;

    /* font sizes */
    --font-size-main: 14px;
    --font-size-sub: 11px;
    --font-size-title: 18px;
    --font-size-input: var(--font-size-main); /* text typed into fields */

    /* side menu sizes */
    --side-menu-width: 40px;
    --menu-toggle-size: 26px; /* the round apps and account buttons in the top bar on phones */ 
    --menu-toggle-right: calc(0.4em + var(--menu-toggle-size) + 6px); /* left of the account button, which has the corner */

    --window-border-radius: 8px;

    /* Stacking order, lowest to highest. Only for things that overlap across components; a z-index
       that only orders a component's own parts can stay a plain number. */
    --z-raised: 2;       /* a ghost button above its neighbours' tap areas */
    --z-sticky: 3;       /* table headers pinned while the rows scroll under them */
    --z-tooltip: 4;      /* the activity chart's value tooltip */
    --z-side-menu: 4;    /* the side menu on a computer, under the top bar and footer */
    --z-bar: 5;          /* top bar and footer */
    --z-above-bar: 6;    /* windows (the #Modal layer) and the phone menu list, in front of the bars */
    --z-overlay: 7;      /* the spinner's veil and the phone apps button */
    --z-notification: 8; /* above the spinner's veil, so a message can be read while it spins */
    --z-popup: 9;        /* a suggestions list, above even the notification */

    /* show-hide transtion for visible content switching */
    --show-hide-transition: opacity 0.5s linear;
}

/* Slightly more compact type on narrow screens */
@media (max-width:768px) {
    :root {
        --font-size-main: 13px;
        --font-size-sub: 10px;
        /* under 16px iPhone Safari zooms the page into a field when it gets focus, this stops it */
        --font-size-input: 16px;
        /* a taller top bar with bigger round buttons (apps, account) - easy to hit with a thumb
           (Apple asks for about 44pt per tap target) */
        --header-height: 48px;
        --menu-toggle-size: 40px;
    }
}

body {
    margin: 0;
    position: relative;
    font-family: var(--main-font);
    height: 100vh;
    height: 100dvh; /* iPhone Safari: the height that is really visible (100vh ignores its toolbar) */
    overflow-x: hidden;
    background: var(--bg);
    font-size: var(--font-size-main);
    color: var(--text-color);
}

* {
    box-sizing: border-box;
}

a {
    text-decoration: none;
    color: var(--color-action-button);
    cursor: pointer;

    & > * {
        pointer-events: none;
    }

    & > svg {
        fill: var(--color-action-button);
        margin: auto;
        vertical-align: middle;
        pointer-events: none;
    }
}

footer {
    text-align: center;
    vertical-align: middle;
    padding: 2px 0;
    background: var(--bg-cut);
    border-top: 1px solid rgba(75, 83, 88, 0.14); /* a whisper of a line: the bar's own tint already sets it apart */
    position: fixed;
    bottom: 0;
    width: 100%;
    font-size: var(--font-size-main); /* a notch above the small print */
    height: var(--footer-height);
    color: var(--text-muted);
    font-family: var(--font-family-logo);
    z-index: var(--z-bar);

    div {
        display: inline-flex;
        align-items: center;
    }
}

/* The browser's own focus ring (a dark or blue outline round whatever has focus) is never drawn here. `:where()`
   gives this rule no weight of its own, so wherever a stylesheet sets an outline on purpose it still wins -
   this only covers the elements that have none. A focused field shows its state in its own way. */
:where(button, input, select, textarea, a, summary, dialog, nav, ul, li, div, span, [tabindex], [contenteditable], [popover]):focus,
:where(button, input, select, textarea, a, summary, dialog, nav, ul, li, div, span, [tabindex], [contenteditable], [popover]):focus-visible {
    outline: none;
}

/* Keyboard focus only (Tab): a quiet ring in the action-button blue round what can be pressed. A click or tap never
   draws it (:focus-visible), so it cannot be mistaken for the browser's own outline this file switches off above. */
:is(button, a[href], select, summary, [tabindex]:not([tabindex="-1"])):focus-visible {
    outline: 2px solid rgb(from var(--color-action-button) r g b / 0.55);
    outline-offset: 2px;
    border-radius: var(--border-radius);
}

input {
    font-family: var(--main-font);
    font-size: var(--font-size-input);
    color:var(--text-color);
    outline: none;
    background-color: transparent;
    border: var(--control-border);
    border-radius: var(--border-radius);
    padding: 5px;

    &:focus {
        outline: none;
    }

    /* lighter than a field that can be changed: a softer edge and softer text, no fill */
    &:disabled {
        background-color: transparent;
        border-color: rgba(75, 83, 88, 0.2);
        color: var(--text-greyed);
        -webkit-text-fill-color: var(--text-greyed); /* Safari ignores color on a disabled field */
    }
}

/* iPhone Safari - fix for date fields spilling over. */
@media (max-width:768px) {
    input[type="date"] {
        -webkit-appearance: none;
        appearance: none;
        display: block;
        min-width: 0;
        max-width: 100%;
        box-sizing: border-box;
        min-height: calc(var(--font-size-input) * 1.15 + 12px); /* an empty one would otherwise collapse */

        /* the date itself on the left, like the text in every other field (Safari centres it) */
        &::-webkit-date-and-time-value {
            text-align: left;
        }
    }
}


/* components/account_menu.css */
/* Account button at the top right of the header, and the small list it opens (account_menu.js) */
.account-menu {
    position: absolute;
    top: 50%;
    right: 0.4em;
    transform: translateY(-50%);
    font-size: var(--font-size-main);

    /* same look as the apps menu button (header.css) */
    .account-badge {
        display: flex;
        align-items: center;
        justify-content: center;
        width: var(--menu-toggle-size);
        height: var(--menu-toggle-size);
        padding: 0;
        background: transparent;
        border: none;
        border-radius: 50%;
        cursor: pointer;
        transition: background-color 0.2s ease, box-shadow 0.2s ease;

        svg {
            width: 20px;
            height: 20px;
            fill: var(--header-text);
        }

        &:hover {
            background-color: var(--header-press);
        }
    }

    &.open .account-badge {
        background-color: var(--lemon);
        box-shadow: var(--shadow-pop);
    }

    .account-icon {
        display: inline-flex;
        position: relative;
    }

    /* the KSeF certificate needs attention: a small dot (status colour) on the icon's top right corner */
    .attention-dot {
        position: absolute;
        top: -1px;
        right: -2px;

        &::before {
            width: 8px;
            height: 8px;
            box-shadow: 0 0 0 1.5px var(--header-bg); /* a thin ring in the header colour, so it stands off the icon */
        }
    }

    .account-list {
        position: absolute;
        top: calc(100% + 6px);
        right: 0;
        min-width: 170px;
        white-space: nowrap;
        margin: 0;
        padding: 0;
        list-style: none;
        background: var(--bg-basic);
        border-radius: 8px;
        overflow: hidden;
        box-shadow: var(--main-box-shadow);
        opacity: 0;
        visibility: hidden;
        pointer-events: none;
        transform: translateY(-6px);
        transition: opacity 0.25s ease, transform 0.25s ease, visibility 0.25s;

        li {
            display: flex;
            align-items: center;
            gap: 0.7em;
            padding: 0.7em 0.9em;
            border-bottom: 1px solid var(--border);
            cursor: pointer;

            &:last-child {
                border-bottom: none;
            }

            /* hover is a light grey, so it never looks like the selected page */
            &:hover {
                background: var(--bg-cell);
            }

            &.active {
                background: var(--hover-action-button);

                svg {
                    fill: var(--color-hover-action-button);
                }
            }

            svg {
                width: 20px;
                height: 20px;
                flex-shrink: 0;
                fill: var(--text-color);
            }
        }
    }

    &.open .account-list {
        opacity: 1;
        visibility: visible;
        pointer-events: auto;
        transform: translateY(0);
    }
}

/* Desktop: plain in the top bar, no border - the account icon, the first name, and a small
   arrow that turns when the list opens. Open, it keeps a soft grey pill behind it. */
@media (min-width: 769px) {
    .account-menu {
        right: 0.75em;

        .account-badge {
            width: auto;
            height: 26px;
            gap: 3px;
            padding: 0 8px 0 6px;
            background-color: transparent;
            border: 1px solid transparent;
            border-radius: 13px; /* the faint hover background reads as a soft pill */
            box-shadow: none;
            color: var(--header-text);
            font: inherit;
            font-size: var(--font-size-main);
            line-height: 1;
            white-space: nowrap;
            transition: box-shadow 0.2s ease, background-color 0.2s ease, border-color 0.2s ease;

            &:hover {
                background-color: transparent;
            }
        }

        /* account_circle is an avatar shape on its own - no dot behind it */
        .account-icon {
            display: inline-flex;

            svg {
                width: 22px;
                height: 22px;
                fill: var(--header-text);
            }
        }

        /* in the logo's font, so the two ends of the header match; a long name is cut off with "..."
           rather than stretching across the header (the full name shows on hover, see account_menu.js) */
        .account-name {
            font-family: var(--font-family-logo);
            font-size: 18px;
            font-weight: bold;
            letter-spacing: 0.02em;
            padding-top: 2px; /* Amatic sits high in its line box */
            max-width: 220px;
            overflow: hidden;
            text-overflow: ellipsis;
        }

        .account-chevron {
            display: inline-flex;
            transform: rotate(90deg);
            transition: transform 0.2s ease;

            svg {
                width: 14px;
                height: 14px;
                fill: var(--header-text-muted);
            }
        }

        /* open: the hover's soft pill stays, a shade darker, so it reads as pressed while the list is shown */
        &.open .account-badge {
            background-color: var(--header-press);
            box-shadow: none;
        }

        &.open .account-chevron {
            transform: rotate(-90deg);
        }
    }
}

/* Phones: in the corner (the apps menu button sits left of it), with the same soft round background */
@media (max-width: 768px) {
    .account-menu {

        .account-badge {
            background-color: transparent;

            svg {
                width: 24px;
                height: 24px;
            }

            &:hover {
                background-color: transparent;
            }
        }

        &.open .account-badge {
            background-color: var(--lemon);

            svg {
                fill: var(--text-color); /* dark on the yellow */
            }
        }
    }
}

/* components/account_modal.css */
.account-edit {
    input {
        width: 100%;
    }
}

/* Add / Update KSeF certificate window (ksef_certificate_modal.js) */
.ksef-certificate-edit {
    input {
        width: 100%;
    }

    .ksef-certificate-note {
        margin-top: 0.75em;
    }

    /* the dashed drop box for both files - one button: click to choose them, or drop them on it (anywhere
       on the window works too); the dashed border says files can be dropped here */
    .file-choice {
        display: flex;
        align-items: center;
        justify-content: center;
        gap: 0.4em;
        min-width: 0;
        min-height: 120px; /* a comfortable target to drop both files on */
        padding: 0 14px;
        border: 1px dashed var(--border-action-button);
        border-radius: var(--window-border-radius);
        color: var(--color-action-button);
        cursor: pointer;
        transition: background-color 0.2s ease, border-color 0.2s ease;

        svg {
            width: 26px;
            height: 26px;
            flex-shrink: 0;
            fill: var(--color-action-button);
        }

        &:hover {
            background-color: var(--bg-action-button);
        }

        .file-name {
            overflow: hidden;
            text-overflow: ellipsis;
            white-space: nowrap;
        }

        /* files added: no plus, just which file is which - one line each, in the normal text colour,
           and the one still needed muted */
        &.chosen .file-name {
            color: var(--text-color);
        }

        .file-list {
            display: grid;
            gap: 0.3em;
            min-width: 0;
        }

        &.chosen .file-name.missing {
            color: var(--text-muted);
        }
    }

    /* Clear files: under the box, on the right, small */
    .file-clear:not(:empty) {
        display: flex;
        justify-content: flex-end;
        margin-top: 0.3em;
    }

    /* files dragged over the window (it all accepts the drop): the drop boxes light up */
    &.dragging .file-choice {
        background-color: var(--hover-action-button);
        border-style: solid;
    }


    /* What just happened - saved, the test's verdict, or a refusal ("This key does not belong to this
       certificate") - in the space left of Test certificate and Save, above the table */
    .ksef-certificate-message {
        flex: 1;
        min-width: 0;
        text-align: left;
    }
}

/* components/action_button.css */
/* CSS */
/* Button that stands out in the ui and does something but doesn't stay in the pressed state */
.action-button {
    background-color: var(--bg-action-button);
    border-radius: 3px;
    border: 1px solid var(--border-action-button);
    box-sizing: border-box;
    color: var(--color-action-button);
    cursor: pointer;
    display: inline-block;
    font-family: var(--main-font);
    font-size: var(--font-size-main);
    font-weight: 400;
    line-height: 1.15385;
    margin: 0;
    outline: none;
    padding: 8px .8em;
    position: relative;
    text-align: center;
    text-decoration: none;
    user-select: none;
    -webkit-user-select: none;
    touch-action: manipulation;
    vertical-align: baseline;
    white-space: nowrap;
    transition: all 0.3s ease;

    &.ghost {
      padding: 0;
      background-color: transparent;
      border: none;
      box-shadow: none;
      z-index: var(--z-raised);
    }
  
    & > svg, span {
      margin: auto;
      vertical-align: middle;
      pointer-events: none;
    }
  
    & > svg {
      fill: var(--color-action-button);
    }

    &:active {
        background-color: var(--hover-action-button);
        box-shadow: none;
        color: var(--color-hover-action-button);
    }

    /* Persistent "pressed" look for a toggle */
    &.active {
        color: var(--color-hover-action-button);
        text-shadow: 0 0 0.6px currentColor, 0 0 0.6px currentColor;

        & > svg {
            fill: var(--color-hover-action-button);
        }
    }

    &:disabled {
      opacity: 0.5;
      pointer-events: none;
    }
  }

  /* A framed button in a colour with a meaning (DESIGN.md): .ready (orange) is the step that makes an
     invoice ready to send, .safe (green) is Send to KSeF once it can go */
  .action-button.ready { --tone: var(--color-ready); }
  .action-button.safe { --tone: var(--color-safe); }

  .action-button.ready,
  .action-button.safe {
    background-color: color-mix(in srgb, var(--tone) 15%, white);
    border-color: var(--tone);
    color: var(--tone);

    & > svg {
      fill: var(--tone);
    }

    /* pressed: a little deeper tint and a darker colour, like the blue action button */
    &:active {
      background-color: color-mix(in srgb, var(--tone) 28%, white);
      color: color-mix(in srgb, var(--tone) 75%, black);

      & > svg {
        fill: color-mix(in srgb, var(--tone) 75%, black);
      }
    }
  }

  /* a ghost one keeps no frame or tint: only its text and icon take the colour */
  .action-button.ghost.ready,
  .action-button.ghost.safe {
    background-color: transparent;
    border: none;

    &:active {
      background-color: transparent;
    }
  }

  @media (hover: hover) {
    .action-button.ghost.ready:hover,
    .action-button.ghost.safe:hover {
      background-color: transparent;
      color: color-mix(in srgb, var(--tone) 75%, black);
    }

    .action-button.ready:hover,
    .action-button.safe:hover {
      background-color: color-mix(in srgb, var(--tone) 28%, white);
      color: color-mix(in srgb, var(--tone) 75%, black);

      & > svg {
        fill: color-mix(in srgb, var(--tone) 75%, black);
      }
    }
  }

  /* hides button label on mobile to keep it short - and lets the button shrink to its icon, also in
     an .actions row, where buttons otherwise keep a 100px minimum (common.css) */
  @media (max-width:768px) {
    .action-button.mobile > span {
      display: none;
    }

    .actions button.action-button.mobile {
      min-width: 0;
    }
  }

/* Action button hover effect, only where there is a pointer to hover with. (A phone or tablet keeps
   :hover after a tap, so the button would stay highlighted until something else is touched.) */
@media (hover: hover) {
  .action-button {

    &.ghost {
      &:hover {
        background-color: transparent;
      }
    }

    &:hover {
      background-color: var(--hover-action-button);
      color: var(--color-hover-action-button);

      & > svg {
        fill: var(--color-hover-action-button);
      }
    }
  }
}

/* A toggle that is on (the search button while its bar is open). On a computer, as before: the label in
   the pressed colour and drawn bold. On a phone the button is only its icon, so it gets a soft background
   instead. Both beat the ghost button's transparent hover. */
.action-button.ghost.toggled,
.action-button.ghost.toggled:hover {
    color: var(--color-hover-action-button);

    & > svg {
        fill: var(--color-hover-action-button);
    }
}

@media (min-width: 769px) {
    .action-button.ghost.toggled {
        text-shadow: 0 0 0.6px currentColor, 0 0 0.6px currentColor;
    }
}

@media (max-width: 768px) {
    .action-button.ghost.toggled,
    .action-button.ghost.toggled:hover {
        background-color: var(--hover-action-button);
    }
}

/* components/breadcrumbs.css */
.breadcrumbs {
    display: flex;
    flex-direction: row;
    align-items: center;
    padding: 0.5em 0;
    gap: 5px;
    * {
        vertical-align: middle;
        font-size: var(--font-size-main);
    }

    > div {
        display: flex;
        align-items: center;
        gap: 0.3em;
    }

    svg {
        fill: var(--color-action-button);
    }

    /* the account's facts - NIP, then the KSeF connection - pushed to the far right of the row */
    .breadcrumbs-account {
        margin-left: auto;
        flex-shrink: 0;
        gap: 0.9em;
    }

    .breadcrumbs-ksef {
        flex-shrink: 0;
        .status,
        .status * {
            font-size: var(--font-size-sub);
        }
    }
}

/* The page's own trail (not the period selector, which borrows .breadcrumbs for its layout): a fine line under
   it sets "where you are" apart from the page itself - title, period selector, table - and gives the page a
   top edge to hang from */
.breadcrumbs.page-trail {
    border-bottom: 1px solid var(--border);
}

/* A long crumb (a client's name) gives way to the account's facts at the right: it is cut with "..." before
   the NIP and the connection are pushed out of the row */
.breadcrumbs.page-trail > div:not(.breadcrumbs-account),
.breadcrumbs.page-trail > div:not(.breadcrumbs-account) .identity-chip {
    min-width: 0;
}

/* only the last crumb gives way (it is the one that can be a long name); the ones before it keep their size */
.breadcrumbs.page-trail > div:not(.breadcrumbs-account) {
    flex-shrink: 0;
}

.breadcrumbs.page-trail > div:nth-last-child(2) {
    flex-shrink: 1;
}

.breadcrumbs.page-trail > div:not(.breadcrumbs-account) .identity-chip .action-button {
    min-width: 0;
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* The crumb of the page you are on is the page's title (there is no separate big heading): its icon and
   name in the text colour, bold and at the title size, where the crumbs you can click on are blue */
.breadcrumbs.page-trail .crumb-current {
    .action-button {
        color: var(--text-color);
        font-weight: bold;
        font-size: var(--font-size-title);
    }

    svg {
        fill: var(--text-color);
    }
}

/* The crumb that says where you are on a page whose heading is below the trail: the size of the other crumbs, but
   in the text colour and not a link */
.breadcrumbs.page-trail .crumb-here {
    color: var(--text-color);
    font-size: var(--font-size-main);
    padding: 0 0.5em;
}

/* What follows the trail used to follow a big title; now it hangs straight off the line, so it gets room of
   its own, and a row of buttons right under the line is not crowded */
.breadcrumbs.page-trail + :not(h2) {
    margin-top: 14px;
}

/* The page's own actions start at the left, under the page's name (Add a contractor, Edit Account): the right
   edge already carries the account's facts above it. A toolbar that has a period selector keeps the selector
   at the left and its actions at the right, so the two never blend into one run of blue text. */
.breadcrumbs.page-trail + .actions {
    gap: 1.6em;
}

.breadcrumbs.page-trail + .actions.right {
    justify-content: flex-start;
}

/* the mark between crumbs: a small dot - quieter than an arrow icon, and not lost among the crumbs' own
   icons the way a slash was */
.breadcrumbs svg.crumb-separator {
    flex: none;
    width: 0.4em;
    height: 0.4em;
    margin: 0 0.25em;
    fill: var(--text-muted);
    opacity: 0.7;
}

/* a note after the current page's name (the dashboard's date): quiet, not part of the title */
.breadcrumbs .crumb-note {
    margin-left: 0.4em;
    color: var(--text-muted);
    font-weight: normal;
}

/* the status text: in full on a computer, short on phones ("Connected"), so a long trail still fits */
.breadcrumbs .breadcrumbs-ksef .ksef-short {
    display: none;
}

@media (max-width: 768px) {
    .breadcrumbs .breadcrumbs-ksef .ksef-long {
        display: none;
    }

    .breadcrumbs .breadcrumbs-ksef .ksef-short {
        display: inline;
    }
}

/* components/clipboard.css */
.clipboard {

    display: flex; 
    flex-direction: row; 
    align-items: center;
    height: 1em;
    margin: auto 0;

    code {
        font-size: var(--font-size-sub);
    }

    button {
        border: none;
        background: transparent;
        vertical-align: baseline;
        cursor: pointer;
        transition: all 0.3s ease, color 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);

        &.active {
            color: var(--text-greyed);
            transition: color 0.15s ease-out;

            svg {
                fill: var(--text-greyed);
                transition: fill 0.15s ease-out;
            }
        }

        svg {
            width: 1.5em;
            height: 1.5em;
            fill: var(--color-action-button);
            transition: fill 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
        }

        & > * {
            pointer-events: none;
        }
    }

}
/* components/confirm.css */
.confirm-dialog-content {
    padding: 1em 0;

    .confirm-message {
        margin-bottom: 1.5em;
        line-height: 1.5;
        color: var(--text-color);
    }

    /* the optional receipt number under the question (Set as Filed) */
    .filing-upo {
        margin-bottom: 1.5em;

        label {
            display: block;
            margin-bottom: 0.4em;
        }

        input {
            width: 100%;
        }
    }

    .confirm-actions {
        display: flex;
        justify-content: flex-end; /* Cancel, then the framed main action in the corner */
        gap: 0.5em;
        
        button {
            min-width: 100px;
        }
    }
}

/* A confirmation is a question, not a form: a window of about 36em, so its sentence wraps into lines that are easy
   to read instead of running across the 800px a form window keeps. (A phone's own rule, below 768px, still wins.) */
dialog.modal:has(.confirm-dialog-content) {
    min-width: 0;
    width: min(36em, calc(100% - 1.5em));
}

/* components/not_found.css */
/* Nothing found: the logo and the message centred in the page, a little above the middle (as on the
   login page) so it doesn't look weighed down */
.not-found {
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.75em;
    padding-bottom: 8vh;
    text-align: center;

    img {
        width: 36px;
        height: auto;
        margin-bottom: 0.9em; /* headroom for the message */
    }

    .not-found-message {
        margin: 0;
        color: var(--text-muted);
        font-size: var(--font-size-main);
    }
}

/* components/custom_checkbox.css */
/* Container for the checkbox and label */
.checkboxLabel {
    display: inline-flex;
    align-items: center;
    gap: 0.2em;
    border-radius: 3px;
    cursor: pointer;
    color: var(--color-action-button);
    transition: all 0.3s ease;
    line-height: 1;

    &.disabled {
      opacity: 0.7;
      pointer-events: none;
    }
  
    /* Hide the native checkbox but keep it accessible */
    input[type="checkbox"] {
      position: absolute;
      opacity: 0;
      width: 0;
      height: 0;

      &:checked ~ .customCheckbox {
        .icon-checked {
            display: inline;
        }

        .icon-unchecked {
            display: none;
        }
      }
    }
  
  }
  
  /* Custom checkbox appearance */
  .customCheckbox {
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    position: relative;
    transition: all 0.3s ease;

    .icon-checked { 
      display: none; 
    }
    
    .icon-unchecked { 
      display: inline; 
    }
  
    svg {
      height: 1.75em;
      fill: var(--color-action-button);
    }
  
    /* Disabled state */
    &.disabled {
      opacity: 0.5;
    }
  }
/* components/custom_radio.css */
/* Container for the checkbox and label */
.radioLabel {
    display: inline-flex;
    align-items: center;
    gap: 0.2em;
    border-radius: 3px;
    cursor: pointer;
    color: var(--color-action-button);
    transition: all 0.3s ease;
    line-height: 1;

    &.disabled {
      opacity: 0.7;
      pointer-events: none;
    }
  
    /* Hide the native checkbox but keep it accessible */
    input[type="radio"] {
      position: absolute;
      opacity: 0;
      width: 0;
      height: 0;

      &:checked ~ .customRadio {
        .icon-checked {
            display: inline;
        }

        .icon-unchecked {
            display: none;
        }
      }
    }
  
  }
  
  /* Custom checkbox appearance */
  .customRadio {
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    position: relative;
    transition: all 0.3s ease;

    .icon-checked { 
        display: none; 
    }

    .icon-unchecked { 
        display: inline; 
    }
    
  
    svg {
      height: 1.75em;
      fill: var(--color-action-button);
    }
  
    /* Disabled state */
    &.disabled {
      opacity: 0.5;
    }
  }
/* components/dashboard.css */
/* Reporting-cycle and month-progress bars: the VAT deadline (the one to act on) on the left, 60%, and the month
   on the right, 40% - the same split and gap as the cards below, so their edges line up */
.progress-bars-row {
    display: grid;
    grid-template-columns: minmax(0, 3fr) minmax(0, 2fr);
    gap: 1.2em;
    margin-bottom: 1.5em;
}

.reporting-progress {
    min-width: 0;

    .reporting-progress-header {
        display: flex;
        flex-direction: row;
        gap: 0.5em;
        margin-bottom: 0.4em;
        font-size: var(--font-size-sub);
        font-weight: bold;
    }

    .reporting-progress-countdown {
        font-weight: normal;
        font-size: var(--font-size-sub);
        color: var(--text-muted);
    }
}

.progress-bar {
    height: 10px;
    border-radius: 999px;
    background: var(--bg-cell);
    border: 1px solid var(--border); /* a line, not a soft shadow (DESIGN.md: lines, not blur) */
    overflow: hidden;

    .progress-bar-fill {
        height: 100%;
        border-radius: 999px;
        min-width: 10px;
        background: linear-gradient(90deg, var(--color-ready), var(--lemon));
        background-size: 200% 100%;
        transition: width 0.6s ease;
        position: relative;
        overflow: hidden;

        /* the month bar is informational, not a deadline - a calmer solid
           blue (reusing the action-button colour) instead of the reporting
           bar's orange/lemon gradient */
        &.month {
            background: var(--border-action-button);
            background-size: 200% 100%;
        }

        /* shine sweeping across the fill, looping - kept subtle so it reads
           as a texture, not a flashing effect. Reserved for the reporting
           bar only - the month bar is informational, no animation needed. */
        &::after {
            content: '';
            position: absolute;
            inset: 0;
            background: linear-gradient(
                115deg,
                transparent 30%,
                rgba(255, 255, 255, 0.35) 45%,
                transparent 60%
            );
            background-size: 200% 100%;
            animation: reporting-progress-shine 3.5s ease-in-out infinite;
        }

        &.month::after {
            animation: none;
            content: none;
        }
    }
}

@keyframes reporting-progress-shine {
    0% { background-position: 150% 0; }
    100% { background-position: -50% 0; }
}

@media (prefers-reduced-motion: reduce) {
    .progress-bar .progress-bar-fill::after {
        animation: none;
    }
}

/* Stack to one full-width column on mobile instead of squeezing both bars
   side by side (matches the app's usual 768px mobile breakpoint) */
@media (max-width: 768px) {
    .progress-bars-row {
        grid-template-columns: minmax(0, 1fr);
        gap: 0.75em;
    }
}

/* Dashboard tables are as tall as their rows; the plain .table-section (flex: 1) would stretch each
   one to share the page height and leave a big gap under a short table. They still shrink, and
   scroll inside themselves, when there are more rows than room. (Phones scroll the whole page.) */
.table-section.dashboard-section {
    flex: 0 1 auto;
    min-height: 7em; /* never squeezed below a heading and a row or two by a long neighbour */
}

/* "Ready to send": the list (not its title) stops growing at five rows (header row + 5 rows of
   3.42em) and then scrolls, loading more rows as it does; a short list stays as small as its rows. The extra
   selectors beat the phone rule that lets every .table-section grow and the page scroll instead. */
.viewport #App .table-section.dashboard-list {
    flex: none;
    max-height: 19.9em;
    overflow-y: auto;
    /* no elastic bounce at the ends of the list: in Safari it drags the pinned header along and
       out of sight */
    overscroll-behavior: none;
}

/* Title with a count pill: the pill is centred on the text and its extra height is pulled back with
   negative margins, so the title row is the same height as the other dashboard headings */
.dashboard-section h3 {
    display: flex;
    align-items: center;
    gap: 0.5em;
    /* on the type scale: the main size, bold - the same as a table's header bar. (The browser's own h3 size
       is 1.17 times that, which is nowhere on the scale.) */
    font-size: var(--font-size-main);
    font-weight: 700;

    .label {
        margin: -4px 0;
    }
}

/* A date with its relative-day label ("3 days ago"), centred on each other. Centring the boxes leaves
   the label 0.4px below the middle of the date's digits (measured), so it is nudged up by that. */
.date-line {
    display: flex;
    align-items: center;
    gap: 0.8em;

    .label {
        margin: 0;
        transform: translateY(-0.04em);
    }
}

/* The lists have no scrollbar, so a soft shadow along the bottom edge says "more below". It is only
   there while rows are hidden below (watchScrollable() in dashboard.js sets [data-more-below]). It
   sticks to the bottom of the visible area and overlaps the last rows instead of adding height. */
.viewport #App .table-section.dashboard-list::after {
    content: '';
    display: block;
    position: sticky;
    bottom: 0;
    height: 2.2em;
    margin-top: -2.2em;
    pointer-events: none;
    background: linear-gradient(to bottom, transparent, rgb(from var(--text-color) r g b / 0.16));
    opacity: 0;
    transition: opacity 0.2s ease;
}

.viewport #App .table-section.dashboard-list[data-more-below]::after {
    opacity: 1;
}

/* The cards in two columns of different widths: the big one (Upcoming Payments, Invoices to Issue) takes two
   thirds, the "do this next" lists (Ready to Send, Pending Expenses) a narrow column beside it, so the eye starts
   on the left and travels right. The cards of a row are as tall as each other, so their edges line up across the
   page; each list scrolls inside its own card when it is long. Stacked on narrow screens, in this order. */
.viewport #App .dashboard-grid {
    flex: none;
    display: grid;
    grid-template-columns: minmax(0, 3fr) minmax(0, 2fr);
    grid-template-areas:
        "invoices ready"
        "payments expenses";
    gap: 1.2em;
    margin-top: 1.2em; /* room between the grid and the block above it */
    align-items: stretch;
}

.viewport #App .dashboard-cell {
    display: flex;
    flex-direction: column;
    min-width: 0;

    /* the card fills its cell, so the two cards of a row end on the same line */
    > .table-section.dashboard-section {
        flex: 1 1 auto;
    }
}

@media (max-width: 1100px) {
    .viewport #App .dashboard-grid {
        grid-template-columns: minmax(0, 1fr);
        grid-template-areas:
            "invoices"
            "ready"
            "payments"
            "expenses";
    }
}

/* Invoices to Issue and Upcoming Payments sit one above the other on a computer: the same three columns, so the
   buttons (Add an invoice, Set as Paid) start at the same place in both. Phones size them by content. */
@media (min-width: 769px) {
    .dashboard-list table.three-columns {
        table-layout: fixed;
        width: 100%;

        th:nth-child(1) { width: 34%; }
        th:nth-child(3) { width: 28%; }
    }
}

/* A compact list (Pending Expenses, Ready to Send): the name cell takes the room the button leaves - max-width 0
   lets it shrink below the name's length - so a long name is cut short and the date tag and the button
   stay whole */
.dashboard-list table.compact td.contractor-name {
    width: 100%;
    max-width: 0;
}

/* On phones the contractor name is cut short with an ellipsis, so the billing date and the add-invoice
   button next to it stay on screen instead of scrolling out of sight */
@media (max-width: 768px) {
    .dashboard-list td.contractor-name {
        max-width: 9.5em;
        overflow: hidden;
        text-overflow: ellipsis;
    }

    /* tighter gaps between the tags, so a row of Upcoming Payments (code, Sale/Purchase, days)
       still leaves room for the Set as Paid button at the right edge */
    .dashboard-list .date-line {
        gap: 0.35em;
    }

    /* the middle column takes the room that is left, so the button's column is only as wide as the button
       and the button ends at the right edge, as in the one-cell lists */
    .dashboard-list table.three-columns th:nth-child(2) {
        width: 100%;
    }
}

/* The very smallest phones (320px): no room for the relative-day labels next to the dates, and the
   buttons matter more - the dates themselves stay */
@media (max-width: 360px) {
    .dashboard-list .date-line .label {
        display: none;
    }
}

/* A name followed by its label (a date, a status): a long name is cut short with an ellipsis and the label stays whole */
.date-line a {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Each dashboard block (Upcoming Billing, Ready to send, Activity) is a card with a fine border, so it is
   clear they are separate things. The tables inside have no outline of their own: the card's border is enough. */
.viewport #App .dashboard-section {
    /* the card's padding, named so the title band can cancel exactly the same amount (phones set their own) */
    --card-pad-x: 0.9em;
    --card-pad-top: 0.7em;

    border: 1px solid var(--border);
    border-radius: var(--window-border-radius);
    padding: var(--card-pad-top) var(--card-pad-x) 0.4em;
    background: var(--bg-basic);

    /* the title sits on the same warm off-white band the table header bars use elsewhere, running edge to edge
       across the top of the card (negative margins cancel the card's padding) */
    h3 {
        margin: calc(-1 * var(--card-pad-top)) calc(-1 * var(--card-pad-x)) 0.2em;
        padding: 0.7em var(--card-pad-x);
        background: var(--bg-cut);
        border-bottom: 1px solid var(--border);
        border-radius: calc(var(--window-border-radius) - 1px) calc(var(--window-border-radius) - 1px) 0 0;
    }

    table {
        box-shadow: none;
    }

    /* No header row: each table here has one named column ("Invoice", "Contractor"), which only repeats the
       card's title. The row is collapsed, not removed, because the column widths are set on its cells (the
       buttons of the two top cards start in the same place). */
    table thead tr {
        visibility: collapse;
    }

    /* and the last row of a list of three or more has no line under it: the card's own border is the bottom edge.
       A list of two rows keeps its line, which closes it - the card is taller than the list, and without a
       line the row floats in it. A single entry has no line either: one row needs nothing to separate it */
    table > tbody:has(> tr:nth-child(3)) > tr:not(:has(~ tr:not([style*="display: none"]))),
    table > tbody:has(> tr:nth-child(3)) > tr:not(:has(~ tr:not([style*="display: none"]))) > td,
    table > tbody > tr:only-child,
    table > tbody > tr:only-child > td {
        border-bottom: none;
    }
}

/* The dashboard's rows are things to act on, so they read as a list and not as a data grid:
   - no zebra stripe (with two or three rows it looks like a selected row); a fine line separates them
   - taller rows, with room to breathe
   - the name of the thing (the invoice number, the contractor) is the heaviest text in the row
   - the row under the pointer is picked out, where there is a pointer */
.viewport #App .dashboard-list table tbody tr,
.viewport #App .dashboard-list table tbody tr:nth-child(even),
.viewport #App .dashboard-list table tbody tr:nth-child(odd) {
    background: transparent;
}

.viewport #App .dashboard-list table tbody td {
    padding-top: 0.6em;
    padding-bottom: 0.6em;
}

.viewport #App .dashboard-list table tbody td:first-child a {
    font-weight: 600;
}

@media (hover: hover) {
    .viewport #App .dashboard-list table tbody tr:hover {
        background: var(--bg-cell);
    }
}

@media (max-width: 768px) {
    .viewport #App .dashboard-section {
        /* less padding on phones, where every pixel of width counts (the title band follows it) */
        --card-pad-x: 0.6em;
        --card-pad-top: 0.6em;
    }
}

/* An empty to-do list: a green tick, a positive line and what's next, under it, smaller and muted */
.all-clear {
    display: flex;
    align-items: flex-start;
    gap: 0.6em;
    padding: 0.5em 0.2em;

    svg {
        width: 22px;
        height: 22px;
        flex-shrink: 0;
        fill: var(--color-safe);
    }

    > div {
        display: grid;
        gap: 0.25em;
        padding-top: 3px; /* the first line's middle level with the icon's */
    }

    .all-clear-next {
        color: var(--text-muted);
        font-size: var(--font-size-sub);
    }
}

/* In a card the message is centred in the space the card has, like an empty list: the tick above, the line and
   the "next" under it. (The card is a column so the message can take the room left under the title band.) */
.viewport #App .dashboard-section {
    display: flex;
    flex-direction: column;
}

.viewport #App .dashboard-section .all-clear {
    flex: 1;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    text-align: center;
    gap: 0.6em;
    padding: 1.2em 0.5em;

    svg {
        width: 28px;
        height: 28px;
    }

    > div {
        justify-items: center;
        padding-top: 0;
    }
}

/* the small spinner in a button while its invoice is being sent: the icon's size, level with the label,
   no extra margin that would make the row taller */
.dashboard-list button .viewport_spinner.small {
    margin: 0;
    vertical-align: middle; /* level with the label, as the icon it replaces is */
}

/* components/header.css */
/* main page shell header with logo - light, like the footer, with a fine line underneath; the dark
   side menu alone frames the page */
header {
    /* colours for anything in the header (the account button reads these too) */
    --header-bg: var(--bg-cut); /* the same warm off-white as the footer, so the two bars pair up */
    --header-text: var(--text-color);
    --header-text-muted: var(--text-muted);
    --header-press: rgb(0 0 0 / 0.06); /* a button's hover / pressed background on the light bar */
    background-color: var(--header-bg);
    box-shadow: 0 1px 0 var(--border); /* the fine line, drawn outside so the bar keeps its height */
    padding: 6px 0;
    position: fixed;
    height: var(--header-height);
    width: 100%;
    z-index: var(--z-bar);
    
    .logo-header {
        display: inline-flex;
        flex-direction: column;
        width: 100%;
        align-items: start;

        /* the logo image and the brand name centred on each other */
        #logo {
            display: inline-flex;
            align-items: center;
            gap: 5px;
        }

        #logo div {
            display: inline-flex; 
            gap: 4px;
            font-size: var(--font-size-title);
            color: var(--header-text);
            font-family: var(--font-family-logo);
            line-height: 1;
            align-items: baseline;

            /* "by LemonSeed" - the maker's name small and lighter, like a signature */
            small {
                font-size: 0.75em;
                color: var(--header-text-muted);
            }

            &.logo {
                margin-left: 0.5em;
                /* a thin golden edge (a darker shade of the seed's own yellow, not grey, which looks dirty)
                   keeps the seed from fading into the light bar */
                filter: drop-shadow(0 0 0.6px rgb(185 160 0 / 0.9));
            }
        }
    }
}

/* Three-dot button at the top right, next to the account button - phones only, desktop keeps its hover menu. */
.menu-toggle {
    display: none;
}

@media (max-width: 768px) {
    .menu-toggle {
        display: flex;
        align-items: center;
        justify-content: center;
        position: fixed;
        top: calc((var(--header-height) - var(--menu-toggle-size)) / 2); /* centred in the top bar */
        right: var(--menu-toggle-right);
        z-index: var(--z-overlay);
        width: var(--menu-toggle-size);
        height: var(--menu-toggle-size);
        padding: 0;
        border: none;
        border-radius: 50%;
        cursor: pointer;
        background-color: transparent;
        transition: background-color 0.2s ease, box-shadow 0.2s ease;

        svg {
            width: 20px;
            height: 20px;
            fill: var(--header-text);
        }

        &:hover {
            background-color: transparent;
        }
    }

    body.menu-open .menu-toggle {
        background-color: var(--lemon);
        box-shadow: var(--shadow-pop);

        svg {
            fill: var(--text-color); /* dark on the yellow */
        }
    }
}

/* Phones: a taller bar (globals.css), so the logo is centred in it, and the round buttons' icons grow
   with the buttons */
@media (max-width: 768px) {
    header {
        display: flex;
        align-items: center;
        padding: 0;
    }

    .menu-toggle svg {
        width: 24px;
        height: 24px;
    }
}

/* components/info_tooltip.css */
/* Small "ⓘ" hint - see public/js/components/info_tooltip.js.
   The circle is already part of the glyph itself, not drawn here. */
.info-tooltip {
    cursor: help;
    color: var(--text-muted);
    font-size: 14px;
    vertical-align: middle;
}

/* components/contractor_detail.css */
/* Contractor detail page */

/* Add an invoice and Edit Contractor: the page's own buttons, at the left under its name, apart enough to read as two */
.actions.contractor-actions {
    gap: 1.75em;
}

/* Delete Contractor: out of the way, at the bottom right */
.delete-button-row {
    display: flex;
    justify-content: flex-end;
    max-width: 720px; /* the field card's width, so it lines up with the card's right edge */
    margin: 1em 0;
}

/* Contractor page heading: name, note (if any) and the buttons. On a computer the note follows the name
   on the same line and the buttons sit at the right; on a phone the name and the buttons share the first
   line and the note gets a line of its own below */
.detail-header.contractor-header {
    flex-wrap: wrap;
    column-gap: 0.75em;
    row-gap: 0.2em;

    .contractor-name {
        min-width: 0;
    }

    .contractor-note {
        color: var(--text-muted);
        font-size: var(--font-size-main);
        font-style: italic;
        font-weight: 300;
    }

    .detail-header-actions {
        margin-left: auto;
    }
}

@media (max-width: 768px) {
    .detail-header.contractor-header {
        .contractor-name {
            flex: 1;
        }

        .detail-header-actions {
            order: 2;
        }

        .contractor-note {
            order: 3;
            flex-basis: 100%;
        }
    }
}

/* Name and note share the header's first line; the note is a quiet aside (a contractor's invoices page) */
.contractor-title {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    column-gap: 0.75em;

    .contractor-note {
        color: var(--text-muted);
        font-size: var(--font-size-main);
        font-style: italic;
        font-weight: 300;
    }
}

/* Invoice count in the Info table, with a link beside it to the contractor's invoice history */
.invoices-count {
    .invoices-link {
        margin-left: 1em;
    }
}

.invoices-link {
    display: inline-flex;
    align-items: center;
    gap: 0.25em;
    vertical-align: middle;
    /* keep the row as tall as its neighbours */
    margin: -4px 0;

    svg {
        width: 1.2em;
        height: 1.2em;
    }
}

/* components/contractor_modal.css */
.new-contractor {
    input {
        width: 100%;
    }
}

/* One label column width for all three tables (Identity, Address, Billing): left to itself each sized its label
   column to its longest label, so the fields started in three different places. (Phones: further down.) */
.table-section.new-contractor > table {
    table-layout: fixed;

    thead th:first-child,
    tbody td:first-child {
        width: 30%;
    }

    /* Every row the same height: a field's row (a 30px field and the cell's padding, 52px). The Tax ID Type
       row held its radio buttons in an .actions block with its own margins (66px), and a table's last row
       is one line short, its bottom line being the table's outline, so each is set to the same height. */
    tbody td {
        height: 52px;
    }

    .actions {
        margin: 0;
    }
}

/* Note under the modal title, same look as the invoice modal's description */
.contractor-notes {
    flex: 1;

    input {
        min-height: 30px;
        border: none;
        color: var(--text-muted);
        font-style: italic;

        &::placeholder {
            font-style: italic;
        }
    }
}

/* Phones: the description stays beside the (icon only) Save button while its placeholder fits there;
   on a narrower phone the button moves to a row of its own underneath instead of cutting it off */
@media (max-width:768px) {
    .table-section.new-contractor > .actions {
        flex-wrap: wrap;
        row-gap: 0.25em;

        .contractor-notes {
            flex: 1 0 210px; /* never narrower than its placeholder */
        }

        & > button {
            margin-left: auto;
        }
    }
}

/* Phones: the form fits the screen - labels and the three tax id types wrap instead of keeping to one
   line (tables.css keeps phone table cells on one line, which would push the fields off the side) */
@media (max-width:768px) {
    .table-section.new-contractor > table {
        table-layout: fixed;

        thead th:first-child,
        tbody td:first-child {
            width: 38%;
        }

        thead th,
        tbody td {
            white-space: normal;
        }

        input {
            min-width: 0;
        }

        .actions {
            flex-wrap: wrap;
            gap: 0.6em 1em;
        }
    }
}

/* Phones: each field goes on a line of its own, under its label, so it has the whole width of the window - a long
   street or a long name shows in full (and is reachable) instead of in a narrow box beside its label */
@media (max-width:768px) {
    .table-section.new-contractor > table {
        /* no table columns any more: the table, its rows and its cells are plain blocks, each as wide as the window */
        display: block;

        thead,
        tbody,
        thead tr {
            display: block;
        }

        thead th,
        thead th:first-child {
            display: block;
            width: auto;
        }

        thead th:empty {
            display: none;
        }

        tbody tr {
            display: block;
            padding: 0.6em 0.8em 0.8em;
            border-bottom: 1px solid var(--border);
        }

        tbody tr:last-child {
            border-bottom: none;
        }

        tbody td,
        tbody td:first-child {
            display: block;
            width: auto;
            height: auto;
            padding: 0;
            border-bottom: none;
        }

        tbody td:first-child {
            margin-bottom: 0.35em;
            color: var(--text-muted);
            font-size: var(--font-size-sub);
        }

        /* the labels' small hints stay hidden on a phone (modal.css); a field's own widgets fill the line */
        tbody td input {
            width: 100%;
            box-sizing: border-box;
        }
    }
}

/* components/country_autocomplete.css */
.country-autocomplete {
    position: relative;

    input {
        width: 100%;
    }

    .autocomplete-suggestions {
        position: fixed;
        z-index: var(--z-popup);
        background: var(--bg);
        border: 1px solid var(--border);
        border-radius: var(--window-border-radius);
        box-shadow: var(--main-box-shadow);
        max-height: 200px;
        overflow-y: auto;
        scrollbar-width: none; /* Firefox */

        &::-webkit-scrollbar {
            display: none; /* Chrome, Safari */
        }
    }

    .autocomplete-item {
        padding: 0.5em;
        cursor: pointer;
        border-bottom: 1px solid var(--border);

        &:last-child {
            border-bottom: none;
        }

        &:hover {
            background: var(--bg-cut);
        }
    }
}

/* components/empty_state.css */
/* An empty list: a quiet card with an icon, a headline, a sentence and where to start (empty_state.js) */
.empty-state {
    max-width: 44em;
    margin: 2.5em auto 0;
    padding: 2em 1.5em;
    text-align: center;
    border: 1px solid var(--border);
    border-radius: var(--window-border-radius);
    background: var(--bg-basic);

    .empty-state-icon svg {
        width: 40px;
        height: 40px;
        fill: var(--border-action-button); /* the action colour, softened: an invitation, not a button */
    }

    h3 {
        margin: 0.5em 0 0.4em;
        font-size: var(--font-size-main); /* body size: the page header is the biggest text, not the empty message */
        font-weight: 700;
        color: var(--text-color);
        text-wrap: balance; /* a long headline breaks into even lines, never one word left alone */
    }

    p {
        margin: 0 auto;
        max-width: 34em;
        font-size: var(--font-size-main);
        color: var(--text-muted);
        line-height: 1.5;
        text-wrap: pretty;
    }

    .empty-state-action {
        display: inline-flex;
        align-items: center;
        justify-content: center;
        gap: 0.5em;
        margin-top: 1.5em;
        padding: 8px 1.2em;
    }
}

/* Inside the list's own table frame (the same header bar and border as the list will have, so the page
   has its lines and the message sits in the place the rows will come): no card of its own */
.empty-state.bare {
    max-width: none;
    margin: 0;
    padding: 3.5em 1em 4em;
    border: none;
    border-radius: 0;
    background: transparent;
}

/* The column headings of an empty list are a preview of what will fill it */
table.is-empty thead th {
    color: rgb(from var(--text-color) r g b / 0.55);
}

td.empty-state-cell {
    padding: 0;
    white-space: normal;
}

@media (max-width: 768px) {
    .empty-state {
        margin-top: 1.5em;
        padding: 1.5em 1em;
    }

    .empty-state.bare {
        margin: 0;
        padding: 2.5em 1em 3em;
    }
}

/* components/invoice_detail.css */
/* Sale/purchase invoice detail pages - sale_detail.js and purchase_detail.js
   render an identical structure, so their layout classes live in one
   shared file rather than being duplicated per component. */

.invoice-header {
    display: flex;
    align-items: center;
    gap: 0.5em;
}

/* the step button beside the title (Mark as Ready / Back to Draft): a little apart from the number */
.invoice-step {
    margin-left: 0;
    display: flex;
    gap: 1.2em; /* Mark as Ready and Set as Pre-KSeF, side by side */
    align-items: center; /* the button is smaller than the title: its middle on the title's middle line */
    line-height: 1; /* not the heading's tall line, which would push the button off centre */

    button.action-button {
        margin: 0;
        vertical-align: middle;

        svg {
            vertical-align: middle;
        }
    }
}

.vat-id-row {
    display: inline-flex;
    align-items: center;
    gap: 0.3em;
    padding: 6px 0;
    margin-bottom: 0.5em;
}

.copy-address-row {
    margin-top: 0.75em;
}

.ksef-send-button {
    width: min-content;
}

.ksef-send-actions {
    display: inline-flex;
    gap: 1em;
    justify-content: right;
}

/* Invoice as PDF and Send to KSeF at the right end of the invoice's title row (Send last: the main action) */
.invoice-header-actions {
    display: inline-flex;
    align-items: center;
    gap: 1.5em;
    font-size: var(--font-size-main);
    font-weight: normal;
}

/* On a phone Invoice as PDF is hidden (print_invoice.js) and Send stays at the right end of the title row: nothing
   comes between the invoice's title and its KSeF number. Send cannot go out by a slip - it is disabled on a draft and
   asks for a confirmation when it can go. */
@media (max-width: 768px) {
    /* a Send that cannot be pressed (a draft, or already sent) is only clutter on a narrow row: it shows once it can go */
    .invoice-header .ksef-send-button:disabled {
        display: none;
    }
}

/* Download UPO in the Confirmation row of the e-invoicing table. Built like the copy-address button
   (see clipboard.css): a ghost action button, icon then text, inside a box only 1em tall
   so the 1.5em icon overflows it instead of stretching the row. */
.download-action {
    display: flex;
    flex-direction: row;
    align-items: center;
    justify-content: flex-start;
    height: 1em;
    margin: auto 0;

    a {
        cursor: pointer;

        svg {
            width: 1.5em;
            height: 1.5em;
            fill: var(--color-action-button);
        }
    }
}

/* The KSeF number sits right under the invoice title in safe colour. On the sales page it is rendered
   into a slot (.ksef-number-slot) that is refreshed when KSeF accepts the invoice, so the title's
   tighter spacing has to match both the bare number and the slot holding one. */
.invoice-header:has(+ .ksef-number),
.invoice-header:has(+ .ksef-number-slot > .ksef-number),
.invoice-header:has(+ .invoice-description),
.invoice-header:has(+ .ksef-number-slot:empty + .invoice-description) {
    margin-bottom: 0.25em; /* just enough to read as a line of its own */
}

.currency-line {
    margin: 0.25em 0;
    color: var(--text-muted);
    font-size: var(--font-size-sub);

    .used-as-currency {
        margin-left: 0.8em;
    }
}

.ksef-number {
    margin: 0.25em 0;
    display: flex;
    align-items: center;
    gap: 0.5em;

    /* the link to the invoice in KSeF: an icon after the number, in the same green */
    .icon-link {
        display: inline-flex;
        align-items: center;

        svg {
            width: 20px;
            height: 20px;
            fill: var(--color-safe);
        }
    }

    code {
        font-size: var(--font-size-sub);
        font-weight: 600;
        white-space: nowrap;
        color: var(--color-safe);
    }

    .clipboard button:not(.active) svg {
        fill: var(--color-safe);
    }
}

/* Error line in the "Record KSeF number" form */
.record-ksef .form-error {
    color: var(--color-danger);
    margin: 0.6em 0 0;
    min-height: 1.2em;
}

/* The review step of the "Update Status" form */
.record-ksef {
    /* the number to check: green, monospaced so every character can be told apart */
    .record-ksef-number {
        margin: 0.6em 0;
        overflow-wrap: anywhere;
        font-family: ui-monospace, 'SF Mono', Menlo, monospace;
        font-weight: 600;
        color: var(--color-safe);
    }

    /* the invoice URL is long: its label on one line and the address, in plain type, below it */
    .record-ksef-url-label {
        margin: 1em 0 0.2em;
    }

    .record-ksef-url {
        margin: 0;
        overflow-wrap: anywhere;
    }

    /* the warning looks like one of the notifications (the orange "pending" one), but sits in the form */
    .record-ksef-warning {
        margin-top: 1.2em;
        padding: 16px 18px;
        border: 1px solid color-mix(in srgb, var(--color-ready) 45%, white);
        border-radius: var(--window-border-radius);
        text-align: center;
        background: color-mix(in srgb, var(--color-ready) 15%, white);
        color: var(--color-ready);
    }
}

/* Detail pages stack several sections (details grid, then items). The default .table-section is flex: 1
   and scrolls inside #App's fixed height, so the sections split that height and the items table gets
   clipped after its total row. Let them keep their natural height and scroll the page instead. */
.viewport #App:has(.table-section.stacked) {
    overflow-y: auto;
}

.viewport #App .table-section.stacked {
    flex: none;
    overflow-y: visible;
}

/* The contractor's address under the VAT ID: normal text size (the shared .details-section is the
   small print used for hints), still muted so the VAT ID above it leads */
.details-section.contractor-address {
    font-size: var(--font-size-main);
    line-height: 1.4;
}

/* Delete Invoice sits at the bottom right, under the items, away from the invoice title -
   a destructive action shouldn't be the first thing next to the name */
.invoice-footer-actions {
    display: flex;
    justify-content: flex-end;
    align-items: center;
    gap: 1.75em; /* buttons side by side read as separate actions */
    margin: 1em 0 2em;
}

/* The invoice's description right under its title (and KSeF number) - a quiet aside, like a contractor's
   note on its page: muted, italic, close to what it describes, with room below before the contractor's
   details start */
.invoice-description {
    margin: 0.75em 0 0.75em;
    color: var(--text-muted);
    font-size: var(--font-size-main);
    font-style: italic;
    font-weight: 300;
}

/* An invoice shown in its own currency: a note on the Items heading's line, right after the heading,
   and under each total its PLN equivalent */
h4:has(> .foreign-note) {
    display: flex;
    align-items: center;
    column-gap: 0.4em; /* the space between the icon and the words, which a flex row would drop */
}

.foreign-note {
    margin-left: 0.6em;
    color: var(--text-muted);
    font-size: var(--font-size-sub);
    font-weight: normal;
}



/* A green tick and "invoice currency" after the currency code in the details */
.used-as-currency {
    display: inline-flex;
    align-items: center;
    gap: 0.3em;
    margin-left: 0.7em;
    vertical-align: middle;
    color: var(--color-safe);

    svg {
        width: 16px;
        height: 16px;
        fill: currentColor;
    }

    small {
        font-size: var(--font-size-sub);
    }
}

/* The invoice page's two cards side by side: the invoice (buyer or vendor, dates) and its e-invoicing status */
.invoice-cards {
    display: grid;
    /* twelve columns: the client and the dates take six each; below them the description takes a quarter and the
       KSeF card three quarters, side by side */
    grid-template-columns: repeat(12, minmax(0, 1fr));
    gap: 1em 1.6em;
    align-items: start;
    margin: 1.2em 0 1.6em;

    .field-panel {
        max-width: none;
    }

    #Party,
    #Details {
        grid-column: span 6;
    }

    #Description {
        grid-column: span 3;
    }

    #Status {
        grid-column: span 9;
    }

    /* the two cards of a row are one height: the shorter one stretches to the taller */
    #Party,
    #Details,
    #Description,
    #Status {
        align-self: stretch;
    }

    #Party > .field-panel,
    #Details > .field-panel,
    #Description > .field-panel,
    #Status > .field-panel {
        height: 100%;
        box-sizing: border-box;
    }

    /* no description: the KSeF card has the row to itself */
    &:not(:has(> #Description)) #Status {
        grid-column: 1 / -1;
    }

    /* KSeF's own message, always there, in small muted type under the fields */
    .system-message {
        margin: 1.3em 0 0;
        line-height: 1.7; /* a long message wraps with room between its lines */
        font-size: var(--font-size-sub);
        color: var(--text-muted);
        overflow-wrap: anywhere;

        span {
            text-transform: uppercase;
            letter-spacing: 0.04em;
        }
    }

    /* the e-invoicing facts side by side, each as wide as it needs and no wider, wrapping when the row is full */
    .status-fields .field-list {
        display: flex;
        flex-wrap: wrap;
        gap: 1.2em 4em;
    }

    /* the status with its buttons beside it (Set as Ready, Pre-KSeF, Set as Accepted): more room before the next
       field, so they never crowd it */
    .status-fields.with-button .field-list {
        column-gap: 6em;
    }

    .status-fields .field {
        flex: 0 0 auto;
        grid-column: auto !important;
    }

    .status-fields .ksef-number {
        margin: 0;
    }

    /* a button or link inside a field: it sits on the value's own line, apart from the text before it */
    .field-with-action,
    .inline-action-cell {
        display: inline-flex;
        align-items: center;
        gap: 0.8em;
        flex-wrap: wrap;
    }

    /* the address one line to a row, with room between the rows - it fills the card beside the dates */
    .address-lines {
        display: flex;
        flex-direction: column;
        gap: 0.9em;
    }

    /* the status with its buttons: each button well apart from the next */
    .status-fields .inline-action-cell {
        gap: 0.8em 2.2em;
    }
}

@media (max-width: 900px) {
    .invoice-cards {
        grid-template-columns: minmax(0, 1fr);
    }

    .invoice-cards #Party,
    .invoice-cards #Details,
    .invoice-cards #Description,
    .invoice-cards #Status {
        grid-column: 1 / -1;
    }

    .invoice-cards .status-fields .field-list {
        flex-direction: column;
    }
}

/* the currency of the items' amounts, in a small line over the table - only when it is not PLN */
.foreign-note {
    margin: 0 0 0.5em;
    font-size: var(--font-size-sub);
    color: var(--text-muted);

    &:empty {
        display: none;
    }
}

/* components/invoice_modal.css */
/* Invoice Modal Overlay - sits above EVERYTHING */

    .invoice-details {
        input {
            width: 200px;
        }
    }

    /* Import and Create side by side, as one item of the row above */
    .invoice-form-buttons {
        margin: 0;
    }

    .invoice-notes {
        /* was width: stretch - Chrome-only;
        Safari - flex: 1 is the standard, cross-browser way to fill a flex row. */
        flex: 1;

        input {
            width: 100%;
            min-height: 30px;
            border: none;
            color: var(--text-muted);
            font-style: italic;

            &::placeholder {
                font-style: italic;
            }
        }
    }

    .invoice-items {
        margin-top: 2em; /* room between the form and the items - there is no heading to hold them apart */

        table.layout-fixed {
            table-layout: fixed;


            thead th,
            tbody td {

                &.description {
                    min-width: 200px;
                    width: auto;
                }

                &.net_price,
                &.gross_amount {
                    width: 150px;
                }

                &.quantity,
                &.unit,
                &.vat_rate {
                    width: 100px;
                }

                &.action {
                    width: 50px;
                }

                &>input {
                    width: 100%;
                    min-height: 30px;
                    border: none;
                }
            }
        }
    }


/* Phones - the same treatment as the contractor form (contractor_modal.css) */
@media (max-width:768px) {

    /* the description stays beside the (icon only) Import and Create while its placeholder fits there;
       on a narrower phone the buttons move to a row of their own underneath instead of cutting it off */
    .table-section.invoice-form > .actions {
        flex-wrap: wrap;
        row-gap: 0.25em;

        .invoice-notes {
            flex: 1 0 210px; /* never narrower than its placeholder */
        }

        /* Import and Create move as one, and stay on the right */
        .invoice-form-buttons {
            margin: 0 0 0 auto;
        }
    }

    /* the details fit the screen: labels wrap instead of keeping to one line (tables.css keeps phone
       table cells on one line), and fields take the rest of the row instead of a fixed 200px (the small
       label hints are hidden on phones for every window, see modal.css) */
    .table-section.invoice-form > table {
        table-layout: fixed;

        thead th:first-child,
        tbody td:first-child {
            width: 38%;
        }

        thead th,
        tbody td {
            white-space: normal;
        }

        input {
            width: 100%;
            min-width: 0;
        }
    }

    /* the items table keeps its columns but tighter, and scrolls sideways when it needs to - squeezed
       into the screen, the description column (the only one without a fixed width) shrank to nothing */
    .invoice-items table.layout-fixed {
        min-width: 600px;

        thead th,
        tbody td {
            &.description { width: 170px; min-width: 0; }
            &.net_price, &.gross_amount { width: 100px; }
            &.quantity { width: 60px; }
            &.unit, &.vat_rate { width: 80px; }
            &.action { width: 40px; }
        }
    }
}

/* components/labels.css */
/* The colour of each status, as --tint: the chips (.label) and the dots (.status) both read it.
   Colours with a meaning (see DESIGN.md): money going out is purple, money coming in is green. */
span.label,
span.status {
    &.ready { --tint: var(--color-ready); }
    &.sent { --tint: var(--color-info); }
    &.rejected { --tint: var(--color-danger); }
    &.accepted { --tint: var(--color-safe); }
    &.expense { --tint: var(--color-info); } /* money going out */
    &.charge { --tint: var(--color-safe); } /* money coming in */
}

/* Labels - quiet outlined chips: a soft tint + a slightly stronger tinted
   border for definition, no shadow (a shadow reads as a raised button, not
   a tag) */
span.label {
    background: var(--bg-action-button);
    color: var(--color-action-button);
    border: 1px solid var(--border-action-button);
    padding: 4px 8px;
    border-radius: 6px;
    letter-spacing: 0.01em;
    margin-right: 6px;
    cursor: default;
    display: inline-flex;
    align-items: center;
    line-height: 1;

    &.sm {
        padding: 2px 6px;
        font-size: var(--font-size-sub);
    }

    /* e-invoice status (ready, sent, rejected, accepted) and upcoming bill status (expense, charge) */
    &.ready,
    &.sent,
    &.rejected,
    &.accepted,
    &.expense,
    &.charge {
        color: var(--tint);
        background-color: rgb(from var(--tint) r g b / 0.1);
        border-color: rgb(from var(--tint) r g b / 0.4);
    }
}

/* Status dot - a colored dot + bold text at regular size, no chip/border.
   Used where a status sits alone in a table */
span.status {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    font-size: var(--font-size-main);
    font-weight: 600;
    color: var(--text-muted);

    &::before {
        content: '';
        width: 10px;
        height: 10px;
        border-radius: 50%;
        background-color: var(--dot-color, var(--color-action-button));
        flex-shrink: 0;
    }

    &.sm {
        font-size: var(--font-size-sub);
    }

    &.ready,
    &.sent,
    &.rejected,
    &.accepted { --dot-color: var(--tint); }
    &.off { --dot-color: var(--text-greyed); }

    /* "Breathing" dots: a working KSeF connection (.live) and an invoice ready to be sent (.ready).
       The dot swells a touch and a soft ring of its own colour fades out around it - alive, not alarming.
       (Not the account button's attention dot: that one keeps its still ring in the header colour.)
       People who asked their device for less motion get still dots. */
    @media (prefers-reduced-motion: no-preference) {
        &.live::before,
        &.ready:not(.attention-dot)::before {
            animation: status-breathe 2.4s ease-in-out infinite;
        }
    }

    /* a soft rounded pill in the status colour: tinted background, coloured text, smaller dot
       (the KSeF certificate status in the account menu and on the dashboard) */
    &.pill {
        padding: 3px 8px;
        border-radius: 10px;
        color: var(--dot-color);
        background-color: rgb(from var(--dot-color) r g b / 0.1);

        &::before {
            width: 7px;
            height: 7px;
        }
    }
}
@keyframes status-breathe {
    0% {
        transform: scale(1);
        box-shadow: 0 0 0 0 rgb(from var(--dot-color) r g b / 0.45);
    }

    50% {
        transform: scale(1.12);
    }

    70% {
        box-shadow: 0 0 0 4px rgb(from var(--dot-color) r g b / 0);
    }

    100% {
        transform: scale(1);
        box-shadow: 0 0 0 0 rgb(from var(--dot-color) r g b / 0);
    }
}

/* components/login.css */
/* Standalone login page - not part of the SPA shell/router. */
.login-page {
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 100vh;
    /* extra room below lifts the form a little above the middle: the exact centre looks low to the eye */
    padding-bottom: calc(var(--footer-height) + 14vh);
    box-sizing: border-box;
}

.login-form {
    display: flex;
    flex-direction: column;
    gap: 12px;
    width: 280px;

    h2 {
        font-family: var(--font-family-logo);
        font-size: 28px;
        font-weight: normal;
        /* the logo image and the text centred on each other */
        display: flex;
        align-items: center;
        gap: 0.25em;

        img {
            width: 20px;
            height: 20px;
            vertical-align: middle;
        }

        /* the seed balanced on its triangle (the app icon in small), as one image centred on the text */
        img.seed-balanced {
            height: auto;
        }
    }

    label {
        font-size: var(--font-size-main);
        color: var(--text-muted);
    }

    input {
        font-family: var(--main-font);
        font-size: var(--font-size-input);
        padding: 8px;
        border: 1px solid var(--border-action-button);
        border-radius: var(--border-radius);
        background-color: var(--bg-basic); /* white fields stand out on the grey page */

        &:focus {
            outline: none;
        }

    }

    /* the one thing to do on this page, so it is filled in the action colour rather than the
       app's usual quiet tinted button */
    button[type="submit"] {
        align-self: flex-end;
        width: 50%; /* half the width of the fields above */
        background-color: var(--color-action-button);
        border-color: var(--color-action-button);
        color: var(--bg-basic);
        font-weight: 600;

        &:hover,
        &:active {
            background-color: var(--color-hover-action-button);
            border-color: var(--color-hover-action-button);
            color: var(--bg-basic);
        }
    }
}

.login-error {
    color: var(--color-danger);
    min-height: 1.2em; /* keeps the line for a "wrong password" message, so nothing jumps when it appears */
    margin: 0; /* the form's own gap is spacing enough - the browser's paragraph margins left the button stranded */
    font-size: var(--font-size-main);
}

.login-tagline {
    position: fixed;
    left: 0;
    right: 0;
    bottom: calc(var(--footer-height) + 20px);
    margin: 0;
    text-align: center;
    font-family: var(--font-family-logo);
    font-size: 18px;
    color: var(--text-muted);
}

/* The page greets in one language after another until the person types (public/js/login_languages.js).
   A change lays the new wording over the old in the same place (a grid cell), so the page is never without
   text: the old fades out as the new fades in. The time matches SWAP_MS there. */
[data-i18n].stack {
    display: grid;
}

span[data-i18n].stack {
    display: inline-grid;
}

[data-i18n].stack > .layer {
    grid-area: 1 / 1;
    transition: opacity 0.9s ease;
}

[data-i18n].stack > .layer.in,
[data-i18n].stack > .layer.out {
    opacity: 0;
}

@media (prefers-reduced-motion: reduce) {
    [data-i18n].stack > .layer {
        transition: none;
    }
}

/* components/modal.css */
/* Modal Overlay - sits above EVERYTHING */
dialog.modal {
    background: var(--bg-basic); /* every window is one plain white surface; its tables have an outline, not a shadow */
    border: 1px solid var(--border);
    border-radius: var(--window-border-radius);
    min-width: 800px; /* phones drop this below, in the 768px media query */
    max-width: 1200px;
    isolation: isolate;
    margin: 1em auto;
    position: static;
    scrollbar-width: none; /* Firefox */

    &::-webkit-scrollbar {
        display: none; /* Chrome, Safari */
    }

    &:focus {
        outline: none;
    }

    /* A long window scrolls its content under a header that stays put (title and close button always
       there): the window is a column - header fixed, body takes the rest and scrolls. The browser's own
       limit on a modal's height is made explicit here so the body has something to be shorter than. */
    &[open] {
        display: flex;
        flex-direction: column;
        max-height: calc(100dvh - 2em);
        overflow: hidden;
    }

    &::backdrop {
        background-color: var(--bg-backdrop);
    }
}

/* a window with little in it (the KSeF certificate) */
dialog.modal.narrow {
    min-width: 0;
    width: min(560px, calc(100vw - 2em));
}

/* Header */
.modal-header {
    flex-shrink: 0;
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 1rem;
    padding: 0.25rem 0;
    border-bottom: var(--input-border);
    color: var(--color-action-button);

    svg {
        fill: var(--color-action-button);
    }
}

.modal-header h3 {
    margin: 0;
}

.modal-header button {
    background: none;
    border: none;
    font-size: 1.5rem;
    line-height: 1;
    padding: 0;
}

/* Phones: a long title (a contractor's name) wraps beside its icon, the subtitle ("New Invoice") gets
   its own line under the title, and the close button stays level with the title's first line instead of
   floating in the middle of a two- or three-line header */
@media (max-width: 768px) {
    .modal-header {
        align-items: flex-start;
        gap: 0.75em;

        h3 {
            display: grid;
            grid-template-columns: auto 1fr;
            column-gap: 0.4em;
            align-items: start;
            /* the main text size: bold for the title, normal italic for the subtitle - the browser's
               larger heading size crowds a phone */
            font-size: var(--font-size-main);

            small {
                font-size: inherit;
            }

            & > :not(svg):not(span:first-of-type) {
                grid-column: 2;
            }

            .list-msg {
                padding: 0 0 0.25em;
            }
        }

        & > button {
            margin-top: -0.3em; /* centred on the title's first line */
        }
    }
}

/* Small print at the foot of a window: the notes that explain why something is the way it is.
   Quiet and small, apart from the form by a line, and not part of the form's rhythm. */
.window-notes {
    margin-top: 1rem;
    padding-top: 0.6rem;
    border-top: var(--input-border);
    color: var(--text-muted);
    font-size: var(--font-size-sub);

    p {
        margin: 0 0 0.4em;
    }

    p:empty {
        display: none;
    }

    /* nothing to say at the moment: no line either */
    &:not(:has(p:not(:empty))) {
        display: none;
    }
}

/* The warning about changing an invoice from a month that is over: the same look as the KSeF number warning
   (invoice_detail.css, .record-ksef-warning) - an orange notice sitting in the window, not small print. */
.window-notes .past-month-note {
    margin: 0.4em 0 0;
    padding: 16px 18px;
    border: 1px solid color-mix(in srgb, var(--color-ready) 45%, white);
    border-radius: var(--window-border-radius);
    text-align: center;
    background: color-mix(in srgb, var(--color-ready) 15%, white);
    color: var(--color-ready);
    font-size: var(--font-size-main);
    font-weight: 700;
}

/* Body */
.modal-body {
    flex: 1 1 auto;
    min-height: 0; /* lets it be shorter than its content, so it scrolls instead of pushing the header off */
    overflow-y: auto; /* Scroll if content is too tall */
    scrollbar-width: none; /* Firefox */

    &::-webkit-scrollbar {
        display: none; /* Chrome, Safari */
    }

    input {
        outline: none;
        min-height: 30px;
        background-color: transparent;

        &:disabled {
            background-color: transparent;
        }
    }
}



/* On phones a modal fits the screen instead of keeping its 800px minimum width */
@media (max-width: 768px) {
    dialog.modal {
        min-width: 0;
        width: calc(100% - 1.5em);
        box-sizing: border-box;

        /* the small hints after a form label - "(optional)", "(3-letter alias)" - desktop only: no room
           on a phone, and the asterisk already marks what is required */
        td:first-child small {
            display: none;
        }
    }
}

/* components/notification.css */
/*
CSS Only
Usage
render(document.getElementById('Notification'), 
html`<div class="notification"><strong>Done.</strong> Invoice has been saved.`);
*/
.notification {
    position: absolute;
    top: 80px;
    left: 50%;
    text-align: center;
    transform: translate(-50%, -50%) translateY(-100%);
    background: var(--bg-action-button);
    color: var(--color-action-button);
    padding: 16px 18px;
    border: 1px solid var(--border-action-button);
    border-radius: var(--window-border-radius);
    min-width: 240px;
    max-width: 400px;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.6em;

    /* the icon in the notice's own colour, a little taller than the text */
    svg {
        flex: none;
        width: 22px;
        height: 22px;
        fill: currentColor;
    }
    z-index: var(--z-notification); /* above the spinner's veil, so the message can be read while it spins */
    
    /* Slide-in animation */
    opacity: 0;
    animation: slide-in 0.35s forwards, fade-out 0.5s 4s forwards;

    &.error {
      background: var(--bg-danger);
      color: var(--color-danger);
      border-color: color-mix(in srgb, var(--color-danger) 45%, white);
    }

    /* waiting for something to finish (e.g. KSeF's verdict) */
    &.pending {
      background: color-mix(in srgb, var(--color-ready) 15%, white);
      color: var(--color-ready);
      border-color: color-mix(in srgb, var(--color-ready) 45%, white);
    }

    /* something succeeded for good (e.g. accepted by KSeF) */
    &.safe {
      background: color-mix(in srgb, var(--color-safe) 15%, white);
      color: var(--color-safe);
      border-color: color-mix(in srgb, var(--color-safe) 45%, white);
    }
  
  }
  
  @keyframes slide-in {
    to { 
        transform: translate(-50%, -50%) translateY(0); 
        opacity: 1; 
    }
  }
  
  @keyframes fade-out {
    to { opacity: 0; }
  }
/* components/nested_selector.css */
.breadcrumbs .action-button.ghost.period-button {
    display: inline-flex;
    align-items: center;
    gap: 0.4em;

    /* an underline, like a field's: the button is the control for what the page shows, so it looks pressable */
    padding: 6px 0.2em;
    border: none;
    border-bottom: 1px solid var(--border);
    border-radius: 0;

    /* a chevron turned down: the button opens a list */
    svg:last-child {
        width: 18px;
        height: 18px;
        margin: 0 -0.2em 0 -0.1em;
        transform: rotate(90deg);
    }
}

.flyoutMenuWrapper {
    background: var(--bg-basic); /* a menu, not a window: white like the account and apps menus */
    color: var(--text-color); /* a popover would otherwise draw the browser's black */
    border: 1px solid var(--border);
    border-radius: var(--window-border-radius);
    box-shadow: var(--main-box-shadow);
    min-width: max-content;
    min-width: 200px;
    max-height: 400px;
    overflow-y: auto; /* Scroll if list is long */
    scrollbar-width: none; /* no scrollbar, as on the page's other lists: Firefox */

    &::-webkit-scrollbar {
        display: none; /* Chrome, Safari */
    }

    /* a soft shadow along the bottom edge says "more below" while rows are hidden there
       (watchScrollable() sets [data-more-below]); it sticks to the bottom of the visible area and overlaps
       the last rows instead of adding height - as on the dashboard's lists */
    &::after {
        content: '';
        display: block;
        position: sticky;
        bottom: 0;
        height: 2.2em;
        margin-top: -2.2em;
        pointer-events: none;
        background: linear-gradient(to bottom, transparent, rgb(from var(--text-color) r g b / 0.16));
        opacity: 0;
        transition: opacity 0.2s ease;
    }

    &[data-more-below]::after {
        opacity: 1;
    }
}

.flyoutMenu {
    /* Base state: Show main view, hide nested view container */
    &.view-main {
        .main-view {
            display: block;
        }

        .nested-view {
            display: none;
        }
    }

    /* Drill-down state: Hide main view, show nested view container */
    &.view-nested {
        .main-view {
            display: none;
        }

        .nested-view {
            display: block;
        }
    }

    /* Individual sections inside the nested container */
    .nested-grid {
        display: none;
        grid-template-columns: repeat(3, auto);
        text-align: center;

        &.active {
            display: grid;
        }
    }

    /* General utility styles */
    .item {
        justify-content: space-between;
        padding: 0.5em;
        border-bottom: 1px solid var(--border);

        &.center-text {
            justify-content: center;
        }

        &:last-child {
            border-bottom: none;
        }
    }

    .nested-item {
        justify-content: center;
        padding: 1em;
        margin: 4px;
        border-radius: var(--window-border-radius);
        border: 1px solid transparent;

        &:hover {
            border: 1px solid var(--border);
        }

        /* the month on show */
        &.selected {
            border: 1px solid var(--border-action-button);
            background-color: var(--bg-action-button);

            span {
                font-weight: bold;
                color: var(--color-hover-action-button);
            }
        }
    }

    .item, .nested-item {
        display: flex;
        align-items: center;
        cursor: pointer;
        transition: background 0.2s;


        &:hover {
            background-color: var(--bg-cell);
            color: var(--text-color);
        }

        & > * {
              pointer-events: none; /* prevent events bubbling */
        }

        svg {
            fill: var(--text-muted);
            height: 1em;
            width: 1em;
        }
    }

    /* the year on show, in the year list: blue like the month on show (a press stays a press: no change on hover) */
    .item.selected,
    .item.selected:hover {
        background-color: var(--bg-action-button);
        color: var(--color-hover-action-button);
        font-weight: bold;
    }

    /* the list's heading ("Select Tax Year"): a quiet caption, nothing to press */
    .list-title,
    .list-title:hover {
        background-color: transparent;
        color: var(--text-muted);
        font-size: var(--font-size-sub);
        cursor: default;
    }

    /* the months of a year: the arrows step to the year before and after, the year itself opens the list of years */
    .year-nav {
        display: flex;
        align-items: center;
        justify-content: space-between;
        padding: 0.3em 0.5em;
        border-bottom: 1px solid var(--border);

        button {
            display: inline-flex;
            align-items: center;
            justify-content: center;
            background: transparent;
            border: none;
            border-radius: var(--border-radius);
            font: inherit;
            color: var(--text-color);
            cursor: pointer;
            padding: 0.4em 0.6em;

            &:hover:not(:disabled) {
                background-color: var(--bg-cell);
            }

            &:disabled {
                opacity: 0.3;
                cursor: default;
            }
        }

        .year-title {
            font-weight: bold;
        }

        svg {
            fill: var(--color-action-button);
            height: 1.3em;
            width: 1.3em;
        }
    }

    /* the way back to today's month after browsing other years: a quiet link under the months, greyed out while
       the month on show already is the current one */
    .current-row {
        display: flex;
        justify-content: center;
        padding: 0.3em 0.5em 0.4em;
        border-top: 1px solid var(--border);
        margin-top: 4px;

        .current-button {
            background: transparent;
            border: none;
            font: inherit;
            font-size: var(--font-size-sub);
            color: var(--color-action-button);
            cursor: pointer;
            padding: 0.4em 0.8em;
            border-radius: var(--border-radius);

            &:hover:not(:disabled) {
                background-color: var(--bg-cell);
                color: var(--color-hover-action-button);
            }

            &:disabled {
                opacity: 0.4;
                cursor: default;
            }
        }
    }

    .back-item {
        transition: background 0.2s;
        justify-content: flex-start;
        gap: 8px;
    }

    /* the year when it is the only one: a heading, not something to press */
    .heading-item {
        justify-content: center;
        cursor: default;

        &:hover {
            background-color: transparent;
        }
    }
}

/* components/health_insurance.css */
/* health_insurance.css - the row being edited fades its fields and buttons in, so opening it is not abrupt */
.hi-fade {
    animation: hi-fade-in 0.25s ease-out;
}

/* Save and Cancel side by side with room between them */
.hi-buttons {
    display: inline-flex;
    align-items: center;
    gap: 1.2em;
}

@keyframes hi-fade-in {
    from { opacity: 0; }
    to { opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
    .hi-fade {
        animation: none;
    }
}

/* the Save icon fills its box, the Cancel cross has air around it: a little extra room evens the look of icon and
   text across the buttons (the Edit icon has its own room, built into its box) */
.hi-icon-gap svg {
    margin-right: 0.3em;
}

/* amounts are right-aligned everywhere, in the field too */
.hi-amount {
    text-align: right;
}

/* a tennis tiebreak scoreboard for the year: one dot a month, green once paid, grey while open, faint when skipped */
.hi-dots {
    display: flex;
    gap: 0.45em; /* 12 dots come to about 11.5em, which fits the narrowest card */
    margin-top: 1.1em; /* clear of the number above */

    i {
        flex: 0 1 0.55em; /* shrink on a narrow card instead of running out of it */
        aspect-ratio: 1;
        border-radius: 50%;
        background: var(--text-greyed);

        &.paid { background: var(--color-safe); }
        &.skipped { background: transparent; border: 1px solid var(--text-greyed); box-sizing: border-box; }
    }
}

/* the table on the left, the year's summary card beside it; stacked on a phone */
.hi-layout {
    display: flex;
    align-items: flex-start;
    gap: 1.5em;

    #HealthInsurance {
        flex: 1 1 auto;
        min-width: 0;
    }
}

/* the summary is a dashboard card (its border and title bar); the figures sit under the title */
.viewport #App .hi-layout .hi-summary {
    flex: 0 0 25%; /* a quarter of the row at least, never narrower than the figures need */
    min-width: 15em;

    .hi-facts {
        display: grid;
        gap: 0.5em;
        margin: 0;
        padding: 0.4em 1.2em 1.2em;
    }

    dt {
        color: var(--text-muted);
        font-size: var(--font-size-sub);
        margin-top: 1.1em;
    }

    dd {
        margin: 0;
        font-size: var(--font-size-title);
        font-weight: bold;
        font-variant-numeric: tabular-nums;

        small {
            font-size: var(--font-size-sub);
            font-weight: normal;
            color: var(--text-muted);
        }
    }
}

@media (max-width: 768px) {
    /* the card is short on a phone: Paid and Months paid side by side, the dots and the next payment below */
    .viewport #App .hi-layout .hi-summary .hi-facts {
        grid-template-columns: 2fr 1fr;
        column-gap: 2.6em; /* Months paid stands clear of the middle, not against it */
        padding-bottom: 1.2em;

        .hi-fact.wide {
            grid-column: 1 / -1;
        }

        /* more room between the numbers, the dots and the next payment */
        .hi-fact.next dt {
            margin-top: 1.4em;
        }
    }

    .hi-layout {
        flex-direction: column-reverse;
        align-items: stretch;
    }

    .viewport #App .hi-layout .hi-summary {
        flex-basis: auto;
        min-width: 0;
    }
}

/* Date Paid is right-aligned like the amounts: its edge is a straight line down the table, and its distance from
   Balance is the columns' own - no padding to keep in step. The date field follows when a row is open. */
.hi-date-col {
    text-align: right;
}

.hi-date {
    text-align: right;
}

/* On a phone the table is only what fits (the status dot before the month has its words in its hover text), and
   editing and skipping are for a computer. No sideways scrolling. */
@media (max-width: 768px) {
    .hi-actions-col {
        display: none;
    }

    /* the actions column is hidden, so the corner cells the table rounds (its last column) are not on show: the
       last column you can see - Date Paid - takes the rounded corners instead */
    #HealthInsurance table thead th.hi-date-col {
        border-top-right-radius: var(--window-border-radius);
    }

    #HealthInsurance table tbody tr:last-child td.hi-date-col {
        border-bottom-right-radius: var(--border-radius);
    }

    #HealthInsurance {
        overflow-x: hidden;

        table {
            width: 100%;
            table-layout: auto;
        }

        th,
        td {
            padding-left: 0.6em;
            padding-right: 0.6em;
            white-space: nowrap; /* a date or an amount never breaks across lines */
        }
    }
}

/* a skipped month stays in the list, quieter than the months still to pay */
.hi-skipped td:not(:last-child) {
    color: var(--text-greyed);
}

/* a month still running or not started yet: quiet like a skipped one, but it keeps its buttons */
.hi-upcoming td:not(:last-child) {
    color: var(--text-greyed);
}

/* the status dot sits in front of the month's name */
#HealthInsurance td > span.status {
    margin-right: 8px; /* in px: the dot's own text size is the small one, so em would come out tight */
}

/* a date pencilled in: the due date stands in for the payment until it is recorded */
.hi-pencilled {
    font-style: italic;
    color: var(--text-muted);
}

/* components/reports.css */
/* Reports: a list of months with the selected month's VAT return next to it */
.reports-split {
    display: grid;
    /* the list grows with the screen, between 14em and 18em, so the month's pane never gets too long to read.
       The chart column in the pane is held to the same width, so the list is never narrower than it. */
    --list-width: clamp(14em, 20vw, 18em);
    grid-template-columns: var(--list-width) minmax(0, 1fr);
    gap: 1.6em;
    align-items: start;
    margin-top: 1.2em;
}

.reports-split .reports-list tr.report-row {
    cursor: pointer;

    /* the table's stripes already use --bg-cell on every other row, so the open month needs its own tint */
    &.selected td {
        background: var(--bg-action-button);
        font-weight: bold;
    }
}

/* a refund (VAT due below zero) is money coming back: green, the app's "money in" colour, never red */
.reports-split .reports-list td.refund,
.report-figure.refund .figure-value {
    color: var(--color-safe);
}

/* the same room at both edges: the dot at the left and the figures at the right sit the same distance from the
   table's edge, and a little more than a plain table cell gives */
.reports-list table th:first-child,
.reports-list table td:first-child {
    padding-left: 1.4em;
}

.reports-list table td:last-child {
    padding-right: 1.4em;
}

/* the status dot has a narrow column of its own, in front of the month's name, so the heading can be centred over
   the names alone; the room at the right edge is the last column's padding */
.reports-list td.dot-cell {
    width: 1%;
    padding-right: 0;
}

.reports-list td.month-name {
    padding-left: 0.6em;
}

/* the one action under the list: months filed outside OnPoint */
.reports-list .report-earlier {
    margin-top: 0.8em;
}

/* where the list is the whole page (a phone) it says what to do; beside an open month it would be noise */
.report-hint {
    display: none;
    margin: 0 0 0.8em;
    color: var(--text-muted);
}

@media (max-width: 900px) {
    .report-hint {
        display: block;
    }
}

.report-pane {
    min-width: 0;

    .pane-back {
        display: none;
        margin-bottom: 0.8em;
    }

    /* the title, with Set as Filed (or what filing left behind) at the right end of the same row, over a fine
       line that sets the heading apart from the month's figures. The line has room under the heading and
       above the caption, so neither sits on it. */
    .report-head {
        display: flex;
        flex-wrap: wrap;
        align-items: center;
        justify-content: space-between;
        gap: 0.4em 1em;
        padding-bottom: 0.6em;
        border-bottom: 1px solid var(--border);
    }

    /* the title and, right after it, whether the month is filed */
    .report-head-title {
        display: flex;
        flex-wrap: wrap;
        align-items: center;
        gap: 0.3em 0.9em;
        min-width: 0;
    }

    /* the dot level with the date's figures: a line box has room for descenders below the digits, so a dot
       centred in it sits a little low against them */
    .report-head-title .status::before {
        transform: translateY(-1px);
    }

    /* the date of filing in a regular weight (a status is bold elsewhere; here it sits beside a bold title) */
    .report-head-title .status.sm {
        font-weight: 400;
        font-size: var(--font-size-main);
        color: var(--text-color); /* the state of the month matters: the full text colour, not the muted one */
    }

    .report-title {
        margin: 0;
        font-size: var(--font-size-title);
    }

    /* the caption: the period, how often, the form - and when it is due */
    .report-sub {
        margin: 0.7em 0 1.2em;
        color: var(--text-muted);
    }
}

/* the three figures stacked on the left, the two small charts on the right */
.report-body {
    display: grid;
    /* the charts are as wide as the month list beside the pane (and never narrower than 11em); the figures
       take the rest */
    grid-template-columns: minmax(11em, 1fr) minmax(11em, var(--list-width, 18em));
    gap: 1.6em;
    margin-bottom: 1.2em;
}

.report-figures {
    grid-column: 1;
    grid-row: 1;
    display: flex;
    flex-direction: column;
    gap: 1em;
}

/* the gateway's confirmation number of a filed month, under the figures: small, monospaced and muted - a
   reference to copy, not a figure */
.filing-receipt {
    display: flex;
    align-items: center;
    gap: 0.4em;
    margin-top: -0.2em;

    svg {
        flex: none;
        width: 14px;
        height: 14px;
        fill: var(--text-muted);
    }

    code {
        font-family: ui-monospace, 'SF Mono', Menlo, monospace;
        font-size: var(--font-size-sub);
        color: var(--text-muted);
        user-select: text;
        word-break: break-all;
    }

    .clipboard {
        height: auto;
        gap: 0.4em;
        justify-content: flex-start;
    }
}

/* the checkbox and the download belong to the figures - the numbers that go into the file - so they sit
   under them, in their column, not under the charts */
.report-actions {
    /* the whole width of the pane, under the tiles and the charts: Save as XML and the gateway link side by
       side need more room than the column of tiles has */
    grid-column: 1 / -1;
}

.report-charts {
    grid-column: 2;
    grid-row: 1;
    display: flex;
    flex-direction: column;
    gap: 1em;
    min-width: 0;

    /* the two cards share the height of the tiles beside them */
    .sparkline {
        flex: 1;
    }
}

/* a line, not a shadow (DESIGN.md). What it is and how many on the left, the figure on the right: two
   short lines instead of three */
.report-figure {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1em;
    border: 1px solid var(--border);
    border-radius: 3px;
    padding: 0.6em 1em;
    flex: 1; /* the stack of tiles is as tall as the charts beside it: each tile takes an equal share */

    &.due {
        background: var(--bg-cell);
    }

    .figure-text {
        display: flex;
        flex-wrap: wrap;
        align-items: center;
        gap: 0.5em;
        min-width: 0;
    }

    .figure-label {
        display: flex;
        align-items: center;
        gap: 0.4em;
        color: var(--text-muted);
        font-size: var(--font-size-main);
        font-weight: bold;

        svg {
            width: 1.7em;
            height: 1.7em;
        }
    }

    .figure-value {
        font-size: var(--font-size-title);
        font-weight: bold;
        font-variant-numeric: tabular-nums;
    }

    /* a second line under the label and note: what the month deducted from the one before */
    .figure-note-2 {
        flex-basis: 100%;
        font-size: var(--font-size-sub);
        color: var(--text-muted);
    }

    /* on the same line as the label, after a dot: "Charged · 9 invoices" */
    .figure-note {
        font-size: var(--font-size-main);
        color: var(--text-muted);

        &::before {
            content: '·';
            margin-right: 0.5em;
        }
    }
}

.report-actions {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 0.8em;

    .checkboxLabel {
        white-space: nowrap;
    }

    /* Save as XML, and the government gateway where it is sent next to it: the two steps one after the other */
    .report-actions-row {
        display: flex;
        flex-wrap: wrap;
        align-items: center;
        gap: 0.6em 1.4em;
    }

    .report-actions-row .action-button svg + span {
        margin-left: 0.3em;
    }
}

.report-filing {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    flex-wrap: wrap;
    gap: 0.6em 1em;

    /* the action is the same size as the status it follows: the title large, and beside it a line of the main
       text size - the status and the action, one after the other */
    .action-button {
        font-size: var(--font-size-main);
    }

    .action-button svg {
        width: 1.7em;
        height: 1.7em;
    }

    /* room between a button's icon and its words (Set as Filed, Undo filing) */
    .action-button svg + span {
        margin-left: 0.3em;
    }
}

/* the explanation of "to correct" takes a line of its own, under the title row's line */
.report-pane .filing-note {
    margin: 0.7em 0 0;
    color: var(--text-muted);
}

@media (max-width: 640px) {
    /* tall enough to tap with a thumb */
    .reports-split .reports-list tr.report-row {
        height: 52px;
    }

    .report-body {
        grid-template-columns: minmax(0, 1fr);
    }

    .report-figures,
    .report-charts {
        grid-column: 1;
        grid-row: auto;
    }

    /* under the figures, the two cards side by side in one line */
    .report-charts {
        flex-direction: row;
    }
}

/* Narrow screens: the list is the page; a tap opens the month in its place, with a Back button */
@media (max-width: 900px) {
    .reports-split {
        grid-template-columns: minmax(0, 1fr);
    }

    .reports-split .report-pane {
        display: none;
    }

    .reports-split.detail-open {
        .report-pane { display: block; }
        .reports-list { display: none; }
    }

    /* a month is open in place of the list: the year picker belongs to the list, so it goes with it */
    .actions.toolbar:has(~ .reports-split.detail-open) {
        display: none;
    }

    .report-pane .pane-back {
        display: inline-flex;
    }
}

/* the surplus brought forward: a line in the top right corner, beside the year picker - the label, the amount and
   its Edit button */
.report-opening {
    display: flex;
    align-items: center;
    gap: 0.6em;
    font-size: var(--font-size-sub);
    color: var(--text-muted);

    .report-opening-value {
        font-size: var(--font-size-main);
        font-weight: bold;
        color: var(--text-color);
        font-variant-numeric: tabular-nums;
    }
}

/* On a phone the reference number (UPO) is read, not copied: the copy button goes */
@media (max-width: 768px) {
    .filing-receipt .clipboard button {
        display: none;
    }
}

/* components/taxes.css */
/* the year's table on the left, a grid of small cards beside it; stacked on narrow screens */
.tx-layout {
    display: flex;
    align-items: flex-start;
    gap: 1.5em;

    #Summary {
        flex: 1 1 auto;
        min-width: 0;
    }
}

/* the right-hand side: a third of the row, two small cards across */
.viewport #App .tx-layout .tx-sparks {
    flex: 0 0 34%;
    min-width: 20em;
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 1em;

    &:empty {
        display: none;
    }
}

@media (max-width: 1000px) {
    .tx-layout {
        flex-direction: column;
        align-items: stretch; /* the table and the cards take the whole width, not just what their content needs */
    }

    .viewport #App .tx-layout .tx-sparks {
        flex-basis: auto;
        min-width: 0;
        width: 100%;
    }
}

/* a small card with one number and a thin line of its months (components/sparkline.js) */
.sparkline {
    padding: 0.7em 1em 0.6em;
    border: 1px solid var(--border);
    border-radius: 3px;
    background: var(--bg-basic);

    .sparkline-title {
        color: var(--text-muted);
        font-size: var(--font-size-sub);
    }

    .sparkline-value {
        margin: 0.1em 0 0.3em;
        font-size: var(--font-size-title);
        font-weight: bold;
        font-variant-numeric: tabular-nums;
    }

    .sparkline-line {
        display: block;
        width: 100%;
        height: 36px;
        overflow: visible;
    }

    .sparkline-base {
        stroke: var(--border);
        stroke-width: 1;
        opacity: 0.6;
    }

    .sparkline-curve {
        fill: none;
        stroke: var(--border-action-button);
        stroke-width: 1.6;
        stroke-linecap: round;
        stroke-linejoin: round;
    }

    /* the line's own box: the dot at the tip is laid over it (left / top in percent), so it stays a true circle
       whatever width the line is stretched to */
    .sparkline-plot {
        position: relative;
    }

    .sparkline-dot {
        position: absolute;
        width: 6px;
        height: 6px;
        border-radius: 50%;
        transform: translate(-50%, -50%);
        background: var(--border-action-button);
    }

    /* a supplement to the figures beside it: its number is a notch down and muted, never louder than theirs */
    &.quiet .sparkline-value {
        color: var(--text-muted);
        font-size: var(--font-size-main);
    }

    /* purple line: money going out (DESIGN.md) */
    &.info {
        .sparkline-curve { stroke: var(--color-info); }
        .sparkline-dot { background: var(--color-info); }
    }

    /* green line: what is fine or comes in (DESIGN.md) - on Reports, the balance */
    &.safe {
        .sparkline-curve { stroke: var(--color-safe); }
        .sparkline-dot { background: var(--color-safe); }
    }
}

/* components/side_menu.css */
/* Side Menu that holds links to app sections */
:root {
    --side-menu-hover-width: 200px;
}
.side-menu {
    position: fixed;
    top: 0;
    left: 0;
    z-index: var(--z-side-menu);
    width: var(--side-menu-width);
    background: linear-gradient(to bottom, rgb(from var(--bg-contrast) r g b / 1), rgb(from var(--bg-contrast) r g b / 0.7));
    opacity: 0.9;
    height: 100vh;
    backdrop-filter: blur(30px);
    transition: all 0.3s ease;
    color: var(--text-color-contrast);
    font-family: var(--main-font);

    ul {
        list-style: none;
        margin: 0;
        padding-left: 0;
        display: flex;
        flex-direction: column;
        justify-content: center;
        align-items: flex-start;
        height: 100%;
        padding-right: .5em;

        li {
            display: flex;
            align-items: center;
            gap: .5em;
            width: 100%;
            padding: .5em;
            cursor: pointer;
            transition: .3s;
            vertical-align: middle;

            &:hover {
                background: var(--shade);
            }

            & > * {
                pointer-events: none;
            }

            .desktop-only {
                opacity: 0;
                transition: .3s;
            }

            svg {
                fill: var(--text-color-contrast);
                width: 24px;
                height: 24px;
                min-width: 24px;
                min-height: 24px;
                flex-shrink: 0;
                flex-grow: 0;
            }

            .menu-icon {
                position: relative;
                display: inline-flex;
            }

            .active-dot {
                display: none;
                position: absolute;
                top: -2px;
                left: -3px;
                width: 6px;
                height: 6px;
            }

            &.active .active-dot {
                display: block;
            }
        }
    }
}

/* Menu on phones: no sidebar. The same menu becomes a list of icons and labels that drops down under the apps button in the top bar */
@media (max-width:768px) {

    .side-menu {
        /* The list is in front of the top bar. */
        z-index: var(--z-above-bar);
        top: calc((var(--header-height) + var(--menu-toggle-size)) / 2);
        left: auto;
        right: var(--menu-toggle-right); /* drops down under the apps button */
        width: 190px;
        height: auto;
        background: var(--bg-basic);
        color: var(--text-color);
        border-radius: 8px;
        overflow: hidden;
        box-shadow: var(--main-box-shadow);
        backdrop-filter: none;
        opacity: 0;
        visibility: hidden;
        pointer-events: none;
        transform: translateY(-6px);
        transition: opacity 0.25s ease, transform 0.25s ease, visibility 0.25s;

        ul {
            height: auto;
            padding-right: 0;
            justify-content: flex-start;

            li {
                gap: 0.7em;
                padding: 0.8em 0.9em;
                border-bottom: 1px solid var(--border);

                &:last-child {
                    border-bottom: none;
                }

                /* hover is a light grey, so it never looks like the selected page */
                &:hover {
                    background: var(--bg-cell);
                }

                &.active {
                    background: var(--hover-action-button);

                    svg {
                        fill: var(--color-hover-action-button);
                    }
                }

                /* no seed beside the selected page here: the row's own colour says it */
                &.active .active-dot {
                    display: none;
                }

                svg {
                    fill: var(--text-color);
                }

                /* the labels stay visible here: the list has room for them */
                .desktop-only {
                    display: inline;
                    opacity: 1;
                }
            }
        }
    }
}

/* Menu opened with the top bar's button on phones.
   (A plain rule rather than nested, since older browsers ignore nesting that starts with an element name.) */
@media (max-width:768px) {
    body.menu-open .side-menu {
        opacity: 1;
        visibility: visible;
        pointer-events: auto;
        transform: translateY(0);
    }
}

/* Menu hover effect on desktop only. (Not (hover: hover): on a tablet the menu opens by a tap, which
   it treats as a hover.) 769, so it never overlaps the phone rules at max-width 768. */
@media (min-width:769px) {
    .side-menu {
        &:hover {
            width: var(--side-menu-hover-width);

            .desktop-only {
                opacity: 1;
            }

            ul li {
                border-radius: 0 20px 20px 0;
            }
        }
    }
}


/* components/tables.css */
/* A plain wrapper around a .table-section (the contractors list re-renders its table inside one).
   It has to pass the page's fixed height down, otherwise the table just grows to its full height
   and runs under the footer instead of scrolling inside its own area. */
.table-wrapper {
    display: flex;
    flex-direction: column;
    flex: 1;
    min-height: 0;
}

/* rework all this into variables and common classes for tables */
.table-section {
    overflow-x: auto; 
    gap: 0.5em;
    flex: 1;
    min-height: 0;   
    overflow-y: auto;
    color: var(--text-color);
    /* Rows scroll under the sticky header; without this they show through its rounded corners */
    border-radius: var(--window-border-radius) var(--window-border-radius) var(--border-radius) var(--border-radius);

    /* scrolls, hide scrollbar */
    scrollbar-width: none;

    &::-webkit-scrollbar {
        display: none;
    }

    /* thematic sections stacked in one column - excludes .grid, where
       sibling tables sit beside each other in the same row, not below */
    &:not(.grid) > table + table {
        margin-top: 1em;
    }

    &.grid {
        /*  Make a section two-column to contain two tables */
        display: grid;
        grid-gap: 1em;
        /* minmax(0, ...) keeps the two tables exactly equal; plain 1fr lets a table with wider content take more room */
        grid-template-columns: repeat(2, minmax(0, 1fr));
        align-items: start;
        flex: unset;
        margin-top: 1em;
        /* the section scrolls (overflow), which clips its tables' shadows: give them a few pixels of room
           at the sides and bottom, pulled back with negative margins so the tables stay where they were -
           otherwise the outer tables' rounded top corners look cut off */
        padding: 0 4px 6px;
        margin-left: -4px;
        margin-right: -4px;
        
        /* align actions with the changes above */
        & > .actions {
            grid-column: 1 / span 2;
            display: flex;
            flex-direction: row
        }

        /* An odd table out that should span the full row */
        & > .full-width {
            grid-column: 1 / span 2;
        }
    }

    & > table {
        width: 100%;
        text-align: left;
        background: transparent;
        border-collapse: separate;
        border-spacing: 0;
        font-size: var(--font-size-main);
        /* no shadow: every table has an outline instead (below) */
        /* Match the header cells' own top-corner rounding; the bottom corners a little rounded (--border-radius) */
        border-radius: var(--window-border-radius) var(--window-border-radius) var(--border-radius) var(--border-radius);

        /* money columns line up on the decimal point */
        th.align-right, td.align-right {
            text-align: right;
        }

        &.col-30 { /* first column 30% width without contraints on the header, used in contractor details */
            tbody tr td:first-child {
                width: 30%;
            }
        }

        &.col-66 { /* first column two thirds, the value a third: a long label and a short value (surplus brought forward) */
            tbody tr td:first-child {
                width: 66.6%;
            }
        }

        thead {
            border-bottom: 1px solid var(--border);

            th {
                background-color: var(--bg-cut);
                border-bottom: solid 1px var(--border);
                padding: 0.75em 0.5em;
                position: sticky;
                top: 0;
                z-index: var(--z-sticky);

                &.dynamic {
                    font-weight: bold; 
                    min-width: 110px;
                }

                &.fixed-width {
                    width: 360px;
                }

                &.sortable {
                    cursor: pointer;
                    user-select: none;

                    /* Sort icon (placed after the text by sortable_columns.js): always visible, in the
                       action-button blue - the sort icon, softened, while the column is unsorted; an up or
                       down arrow, at full strength, once it is sorted. */
                    svg {
                        width: 1.2em;
                        height: 1.2em;
                        vertical-align: middle;
                        margin-left: 0.25em; /* gap between the text and the icon */
                        fill: rgb(from var(--color-action-button) r g b / 0.5);
                        transition: fill 0.2s ease;
                    }

                    &.sorted-asc svg,
                    &.sorted-desc svg {
                        fill: var(--color-action-button);
                    }
                }

                &:first-child {
                    border-top-left-radius: var(--window-border-radius);
                }

                &:last-child {
                    border-top-right-radius: var(--window-border-radius);
                }
            }
        }

        tbody {
            /* the last visible row's corner cells follow the table's bottom corners (rows hidden for the case at
               hand, like the invoice window's reverse-charge rows, do not count) */
            tr:not(:has(~ tr:not([style*="display: none"]))) {
                td:first-child { border-bottom-left-radius: var(--border-radius); }
                td:last-child { border-bottom-right-radius: var(--border-radius); }
            }

            tr {
                border-bottom: 1px solid var(--bg-cell); 
                transition: background 0.2s;

                &:nth-child(even) {
                    background-color: var(--bg-cell);
                }

                &:nth-child(odd) {
                    background-color: var(--bg-basic);
                }

                td {
                    padding: 0.75em 0.5em;
                    border-bottom: solid 1px var(--border);

                    &.alt {
                        white-space: nowrap; 
                    }

                    &.interactive {
                        color: var(--color-action-button);
                        cursor: pointer;
                    }

                    &.left-border {
                        border-left: solid 1px var(--border);
                    }

                    &.right-border {
                        border-right: solid 1px var(--border);
                    }
                }
            }

            &.last-row-alt {
                tr:last-child {
                    background-color: var(--bg-action-button);
                    color: var(--color-action-button);
                    font-weight: bold;
                }
            }

        }
    }
}


/* Every table has the same outline - the cut-off from the page now that tables have no shadow - and the same
   single-weight edges:
   - the last row you can see has no line of its own under it (the outline is that line); rows hidden for the case
     at hand (inline display: none) and the list's scroll-trigger row do not count as rows
   - a cell's own left / right divider is for between columns: on the table's edge the outline is that line
   Not the dashboard's tables: they sit inside cards that already have a border. */
.table-section:not(.dashboard-list) > table {
    border: 1px solid rgba(75, 83, 88, 0.4);
}

.table-section:not(.dashboard-list) > table > tbody > tr:not(:has(~ tr:not([style*="display: none"]):not(.infinite-scroll-sentinel))):not(.infinite-scroll-sentinel) > td {
    border-bottom: none;
}

.table-section:not(.dashboard-list) > table > tbody > tr > td.left-border:first-child {
    border-left: none;
}

.table-section:not(.dashboard-list) > table > tbody > tr > td.right-border:last-child {
    border-right: none;
}

/* Invoice items table on the sale/purchase DETAIL pages only. Description wraps if long */
table#InvoiceItems {
    table-layout: fixed;
    width: 100%;
    /* the numbers' columns have fixed widths and the description takes what is left, so the table fits
       its section on a computer; below this it scrolls sideways (as it does on phones) */
    min-width: 860px;

    &:has(th.item-actions) {
        min-width: 944px; /* the actions column is 84px more */
    }

    th,
    td {
        &.description {
            white-space: normal;
            overflow-wrap: anywhere;
        }

        &.net_price,
        &.gross_amount,
        &.vat_amount {
            width: 130px;
        }

        &.unit,
        &.quantity,
        &.vat_rate {
            width: 90px;
        }
    }
}

/* A section's own action (Edit) in the top right corner of its header row */
.table-section > table thead th.section-action {
    text-align: right;
    padding-top: 0;
    padding-bottom: 0;
    white-space: nowrap;
}

/* The item table's last column, with fix and remove (only while the invoice may still be adjusted): narrow, and its
   buttons side by side. The "Add an item" row sits among the item rows, so it is plain, not a data row. */
table#InvoiceItems {
    th.item-actions,
    td.item-actions {
        width: 84px;
        white-space: nowrap;
        text-align: right;
    }

    tr.add-item-row td {
        padding: 0.35em 0.5em;
    }
}

/* Details / e-Invoicing tables on the sale and purchase DETAIL pages: keep dates and
   timestamps on one line. When the two tables share a narrow row, the section scrolls
   sideways instead (as it already does on phones) rather than breaking "2026-09-01".
   The system message can be a long sentence, so it may still wrap. */
table#Details td,
table#Status td:not(.wrap-text) {
    white-space: nowrap;
}

/* The labels need less room than the values (dates with a Set as Paid button, the status with its
   action, the system message), so the value column gets the larger share */
table#Details th:first-child,
table#Details td:first-child,
table#Status th:first-child,
table#Status td:first-child {
    width: 40%;
}

/* The system message (why a send failed, why KSeF rejected an invoice) in small, muted type: a note, not a value */
table#Status td.wrap-text {
    font-size: var(--font-size-sub);
    color: var(--text-muted);
}

/* Every row of these (and of the items table below them) is as tall as a dashboard row (--row-height),
   so a row with a button in it (Set as Paid, Download) is no taller than the rest and the two
   side-by-side tables stay in line */
table#Details tbody tr,
table#Status tbody tr,
table#InvoiceItems tbody tr {
    height: var(--row-height);
}

/* Stack the two-column grid (e.g. invoice detail's Details/e-Invoicing
   tables) into one column on mobile instead of squeezing both side by side */
@media (max-width:768px) {
    /* The Details and e-Invoicing tables are stacked here, so their value columns should line up:
       both get the same column split instead of each sizing its columns to its own content */
    table#Details,
    table#Status {
        table-layout: fixed;
        width: 100%;

        th:first-child,
        td:first-child {
            width: 45%;
        }

        /* the system message is a sentence: it wraps inside its cell now that the cell can't grow */
        td.wrap-text {
            white-space: normal;
            overflow-wrap: anywhere;
        }
    }

    /* Account page: label + value tables fit the screen - the label column keeps one line, a long
       value (e-mail, street, bank account) wraps inside its cell instead of running off the side */
    .account.table-section > table {
        table-layout: fixed;

        th.fixed-width,
        tbody td:first-child {
            width: 40%;
        }

        thead th:last-child,
        tbody td:last-child {
            white-space: normal;
            overflow-wrap: anywhere;
        }
    }

    .table-section.grid {
        grid-template-columns: 1fr;


        & > .actions,
        & > .full-width {
            grid-column: 1;
        }
    }

    /* Without this, narrow screens shrink every column until headers and
       values wrap mid-word (e.g. "VAT Rate" -> "VAT\nRate", dates breaking
       apart) - scroll horizontally instead, the section already supports it */
    .table-section > table thead th,
    .table-section > table tbody td {
        white-space: nowrap;
    }
}

/* Infinite scroll's scroll-into-view trigger row */
.infinite-scroll-sentinel td {
    padding: 0;
    border: none;
    text-align: center;
}

/* "All records have been shown" row once infinite scroll has nothing left to reveal */
.infinite-scroll-end td {
    text-align: center;
    font-size: var(--font-size-sub);
}

/* Contractors table specifically - fixed column widths so the search doesn't reflow columns */
table.contractors-table {
    table-layout: fixed;

    th:nth-child(1), td:nth-child(1) { width: 45%; }
    th:nth-child(2), td:nth-child(2) { width: 20%; }
    th:nth-child(3), td:nth-child(3) { width: 15%; }
    th:nth-child(4), td:nth-child(4) { width: 20%; }

    td.alt {
        overflow: hidden;
        text-overflow: ellipsis;
    }

    /* phones hide the Country column, so its 15% is shared out: otherwise the header bar stops short of the
       table's right edge. The Internal ID column gets enough for its full heading (and the sort icon after it);
       the name, which is cut with "..." anyway, takes less */
    @media (max-width: 768px) {
        th:nth-child(1), td:nth-child(1) { width: 48%; }
        th:nth-child(2), td:nth-child(2) { width: 34%; }
        th:nth-child(4), td:nth-child(4) { width: 18%; }

        /* a long name wraps onto a second line instead of being cut: the end of it often tells similar
           contractors apart, and the rows have the height */
        /* (spelled out in full to outrank the general table's `td.alt { white-space: nowrap }` above) */
        .table-section > & tbody tr td.alt {
            white-space: normal;
            overflow-wrap: anywhere;
            text-overflow: clip;
        }

        /* the currency tag (EUR) is always on a line of its own under the name, at the name's left edge: after a
           wrapped name it would otherwise hang indented, wherever the line happened to end */
        .foreign-currency {
            display: flex;
            margin-left: 0;
            margin-top: 0.15em;
        }
    }

    /* the contractor's description after the name (desktop only): small and muted, so the name leads */
    .contractor-note {
        margin-left: 0.6em;
        font-size: var(--font-size-sub);
        color: var(--text-muted);
        font-style: italic; /* like an invoice's description on its own page */
    }
}

/* a foreign vendor or client country after its name in the purchases and sales lists: small and green, like the currency tag */
.vendor-country {
    margin-left: 0.6em;
    font-size: var(--font-size-sub);
    color: var(--color-safe);
}

/* Sales/Purchases invoice tables. Columns: Invoice No. / e-Invoice / Client-Vendor / Date /
   Net Amount / VAT Amount / Gross Amount.
   Columns are as wide as their content (a header counts its sort icon too), with a share of the
   table as the preferred width, and the Client/Vendor column takes the rest, so the table always
   fills its container and the same rules serve both lists. */
.table-section > table.invoices-table {
    /* Horizontal padding of every cell, so the gap between two columns is twice this */
    --cell-x: 0.9em;
    /* Room the sort icon takes after a header's text: the icon (1.2em) plus its gap (0.25em) */
    --sort-icon-space: 1.45em;

    /* (the selectors below repeat .table-section > table so they outrank the general cell padding) */
    thead th,
    tbody td {
        padding-left: var(--cell-x);
        padding-right: var(--cell-x);
        white-space: nowrap;
    }

    /* Column shares of the table: the money columns get a comfortable slice and Client/Vendor takes
       what is left (about 28%; long names wrap). A share is only a preference - no column ever
       becomes narrower than its own content, so nothing is cut off. */
    thead th:nth-child(1), tbody td:nth-child(1) { width: 12%; }
    thead th:nth-child(2), tbody td:nth-child(2) { width: 8%; }
    thead th:nth-child(4), tbody td:nth-child(4) { width: 10%; }
    thead th:nth-child(5), tbody td:nth-child(5),
    thead th:nth-child(6), tbody td:nth-child(6),
    thead th:nth-child(7), tbody td:nth-child(7) { width: 14%; }

    tbody td:nth-child(3) {
        white-space: normal;
    }

    /* Headers match their cells: text columns left, dates centered, numbers (Net/VAT/Gross Amount)
       right-aligned. The sort icon always follows the header text, so where the text is right-aligned
       or centered the icon would push it away from the cells below; to keep them lined up, the
       number cells keep the icon's room on their right, and a centered header gets it on its left. */
    thead th {
        text-align: left;
    }

    thead th.align-center,
    tbody td.align-center {
        text-align: center;
    }

    thead th.align-center {
        padding-left: calc(var(--cell-x) + var(--sort-icon-space));
    }

    thead th.align-right,
    tbody td.align-right {
        text-align: right;
    }

    tbody td.align-right {
        padding-right: calc(var(--cell-x) + var(--sort-icon-space));
    }
}

/* Small desktop windows: slightly tighter gaps so seven columns still fit without side-scrolling */
@media (min-width:769px) and (max-width:1000px) {
    .table-section > table.invoices-table {
        --cell-x: 0.7em;
    }
}

/* Phones: the Net/VAT/Date/e-Invoice columns are hidden, leaving Invoice No., Client/Vendor and
   Gross Amount, so the gaps are tighter. A long name is cut with an ellipsis (max-width: 0 lets
   the column shrink to the space that is left) instead of pushing the amount off the screen. */
@media (max-width:768px) {
    .table-section > table.invoices-table {
        --cell-x: 0.4em;

        /* tall enough to tap with a thumb */
        tbody tr {
            height: 52px;
        }

        /* three columns only: the shares above do not apply - Invoice No. and Gross Amount fit their
           content and Client/Vendor takes all the rest */
        thead th:nth-child(1), tbody td:nth-child(1),
        thead th:nth-child(7), tbody td:nth-child(7) {
            width: auto;
        }

        /* the amount gets a comfortable minimum, not just what its digits need */
        thead th:nth-child(7), tbody td:nth-child(7) {
            min-width: 6.5em;
        }

        thead th:nth-child(3),
        tbody td:nth-child(3) {
            width: 100%;
        }

        thead th:nth-child(3),
        tbody td:nth-child(3) {
            padding-left: 1em; /* more air between the invoice number and the name */
        }

        tbody td:nth-child(3) {
            max-width: 0;
            overflow: hidden;
            text-overflow: ellipsis;
            white-space: nowrap;
        }

        /* breathing room from the table edge (and its rounded corner); the amounts keep the sort
           icon's room on top of that, so they line up with the header text */
        thead th:nth-child(7),
        tbody td:nth-child(7) {
            padding-right: 1em;
        }

        tbody td:nth-child(7) {
            padding-right: calc(1em + var(--sort-icon-space));
        }
    }
}

/* Invoice number followed by a small type chip (sale / purchase) in the contractor's invoice list:
   centred on one line, with room between the number and the chip */
.invoice-no {
    display: inline-flex;
    align-items: center;
    gap: 0.75em;
    /* the taller chip must not make its row taller than the rows of the other lists */
    margin: -2px 0;

    .label.sm {
        margin-right: 0;
        padding: 3px 8px;
    }
}

/* components/viewport_spinner.css */
/* Full-screen overlay wrapper */
.overlay {
    position: fixed;
    top: 0;
    left: 0;
    width: 100vw;
    height: 100vh;
    display: flex;
    justify-content: center;
    align-items: center;
    cursor: wait;
    visibility: hidden;
    opacity: 0;
    z-index: var(--z-overlay); /* Above all content */
    background-color: var(--shade);

    &.spin {
      visibility: visible;
      opacity: 1;
      transition: opacity 0.7s ease-in-out, visibility 0.7s ease-in-out;
      cursor: wait;
      pointer-events: auto;
    }
  }
  
  /* Container to center the spinner and message */
  .spinnerWrapper {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 1.5rem;
    text-align: center;
  }
  
  /* YOUR SPINNER CSS - adapted for full-screen context */
  .viewport_spinner {
    width: 72px;
    height: 72px;
    border-radius: 50%;
    display: inline-block;
    position: relative;
    border: 1px solid;
    border-color: var(--color-action-button) var(--color-action-button) transparent transparent;
    box-sizing: border-box;
    animation: rotation 1s linear infinite;
    top: 50%;
    left: calc(50% - 24px);
  }
  
  .viewport_spinner::after,
  .viewport_spinner::before {
    content: '';
    box-sizing: border-box;
    position: absolute;
    left: 0;
    right: 0;
    top: 0;
    bottom: 0;
    margin: auto;
    border: 1px solid;
    border-color: transparent transparent var(--lemon) var(--lemon);
    width: 56px;
    height: 56px;
    border-radius: 50%;
    animation: rotationBack 0.5s linear infinite;
    transform-origin: center center;
  }
  
  .viewport_spinner::before {
    width: 32px;
    height: 32px;
    border-color: var(--bg-contrast) var(--bg-contrast) transparent transparent;
    animation: rotation 1.5s linear infinite;
  }

  /* Smaller, inline variant - eg. row loader */
  .viewport_spinner.small {
    width: 24px;
    height: 24px;
    margin: 0.5em 0;
    top: 0;
    left: 0;
  }

  .viewport_spinner.small::after {
    width: 18px;
    height: 18px;
  }

  .viewport_spinner.small::before {
    width: 10px;
    height: 10px;
  }

  /* Keyframes */
  @keyframes rotation {
    0% {
      transform: rotate(0deg);
    }
  
    100% {
      transform: rotate(360deg);
    }
  }
  
  @keyframes rotationBack {
    0% {
      transform: rotate(0deg);
    }
  
    100% {
      transform: rotate(-360deg);
    }
  }

/* components/skeleton.css */
/* Skeleton screen shown while a page's data loads (js/components/skeleton.js) */
.skeleton {
    display: flex;
    flex-direction: column;
    gap: 1em;
    width: 100%;

    /* Eases in rather than popping onto the page */
    animation: skeleton-fade-in 0.8s ease-out;

    /* Faint fill, no sweep: the whole placeholder slowly breathes instead */
    .skeleton-bar,
    .skeleton-block {
        border-radius: var(--border-radius);
        background: rgb(from var(--border) r g b / 0.12);
        animation: skeleton-pulse 2.4s ease-in-out infinite;
    }

    .skeleton-heading {
        width: 160px;
        height: 24px;
    }

    .skeleton-rows {
        display: flex;
        flex-direction: column;
        gap: 6px;
    }

    .skeleton-row {
        height: 36px;
    }

    .skeleton-block {
        height: 140px;
    }
}

@keyframes skeleton-fade-in {
    from { opacity: 0; }
    to { opacity: 1; }
}

@keyframes skeleton-pulse {
    0%, 100% { opacity: 0.4; }
    50% { opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
    .skeleton,
    .skeleton .skeleton-bar,
    .skeleton .skeleton-block {
        animation: none;
    }
}

/* components/print_invoice.css */
/* The window that asks for the language of a printed invoice */
.print-window {
    min-width: 0;

    .print-languages {
        display: flex;
        gap: 2em;
        margin: 0.75em 0 1em;
    }

    /* the hint under the languages: a few lines, with room between them */
    .text-muted {
        line-height: 1.7;
    }
}

/* The printed invoice: on screen it does not exist visually, on paper it is the only thing there. A4 with
   its own margins, plain black on white (the app's colours are not for paper). */
#PrintDocument { display: none; }

@media print {
    @page { size: A4; margin: 13mm; }

    html, body {
        height: auto !important;
        overflow: visible !important;
        background: #fff !important;
        display: block !important;
    }

    body > *:not(#PrintDocument) { display: none !important; }
    #PrintDocument { display: block !important; }

    .invoice-document {
        --doc-text: #1b1f23;
        --doc-blue: #4a7a9f;
        --doc-tint: #eaf0f5;
        --doc-muted: #6b7782;
        --doc-brand-ink: #2b2c30; /* every piece of text in the brand font (Amatic SC) */
        font-family: Arial, Helvetica, sans-serif;
        font-size: 9pt;
        line-height: 1.5;
        color: var(--doc-text);
        print-color-adjust: exact;
        -webkit-print-color-adjust: exact;

        p { margin: 0; }

        .doc-title {
            font-size: 8pt;
            letter-spacing: 0.14em;
            text-transform: uppercase;
            color: var(--doc-muted);
            margin-bottom: 2mm;
        }

        .doc-top {
            display: flex;
            align-items: center;
            gap: 2mm;
            border-top: 1pt solid var(--doc-blue);
            padding: 4mm 0 5mm;
        }

        .doc-dot { width: 4mm; height: 4mm; flex: none; }
        .doc-seller-name { font-family: var(--font-family-logo); color: var(--doc-brand-ink); font-weight: bold; font-size: 14pt; line-height: 1; letter-spacing: 0.02em; }

        h3 {
            font-size: 6.5pt;
            font-weight: bold;
            letter-spacing: 0.14em;
            text-transform: uppercase;
            color: var(--doc-blue);
            margin: 0 0 3mm;
        }

        .doc-columns {
            display: grid;
            grid-template-columns: 52mm 1fr;
            gap: 10mm;
            margin-bottom: 8mm;
            line-height: 1.75;
        }

        .doc-from { color: var(--doc-muted); font-size: 8.5pt; }
        .doc-contact { margin-top: 4mm; color: var(--doc-blue); }

        .doc-ids {
            display: grid;
            grid-template-columns: max-content 1fr;
            gap: 0 4mm;
            margin: 4mm 0 0;

            dt { margin: 0; }
            dd { margin: 0; }
        }
        .doc-from-name { color: var(--doc-text); font-weight: bold; }
        .doc-name { font-weight: bold; font-size: 10.5pt; margin-bottom: 1mm; }

        /* the numbers and dates on the left, the KSeF QR code at the right end of the same lines */
        .doc-meta-row {
            display: flex;
            justify-content: space-between;
            align-items: flex-start;
            gap: 4mm;
            margin-top: 8mm;
            padding-top: 5mm;
            border-top: 0.5pt solid #d5dbe0;
        }

        .doc-qr {
            margin: 0;
            width: 22mm;
            flex: none;
            text-align: center;

            svg { display: block; width: 22mm; height: 22mm; }
            figcaption { font-family: ui-monospace, 'SF Mono', Menlo, Consolas, monospace; font-size: 4.5pt; line-height: 1.3; margin-top: 0.8mm; word-break: break-all; color: var(--doc-muted); }
        }

        .doc-meta {
            display: grid;
            grid-template-columns: max-content 1fr;
            gap: 1mm 6mm;
            margin: 0;
            flex: 1;
            min-width: 0;
            line-height: 1.5;

            dt { margin: 0; color: var(--doc-muted); }
            dd { margin: 0; word-break: break-all; }
            dd.first { font-weight: bold; }
            dd.mono { font-family: ui-monospace, 'SF Mono', Menlo, Consolas, monospace; font-size: 7.5pt; line-height: 1.9; white-space: nowrap; }
            dd a { color: var(--doc-blue); text-decoration: none; }
            dd a svg { width: 3mm; height: 3mm; vertical-align: -0.6mm; fill: var(--doc-blue); }
        }

        .doc-description { margin-bottom: 6mm; }

        .doc-items {
            width: 100%;
            border-collapse: collapse;

            th, td { padding: 3.5pt 6pt; text-align: left; vertical-align: top; }
            th { background: var(--doc-blue); color: #fff; font-weight: bold; padding-top: 5pt; padding-bottom: 5pt; }
            tbody td { padding-top: 6pt; padding-bottom: 6pt; border-bottom: 0.5pt dashed #c3cbd2; }
            .num { text-align: right; white-space: nowrap; }
            tr { break-inside: avoid; }

            tfoot td { border-bottom: 0.5pt dashed #c3cbd2; padding-top: 4.5pt; padding-bottom: 4.5pt; }
            tfoot tr:first-child td { border-top: 1pt solid var(--doc-blue); }
            tfoot tr.strong td { background: var(--doc-tint); border-top: 1pt solid var(--doc-blue); border-bottom: 1pt solid var(--doc-blue); font-weight: bold; font-size: 10pt; }
            tfoot tr.strong td:first-child { background: none; }
            tfoot td:last-child { text-align: right; }
            .row-label { float: left; }
            .row-value { white-space: nowrap; }
        }

        .doc-small { font-size: 7pt; color: var(--doc-muted); }
        .doc-paid { margin-top: 4mm; text-align: right; font-weight: bold; color: var(--doc-blue); }

        .doc-payment {
            margin-top: 5mm;
            padding: 2mm 4mm;
            width: max-content;
            max-width: 100%;
            background: var(--doc-tint);
            border-left: 2pt solid var(--doc-blue);
            break-inside: avoid;
            line-height: 1.55;
            font-size: 8.5pt;
        }
        .doc-payment h3 { margin-bottom: 1mm; }
        .doc-payment dl {
            display: grid;
            grid-template-columns: max-content 1fr;
            gap: 0 6mm;
            margin: 0;

            dt { margin: 0; color: var(--doc-muted); }
            dd { margin: 0; }
        }
        .doc-payment .mono { font-family: ui-monospace, 'SF Mono', Menlo, Consolas, monospace; font-size: 8pt; }

        .doc-notes { margin-top: 5mm; font-size: 8pt; color: var(--doc-muted); break-inside: avoid; }

        .doc-closing {
            margin-top: 5mm;
            break-inside: avoid;
            p { margin-bottom: 2mm; }
            p:last-of-type { font-weight: bold; }
        }

        .doc-seed { width: 4mm; height: 4mm; display: block; margin-top: 3mm; }
        .doc-foot {
            margin-top: 4mm;
            border-top: 1pt solid var(--doc-blue);
            padding-top: 2mm;
            display: flex;
            justify-content: flex-end;
            align-items: center;
            gap: 1.5mm;
            font-size: 6pt;
            color: var(--doc-muted);
        }
        .doc-onpoint { height: 2.6mm; width: auto; }
        .doc-onpoint-name { font-family: var(--font-family-logo); font-weight: normal; font-size: 8pt; color: var(--doc-brand-ink); }
    }
}

/* components/vertical_tabs.css */
/* A vertical list of tabs with the open tab beside it (vertical_tabs.js) */
.vtabs {
    display: grid;
    --list-width: clamp(11em, 18vw, 14em);
    grid-template-columns: var(--list-width) minmax(0, 1fr);
    gap: 1.6em;
    align-items: start;
    margin-top: 1.2em;
}

.vtabs-list {
    display: flex;
    flex-direction: column;
    gap: 2px;

    .vtab {
        display: flex;
        align-items: center;
        gap: 0.7em;
        width: 100%;
        padding: 0.6em 0.8em;
        border: none;
        border-radius: var(--border-radius);
        background: none;
        color: var(--text-color);
        font: inherit;
        text-align: left;
        cursor: pointer;

        svg {
            width: 22px;
            height: 22px;
            fill: var(--text-color);
        }

        /* hover is a light grey, so it never looks like the open tab */
        &:hover {
            background: var(--bg-cell);
        }

        &.active {
            background: var(--bg-action-button);
            font-weight: bold;
            color: var(--color-action-button);

            svg {
                fill: var(--color-action-button);
            }
        }

        &:focus-visible {
            outline: 2px solid var(--color-action-button);
            outline-offset: -2px;
        }
    }

    .vtab-icon {
        display: inline-flex;
    }

    .vtab-label {
        flex: 1;
    }
}

.vtabs-pane {
    min-width: 0;

    .pane-back {
        display: none;
        margin-bottom: 0.8em;
    }
}

/* Narrow screens: the list is the page; a tap opens the tab in its place, with a Back button */
@media (max-width: 900px) {
    .vtabs {
        grid-template-columns: minmax(0, 1fr);
    }

    .vtabs .vtabs-pane {
        display: none;
    }

    .vtabs.detail-open {
        .vtabs-pane { display: block; }
        .vtabs-list { display: none; }
    }

    .vtabs-pane .pane-back {
        display: inline-flex;
    }
}

/* components/field_list.css */
/* A section as a card: its title, then the facts as labelled fields (field_list.js) - not a table: each field is a
   different thing, so there are no rows, stripes or column heads to read down */
.field-panel {
    border: 1px solid var(--border);
    border-radius: var(--window-border-radius);
    padding: 1.1em 1.4em 1.4em;
    /* no wider than the eye can read across: on a wide screen the two columns of fields stay close */
    max-width: 720px;
    background: var(--bg-basic);
    position: relative;

    .panel-head {
        display: flex;
        align-items: flex-start; /* the action level with the title, not with the line under it */
        justify-content: space-between;
        gap: 1em;
        min-height: 2.2em; /* the same height with or without an action */
        padding-bottom: 0.7em;
        margin-bottom: 1.2em;
        border-bottom: 1px solid var(--border);
    }

    /* room at the right for the action of an untitled card, so it never sits on a value */
    &:has(> .panel-head.untitled) > .field-list {
        padding-right: 4.5em;
    }

    /* no title: just the action, in the card's top right corner, over the content - so the content starts at the
       top of the card, level with a neighbouring card's */
    .panel-head.untitled {
        position: absolute;
        top: 0.8em;
        right: 1.2em;
        min-height: 0;
        padding: 0;
        margin: 0;
        border-bottom: none;
    }

    h3 {
        margin: 0;
        font-size: var(--font-size-title);
        color: var(--text-color);
    }

    .panel-subtitle {
        margin: 0.3em 0 0;
        color: var(--text-muted);
    }

    .panel-note {
        margin: 1.2em 0 0;
        color: var(--text-muted);
    }
}

/* under a card, at its right edge (Copy address) */
.panel-below {
    display: flex;
    justify-content: flex-end;
    max-width: 720px; /* the card's width */
    margin-top: 0.8em;

    /* two actions under a card: one at each edge (Update certificate at the left, Delete at the right) */
    &:has(> :nth-child(2)) {
        justify-content: space-between;
    }
}

/* the KSeF card's status: dot and words in the weight of the other values, then "· Test" */
.ksef-card-status {
    display: inline-flex;
    align-items: center;
    gap: 0.5em;

    .status {
        font-weight: normal;
        font-size: inherit;
        color: var(--text-color);
    }

    .link-button {
        padding: 0;
        border: none;
        background: none;
        font: inherit;
        color: var(--color-action-button);
        cursor: pointer;
        display: inline-flex;
        align-items: center;
        gap: 0.25em;

        svg {
            width: 1.3em;
            height: 1.3em;
            fill: var(--color-action-button);
        }
    }
}

.field-list {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 1.9em 2.4em; /* rows well apart, so each label sits clearly with its own value */
    margin: 0;

    .field {
        min-width: 0;

        &.wide {
            grid-column: 1 / -1;
        }

        &.tall {
            grid-row: span 2;
        }
    }

    dt {
        margin: 0 0 0.5em; /* clear room between a label and its value */
        font-size: var(--font-size-sub);
        letter-spacing: 0.04em;
        text-transform: uppercase;
        color: var(--text-muted);
    }

    dd {
        margin: 0;
        min-width: 0;
        overflow-wrap: anywhere;
        color: var(--text-color);
    }

    /* ids and numbers in the code font, at the size of the text around them */
    dd code {
        font-size: 0.95em;
    }

    .field-copy {
        display: inline-flex;
        align-items: center;
        gap: 0.4em;
    }

    .field-empty {
        color: var(--text-muted);
    }
}

@media (max-width: 768px) {
    .field-panel {
        padding: 1em 1em 1.2em;
    }

    .field-list {
        grid-template-columns: minmax(0, 1fr);
    }
}

/* components/form_fields.css */
/* An edit window's fields: a label over its input, two to a row (one on a phone) - a form, not a table */
.form-fields {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 1.2em 1.6em;
    padding: 0.4em 0 0;

    .form-field {
        display: flex;
        flex-direction: column;
        gap: 0.4em;
        min-width: 0;

        /* long values (a street, a bank account number) take the whole row */
        &.wide {
            grid-column: 1 / -1;
        }

        /* a field shown only when needed (the passphrase of an encrypted key) */
        &[hidden] {
            display: none;
        }
    }

    /* a field's own label (its first child) - not a label used as a button inside it, like the file drop box */
    .form-field > label:first-child,
    .form-label {
        /* one fixed height, so a label with an (i) icon (Currency) lines up with the one beside it */
        display: flex;
        align-items: center;
        gap: 0.3em;
        min-height: 16px;
        font-size: var(--font-size-sub);
        letter-spacing: 0.04em;
        text-transform: uppercase;
        color: var(--text-muted);

        small {
            text-transform: none;
            letter-spacing: 0;
        }
    }

    input {
        width: 100%;
    }

    /* a column of fields inside the grid (New Invoice: the three dates under one another, the currency beside them) */
    .form-stack {
        display: flex;
        flex-direction: column;
        gap: 1.2em;
        min-width: 0;
    }

    /* a field that takes the rest of its column's height (the invoice's description beside the dates) */
    .form-grow {
        flex: 1;

        textarea {
            flex: 1;
            min-height: 4.5em;
        }
    }

    textarea {
        width: 100%;
        box-sizing: border-box;
        resize: none;
        font-family: var(--main-font);
        font-size: var(--font-size-input);
        color: var(--text-color);
        background-color: transparent;
        border: var(--control-border);
        border-radius: var(--border-radius);
        padding: 5px;
        outline: none;
    }

    /* a control that is not an input (the currency selector) takes an input's height, so its line matches the
       input beside it */
    .form-control {
        display: flex;
        align-items: center;
        height: 30px;
    }

    /* a short explanation under a field */
    .form-hint {
        margin: 0;
        font-size: var(--font-size-sub);
        font-style: italic; /* an aside, not a label */
        color: var(--text-muted);
    }
}

/* a note about the whole form, under its fields (why some fields cannot be changed): small, muted, in italics -
   an aside, not a field */
.form-note {
    margin: 1em 0 0;
    font-size: var(--font-size-sub);
    font-style: italic;
    color: var(--text-muted);
}

/* a note inside the grid (why the fields above are disabled): close under them, the grid's own row gap is room enough */
.form-fields > .form-note {
    margin: -0.8em 0 0;
}

/* a long form in sections (a new contractor): a small heading over each group of fields */
.form-section-title {
    margin: 1.6em 0 0.4em;
    font-size: var(--font-size-main);
    color: var(--text-color);

    &:first-child {
        margin-top: 0.4em;
    }
}

/* choices side by side in a field (sale or purchase, services or goods) */
.form-field .form-choices,
.form-tools .form-choices {
    display: flex;
    flex-wrap: wrap;
    gap: 0.6em 2em;
    min-height: 30px;
    align-items: center;
}

/* the window's first line (New Invoice): the kind of invoice and the currency at the left, its actions at the right (Import, then the
   framed Create) - at the top because a long items list puts the bottom of the window far away */
.form-tools {
    display: flex;
    justify-content: space-between;
    align-items: flex-start; /* the labels on one line */
    flex-wrap: wrap;
    gap: 1em 1.5em;
    margin-bottom: 1.2em;

    .form-tools-actions {
        display: inline-flex;
        align-items: center;
        gap: 1.5em;
        margin-left: auto;
        align-self: center;
    }

    /* outside the field grid it still reads as a field: label over its choices */
    .form-field {
        display: flex;
        flex-direction: column;
        gap: 0.4em;
    }

    /* the kind of invoice and its currency: two separate choices, well apart */
    .form-field + .form-field {
        margin-left: 2em;
    }

    /* the currency selector takes an input's height, so its line matches the radios beside it */
    .form-control,
    .form-control.invoice-currency {
        display: flex;
        align-items: center;
        height: 30px;
        /* the selector's text sits 7px below its middle (its own padding for the underline): lift the line so
           "EUR 4.3661" reads on the same line as "Sale  Purchase" */
        margin-top: -7px;
    }

    .form-label {
        font-size: var(--font-size-sub);
        letter-spacing: 0.04em;
        text-transform: uppercase;
        color: var(--text-muted);
        /* one fixed height, so a label with an (i) icon (Currency) is no taller than one without */
        display: flex;
        align-items: center;
        gap: 0.3em;
        height: 16px;
    }

    .action-button:not(.ghost) {
        min-width: 140px;
        justify-content: center;
    }
}

/* the invoice's currency: the selector, the exchange rate and "use as invoice currency" on one line */
.form-control.invoice-currency {
    gap: 1.2em;
    flex-wrap: wrap;
    height: auto;
    min-height: 30px;
}

/* choices side by side in a field (the kind of tax id) */
.form-field .tax-id-types {
    gap: 1.6em;
    margin: 0.2em 0 0;
}

/* under the form: Cancel at the left, the framed Save in the right corner */
.dialog-actions {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-top: 1.8em; /* clear room between the last field and the buttons */

    /* the framed main action (Save) has room around its label, not just enough to hold it */
    .action-button:not(.ghost) {
        min-width: 110px;
        justify-content: center;
    }

    /* Cancel is bare text: it keeps in from the edge by about what Save keeps its label in from its frame, so the
       two sides of the row balance */
    & > .action-button.ghost {
        margin-left: 1em;
    }

    /* more than one action at the right end: a message, Test, Save */
    .dialog-actions-end {
        display: inline-flex;
        align-items: center;
        gap: 1.5em;
    }
}

@media (max-width: 768px) {
    .form-fields {
        grid-template-columns: minmax(0, 1fr);
    }
}

/* A window holding a form is one plain white surface: the inputs have their own borders, so no grey window and
   no box inside it (the grey stays for windows that still list their fields in a table, where it sets off the
   white rows) */
dialog.modal:has(.form-fields) {
    background: var(--bg-basic);
}

/* the warning flash on a field left empty (in a form or on a window's first line): room round it and the small rounded corners
   not a table row's hard edge; the negative margin keeps everything else where it was */
.form-field.flash-once {
    margin: -0.5em;
    padding: 0.5em;
    border-radius: var(--border-radius);
}

/* the (i) beside a label is a 14px glyph: with its own line height it made Currency's label taller, and its text
   sat lower than the label beside it - a line height of 1 keeps every label the same height */
.form-fields .info-tooltip,
.form-tools .info-tooltip {
    line-height: 1;
}

/* a section of its own under the grid (the reverse charge and its kind): boxed, and its two columns start exactly
   where the two columns of fields above do. The box is as wide as the fields; its padding is 0.8em, so the first
   column is 50% + half the 1.6em gap of the grid above (the second one then starts under the second column of
   fields). Hidden while both its fields are. */
.form-row {
    display: grid;
    grid-template-columns: calc(50% + 0.8em + 1px) minmax(0, 1fr);
    gap: 1.2em 0;
    align-items: start;
    margin: 1.4em 0 0;
    padding: 0.9em 0.8em;
    border: 1px solid var(--border);
    border-radius: var(--border-radius); /* the small 3px one: it is a part of the form, not a window or a card */

    .form-field {
        display: flex;
        flex-direction: column;
        gap: 0.4em;
        min-width: 0;
    }

    .form-field:first-child {
        padding-right: 1.6em;
    }

    &:not(:has(> .form-field:not([style*="display: none"]))) {
        display: none;
    }
}

/* Create is at the top of the window, where a long items list cannot push it far away - and on a phone, at the bottom
   instead, under the items, where the eye ends. Import stays on the kind of invoice's line, with its label. */
.create-at-bottom {
    display: none;
}

@media (max-width: 768px) {
    .form-tools .form-tools-actions {
        display: flex;
        margin-left: auto; /* on the radios' line, at the right end */
        align-self: flex-end;
        min-height: 30px; /* the radios' height, so their middle lines meet */

        .action-button:not(.ghost) {
            display: none;
        }

        .action-button.ghost > span {
            display: inline;
        }
    }

    .create-at-bottom {
        display: flex;
        justify-content: flex-end;
        margin: 0.5em 0 0.5em;

        .action-button {
            min-width: 140px;
            justify-content: center;
        }
    }

    .form-row {
        grid-template-columns: minmax(0, 1fr);
    }

    .form-row .form-field:first-child {
        padding-right: 0;
    }
}

/* labels in that row look like every other form label */
.form-row .form-label {
    display: flex;
    align-items: center;
    gap: 0.3em;
    min-height: 16px;
    font-size: var(--font-size-sub);
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--text-muted);
}

.form-row .form-control {
    display: flex;
    align-items: center;
    height: 30px;
}

.form-row .info-tooltip {
    line-height: 1;
}

/* The same pulse on a table row (an item left empty): the row's cells pulse together as one flat strip. They are
   not rounded - the cells have their own border lines, and rounding a cell rounds its border into rough bumps
   against the table's straight edge. */
tr.flash-once {
    animation: none;
}

tr.flash-once > td {
    animation: warning-flash 1.5s ease-in-out 1;
}

/* an error under a form (a missing or invalid value): red, with room for a line so the buttons do not jump */
.form-error {
    margin: 0.8em 0 0;
    min-height: 1.2em;
    color: var(--color-danger);
}

/* The VAT rate in the item windows: the app's usual selector (its underline, nothing boxed) stretched to the full
   width of its column, so it is as easy to hit as the fields beside it; its menu is as wide too (matchWidth) */
.item-edit .form-control {
    height: auto;
    margin-top: 0;
    display: block;

    .breadcrumbs {
        display: block;
        width: 100%;
        margin: 0;
        padding: 0;
    }

    .action-button.ghost.period-button {
        display: flex;
        align-items: center;
        justify-content: space-between;
        width: 100%;
        min-height: 30px;
        margin: 0;
        box-sizing: border-box;

        span {
            flex: 1;
            text-align: left;
        }
    }
}


.no-drag {
    /* Disallow selection – helps stop the “text‑drag” behavior */
    user-select: none;
    -webkit-user-select: none; /* Safari */

    /* Disallow the built‑in image/element dragging */
    -webkit-user-drag: none;
    /* Chrome/Safari */

    &>img {
        -webkit-user-drag: none;
    }
}

.no-select {
    -webkit-user-select: none; /* Safari */
    user-select: none;
}

/* any h tag with svg inside */
.icon-header {

    svg,
    span {
        vertical-align: middle;
    }
}

/* message to show instead of an empty table in italics */
.list-msg {
    color: var(--text-muted);
    font-style: italic;
    font-weight: 300;
    padding: 0.5em 0;

    /* Same pill look as .notification (see notification.css), just staying
       inline in the page (no absolute position / auto-dismiss) since it
       needs to persist for as long as the filter is active */
    &.filter-note {
        text-align: center;
        font-style: normal;
        font-weight: 400;
        padding: 0 0 0.75em;

        .filter-note-pill {
            display: inline-flex;
            align-items: center;
            gap: 0.4em;
            background: var(--bg-action-button);
            color: var(--color-action-button);
            font-size: var(--font-size-main);
            padding: 8px 10px 8px 16px;
            border: 1px solid var(--border-action-button);
            border-radius: var(--window-border-radius);
        }

        .filter-note-clear {
            display: flex;
            background: none;
            border: none;
            padding: 2px;
            cursor: pointer;
            line-height: 0;

            svg {
                width: 24px;
                height: 24px;
                fill: var(--color-action-button);
            }

            &:hover svg {
                fill: var(--color-hover-action-button);
            }
        }
    }
}

.no-events {
    pointer-events: none;
}

.display-none {
    display: none;
}

.text-muted {
    color: var(--text-muted);
}

.text-accent {
    color: var(--color-action-button);
}

.align-middle {
    vertical-align: middle;
}

/* Pushes elements to the end of a flex row, while earlier siblings stay at the start */
.push-right {
    margin-left: auto;
}

.text-right {
    text-align: right;

    .clipboard {
        justify-content: flex-end;
    }
}

.identity-chip {
    display: inline-flex;
    align-items: center;
    gap: 0.3em;
}

.tax-id-chip {
    display: inline-flex;
}

.detail-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
}

.detail-header-actions {
    display: flex;
    align-items: center;
    gap: 1em;
}

/* Warning flash */
@keyframes warning-flash {
    0% {
        background-color: transparent;
    }

    50% {
        background-color: var(--color-warning); /* Soft amber warning */
    }

    100% {
        background-color: transparent;
    }
}

.flash-once {
    /* Runs once over 1.5 seconds */
    animation: warning-flash 1.5s ease-in-out 1;
}
/* End Warning flash */

/* Good flash - gentle green confirmation that a value just changed (the amber warning flash is above) */
@keyframes good-flash {
    0% {
        background-color: transparent;
        animation-timing-function: ease-in-out;
    }

    30% {
        background-color: rgb(from var(--color-safe) r g b / 0.14);
        animation-timing-function: ease-in-out;
    }

    50% {
        background-color: rgb(from var(--color-safe) r g b / 0.2);
        animation-timing-function: ease-in-out;
    }

    70% {
        background-color: rgb(from var(--color-safe) r g b / 0.14);
        animation-timing-function: ease-in-out;
    }

    100% {
        background-color: transparent;
    }
}

.flash-good {
    animation: good-flash 1.8s ease-in-out 1;
}
/* End Good flash */

/* Update fade - draws the eye to a label whose text just changed by fading
   it out, swapping the content once invisible, then fading it back in */
@keyframes fade-out-text {
    from {
        opacity: 1;
    }

    to {
        opacity: 0;
    }
}

@keyframes fade-in-text {
    from {
        opacity: 0;
    }

    to {
        opacity: 1;
    }
}

.fade-out-text {
    animation: fade-out-text 0.15s ease-in 1;
}

.fade-in-text {
    animation: fade-in-text 0.15s ease-out 1;
}
/* End Update fade */

/* Fading content */
.fade-in {
    opacity: 1;
    pointer-events: all;
    transition: var(--show-hide-transition);
}

.fade-out {
    pointer-events: none;
    opacity: 0;
    transition: var(--show-hide-transition);
}
/* End Fading content */

/* Actions row */
.actions {
    display: flex; 
    flex-direction: row; 
    align-items: center;
    justify-content: space-between; 
    /* room before the table below, so the buttons sit between title and table rather than on the
       table (and their phone tap areas stay clear of its header) */
    margin-bottom: 12px;
    margin-top: 5px;
    gap: 1em;

    button.action-button {
        min-width: 100px;
    }

    button.ghost {
        min-width: min-content;
    }

    &.left {
        justify-content: flex-start;
    }

    &.right {
        justify-content: flex-end;
        /* buttons side by side (Search, Add a contractor) read as separate actions, not one phrase */
        gap: 1.75em;
    }

}

/* Phones: a ghost button is often just its icon (24px) - too small to hit with a thumb. An invisible
   8px all round makes it 40px to tap (like the top bar's buttons) while the negative margin keeps the
   icon exactly where it was, flush with the edge; the wider gap keeps neighbours' tap areas apart. */
@media (max-width: 768px) {
    .actions {
        gap: 20px;

        /* not the period selector: its underline is part of what you see, so it must not stick out 8px at the
           left of the page's other lines */
        button.action-button.ghost:not(.period-button) {
            padding: 8px;
            margin: -8px;
        }
    }
}
/* End Actions row */

/* Used for displaying muted details below the header when they're just extra info */
.details-section {
    display: grid; 
    color: var(--text-muted);
    font-size: var(--font-size-sub);
}

/* Gentle pulse animation to use anywhere */
@keyframes pulse-gentle {
    0% {
        transform: scale(1);
    }

    50% {
        transform: scale(1.05);
    }

    100% {
        transform: scale(1);
    }
}


@media (max-width:768px) {
    .desktop-only {
        display: none;
    }

    /* an actions row holding only desktop-only buttons (e.g. Edit Account) would just be an empty gap */
    .actions:not(:has(> :not(.desktop-only))),
    .table-section.grid > .actions:not(:has(> :not(.desktop-only))) {
        display: none;
    }
}

/* Toggleable search bar shown above a list (e.g. contractors) */
.search-bar {
    margin-bottom: 0.75em;

    input {
        width: 100%;
        text-align: center;
        border: none;
        border-bottom: var(--input-border);
        border-radius: 0;
    }
}

/* A table cell with a value and a small action button right after it (Payment Due Date + Set as Paid, Status + Set as Accepted) */
.inline-action-cell {
    display: flex;
    align-items: center;
    gap: 1.5em;
}

/* a Paid label needs less room than the button */
.inline-action-cell:has(.paid-label) {
    gap: 0.5em;
}

/* A second, small amount under an amount in a table: the PLN equivalent under an invoice's own currency, or the
   invoice-currency amount under the PLN one in a list */
.amount-note {
    display: block;
    color: var(--text-muted);
    font-size: var(--font-size-sub);
    font-weight: 400;
}

/* In a list: a green tick and the currency an invoice was issued in, after its number */
.foreign-currency {
    display: inline-flex;
    align-items: center;
    gap: 0.25em;
    margin-left: 0.6em;
    vertical-align: middle;
    color: var(--color-safe);

    svg {
        width: 14px;
        height: 14px;
        fill: currentColor;
    }

    small {
        font-size: var(--font-size-sub);
    }
}


.viewport {
    margin-left: var(--side-menu-width); /* accommodates folded side menu */
    height: 100%;
    position: relative;
    background-color: var(--bg-basic);

    #logo {
        display: inline-flex;
        gap: 2px;
    }

    #App {
        position: relative;
        top: var(--header-height);
        max-width: 1440px;
        width: calc(100% - 8px);
        margin: auto;
        padding: 1em;
        pointer-events: none;
        opacity: 0;

        display: flex;
        flex-direction: column;
        height: calc( 100vh - var(--header-height) - var(--footer-height));
        height: calc( 100dvh - var(--header-height) - var(--footer-height)); /* see body in globals.css */
        
        &.show {
            opacity: 1;
            pointer-events: auto;
            transition: var(--show-hide-transition);
        }

        /* the page can scroll, no scrollbar */
        scrollbar-width: none;

        &::-webkit-scrollbar {
            display: none;
        }

    }

    #Spinner {
        position: absolute;
        width: 100%;
        height: 100%;
        top: 0;
        left: 0;
        pointer-events: none;
        background: transparent;
    }

}

#Modal {
    position: fixed;
    width: 100%;
    top: 0;
    left: 0;
    z-index: var(--z-above-bar); /* above header/sidebar/footer */
}

/* Adjust menu width to viewport margin on mobile device */
@media (max-width:768px) {
    .viewport {
        margin-left: 0; /* the menu is hidden until the top bar's button opens it, so no strip beside the content */

        #App {
            /* a strip of plain page on each side, so a swipe there scrolls the page instead of a table that
               has its own scrolling */
            padding: 1em 12px;
            /* #App's fixed height relies on each .table-section scrolling
               internally, which works for a single list but not a detail
               page stacking several sections - let the whole page scroll
               instead, and stop sections fighting each other for height */
            overflow-y: auto;

            .table-section {
                overflow-y: visible;
                flex: none;
            }

            /* ...except on a list page (its list marked .list-scroll): the heading, breadcrumbs and search
               stay put and only the list scrolls, under its sticky column headers - as on a computer */
            &:has(.table-section.list-scroll) {
                overflow-y: hidden;
            }

            .table-section.list-scroll {
                flex: 1;
                min-height: 0;
                overflow-y: auto;
                /* no elastic bounce at the ends of the list: in Safari it drags the table's top edge and
                   sticky header out of sight (as on the dashboard's lists) */
                overscroll-behavior: none;
            }
        }
    }
}