/* Tracker — hand-written, no build step.
   Mobile-first: every layout starts as a single column and widens at 640px. */

:root {
    --bg: #f6f7f9;
    --surface: #ffffff;
    --surface-2: #f1f3f5;
    --text: #1a1d21;
    --muted: #6b7280;
    --line: #e3e6ea;
    --accent: #4f46e5;
    --accent-text: #ffffff;
    --danger: #b42318;
    --danger-bg: #fdeceb;
    --success: #12805c;
    --success-bg: #e7f5ef;
    --radius: 10px;
    --shadow: 0 1px 2px rgba(16, 24, 40, .06), 0 1px 3px rgba(16, 24, 40, .1);
}

@media (prefers-color-scheme: dark) {
    :root {
        --bg: #14171a;
        --surface: #1c2024;
        --surface-2: #23282d;
        --text: #e8eaed;
        --muted: #9aa2ad;
        --line: #2c3238;
        --accent: #7c74f4;
        --accent-text: #ffffff;
        --danger: #f27c73;
        --danger-bg: #3a1d1b;
        --success: #4ad4a0;
        --success-bg: #143028;
        --shadow: 0 1px 2px rgba(0, 0, 0, .4);
    }
}

*, *::before, *::after { box-sizing: border-box; }

body {
    margin: 0;
    background: var(--bg);
    color: var(--text);
    font: 16px/1.55 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
    -webkit-text-size-adjust: 100%;
}

a { color: var(--accent); }

/* ------------------------------------------------------------------ layout */

.shell { max-width: 960px; margin: 0 auto; padding: 0 1rem 4rem; }

.topbar {
    background: var(--surface);
    border-bottom: 1px solid var(--line);
    position: sticky;
    top: 0;
    z-index: 10;
}

.topbar-inner {
    max-width: 960px;
    margin: 0 auto;
    padding: .75rem 1rem;
    display: flex;
    align-items: center;
    gap: 1rem;
}

.brand { font-weight: 700; letter-spacing: -.01em; text-decoration: none; color: var(--text); }

.nav { display: flex; gap: .25rem; margin-left: auto; align-items: center; flex-wrap: wrap; }

.nav a {
    padding: .45rem .7rem;
    border-radius: 8px;
    text-decoration: none;
    color: var(--muted);
    font-size: .92rem;
    font-weight: 500;
}

.nav a:hover { background: var(--surface-2); color: var(--text); }
.nav a[aria-current="page"] { background: var(--surface-2); color: var(--text); }

/* flex-shrink: 0 because these sit in .entry and .person-summary, which are
   flex rows — without it a long description squashes the circle into an
   ellipse rather than wrapping itself. */
.avatar { width: 30px; height: 30px; border-radius: 50%; display: block; flex: 0 0 auto; }

/* The stand-in for a person with no photograph on their Google account.
   Neutral rather than a colour derived from the name: a hashed hue has to be
   contrast-checked against both themes for every name it can produce, and
   these sit next to the name they abbreviate, so they are not what anybody is
   reading. */
.avatar-initials {
    background: var(--surface-2);
    border: 1px solid var(--line);
    color: var(--muted);
    font-size: .75rem;
    font-weight: 600;
    line-height: 28px;
    text-align: center;
    letter-spacing: .02em;
    user-select: none;
}

/* For the calendar grid, where the chip is .72rem type on a 15px line box.
   Sized to that rather than to the circles elsewhere, so putting a face on
   every chip does not add a pixel to the height of the month. */
.avatar-tiny { width: 16px; height: 16px; }
.avatar-tiny.avatar-initials { font-size: .5rem; line-height: 14px; letter-spacing: 0; }

/* ------------------------------------------------------------------- cards */

.card {
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    box-shadow: var(--shadow);
    padding: 1.25rem;
    margin-top: 1.25rem;
}

h1 { font-size: 1.4rem; margin: 1.5rem 0 0; letter-spacing: -.01em; }
h2 { font-size: 1.1rem; margin: 0 0 .75rem; }
.muted { color: var(--muted); }
.small { font-size: .875rem; }

/* ----------------------------------------------------------------- buttons */

