/* ── theme/site.css ───────────────────────────────────────────────────────────
   The one stylesheet. tools/build_site.py copies it to the output root, so every
   url() in here resolves relative to index.html, not to theme/.

   Contrast: the brand palette is a print palette, and two of its colours cannot
   carry small text on the web. Measured against white: brand teal #4097AB is
   3.37:1 and brand gray #858B93 is 3.44:1, both short of the 4.5:1 that body text
   needs. So each has a darkened text-only sibling (--teal-text 5.67:1,
   --muted 5.91:1 on white and 5.36:1 on mist) and the bright originals are kept
   for fills, rules and decoration, where the rule does not apply. Do not "tidy"
   the two into one.
   ──────────────────────────────────────────────────────────────────────────── */

@font-face { font-family: Barlow; font-weight: 400; font-style: normal;
             src: url("fonts/Barlow-Regular.otf") format("opentype"); font-display: swap; }
@font-face { font-family: Barlow; font-weight: 400; font-style: italic;
             src: url("fonts/Barlow-Italic.otf") format("opentype"); font-display: swap; }
@font-face { font-family: Barlow; font-weight: 500; font-style: normal;
             src: url("fonts/Barlow-Medium.otf") format("opentype"); font-display: swap; }
@font-face { font-family: Barlow; font-weight: 600; font-style: normal;
             src: url("fonts/Barlow-SemiBold.otf") format("opentype"); font-display: swap; }
@font-face { font-family: Barlow; font-weight: 700; font-style: normal;
             src: url("fonts/Barlow-Bold.otf") format("opentype"); font-display: swap; }
@font-face { font-family: Barlow; font-weight: 800; font-style: normal;
             src: url("fonts/Barlow-ExtraBold.otf") format("opentype"); font-display: swap; }
@font-face { font-family: "JetBrains Mono"; font-weight: 400; font-style: normal;
             src: url("fonts/JetBrainsMono-Regular.ttf") format("truetype"); font-display: swap; }
@font-face { font-family: "JetBrains Mono"; font-weight: 700; font-style: normal;
             src: url("fonts/JetBrainsMono-Bold.ttf") format("truetype"); font-display: swap; }

:root {
    /* Brand palette, shared with template/doc.typ and logo.typ. */
    --navy:  #1A2A6B;
    --teal:  #4097AB;
    --ink:   #43464A;
    --gray:  #858B93;
    --mist:  #F2F4F7;
    --dark:  #0D1430;

    /* Text-safe siblings; see the note at the top. */
    --teal-text: #2E6F80;
    --muted:     #5B6573;
    --teal-lt:   #7FD1E0;   /* on --dark only: 10.43:1 */

    /* Something the reader has to fix, currently only in the setup form. A darkened
       sibling of --c-taxrate rather than a new red, so the palette stays the brand's:
       5.83:1 on white, 5.29:1 on mist, which puts it alongside --teal-text and
       --muted rather than shouting over them. */
    --warn-text: #B03A5B;

    --rule:   #DDE2EA;
    --codefg: #C8D0E0;

    /* The six groups of the retired annotated-invoice overlay. The bright value
       filled rectangles and strokes; the -badge value carried white digits and is
       darkened to clear 4.5:1 (the bright ones measure 3.14 to 5.04, so three of
       the six fail). No rule consumes these now that the annotated document is a
       raster image with its colours baked in, except --c-taxrate, which the setup
       form's invalid border uses. The overlay SVG itself was deleted on 2026-08-27,
       so this is the only record of the palette left. */
    --c-sender:   #2E8C9E;  --c-sender-badge:   #2A7F90;
    --c-receiver: #7E5BC2;  --c-receiver-badge: #7E5BC2;
    --c-general:  #C9821F;  --c-general-badge:  #A16819;
    --c-items:    #3B6FD4;  --c-items-badge:    #3B6FD4;
    --c-taxrate:  #CC4E78;  --c-taxrate-badge:  #C24A72;
    --c-total:    #2E9E6B;  --c-total-badge:    #268359;

    --sans: Barlow, "Segoe UI", "Helvetica Neue", Arial, sans-serif;
    --mono: "JetBrains Mono", "SF Mono", Consolas, ui-monospace, monospace;
}

*, *::before, *::after { box-sizing: border-box; }

html { scroll-behavior: smooth; scroll-padding-top: 4.5rem; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
    margin: 0;
    font-family: var(--sans);
    font-size: 17px;
    line-height: 1.62;
    color: var(--ink);
    background: #fff;
    -webkit-text-size-adjust: 100%;
}

