/* loglog base styles */
*, *::before, *::after { box-sizing: border-box; }
/* The header background is shared: the wordmark knocks its text out of the same
   forest backdrop so the header shows through the letterforms.
   A cartoonish forest canopy: deep pine at the top blending into a mossy green,
   with a warm patch of dappled sunlight breaking through the leaves. */
:root {
    /* A dark scrim over the canopy lifts contrast for the white nav text. */
    --header-gradient:
        linear-gradient(rgba(11, 31, 20, 0.35), rgba(11, 31, 20, 0.35)),
        radial-gradient(circle at 80% 0%, rgba(255, 224, 130, 0.55), rgba(255, 224, 130, 0) 55%),
        linear-gradient(200deg, rgba(59, 130, 71, 0.55), rgba(59, 130, 71, 0) 60%),
        linear-gradient(160deg, rgba(23, 87, 51, 1) 0%, rgba(15, 61, 38, 1) 55%, rgba(10, 46, 30, 1) 100%);
    /* A faint scatter of leaf silhouettes tiled across the canopy, for a little
       cartoonish foliage texture without competing with the nav text. */
    --header-foliage:
        url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120' viewBox='0 0 120 120'%3E%3Cg fill='%23ffffff' fill-opacity='0.05'%3E%3Cpath d='M20 20c14 0 24 10 24 24-14 0-24-10-24-24z'/%3E%3Cpath d='M85 55c14 0 24 10 24 24-14 0-24-10-24-24z'/%3E%3Cpath d='M45 85c14 0 24 10 24 24-14 0-24-10-24-24z'/%3E%3C/g%3E%3C/svg%3E");

    /* Woodland accent palette: bark brown replaces the old navy as the primary
       action colour, with a leaf green and amber/mushroom for secondary accents. */
    --bark: #6b4423;
    --bark-dark: #563519;
    --leaf: #2f8a4e;
    --leaf-dark: #24703d;
    --sunlight: #e0c96b;

    /* One content measure for the whole page: the header nav, the tab strip and
       main all align to the same column, while full-bleed backgrounds (the
       header canopy) keep spanning the viewport.
       The measure is fluid: it tracks the viewport up to a wide ceiling, so on
       large displays the tables get the extra room instead of leaving it as
       empty margin. */
    --content-width: clamp(20rem, 70vw, 1800px);
    --content-gutter: 1rem;

    /* Flat cartoon log palette, matching a single reference illustration: a
       mid-brown barrel, a darker shading band and bark marks, and a light
       tan/peach cut end with concentric rings. */
    --log-base: #a15c3a;
    --log-shadow: #8a4a2e;
    --log-mark: #7a3b22;
    --log-outline: #6e3a20;
    --log-cap-outer: #d9a876;
    --log-cap-mid: #f0c9a0;
    --log-cap-inner: #f8dfc2;

    /* Nunito carries the UI chrome and prose; IBM Plex Mono is reserved for
       tabular and form data, where the fixed advance keeps figures aligned. */
    --font-sans: "Nunito", system-ui, -apple-system, "Segoe UI", sans-serif;
    --font-mono: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, monospace;

    /* The page surface is a soft translucent cream rather than plain white, so the
       forest canopy reads through it faintly. --paper-solid is the same colour
       composited over white, for surfaces that sit on a dark backdrop and so
       cannot show the browser's white default through them. */
    --paper: #fcfbd67d;
    --paper-solid: #fdfdea;

    /* A hair of roundedness on tabs and form controls: enough to soften the
       corners, not enough to read as a pill. */
    --radius-sm: 3px;

    /* Row shading in two families so the eye can tell structure from arithmetic:
       subtotal rows (a weekday's total, a user's total) take a warm sand and
       grand totals a deeper amber drawn from the sunlight patch. Both are soft
       enough to sit under the cream page surface. */
    --row-subtotal: #f6f1dc;
    --row-subtotal-border: #e2d9b4;
    --row-total: #f4e8c0;

    /* Links take the bark brown so that a link and a link-styled button read
       alike (REQ 49.2), and a visited link shifts to a muted berry: a clear
       sibling of the brown rather than the default violet, which fights the
       warm palette. Both clear 7:1 on the cream page surface. */
    --link: var(--bark);
    --link-hover: var(--bark-dark);
    --link-visited: #7a3f57;

    /* A destructive action takes a warning red, deep enough to clear 7:1 on the cream
       page surface, with pale tints for the outlined variant's fill. The red is warmed
       slightly towards the bark palette so it reads as part of this page rather than as
       a browser alert. */
    --destructive: #a3231f;
    --destructive-dark: #7f1a17;
    --destructive-bg: #fdf0ee;
    --destructive-bg-hover: #f8dedb;
}
body { font-family: var(--font-sans); margin: 0; padding: 0; line-height: 1.6; background: var(--paper); }
/* Headings share the sans face at heavier weights and a tighter leading, so they
   read as titles rather than as a different voice. */