.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: .5rem;
    padding: .7rem 1.1rem;
    border-radius: 8px;
    border: 1px solid var(--line);
    background: var(--surface);
    color: var(--text);
    font: inherit;
    font-weight: 600;
    font-size: .95rem;
    cursor: pointer;
    text-decoration: none;
    /* Comfortable thumb target on a phone. */
    min-height: 44px;
}

.btn:hover { background: var(--surface-2); }

.btn-primary {
    background: var(--accent);
    border-color: var(--accent);
    color: var(--accent-text);
}

.btn-primary:hover { filter: brightness(1.08); background: var(--accent); }

.btn-block { width: 100%; }

/* ------------------------------------------------------------------ alerts */

.alert {
    padding: .8rem 1rem;
    border-radius: 8px;
    border: 1px solid;
    margin-top: 1.25rem;
    font-size: .93rem;
}

.alert-error { background: var(--danger-bg); border-color: var(--danger); color: var(--danger); }

/* Louder than a flash message, because it is not about what you just did and
   it does not go away when you navigate. */
.alert-loud {
    background: var(--danger-bg);
    border-color: var(--danger);
    border-width: 2px;
    color: var(--danger);
    font-size: 1rem;
}

.alert-loud strong { display: block; font-size: 1.05rem; margin-bottom: .2rem; }
.alert-success { background: var(--success-bg); border-color: var(--success); color: var(--success); }

/* Something to know, not something wrong. A day booked before the Saturday
   that pays for it is neither refused nor a mistake, and dressing it in the
   danger colour would read as a rejection of a booking nobody objects to. */
.alert-note { background: var(--surface-2); border-color: var(--line); color: var(--text); }

/* ------------------------------------------------------------- guest / login */

.guest {
    min-height: 100dvh;
    display: grid;
    place-items: center;
    padding: 1.5rem;
}

.guest-card {
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: 14px;
    box-shadow: var(--shadow);
    padding: 2rem;
    width: 100%;
    max-width: 380px;
    text-align: center;
}

.guest-card h1 { margin: 0 0 .35rem; font-size: 1.35rem; }

.google-btn {
    margin-top: 1.5rem;
    width: 100%;
    background: var(--surface);
}

.google-mark { width: 18px; height: 18px; }

/* ------------------------------------------------------------------- timer */

.timer-live {
    display: flex;
    align-items: center;
    gap: 1rem;
    flex-wrap: wrap;
}

.timer-clock {
    font-size: 2.4rem;
    font-weight: 700;
    letter-spacing: -.02em;
    font-variant-numeric: tabular-nums;
    /* Fixed width so ticking digits do not shuffle the layout. */
    font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
}

.btn-start { margin-left: auto; }
.btn-stop, .btn-start { min-width: 110px; }

/* Discard and Stop travel together, so the pair is pushed to the right rather
   than Stop alone — otherwise Discard is left stranded against the clock. */
.timer-actions { display: flex; align-items: center; gap: .5rem; margin-left: auto; }

/* -------------------------------------------------------- start-time popup */

/* Anchors the panel, which is absolutely positioned so opening it does not
   shove the Stop button down the page. */
.start-edit { position: relative; }

.timer-tap {
    display: inline-flex;
    align-items: center;
    gap: .4rem;
    cursor: pointer;
    list-style: none;
    border-radius: 8px;
}

.timer-tap::-webkit-details-marker { display: none; }

/* The only sign the clock is a control. A caret rather than an underline: the
   digits are 2.4rem and anything drawn through them competes with reading the
   time, which is what the clock is there for. */
.timer-tap::after {
    content: "";
    width: 0;
    height: 0;
    border: 5px solid transparent;
    border-top-color: currentColor;
    /* Nudged up so the triangle sits on the digits' centre line rather than
       hanging off their baseline. */
    margin-top: 5px;
    color: var(--muted);
    transition: transform .15s ease;
}

.timer-tap:hover::after, .start-edit[open] .timer-tap::after { color: var(--accent); }
.start-edit[open] .timer-tap::after { transform: rotate(180deg); margin-top: -5px; }

.start-pop {
    position: absolute;
    top: calc(100% + .4rem);
    left: 0;
    z-index: 20;
    /* Never wider than the phone it is read on, card padding included. */
    width: min(19rem, calc(100vw - 3.5rem));
    padding: .9rem;
    border: 1px solid var(--line);
    border-radius: 10px;
    background: var(--surface);
    box-shadow: 0 8px 24px rgba(0, 0, 0, .18);
}