.skiplink {
    position: absolute; left: -9999px;
    background: var(--navy); color: #fff; padding: .6rem 1rem; z-index: 100;
}
.skiplink:focus { left: .5rem; top: .5rem; }

:focus-visible { outline: 2px solid var(--teal-text); outline-offset: 2px; }

/* ── Topbar ─────────────────────────────────────────────────────────────── */

/* Fixed rather than sticky, so it takes no space in the flow and the hero can start
   at the very top of the page. site.js then tucks it away while the hero is on screen
   and slides it back the moment the hero is scrolled past: the brand is already three
   times the size in the hero, and repeating it in a bar over the top of it is noise.
   Without JavaScript the bar simply stays put, which is what it did before. */
.topbar {
    position: fixed; top: 0; left: 0; right: 0; z-index: 50;
    display: flex; align-items: center; gap: .9rem;
    padding: .55rem 1.5rem;
    /* --dark, not --navy: this is the hero's own colour, so the bar reads as the hero
       following the reader down rather than as a second, differently blue band. The
       shadow is what separates it from white content, since a dark bar on white needs
       no border to be visible but does need to sit above the page. */
    background: var(--dark);
    box-shadow: 0 2px 14px rgba(13, 20, 48, .22);
    color: #fff;
    transition: transform .18s ease, opacity .18s ease;
}
.topbar.is-tucked {
    transform: translateY(-100%);
    opacity: 0;
    /* Out of reach of the pointer and of tab order while it is out of sight. */
    pointer-events: none;
    visibility: hidden;
}
@media (prefers-reduced-motion: reduce) { .topbar { transition: none; } }
/* The same wordmark the hero shows, at the height the name used to be set at, so
   scrolling past the hero hands the mark over rather than swapping it for a different
   rendering of the same word. */