h1, h2, h3, h4, h5, h6 {
    font-family: var(--font-sans);
    font-weight: 800;
    line-height: 1.25;
    letter-spacing: -0.01em;
}
header {
    /* The header uses Nunito while tables and forms stay monospaced. */
    font-family: var(--font-sans);
    background: var(--header-gradient), var(--header-foliage);
    background-repeat: no-repeat, repeat;
    /* Fixed attachment so the wordmark's knocked-out letters sample the exact
       same gradient pixels as the header behind it. */
    background-attachment: fixed, scroll;
    color: #fff;
    /* No inline padding here: the inner navs carry the shared content measure so
       the background still runs edge to edge. */
    padding: 1rem 0 0;
    position: relative;
}
/* Header content aligns to the same column as main. */
header > nav {
    max-width: var(--content-width);
    margin: 0 auto;
    padding: 0 var(--content-gutter);
}
/* A row of cartoonish pine silhouettes along the bottom edge of the header,
   like a distant treeline at the far side of the canopy. The tabs sit over it.
   Purely decorative, so it never intercepts clicks and never affects layout. */
header::after {
    content: "";
    display: block;
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    height: 22px;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='40' viewBox='0 0 160 40'%3E%3Cg fill='%230b1f14'%3E%3Cpath d='M20 4 32 22 26 22 36 34 4 34 14 22 8 22Z'/%3E%3Cpath d='M60 0 74 20 67 20 79 34 41 34 53 20 46 20Z'/%3E%3Cpath d='M100 6 112 22 106 22 116 34 84 34 94 22 88 22Z'/%3E%3Cpath d='M140 2 153 21 146 21 157 34 123 34 134 21 127 21Z'/%3E%3C/g%3E%3C/svg%3E");
    background-repeat: repeat-x;
    background-size: 160px 22px;
    background-position: bottom left;
    pointer-events: none;
    z-index: 0;
}
header nav a { color: #fff; text-decoration: none; margin-right: 1rem; font-weight: 600; }
/* Nav labels sit on a variable gradient, so a soft dark shadow keeps them legible
   over the lighter areas. */
header nav.nav-primary a:not(.brand) { text-shadow: 0 1px 2px rgba(0, 0, 0, 0.45); }
header nav.nav-primary { display: flex; align-items: center; flex-wrap: wrap; padding-bottom: 2.5rem; position: relative; z-index: 1; }
/* The brand link is a two-tone national-forest-style trail sign: a pale top
   band carrying the wordmark in caps, and a bark-brown bottom band carrying a
   small script tagline underneath, like "National Forest" under a park name.
   A dark outline and a slight tilt/offset shadow sell the routed-wood-sign feel. */
header nav.nav-primary .brand {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    margin-right: 2rem;
    border: 3px solid #241609;
    border-radius: 6px 18px 6px 6px;
    overflow: hidden;
    transform: rotate(-1.5deg);
    box-shadow: 3px 4px 0 rgba(0, 0, 0, 0.35);
}
header nav.nav-primary .brand .sign-top {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.35rem;
    padding: 0.3rem 0.9rem 0.2rem;
    background: #f0e8d4;
}
/* The little pine-tree mark beside the wordmark, filled solid leaf green so it
   reads clearly on the light plate. */
header nav.nav-primary .brand .brand-icon {
    width: 1.1rem;
    height: 1.1rem;
    fill: var(--leaf-dark);
    flex-shrink: 0;
}
/* The wordmark reads like routed caps lettering: a bark-brown fill with a
   subtle deboss so it looks carved into the pale top band. */
header nav.nav-primary .brand .wordmark {
    font-family: "Baloo 2", var(--font-sans);
    font-size: 1.2rem;
    font-weight: 800;
    letter-spacing: 0.06em;
    color: var(--bark-dark);
    text-shadow: 0 1px 0 rgba(255, 255, 255, 0.6);
}
/* The tagline sits in the lower, darker band, set in a script face the way a
   forest sign carries its subtitle in a looser hand than the park name above. */
header nav.nav-primary .brand .sign-bottom {
    padding: 0.05rem 0.9rem 0.2rem;
    background: var(--bark-dark);
    color: #f0e8d4;
    font-family: "Baloo 2", var(--font-sans);
    font-style: italic;
    font-weight: 500;
    font-size: 0.7rem;
    letter-spacing: 0.02em;
    text-align: center;
    white-space: nowrap;
}
header nav.nav-primary .brand:hover { text-decoration: none; transform: rotate(-1.5deg) translateY(-1px); }
/* The account menu is pushed to the far right of the primary nav. */
header nav.nav-primary .account { margin-left: auto; }
header nav.nav-primary a.active { text-decoration: underline; text-underline-offset: 4px; }
/* Tabs stack above the header treeline. */
header nav.nav-tabs { display: flex; flex-wrap: wrap; gap: 0.25rem; position: relative; z-index: 1; }
/* Inactive tabs use a dark translucent fill rather than a light one, so the white
   label keeps contrast wherever the gradient is bright. */
/* Tabs meet the page along their bottom edge, so only the top corners are rounded. */
header nav.nav-tabs a { margin-right: 0; padding: 0.5rem 1.1rem; border-radius: var(--radius-sm) var(--radius-sm) 0 0; background: rgba(11, 31, 20, 0.4); font-weight: 600; text-shadow: 0 1px 2px rgba(0, 0, 0, 0.45); }
header nav.nav-tabs a:hover { background: rgba(11, 31, 20, 0.55); }
/* The active tab reads as the page surface pulled up into the header, so it takes
   the composited paper colour: over the dark canopy the translucent value alone
   would darken and lose the dark label. */
header nav.nav-tabs a.active { background: var(--paper-solid); color: #14361f; font-weight: 700; text-shadow: none; }
main { max-width: var(--content-width); margin: 2rem auto; padding: 0 var(--content-gutter); min-width: 0; }
main a { color: var(--link); text-decoration-thickness: from-font; text-underline-offset: 2px; }
main a:visited { color: var(--link-visited); }
main a:hover { color: var(--link-hover); }
main a:focus-visible { outline: 2px solid var(--link); outline-offset: 2px; }
/* Secondary metadata links stay quiet, visited or not. */
main a.meta, main a.meta:visited { color: #666; }
label { display: block; margin-top: 1rem; font-weight: 500; }
/* Form controls carry data, so they stay monospaced even though the surrounding
   page is set in the sans face. Labels stay sans. */
input, select, textarea, button { font-family: var(--font-mono); }
input, select, textarea { display: block; width: 100%; padding: 0.5rem; margin-top: 0.25rem; border: 1px solid #ccc; border-radius: var(--radius-sm); }
/* Chromium draws a select's caret hard against the right border, which reads as cramped
   against the control's rounded corner. The caret is drawn here instead, inset from the
   edge, which also gives every browser the same control rather than each one's own
   native arrow. The background colour is restored explicitly: suppressing the native
   appearance takes the control's default background with it, and the field would
   otherwise let the page's cream show through where the text inputs beside it are
   white. */
select {
    -webkit-appearance: none;
    appearance: none;
    padding-right: 2rem;
    background-color: #fff;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%236b4423' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 0.7rem center;
    background-size: 0.625rem 0.375rem;
}
input[type="checkbox"], input[type="radio"] { width: auto; accent-color: var(--bark); }
/* An ordinary button is quiet: an outlined, cream-filled control. Only .primary
   takes the log illustration, so the log reads as the one action that matters on
   a form rather than as generic button chrome. A link that acts as a control
   (a.button) borrows the same quiet treatment, so a navigation action can sit
   beside a form action and read alike. */
/* A button keeps the browser's own control font unless told otherwise, so both the
   family and the size are set here for a button and for a link acting as one.
   Otherwise the two sit side by side at different sizes.
   REQ 49.2: Actions that sit side by side read alike. */
button, a.button {
    font-family: var(--font-mono);
    font-size: 1rem;
    line-height: 1.2;
    margin-top: 1.5rem;
    padding: 0.45em 1.1em;
    border: 1px solid var(--bark);
    border-radius: var(--radius-sm);
    background: var(--paper-solid);
    color: var(--bark-dark);
    font-weight: 600;
    cursor: pointer;
}
button:hover, main a.button:hover { background: #f2ebd4; border-color: var(--bark-dark); }
button:focus-visible, main a.button:focus-visible { outline: 2px solid var(--bark); outline-offset: 2px; }
/* A link needs the box behaviour a button has by default, and it keeps the button
   label colour whether or not it has been visited. */
main a.button, main a.button:visited, main a.button:hover {
    display: inline-block;
    color: var(--bark-dark);
    text-decoration: none;
}
a.button { margin-top: 0; }
/* A row of actions sits with a gap between them rather than the single space that
   separates prose links, and a plain link beside a button lines up on its centre. */
/* A row of actions sits with a gap between them, whether each is a link or a
   single-button form. A row holding a form is a div rather than a p, since a form is
   not phrasing content.
   REQ 49.4: A page-level action reads as a button. */
.actions { display: flex; flex-wrap: wrap; align-items: center; gap: 0.75rem; }
/* A button in a row of actions lines up with the links beside it, rather than carrying
   the space a button leaves above itself in a form. That holds however it sits in the
   row: on its own, in a single-button form, or beside the hint that explains why it is
   disabled. */
.actions button { margin-top: 0; }
/* A second row of actions, such as the actions that add to a record beneath those on
   the record itself, sits a gap below the first rather than touching it. */
.actions + .actions { margin-top: 0.75rem; }

/* A destructive action keeps the quiet outlined shape but takes the warning red, so
   that it reads as the one action in a row that cannot be undone. The fill stays pale
   until hover: a red slab would shout louder than the action deserves next to the
   ordinary actions it sits with.
   REQ 49.5: An irreversible action is set apart from the actions beside it by colour
   alone, keeping the shape and size they share.
   REQ 49.2: Actions that sit side by side read alike. */
button.destructive, main a.button.destructive, main a.button.destructive:visited {
    border-color: var(--destructive);
    color: var(--destructive);
    background: var(--destructive-bg);
}
button.destructive:hover, main a.button.destructive:hover {
    border-color: var(--destructive-dark);
    color: var(--destructive-dark);
    background: var(--destructive-bg-hover);
}
button.destructive:focus-visible, main a.button.destructive:focus-visible {
    outline-color: var(--destructive);
}

button.primary {
    position: relative;
    padding: 0.6em 2.6em 0.6em 1.4em;
    border: 2px solid var(--log-outline);
    border-radius: 999px;
    color: #fff6ea;
    font-weight: 700;
    /* A flat cartoon log: a solid barrel colour, a darker shading crescent along
       the bottom-left the way a cylinder shades away from the light, and short
       scattered bark marks rather than a smooth wood-grain gradient. */
    background:
        linear-gradient(90deg, var(--log-mark) 0 55%, transparent 55% 100%) 8% 24% / 22px 4px repeat-x,
        linear-gradient(90deg, var(--log-mark) 0 45%, transparent 45% 100%) 32% 52% / 30px 4px repeat-x,
        linear-gradient(90deg, var(--log-mark) 0 50%, transparent 50% 100%) 4% 78% / 20px 4px repeat-x,
        radial-gradient(ellipse 90% 75% at 12% 105%, rgba(0, 0, 0, 0.32), transparent 70%),
        var(--log-base);
    box-shadow: 0 2px 0 rgba(0, 0, 0, 0.25);
}
/* A short broken branch stub pokes up from the top of the log, the way the
   reference illustration has one knot sprouting near the top-left. */
button.primary::before {
    content: "";
    position: absolute;
    top: -0.5em;
    left: 1.1em;
    width: 0.55em;
    height: 0.9em;
    background: var(--log-cap-outer);
    border: 2px solid var(--log-outline);
    border-radius: 999px;
    transform: rotate(18deg);
}
/* The sawn-off end of the log: a single cut face on the right, with flat
   concentric rings rather than a repeating pattern, matching the reference. */
button.primary::after {
    content: "";
    position: absolute;
    top: 50%;
    right: 0.15em;
    width: 1.7em;
    height: 1.7em;
    transform: translateY(-50%);
    border-radius: 50%;
    border: 2px solid var(--log-outline);
    background: radial-gradient(
        circle,
        var(--log-cap-inner) 0 52%,
        var(--log-cap-mid) 52% 78%,
        var(--log-cap-outer) 78% 100%
    );
}
button.primary:hover {
    border-color: var(--log-outline);
    background:
        linear-gradient(90deg, var(--log-mark) 0 55%, transparent 55% 100%) 8% 24% / 22px 4px repeat-x,
        linear-gradient(90deg, var(--log-mark) 0 45%, transparent 45% 100%) 32% 52% / 30px 4px repeat-x,
        linear-gradient(90deg, var(--log-mark) 0 50%, transparent 50% 100%) 4% 78% / 20px 4px repeat-x,
        radial-gradient(ellipse 90% 75% at 12% 105%, rgba(0, 0, 0, 0.4), transparent 70%),
        var(--log-shadow);
}
/* A wide listing is scrolled sideways within its own box rather than widening the
   page, so a phone keeps the header, the tabs and the surrounding prose at the
   viewport width and only the table moves. */
.table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; overscroll-behavior-x: contain; }
table { width: 100%; border-collapse: collapse; margin-top: 1rem; font-family: var(--font-mono); }
th, td { text-align: left; padding: 0.5rem; border-bottom: 1px solid #eee; }
/* Header cells carry no fill: an underline is enough to set them off. */
th { background: none; }
thead th { text-decoration: underline; }

form.inline { display: inline; }
form.inline button { margin-top: 0; }

/* Row actions read alike whether they are links or single-button forms, and sit
   side by side rather than stacking. The cell stays a table cell, so the column
   still lines up: the actions flow inline within it.
   REQ 49.2: Row actions within a listing read alike and sit side by side. */
td.row-actions { white-space: nowrap; }
td.row-actions > * { margin-right: 0.75rem; }
td.row-actions > *:last-child { margin-right: 0; }
button.link {
    margin: 0;
    padding: 0;
    background: none;
    border: none;
    box-shadow: none;
    text-shadow: none;
    color: var(--link);
    font: inherit;
    text-decoration: underline;
    cursor: pointer;
}
button.link:hover { background: none; color: var(--link-hover); }
button.link.destructive { color: var(--destructive); }
button.link.destructive:hover { color: var(--destructive-dark); }
/* An action that cannot be taken keeps its place in the row, greyed and inert, with
   its reason in the hint beside it. It reads as the same word in the same column as
   the action it stands in for, so the rows still line up. */
button.link:disabled { color: #999; cursor: not-allowed; }
button.link.destructive:disabled { color: #999; }
/* An outlined button that cannot be used is dimmed and does not respond to the pointer.
   It keeps its own colours, so a disabled destructive action still reads as the
   destructive one, and hovering it changes nothing, since there is nothing to press.
   The selectors carry the hover state so they outrank the hover rules above.
   REQ 49.8: An action that cannot be taken keeps its place, disabled. */
button:not(.link, .primary):disabled,
button:not(.link, .primary):disabled:hover {
    opacity: 0.45;
    cursor: not-allowed;
    border-color: var(--bark);
    color: var(--bark-dark);
    background: var(--paper-solid);
}
button.destructive:not(.link):disabled,
button.destructive:not(.link):disabled:hover {
    border-color: var(--destructive);
    color: var(--destructive);
    background: var(--destructive-bg);
}
.action-disabled { display: inline-flex; align-items: center; white-space: nowrap; }
.action-disabled .hint-trigger { margin-left: 0.15rem; }
/* A flash message carries the weight of its news: a confirmation in green, a refusal
   in the same amber used for cautions elsewhere. The base rule holds the shape so the
   two differ only in colour. */
.flash { padding: 0.75rem 1rem; border-radius: 4px; border: 1px solid; }
.flash-success { background: #e7f5e7; border-color: #b6dfb6; }
.flash-warning { background: #fbe8c6; border-color: #e2bd75; color: #7a4e00; }
.error { color: #b00020; font-size: 0.9rem; }
.empty { color: #666; font-style: italic; }
.total { font-weight: 600; margin-top: 1rem; }
.badge { display: inline-block; padding: 0.1rem 0.5rem; border-radius: 3px; font-size: 0.75rem; font-weight: 600; }
/* A badge qualifying a heading sits on the middle of the text rather than its baseline,
   which a small box beside large display type would otherwise ride low against. */
/* A badge qualifying a heading sits on the middle of the text rather than its baseline,
   which a small box beside large display type would otherwise ride low against. Its size
   is fixed in rem, so beside a heading it would stay at body scale and read as a speck;
   here it takes a step up, still well short of the heading itself. */
h1 .badge {
    vertical-align: middle;
    margin-left: 0.5rem;
    padding: 0.15rem 0.6rem;
    font-family: var(--font-sans);
    font-size: 1rem;
}
/* An unsigned contract is a fact, not a problem, so it reads as neutral gray.
   Expiry is a caution in yellow and overage the severe signal in red. */
.badge-unsigned { background: #eef0f2; color: #444; }
/* An expired contract is a caution, so it takes the mushroom/amber warning treatment. */
.badge-expired { background: #fbe8c6; color: #7a4e00; }
.badge-warning { background: #fdecea; color: #b00020; }
/* The attachments indicator is a mark, not a state: it carries a paperclip and no
   visible words, so it reads as quietly as possible and keeps only the badge's shape.
   REQ 54.33. */
.badge-attachments { background: transparent; padding: 0.15rem 0.2rem; }
/* Text that exists for assistive technology only: removed from view without being
   removed from the accessibility tree, which display:none would do. */
.visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
    border: 0;
}
.badge-status-draft { background: #eef0f2; color: #444; }
.badge-status-sent { background: #e3f3e6; color: var(--leaf-dark); }
.badge-status-void { background: #fdecea; color: #b00020; }
/* Voided invoice rows are de-emphasized when shown, while the status badge stays
   fully legible. */
tr.voided { opacity: 0.55; }
tr.voided .badge { opacity: 1; }
/* A closed contract or task reads as set aside: the row is dimmed as a voided invoice
   row is, while its Closed badge stays fully legible so the reason is readable.
   REQ 56.16. */
tr.closed { opacity: 0.55; }
tr.closed .badge { opacity: 1; }
.badge-closed { background: #eef0f2; color: #444; }
table.line-items caption { text-align: left; font-weight: 600; padding: 0.5rem 0; }
/* Entry notes read below the title they describe, wherever notes are shown: the
   task-level invoice layouts, the detailed invoice layout, and the time entry
   tables. REQ 9.26. Free text keeps the line breaks its author typed. */
.entry-notes { color: #444; margin-top: 0.25rem; white-space: pre-wrap; }
.cap-note { display: block; margin-top: 0.25rem; }
/* The name of the rate a line was billed at, under the task title in the task-level
   invoice layout. It carries a heavier weight than the entry notes beneath it, since it
   qualifies the task title rather than describing the work. REQ 9.37. */
.rate-name { display: block; color: #444; margin-top: 0.25rem; font-weight: 600; }
/* A disclosure whose summary reads as a plain text link, used to keep secondary
   forms collapsed until requested. */
details.disclosure { margin-top: 1.5rem; }
details.disclosure > summary {
    display: inline-block;
    cursor: pointer;
    color: var(--bark);
    font-size: 0.9rem;
    font-weight: 500;
    text-decoration: underline;
    list-style: none;
}
details.disclosure > summary::-webkit-details-marker { display: none; }
details.disclosure > summary:hover { color: var(--bark-dark); }

/* Task metadata sits on its own line beneath the task title, inside the bullet. */
.task-meta { color: #444; font-size: 0.9rem; margin-top: 0.15rem; }
.task-meta form.inline button { margin-top: 0; padding: 0.15rem 0.6rem; font-size: 0.85rem; }

/* Layout switcher: the active invoice layout is emphasized, the other is a link. */
.layout-switcher { color: #666; font-size: 0.875rem; margin-bottom: 0.5rem; }
/* What a contract's credit stands at, stated inside the invoice between the credit it
   applied and the amount due: it explains that line, so it reads quieter than the
   figures around it and its row carries no shading of its own.
   REQ 39.9: A suggested invoice states the contract's credit balance.
   REQ 57.4: The balance is the credit received less the credit already applied. */
tr.credit-note-row td { padding-top: 0; }
.credit-note { color: #666; font-style: italic; }
.layout-switcher strong { color: #222; }
/* The client's configured layout carries a note naming it as that client's default,
   which qualifies the layout beside it rather than competing with it.
   REQ 9.39: The switcher annotates the layout configured for the client. */
.layout-switcher .client-default { color: #888; font-style: italic; }
/* A grand total row closes a table, so it is shaded across every cell and set off
   by a rule above it; subtotals use the lighter sand. */
tr.total-row th, tr.total-row td { background: var(--row-total); font-weight: 700; border-top: 2px solid var(--sunlight); }
/* A subtotal row is shaded across every cell, not just its heading cells, and a
   rule above it separates it from the group before. A subtotal that also heads its
   group (a weekday in the time entry list) carries no underline. */
tr.subtotal-row th, tr.subtotal-row td { background: var(--row-subtotal); font-weight: 600; border-top: 2px solid var(--row-subtotal-border); }
tr.subtotal-row th { text-decoration: none; }
/* Sub-view navigation within one record: a contract, its time entries and its
   members and rates.
   It is deliberately quieter than the header's nav-tabs: those switch between the
   sections of the application, this only moves within the record already open, so it
   reads as a row of links with the current one marked rather than as a second set of
   tabs competing with the first.

   REQ 49.6: A record's detail view and its subviews offer navigation to each other,
   presented identically on each of them, with the current view marked rather than
   linked. */
nav.subnav {
    display: flex;
    flex-wrap: wrap;
    gap: 1.25rem;
    margin: 0 0 1.5rem;
    padding-bottom: 0.4rem;
    border-bottom: 1px solid var(--row-subtotal-border);
    font-size: 0.95rem;
}
/* The current view is not a link, so it takes the emphasis a link would take from
   colour instead, and an underline marks it against the rule below. */
nav.subnav .active {
    font-weight: 700;
    color: var(--bark-dark);
    box-shadow: inset 0 -2px 0 0 var(--bark);
}

fieldset { margin-top: 1.5rem; }
small { color: #666; }

/* Filter bar: three controls side by side, stacking on narrow screens.
   REQ 18.11: The client, contract and user filters sit in a single row of three
   controls, stacking into one column on narrow viewports. */
/* A collapsed filter set keeps its own margin, so the form inside does not add a
   second one. The caret alone says the region expands, so the summary carries no
   fill or border. */
details.filters-disclosure { margin-top: 1rem; }
details.filters-disclosure > form.filters { margin-top: 0.5rem; }
details.filters-disclosure > summary {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    color: #333;
    text-decoration: none;
}
details.filters-disclosure > summary::before {
    content: "\25b8";
    font-size: 0.95rem;
    color: #666;
    transition: transform 120ms ease-in-out;
}
details.filters-disclosure[open] > summary::before { transform: rotate(90deg); }
details.filters-disclosure > summary:hover { color: var(--bark-dark); }
details.filters-disclosure > summary:focus-visible { outline: 2px solid var(--bark); outline-offset: 2px; }
form.filters { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 0 1rem; align-items: end; margin-top: 1rem; }
form.filters .field label { margin-top: 0; }
/* A boolean filter reads as its label with the box beside it, on the baseline of the
   fields it sits among rather than above them like a select's label. REQ 56.13. */
form.filters .field-check label { display: flex; align-items: center; gap: 0.4rem; font-weight: 400; }
form.filters .field-check input { margin: 0; }
/* A field that starts a row of its own, such as the From date of a time entry range,
   which follows the user and contract filters. REQ 18.19. The row's other fields align
   to its foot, so the margin spaces the whole row. */
form.filters .field-break { grid-column: 1; margin-top: 1rem; }
form.filters .filter-actions { display: flex; align-items: center; gap: 1rem; grid-column: 1 / -1; }
form.filters .filter-actions button { margin-top: 1rem; }
@media (max-width: 640px) {
    form.filters { grid-template-columns: minmax(0, 1fr); gap: 0.75rem; }
    form.filters .field-break { margin-top: 0; }
}

/* A short form whose fields read across the line rather than as a stack, for the two
   or three controls that make up one decision. Each field keeps its label above its
   control, and the row wraps to a stack when there is no width for it. */
form.fields-row { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 0 1rem; }
form.fields-row .field { flex: 1 1 14rem; }
form.fields-row .field label { margin-top: 1rem; }
form.fields-row button { margin-top: 1rem; flex: 0 0 auto; }

/* A locked field group states that its value does not apply to the selected
   contract type.
   REQ 3.14: An unbillable contract has no hourly rate, no hour cap and no signed
   status. */
.field.locked { opacity: 0.55; }
.field.locked input { background: #f0f0f0; cursor: not-allowed; }

/* Time entry list: the contract name sits above the task title as secondary text. */
a.meta, .meta { display: block; color: #666; font-size: 0.8rem; }

/* Contract notes are free text, so line breaks in them are kept. */
p.notes { white-space: pre-wrap; color: #333; }

/* Inline hint: a small info icon paired with a popover (see partials/hint.tmpl).
   The trigger's footprint matches the icon, so what is hoverable is what is
   clickable, and it sits on the text baseline beside the label it explains. */
.hint-trigger {
    display: inline-flex;
    margin: 0 0 0 0.25rem;
    padding: 0;
    border: 0;
    border-radius: 0;
    background: none;
    box-shadow: none;
    text-shadow: none;
    color: #666;
    font-weight: normal;
    line-height: 0;
    vertical-align: middle;
    cursor: pointer;
}
.hint-trigger:hover, .hint-trigger:focus-visible { background: none; color: var(--bark); }

.hint-popover {
    /* A popover carries inset: 0 and margin: auto from the browser, which centres
       it; clearing both hands placement back to this rule. The anchor() insets pin
       the hint under the start edge of its own trigger, which the trigger names in
       an inline style so that hints on one page do not collide. A browser without
       anchor positioning ignores them and, with the insets auto, a fixed element
       sits where it would have sat in flow, which is beside the trigger. */
    position: fixed;
    inset: auto;
    inset-block-start: anchor(end);
    inset-inline-start: anchor(start);
    position-try-fallbacks: flip-block, flip-inline;
    margin: 0.25rem 0 0;
    /* The browser's popover rules set width and height to fit-content with
       overflow: auto, which on a narrow anchor leaves the text in a scrolling box
       rather than letting the hint grow. The box is sized from its content instead:
       max-content up to the maximum width, wrapping below it, and the height left to
       the wrapped text with nothing to scroll.
       box-sizing keeps the padding and border inside that maximum. */
    box-sizing: border-box;
    width: max-content;
    max-width: min(90vw, 18rem);
    height: auto;
    max-height: none;
    overflow: visible;
    white-space: normal;
    overflow-wrap: break-word;
    padding: 0.5rem 0.75rem;
    border: 1px solid #d5d9e6;
    border-radius: 4px;
    background: var(--paper-solid);
    box-shadow: 0 2px 8px rgb(0 0 0 / 12%);
    color: #333;
    font-size: 0.85rem;
    font-weight: 400;
    line-height: 1.4;
}

/* The hours field of the log time view is the primary value on the form, so it is
   set large and bold, with the quarter hour steppers flanking it. The row is only
   as wide as the value needs: hours are two or three characters, not a line.
   REQ 6.13: Time is logged in quarter hours, which the steppers move by. */
.hours-field { display: flex; align-items: stretch; gap: 0.5rem; margin-top: 0.25rem; }
.hours-field input {
    margin-top: 0;
    width: 8.5rem;
    flex: 0 0 auto;
    padding: 0.35rem 0.6rem;
    font-size: 2rem;
    font-weight: 700;
    line-height: 1.2;
    text-align: center;
}
/* A stepper is a square sitting the full height of the field, so the pair reads as
   one control rather than as actions belonging to the form. */
.hours-field button.hours-step,
.date-field button.weekday-step {
    margin-top: 0;
    flex: 0 0 auto;
    width: 3rem;
    padding: 0;
    font-size: 1.5rem;
    font-weight: 700;
    line-height: 1;
}

/* Hours and date sit side by side: they are the two halves of one statement, and
   the row wraps on narrow screens rather than squeezing either field. The column gap
   is generous where there is room and closes down as the viewport narrows, so the
   two fields stay one statement instead of drifting apart on a phone. */
.entry-row { display: flex; flex-wrap: wrap; gap: 1rem clamp(1.5rem, 8vw, 9rem); align-items: flex-start; }
.entry-row-field { display: flex; flex-direction: column; min-width: 0; }
.entry-row-field label { margin-top: 0; }

/* The weekday buttons keep the footprint of the hours steppers, so both rows read
   as the same kind of control. The date widget itself is an ordinary field, the size
   of the contract select: it is the way to any other date, not the primary value. */
.date-field { display: flex; align-items: center; gap: 0.5rem; margin-top: 0.25rem; }
.date-field input {
    margin-top: 0;
    width: 16rem;
    flex: 0 0 auto;
    padding: 0.5rem;
    font-size: 1.4rem;
    line-height: 1.2;
}
.date-field button.weekday-step { height: 3.25rem; }

/* The log time view is worked through quickly and often, so its controls are set
   larger than an ordinary form's: the contract and task, the date and the notes all
   read at a glance, in step with the hours field that leads the form. */
.time-entry-form select,
.time-entry-form textarea,
.time-entry-form input[type="text"]:not(#hours),
.time-entry-form input[type="url"] {
    font-size: 1.4rem;
    padding: 0.5rem;
}
.time-entry-form select { padding-right: 2.5rem; }

/* The contract of the selected task, stated under the select because the closed
   control shows only the option's own label. It is a quiet caption: confirmation of
   the selection, not a field of its own. */
.selected-group { display: block; margin-top: 0.25rem; color: var(--bark); font-weight: 600; }
.selected-group[hidden] { display: none; }

/* The notes last written on the selected task. Each is one line, clipped rather
   than wrapped, so the list stays a short menu to scan rather than a block of prose
   competing with the field above it. */
.note-suggestions { margin-top: 0.5rem; }
.note-suggestions[hidden] { display: none; }
.note-suggestions-heading { display: block; color: var(--bark); font-weight: 600; }
.note-suggestions-list { display: flex; flex-direction: column; gap: 0.25rem; margin-top: 0.25rem; }
.note-suggestions button.note-suggestion {
    margin-top: 0;
    display: block;
    width: 100%;
    padding: 0.3rem 0.6rem;
    font-weight: 400;
    text-align: left;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
/* The chosen day is filled in, so the row reports the current date as well as
   setting it. */
.date-field button.weekday-step.is-active {
    background: var(--bark);
    border-color: var(--bark);
    color: var(--paper-solid);
}

/* Phones and small tablets.
   The layout is one column at every width already, so what changes here is the
   chrome around it: the header packs tighter, the tab strip becomes a single
   swipeable row rather than a stack of wrapped rows, the content column gives up
   its side margin, and the wider fixed-width fields (the date row of the log time
   view) fall back to the full line. */
@media (max-width: 720px) {
    :root {
        /* The measure follows the viewport: a fixed 20rem floor would leave a
           phone with an inset column and unused margin on both sides. */
        --content-width: 100%;
        --content-gutter: 0.85rem;
    }

    /* A fixed background is expensive to repaint while scrolling on mobile, and
       the wordmark knockout it serves is not worth that cost on a small screen. */
    header { padding-top: 0.75rem; background-attachment: scroll, scroll; }
    /* On a phone the primary nav reads as two bands rather than one wrapped line:
       the brand sign with sign out opposite it, then the section links beneath.
       Margins between items become a gap, so a wrapped row of links keeps even
       vertical spacing instead of sitting flush. */
    header nav.nav-primary { padding-bottom: 1.9rem; gap: 0.5rem 1.1rem; }
    header nav.nav-primary a { margin-right: 0; }
    header nav.nav-primary .brand { order: -2; margin-right: 0; }
    header nav.nav-primary .account { order: -1; margin-left: auto; }
    /* A full-width, empty flex item after the brand and sign out (both pulled
       ahead with order) forces the section links onto their own line, without a
       wrapper element the desktop layout has no use for. */
    header nav.nav-primary::after { content: ""; order: -1; flex: 0 0 100%; height: 0; }
    header nav.nav-primary .brand .sign-top { padding: 0.25rem 0.7rem 0.15rem; }
    header nav.nav-primary .brand .sign-bottom { padding: 0.05rem 0.7rem 0.15rem; font-size: 0.65rem; }
    header nav.nav-primary .brand .wordmark { font-size: 1.05rem; }
    /* The account menu is not the action this screen is for, so on a phone its button
       drops the log illustration (which needs width for its sawn end and branch) and
       takes the quiet outlined shape at a size that fits beside the brand. */
    header nav.nav-primary .account button.primary {
        margin-top: 0;
        padding: 0.4em 0.9em;
        border: 1px solid var(--log-outline);
        border-radius: var(--radius-sm);
        background: var(--log-base);
        box-shadow: none;
        font-size: 0.9rem;
    }
    header nav.nav-primary .account button.primary::before,
    header nav.nav-primary .account button.primary::after { content: none; }

    /* The tabs are a set: wrapping them into two or three rows buries the later
       ones, so the strip scrolls sideways as one row instead. The scrollbar is
       hidden because the partly visible last tab already says there is more. */
    header nav.nav-tabs {
        flex-wrap: nowrap;
        overflow-x: auto;
        scrollbar-width: none;
        -webkit-overflow-scrolling: touch;
    }
    header nav.nav-tabs::-webkit-scrollbar { display: none; }
    header nav.nav-tabs a { flex: 0 0 auto; padding: 0.5rem 0.9rem; }

    main { margin: 1.25rem auto; }
    h1 { font-size: 1.5rem; }
    h2 { font-size: 1.2rem; }

    /* A page-level action is tapped rather than clicked, so it takes a target
       tall enough for a finger. */
    .actions button, .actions a.button { min-height: 2.75rem; display: inline-flex; align-items: center; }

    /* Both halves of the log time row take the whole line: side by side they would
       leave the date field too narrow for its own control. */
    .entry-row-field { flex: 1 1 100%; }
    /* The date sits on its own line above the weekday shortcuts, and the five
       shortcuts share the width of the line rather than overflowing it. */
    .date-field { flex-wrap: wrap; }
    .date-field input { width: 100%; flex: 1 1 100%; font-size: 1.15rem; }
    .date-field button.weekday-step { flex: 1 1 0; width: auto; min-width: 0; height: 2.75rem; font-size: 1.15rem; }
    .hours-field input { width: 6.5rem; font-size: 1.6rem; }
    .hours-field button.hours-step { width: 2.75rem; font-size: 1.25rem; }
    /* The log time view sets its controls large for quick work at a desk; on a
       phone that scale costs more width than it buys, so it steps back towards
       the body size while staying above the 16px that avoids an iOS zoom on
       focus. */
    .time-entry-form select,
    .time-entry-form textarea,
    .time-entry-form input[type="text"]:not(#hours),
    .time-entry-form input[type="url"] { font-size: 1.15rem; }
}

/* Narrow phones, where even the packed header has to give way. */
@media (max-width: 400px) {
    header nav.nav-primary { gap: 0.4rem 0.85rem; font-size: 0.95rem; }
    header nav.nav-tabs a { padding: 0.45rem 0.75rem; font-size: 0.95rem; }
}

/* The account menu: the signed-in user's own links, opened from their name in the
   header (see base.tmpl). Like a hint, it is a popover pinned under its trigger by
   anchor positioning, here by the trigger's end edge since the trigger sits at the
   right of the header; a browser without anchor positioning shows it where it would
   sit in flow, below the trigger. It is a panel of the form popover's kind, and its
   entries read as the header's links do, in bark on paper rather than white on the
   canopy. */
.account-menu {
    position: fixed;
    inset: auto;
    inset-block-start: anchor(end);
    inset-inline-end: anchor(end);
    position-try-fallbacks: flip-block, flip-inline;
    box-sizing: border-box;
    min-width: 11rem;
    margin: 0.4rem 0 0;
    padding: 0.35rem 0;
    border: 1px solid var(--bark);
    border-radius: 4px;
    background: var(--paper-solid);
    box-shadow: 0 8px 28px rgba(0, 0, 0, 0.25);
}
header nav.nav-primary .account-menu a,
.account-menu button.link {
    display: block;
    width: 100%;
    margin: 0;
    padding: 0.45rem 1rem;
    border: 0;
    border-radius: 0;
    color: var(--bark-dark);
    font-family: var(--font-sans);
    font-size: 1rem;
    font-weight: 600;
    line-height: 1.4;
    text-align: start;
    text-decoration: none;
    text-shadow: none;
}
header nav.nav-primary .account-menu a:hover,
.account-menu button.link:hover { background: #f2ebd4; color: var(--bark-dark); }
header nav.nav-primary .account-menu a.active { text-decoration: underline; text-underline-offset: 4px; }

/* A form opened over the page from an action, for a section of the view that is left out
   while it has nothing in it (see contract_detail.tmpl). The browser centres a popover in
   the viewport; this gives it a panel of its own and dims what is behind it.
   REQ 13.16: The actions bar offers the action of a section it leaves out. */
.form-popover {
    box-sizing: border-box;
    width: min(90vw, 30rem);
    padding: 1rem 1.25rem 1.25rem;
    border: 1px solid var(--bark);
    border-radius: 4px;
    background: var(--paper-solid);
    box-shadow: 0 8px 28px rgba(0, 0, 0, 0.25);
}
.form-popover::backdrop { background: rgba(0, 0, 0, 0.2); }
.form-popover h2 { margin-top: 0; }