.start-fields {
    display: flex;
    gap: .5rem;
    margin: .35rem 0 .7rem;
}

/* The time is what gets typed; the date is usually already right.

   Keyed on the class, not on [type="time"]: the time fields are plain text so
   that they cannot be drawn as a 12-hour clock, and an attribute selector
   naming the old type would have quietly stopped matching the moment they
   changed — leaving the field at its flex default with nothing to say why. */
.start-fields .input-time { flex: 1 1 40%; }
.start-fields input[type="date"] { flex: 1 1 60%; }

/* Digits that do not shift sideways as the field is typed into. */
.input-time { font-variant-numeric: tabular-nums; }

.start-pop .btn { width: 100%; }
.start-pop p { margin: .6rem 0 0; }

/* The page-wide alert spacing is wrong inside a panel this small. */
.start-pop .alert { margin: 0 0 .7rem; padding: .5rem .65rem; font-size: .85rem; }

.timer-fields {
    display: flex;
    flex-direction: column;
    gap: .6rem;
    margin-top: 1rem;
}

/* A nudge, not an error — nothing has gone wrong yet and the timer is still
   running. Danger red would say otherwise, and grey would be read as the
   caption text everywhere else on this screen and skipped. */
.needs-fields { color: var(--accent); font-weight: 600; }

.input {
    width: 100%;
    padding: .7rem .8rem;
    border: 1px solid var(--line);
    border-radius: 8px;
    background: var(--bg);
    color: var(--text);
    font: inherit;
    font-size: .95rem;
    min-height: 44px;
}

.input:focus {
    outline: 2px solid var(--accent);
    outline-offset: -1px;
    border-color: var(--accent);
}

@media (min-width: 640px) {
    .timer-fields { flex-direction: row; align-items: center; }
    .timer-fields .input:first-child { flex: 1 1 auto; }
    .timer-fields select.input { flex: 0 0 200px; }
    .timer-fields .btn { flex: 0 0 auto; }
}

/* ----------------------------------------------------------------- entries */

.card-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    margin-bottom: .75rem;
}

.card-head h2 { margin: 0; }

.total {
    font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
}

/* Now that a description can run to several lines, everything beside it lines
   up with its first line rather than floating against its middle.
   align-items: flex-start is therefore set on every row type that holds a
   description — .entry here, and the two summary rows further down the file.

   It is set inside each block rather than in one rule listing all three, which
   is how it was written first and did not work: that rule sat above the
   blocks defining the two summaries, and a later declaration of equal
   specificity wins, so it was silently discarded for both of them. A duration
   sitting a line and a half below the description it belongs to shipped that
   way for months, visible on every wrapped row. Keep the value where the
   display: flex is. */
.entry {
    display: flex;
    align-items: flex-start;
    gap: 1rem;
    padding: .7rem 0;
    border-top: 1px solid var(--line);
}

.entry:first-of-type { border-top: 0; }

.entry-main { min-width: 0; flex: 1 1 auto; }

/* Wraps onto as many lines as it needs. Truncating was never actually
   happening: .entry-desc is a span inside the flex item, `overflow` does not
   apply to an inline box, and so the ellipsis machinery sat inert while
   `nowrap` worked on its own — one unbroken line straight across the duration
   and off the edge of the card. Blockified so it owns the width it wraps
   inside, and `anywhere` so a pasted path with no spaces in it breaks rather
   than overflowing in exactly the same way. */
.entry-desc {
    display: block;
    font-weight: 500;
    overflow-wrap: anywhere;
}

.entry-duration {
    font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
    font-variant-numeric: tabular-nums;
    color: var(--muted);
    flex: 0 0 auto;
}

/* The duration and the play control, as one item rather than two. */
.entry-tail { display: inline-flex; align-items: center; gap: .75rem; flex: 0 0 auto; }

/* On a phone the tail takes a line of its own, always — not only when the
   description is long, because CSS cannot ask how long the text is and a
   layout that rearranges itself per row is harder to read than one that does
   not. Sharing the line costs the description well over a third of its width,
   which is the difference between five wrapped lines and nine; the price is
   one 25px line under the short rows, and it buys a predictable shape. */
@media (max-width: 640px) {
    .entry-edit > summary,
    .entry-group > summary { flex-wrap: wrap; }

    .entry-tail { flex: 1 1 100%; justify-content: flex-end; }
}