.brand { display: flex; align-items: center; text-decoration: none; border-radius: 3px; }
.brand img { display: block; height: 1.05rem; width: auto; }
.byline { display: flex; align-items: center; gap: .4rem; font-size: .8rem; color: #A9B4CE; }
.byline img { display: block; opacity: .9; }

/* Which version of the docs you are reading. In the topbar because that bar follows
   the reader, so the answer is on screen anywhere below the hero; over the hero itself
   the footer still carries it. `margin-left:auto` pins it to the far right without a
   spacer element. */
.topbar .version {
    margin-left: auto;
    font-family: var(--mono); font-size: .68rem; letter-spacing: .06em;
    padding: .2rem .55rem; border-radius: 999px;
    color: var(--teal-lt); border: 1px solid rgba(127, 209, 224, .38);
    white-space: nowrap;
}
.footer-meta .version { font-family: var(--mono); }

/* ── Hero ───────────────────────────────────────────────────────────────── */

.hero {
    padding: 4.5rem 1.5rem 3.5rem;
    color: #fff;
    background:
        linear-gradient(180deg, rgba(13, 20, 48, .72), rgba(13, 20, 48, .88)),
        url("assets/hero-wave.jpg") center / cover no-repeat,
        var(--dark);
}
.hero-inner { max-width: 76rem; margin: 0 auto; }
/* The wordmark itself rather than the name set in Barlow: the same letters, but it is
   the asset `just logo` regenerates, so it cannot drift from the brand. Sized in rem
   with a vw term so it scales with the viewport the way the old text did. The
   width/height attributes in template.html are the intrinsic ratio, which reserves the
   right box before it loads: they are 341x57, and regenerating the wordmark at a
   different width means changing them too. */
.hero-mark { margin: 0; }
.hero-mark img { display: block; width: clamp(12rem, 30vw, 19rem); height: auto; }

/* Who makes this, set beside the wordmark as one lockup rather than under the pitch.
   It lives here as well as in the topbar because the topbar is tucked away for exactly
   as long as the hero is on screen, so without this line the first thing a reader sees
   would carry no attribution at all.

   `flex-end` puts it on the wordmark's bottom edge, and the small bottom padding lifts
   it onto the baseline of the letters themselves rather than the box below them. (It
   used to clear a trailing teal underscore, which the wordmark no longer carries; the
   padding still earns its place, because the exported SVG keeps a little space under
   the baseline.) It wraps to its own line when the two no longer fit side by side. */
.hero-title {
    display: flex; align-items: flex-end; flex-wrap: wrap;
    column-gap: 1.1rem; row-gap: .4rem;
}
.hero-by {
    display: flex; align-items: center; gap: .5rem;
    margin: 0; padding-bottom: .4rem; font-size: .82rem; color: #A9B4CE;
}
.hero-by a { display: inline-flex; align-items: center; border-radius: 3px; }
.hero-by img { display: block; opacity: .85; transition: opacity .15s ease; }
.hero-by a:hover img, .hero-by a:focus-visible img { opacity: 1; }
@media (prefers-reduced-motion: reduce) { .hero-by img { transition: none; } }
.hero-sub { margin: .5rem 0 0; font-size: 1.15rem; font-weight: 500; color: #D3DAEA; max-width: 46rem; }
.chips { display: flex; flex-wrap: wrap; gap: .5rem; margin: 1.5rem 0 0; padding: 0; list-style: none; }
.chips li {
    font-family: var(--mono); font-size: .68rem; letter-spacing: .09em;
    padding: .32rem .7rem; border-radius: 999px;
    color: var(--teal-lt); border: 1px solid rgba(127, 209, 224, .38);
}

/* ── Shell: sidebar + content ───────────────────────────────────────────── */

.shell {
    display: grid;
    grid-template-columns: 16rem minmax(0, 1fr);
    gap: 3rem;
    max-width: 76rem;
    margin: 0 auto;
    padding: 2.5rem 1.5rem 4rem;
}

.sidenav {
    position: sticky; top: 4.2rem; align-self: start;
    max-height: calc(100vh - 5.5rem); overflow-y: auto;
    font-size: .875rem;
}
.sidenav-h {
    margin: 0 0 .6rem; font-family: var(--mono); font-size: .68rem;
    letter-spacing: .12em; text-transform: uppercase; color: var(--muted);
}
.sidenav ul { list-style: none; margin: 0; padding: 0; }
.sidenav ul ul { margin: .15rem 0 .6rem .8rem; }
.sidenav li { margin: .28rem 0; }
.sidenav a { color: var(--muted); text-decoration: none; display: block; }
.sidenav > ul > li > a { font-weight: 600; color: var(--navy); }
.sidenav a:hover { color: var(--teal-text); text-decoration: underline; }

/* Only the chapter being read shows its sections: all six at once is a wall of
   thirty links that reads as a list rather than a map. The rules are gated on
   the `toc-collapsed` class the sidebar script adds, so a page whose script did
   not run keeps the full outline instead of losing five chapters' sections. */
.sidenav.toc-collapsed > ul > li > ul { display: none; }
.sidenav.toc-collapsed > ul > li.is-current > ul { display: block; }
.sidenav.toc-collapsed > ul > li.is-current > a { color: var(--teal-text); }

main { min-width: 0; }

/* ── Typography ─────────────────────────────────────────────────────────── */

h2, h3, h4 { color: var(--navy); line-height: 1.25; }
h2 {
    font-size: 1.95rem; font-weight: 800; letter-spacing: -.015em;
    margin: 3.4rem 0 1.1rem; padding-bottom: .5rem;
    border-bottom: 2px solid var(--rule);
}
main > section:first-child > h2, main > h2:first-child { margin-top: 0; }
h3 { font-size: 1.3rem; font-weight: 700; margin: 2.2rem 0 .7rem; }
h4 { font-size: 1.03rem; font-weight: 600; margin: 1.7rem 0 .5rem; }

p, ul, ol, .cb, .callout, figure, .table-scroll { margin: 0 0 1.15rem; }
main ul, main ol { padding-left: 1.3rem; }
main li { margin: .3rem 0; }
main li::marker { color: var(--teal); }
strong { color: var(--navy); font-weight: 600; }

a { color: var(--teal-text); }

/* Heading permalinks, added by build_site.py after pandoc. Visible on hover and
   on keyboard focus, never in the sidebar's copy of the heading text. */
.anchor {
    margin-left: .35rem; color: var(--teal); text-decoration: none;
    opacity: 0; transition: opacity .12s;
}
h2:hover .anchor, h3:hover .anchor, h4:hover .anchor, .anchor:focus-visible { opacity: 1; }
@media (prefers-reduced-motion: reduce) { .anchor { transition: none; } }

/* ── Inline code ────────────────────────────────────────────────────────── */

code {
    font-family: var(--mono); font-size: .86em;
    color: var(--navy); background: var(--mist);
    padding: .1em .32em; border-radius: 3px;
    overflow-wrap: break-word;
}
a code { color: inherit; }

/* ── Code blocks: three surfaces ────────────────────────────────────────── */

.cb { border-radius: 7px; overflow: hidden; position: relative; }
.cb pre {
    margin: 0; padding: 1rem 1.1rem;
    overflow-x: auto;
    font-family: var(--mono); font-size: .82rem; line-height: 1.55;
    tab-size: 4;
}
.cb pre code { background: none; color: inherit; padding: 0; font-size: inherit; }

/* A light card for the JSON a reader studies. Token colours follow
   assets/json-light.tmTheme so the site and the tmTheme cannot drift. */
.cb--card { background: var(--mist); color: var(--ink); }
.cb--card .dt                               { color: var(--navy); font-weight: 600; }  /* key */
.cb--card .st, .cb--card .ss, .cb--card .vs,
.cb--card .ch                               { color: #1F7A52; }                        /* string */
.cb--card .dv, .cb--card .fl, .cb--card .bn { color: #9A6317; }                        /* number */
.cb--card .kw, .cb--card .cn                { color: #6B49AC; }                        /* true/false/null */
.cb--card .fu, .cb--card .ot, .cb--card .op,
.cb--card .sc                               { color: var(--muted); }
.cb--card .co, .cb--card .cv, .cb--card .do,
.cb--card .an                               { color: var(--muted); font-style: italic; }
.cb--card .er, .cb--card .al, .cb--card .wa { color: #B03A5F; font-weight: 600; }

/* A dark terminal for what you type or watch scroll past. */
.cb--term { background: var(--dark); color: var(--codefg); }
.cb--term .kw, .cb--term .cf, .cb--term .im       { color: #8FA9F0; }
.cb--term .st, .cb--term .ss, .cb--term .vs,
.cb--term .ch                                     { color: #7FD1A3; }
.cb--term .dv, .cb--term .fl, .cb--term .bn       { color: #E8B25C; }
.cb--term .co, .cb--term .cv, .cb--term .do,
.cb--term .an                                     { color: #7C88A3; font-style: italic; }
.cb--term .fu, .cb--term .bu                      { color: #7FD6E8; }
.cb--term .at, .cb--term .va, .cb--term .dt       { color: var(--codefg); }
.cb--term .op, .cb--term .sc, .cb--term .pp       { color: #9AA6C2; }
.cb--term .er, .cb--term .al, .cb--term .wa       { color: #FF9AA8; font-weight: 600; }

/* Shell sessions rendered by codeblocks.lua. The prompt is unselectable so
   copying a command does not take the `$` with it. */
.cb--console .cb-prompt { color: var(--teal-lt); user-select: none; -webkit-user-select: none; }
.cb--console .cb-cmd    { color: #E8ECF5; }
.cb--console .cb-out    { color: #9AA6C2; }
.cb--console pre > code { display: block; white-space: pre; }
.cb--console span       { display: inline; }

/* What the command printed back. Muted, and labelled, because it always sits
   directly under the command that produced it and must not read as another
   thing to copy. */
.cb--output {
    background: var(--mist); color: var(--muted);
    border-left: 3px solid var(--rule);
    border-radius: 0 7px 7px 0;
}
.cb--output pre { padding-top: 1.75rem; }
.cb--output::before,
.cb[data-source]::before {
    position: absolute; top: .4rem; right: .8rem;
    font-family: var(--mono); font-size: .62rem; letter-spacing: .11em;
    text-transform: uppercase; color: var(--muted);
}
.cb--output::before { content: "output"; }
.cb[data-source]::before { content: "source: " attr(data-source); }
.cb--term[data-source]::before { color: #8C97B0; }

/* ── Callout ────────────────────────────────────────────────────────────── */

.callout {
    background: var(--mist);
    border-left: 3px solid var(--teal);
    border-radius: 0 7px 7px 0;
    padding: .9rem 1.1rem;
}
.callout > :last-child { margin-bottom: 0; }
.callout strong:first-child { color: var(--teal-text); }

/* ── The setup guide's URL form ──────────────────────────────────────────── */

/* Paste two URLs, fill in every command on the page. site.js replaces the div's
   contents with the form and adds `is-live`, so the by-hand instructions in the
   content are what a reader without JavaScript sees, and the form styling below
   never applies to them. */
.setup-form {
    background: var(--mist);
    border-left: 3px solid var(--teal);
    border-radius: 0 7px 7px 0;
    padding: .9rem 1.1rem;
}
.setup-form > :last-child { margin-bottom: 0; }
.setup-form label {
    display: block; margin-bottom: .7rem;
    font-size: .82rem; font-weight: 600; color: var(--navy);
}
.setup-form input {
    display: block; width: 100%; margin-top: .25rem;
    padding: .5rem .6rem;
    font-family: var(--mono); font-size: .82rem; color: var(--ink);
    background: #fff; border: 1px solid var(--rule); border-radius: 6px;
}
.setup-form input:focus-visible {
    outline: 2px solid var(--teal-text); outline-offset: 1px; border-color: var(--teal);
}
/* A URL in the wrong box, or one that is not a Dokumentli archive at all. The border
   is the bright pink from the invoice palette because a border is decoration; the
   message beside it is the darkened sibling, since it is small text. */
.setup-form input.is-invalid { border-color: var(--c-taxrate); }
.setup-form-error:empty { display: none; }
.setup-form-error {
    margin: -.4rem 0 .7rem; font-size: .78rem; color: var(--warn-text);
}
/* The placeholder is an example URL, so it must not read as a filled-in field. */
.setup-form input::placeholder { color: var(--muted); opacity: .65; }
.setup-form-status:empty { display: none; }
.setup-form-status {
    margin: 0 0 .5rem; font-size: .82rem; font-weight: 600; color: var(--teal-text);
}
.setup-form-note { margin: 0; font-size: .78rem; color: var(--muted); }
.setup-form-clear {
    font: inherit; color: var(--teal-text); background: none;
    border: 0; padding: 0; text-decoration: underline; cursor: pointer;
}

/* ── Tables ─────────────────────────────────────────────────────────────── */

/* tables.lua wraps every table in this, so a wide reference table scrolls inside
   its own box instead of widening the page. */
.table-scroll { overflow-x: auto; }
table { border-collapse: collapse; width: 100%; font-size: .9rem; }
th, td { text-align: left; padding: .5rem .75rem; border-bottom: 1px solid var(--rule); vertical-align: top; }
thead th { background: var(--mist); color: var(--navy); font-weight: 600; white-space: nowrap; }
td code { white-space: nowrap; }

/* ── Figures ────────────────────────────────────────────────────────────── */

figure { margin: 1.6rem 0; }
img { max-width: 100%; height: auto; }
figure.diagram svg { width: 100%; height: auto; display: block; }
figcaption {
    margin-top: .6rem; font-size: .82rem; color: var(--muted); text-align: center;
}

/* A diagram that does not need the whole column. `figure.diagram svg` above is
   width:100%, which is right for a diagram carrying the weight of a section and too
   much for one summarising a paragraph: at the full 918px content width, exchange.svg
   is over 500px tall and reads as an illustration rather than a figure. The cap
   scales the labels down with it, so it is a floor as much as a ceiling: much below
   38rem and the small print inside stops being readable. Opt in per include,
   `::: {.include .compact file="..."}`. */
figure.diagram.compact { max-width: 38rem; margin-inline: auto; }

/* The annotated document in the Tutorial ships as a raster image rather than as an
   SVG overlay (assets/example-letter-annotated.jpeg), so its outlines and badge
   digits are baked in and take no colour from here. Capped in width so those digits
   stay legible rather than the page becoming one tall scan. The cap sits on the
   image because a Markdown image carries its attributes on the <img>, not on the
   <figure> pandoc wraps it in. */
img.invoice-map { display: block; max-width: 34rem; margin-inline: auto; }

/* ── Footer ─────────────────────────────────────────────────────────────── */

.sitefooter { background: var(--navy); color: #C6CEE2; padding: 3rem 1.5rem; margin-top: 4rem; }
.sitefooter-inner { max-width: 76rem; margin: 0 auto; }
.sitefooter p { margin: 0 0 .7rem; max-width: 46rem; }
.footer-lead { font-size: 1.4rem; font-weight: 700; color: #fff; }
.sitefooter a { color: var(--teal-lt); }
.footer-meta { font-size: .85rem; color: #93A0BE; }

/* ── Narrow screens ─────────────────────────────────────────────────────── */

@media (max-width: 60rem) {
    .shell { grid-template-columns: minmax(0, 1fr); gap: 0; }
    .sidenav {
        position: static; max-height: none; overflow: visible;
        margin-bottom: 2.5rem; padding-bottom: 1.5rem;
        border-bottom: 1px solid var(--rule);
    }
    .hero { padding: 3rem 1.5rem 2.5rem; }
    h2 { font-size: 1.6rem; margin-top: 2.6rem; }
    .anchor { opacity: 1; }   /* no hover on touch */
}

/* ── Print ──────────────────────────────────────────────────────────────── */

@media print {
    .topbar, .sidenav, .skiplink, .chips, .anchor { display: none; }
    .setup-form.is-live { display: none; }
    .hero, .sitefooter { background: none; color: #000; padding: 0; }
    .hero-mark, .hero-sub, .footer-lead { color: #000; }
    .shell { display: block; max-width: none; padding: 0; }
    .cb, .callout, figure, .table-scroll { break-inside: avoid; }
    .cb--term { background: #fff; color: #000; border: 1px solid #999; }
    .cb--term span { color: #000 !important; }
}