.dot {
    display: inline-block;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    vertical-align: baseline;
}

/* ------------------------------------------------------ week / page headers */

.week-head { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; }
.week-head h1 { margin: 1.5rem 0 0; }
.week-head .total { font-size: 1.2rem; }

/* Title on the left, the day's one decision on the right. */
.page-head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 1rem;
    flex-wrap: wrap;
    margin-top: 1.5rem;
}

.page-head h1 { margin: 0; }
.page-head p { margin: .15rem 0 0; }

.page-head-action { margin-left: auto; text-align: right; max-width: 20rem; }
.page-head-action p { margin: .4rem 0 0; }

/* Narrow screens: the button goes full width rather than hugging an edge,
   because it is meant to be tapped one-handed. */
@media (max-width: 520px) {
    .page-head-action { margin-left: 0; width: 100%; text-align: left; }
    .page-head-action .btn { width: 100%; }
}

/* A week divider between groups of day cards. The padding matches a card's, so
   the weekly total lands in the same column as the daily ones below it rather
   than a few pixels further out. */
.week-bar {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 1rem;
    margin: 2rem 0 -.25rem;
    padding: 0 1.25rem;
}

.week-label { font-weight: 700; font-size: .95rem; }
.week-label .muted { font-weight: 400; font-size: .875rem; }
.week-bar .total { font-size: .95rem; }

.card-day { margin-top: .75rem; }

.load-more { margin-top: 1.25rem; text-align: center; }

.week-nav {
    display: flex;
    align-items: center;
    gap: .5rem;
    flex-wrap: wrap;
    margin-top: 1rem;
}

.week-nav .btn { min-height: 38px; padding: .4rem .8rem; font-size: .88rem; }
.week-nav .btn[aria-current="page"] { background: var(--accent); border-color: var(--accent); color: var(--accent-text); }
.week-nav .muted { margin: 0 auto 0 0; }

.badge {
    display: inline-block;
    min-width: 18px;
    padding: 0 5px;
    border-radius: 9px;
    background: var(--accent);
    color: var(--accent-text);
    font-size: .72rem;
    font-weight: 700;
    text-align: center;
    line-height: 18px;
}

/* ---------------------------------------------------------- disclosure rows */

.entry-edit, .add-entry { border-top: 1px solid var(--line); }
.entry-edit:first-of-type { border-top: 0; }

/* flex-start, for the reason given on .entry: a timesheet row's duration and
   a person's photograph both belong level with the first line of the text
   beside them, not halfway down it. */
.entry-edit > summary, .person-summary {
    display: flex;
    align-items: flex-start;
    gap: 1rem;
    padding: .7rem 0;
    cursor: pointer;
    list-style: none;
}

.entry-edit > summary::-webkit-details-marker,
.person-summary::-webkit-details-marker { display: none; }

.entry-edit > summary:hover, .person-summary:hover { color: var(--accent); }

.add-summary {
    padding: .7rem 0 0;
    cursor: pointer;
    color: var(--muted);
    font-size: .9rem;
    list-style: none;
}

.add-summary:hover { color: var(--accent); }

/* ------------------------------------------------------------ collapsed task */

/* Entries sharing a project and description fold into one row. The heading
   bleeds to the card's edges — negative margins matching .card's padding — so
   the tint reads as a band across the row rather than a box floating in it. */
.entry-group { border-top: 1px solid var(--line); }
.entry-group:first-of-type { border-top: 0; }

.entry-group > summary {
    display: flex;
    align-items: flex-start;
    gap: .75rem;
    padding: .7rem 1.25rem;
    margin: 0 -1.25rem;
    background: var(--surface-2);
    cursor: pointer;
    list-style: none;
}

.entry-group > summary::-webkit-details-marker { display: none; }
.entry-group > summary:hover { color: var(--accent); }

/* The row's total is the reason the group exists, so it is not muted the way
   an individual entry's duration is. */
.entry-group > summary .entry-duration { color: var(--text); }

.caret { display: inline-flex; flex: 0 0 auto; color: var(--muted); transition: transform .15s ease; }
.entry-group[open] > summary .caret { transform: rotate(90deg); }

/* Indented, so an open group reads as belonging to the row above it. */
.entry-group-body { padding-left: 1.25rem; }

/* Clockify's "+2": the entry ended two calendar days after it started, which
   is the only thing that makes 22:00–03:16 read forwards. */
.day-span { font-size: .72em; font-weight: 700; color: var(--accent); }

/* Picks the task back up. Small enough not to compete with the duration beside
   it; the padding, not the glyph, is what makes it a thumb target. */
.btn-resume {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 34px;
    height: 34px;
    padding: 0;
    border: 1px solid transparent;
    border-radius: 50%;
    background: transparent;
    color: var(--muted);
    cursor: pointer;
}

.btn-resume:hover, .btn-resume:focus-visible {
    background: var(--surface);
    border-color: var(--line);
    color: var(--accent);
}

/* ------------------------------------------------------------------- forms */

.edit-form { display: flex; flex-direction: column; gap: .6rem; padding: .5rem 0 1rem; }

/* The hidden attribute has to be said again, louder: it carries display:none
   from the browser's own stylesheet, and any rule of ours that sets a display
   — .edit-times is a flex row — wins over it and leaves the element on the
   screen. */
[hidden] { display: none !important; }

.report-range { margin: .35rem 0 0; }

.edit-times { display: flex; gap: .6rem; flex-wrap: wrap; }
.edit-times > * { flex: 1 1 130px; }

.edit-actions { display: flex; gap: .6rem; flex-wrap: wrap; }

.field { display: flex; flex-direction: column; gap: .25rem; }
.field-label { font-size: .78rem; color: var(--muted); font-weight: 600; }

.checkbox-field { flex-direction: row; align-items: center; gap: .5rem; align-self: end; min-height: 44px; }
.checkbox-field input[type="checkbox"] { width: 18px; height: 18px; }

.input-colour { padding: .25rem; height: 44px; max-width: 80px; flex: 0 0 80px; }

.btn-quiet { background: transparent; border-color: transparent; color: var(--muted); font-weight: 500; }
.btn-quiet:hover { background: var(--surface-2); color: var(--danger); }

.approve-actions { display: flex; gap: .5rem; flex: 0 0 auto; }

/* ------------------------------------------------------------------- stats */

.stats { display: flex; gap: 1rem; flex-wrap: wrap; margin-top: 1.25rem; }

.stat {
    flex: 1 1 140px;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    box-shadow: var(--shadow);
    padding: 1rem 1.25rem;
}

.stat-value { font-size: 2rem; font-weight: 700; line-height: 1.1; letter-spacing: -.02em; }
.stat-label { font-size: .82rem; color: var(--text); margin-top: .25rem; }

.status { font-weight: 600; }
.status-pending { color: var(--muted); }
.status-approved { color: var(--success); }
.status-rejected { color: var(--danger); }

/* ----------------------------------------------------------------- reports */

.chart-row { display: flex; gap: 1.5rem; align-items: center; flex-wrap: wrap; }
.chart { flex: 0 0 auto; }

.breakdown { flex: 1 1 300px; min-width: 0; }

.breakdown-row {
    display: flex;
    align-items: center;
    gap: .6rem;
    padding: .5rem 0;
    border-top: 1px solid var(--line);
}

.breakdown-row:first-child { border-top: 0; }

/* Same as the timesheet: a description is the whole point of the row, so it
   wraps rather than losing its ending to an ellipsis. */
.breakdown-label {
    flex: 1 1 auto;
    min-width: 0;
    overflow-wrap: anywhere;
}

.breakdown-pct {
    flex: 0 0 72px;
    text-align: right;
    color: var(--muted);
    font-variant-numeric: tabular-nums;
    font-size: .9rem;
}

/* Descriptions sit under their project. The rule above clears the border on a
   first child, which every group heading now is, so the separator between one
   project and the next has to be put back. */
.breakdown-group + .breakdown-group .breakdown-row:first-child {
    border-top: 1px solid var(--line);
}

.breakdown-head .breakdown-label { font-weight: 600; }
.breakdown-sub .breakdown-label { padding-left: 1.1rem; }

.breakdown-who { color: var(--muted); font-size: .875rem; }
.breakdown-who::before { content: " · "; }

/* The name used to be moved onto its own line on a phone, because the label
   was one ellipsised line and the name was the half of it that got eaten.
   Now that the label wraps, it wraps the name along with it.

   What the phone still needs is room. Sharing the row with a duration and a
   percentage left the description 140px to wrap inside — eighteen lines for
   one sentence — so the two figures drop underneath it, together, and stay
   right-aligned in the column the eye is already reading them in. Only the
   description rows: a project name is short by nature and a heading that
   split in two would only make the list harder to scan. */
@media (max-width: 640px) {
    .breakdown-sub { flex-wrap: wrap; }
    .breakdown-sub .breakdown-label { flex: 1 1 100%; }
    .breakdown-sub .entry-duration { margin-left: auto; }
}

/* ---------------------------------------------------------------- calendar */

/* One row of seven, hairline-separated by the grid gap showing the background
   through it — a border on each cell would double up between neighbours. */
.calendar {
    display: grid;
    grid-template-columns: repeat(7, minmax(0, 1fr));
    gap: 1px;
    margin-top: .75rem;
    background: var(--line);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    overflow: hidden;
}

.calendar-head {
    background: var(--surface-2);
    padding: .35rem .4rem;
    font-size: .7rem;
    font-weight: 700;
    letter-spacing: .04em;
    text-transform: uppercase;
    text-align: center;
    color: var(--muted);
}

.calendar-day {
    display: flex;
    flex-direction: column;
    gap: .2rem;
    min-width: 0;
    min-height: 84px;
    padding: .3rem .35rem .45rem;
    background: var(--surface);
}

/* Nobody is ever away on one of these — leave skips them — so shading says
   that an empty square is closed rather than unbooked. */
.calendar-day.is-closed { background: var(--surface-2); }
.calendar-day.is-outside .calendar-date { opacity: .4; }
.calendar-day.is-today { box-shadow: inset 0 0 0 2px var(--accent); }

.calendar-date { font-size: .75rem; font-weight: 700; color: var(--muted); }
.calendar-day.is-today .calendar-date { color: var(--accent); }

.calendar-holiday { font-size: .68rem; line-height: 1.3; color: var(--muted); overflow-wrap: anywhere; }

/* A flex row so the face and the name sit on one baseline and a name long
   enough to wrap does so beside the circle rather than under it. align-items
   is flex-start, not centre, for the same reason: two lines of name should
   start level with the face, not straddle it. */
.calendar-who {
    display: flex;
    align-items: flex-start;
    gap: .25rem;
    font-size: .72rem;
    line-height: 1.3;
    padding: .1rem .3rem;
    border-left: 3px solid var(--accent);
    border-radius: 3px;
    background: var(--surface-2);
    overflow-wrap: anywhere;
}

/* The circle keeps its size while the name beside it takes what is left —
   without this a long name squeezes the face into an ellipse. */
.calendar-who .avatar { margin-top: 1px; }
.calendar-who > span:last-child { min-width: 0; }

.calendar-day.is-closed .calendar-who { background: var(--surface); }

/* Undecided, and drawn as such: a dotted edge rather than a solid one, so a
   week that looks booked and a week that might be are not the same picture. */
.calendar-who.is-pending { border-left-style: dotted; color: var(--muted); }
.calendar-who.is-self { font-weight: 700; }

.calendar-list { display: none; }

/* Seven columns of names on a phone is four characters each. The same month
   becomes the days that have somebody on them, which is all a small screen
   has room to say and most of what anybody reads off a calendar anyway. */
@media (max-width: 640px) {
    .calendar { display: none; }
    .calendar-list { display: block; }
    /* There is no grid down here to be shaded. */
    .calendar-note { display: none; }
}

/* One control and a button, sitting on the same line. The select is held to a
   readable width rather than stretched across the card: a full-width select
   drags the browser's own dropdown out to match, which is a list of short
   names in a panel the width of the page. */
.filter-row {
    display: flex;
    align-items: flex-end;
    flex-wrap: wrap;
    gap: .6rem;
    padding: .5rem 0 1rem;
}

/* Grows to a comfortable 260px and shrinks rather than dropping the button
   onto a line of its own — flex wraps before it shrinks, so the basis has to
   be small enough for both to fit a phone. */
.filter-row .field { flex: 1 1 150px; max-width: 260px; }

/* Danger-coloured inline text, for a warning that belongs inside a sentence
   rather than in a block alert of its own. */
.text-danger { color: var(--danger); }
