/* The shipped components. Load tokens.css first. Every measure here is
   twitchlive's, the reference page: change one and twitchlive changes. */

* {
    box-sizing: border-box;
    margin: 0;
    padding: 0;
    font-family: var(--sans);
    -webkit-font-smoothing: antialiased;
}

body {
    display: flex;
    flex-direction: column;
    min-height: 100vh;
    background-color: var(--slate);
    color: var(--ink);
    font-weight: 400;
}

/* Colour says it is a link; no underline, on hover either. */
a {
    color: var(--blue);
    text-decoration: none;
}

/* Components set display, which outranks the UA rule for [hidden]. */
[hidden] {
    display: none !important;
}

code,
kbd,
pre,
.mono {
    font-family: var(--mono);
}

code,
kbd {
    font-size: 0.9em;
}

/* Inline only: a code block's own fill is on the block. */
:not(pre) > code {
    padding: 0.1em 0.35em;
    background: var(--slate);
    border-radius: 3px;
    overflow-wrap: anywhere;
}

.nowrap {
    white-space: nowrap;
}

/* A figure that re-renders in place, a clock or a counter: its width holds. */
.tabular {
    font-variant-numeric: tabular-nums;
}

/* Read aloud, never drawn: clipping keeps the element in the accessibility
   tree, which display:none does not. */
.vh {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
}

.skip {
    position: fixed;
    top: -100px;
    left: 1.5rem;
    z-index: 200;
    padding: 0.5rem 1rem;
    background: var(--surface);
    border: 1px solid var(--blue);
    border-radius: var(--r-control);
}

.skip:focus {
    top: 0.5rem;
}

/* The "leaves the app" mark after an external link: an inline SVG at the
   text's size, stroked in the link's colour. */
.external-icon {
    flex: 0 0 auto;
    width: 0.82em;
    height: 0.82em;
    margin-left: 0.28em;
    vertical-align: -0.09em;
    opacity: 0.75;
}

/* Bar */

.navbar {
    position: sticky;
    top: 0;
    z-index: 100;
    background-color: var(--surface);
    box-shadow: var(--shadow-bar);
}

.nav-container {
    display: flex;
    align-items: center;
    justify-content: space-between;
    max-width: var(--page-max);
    margin: 0 auto;
    padding: 0.5rem 1.5rem;
}

.nav-logo {
    flex: 0 0 auto;
}

.nav-logo a {
    color: var(--blue);
    font-size: 1.25rem;
    font-weight: 500;
    white-space: nowrap;
}

/* The link row is the item that gives: on a phone it scrolls sideways under
   its own edge rather than wrapping below the logo. */
.nav-links {
    display: flex;
    gap: 1.5rem;
    min-width: 0;
    overflow-x: auto;
    list-style: none;
    scrollbar-width: none;
}

.nav-links::-webkit-scrollbar {
    display: none;
}

.nav-link {
    color: var(--muted);
    font-size: 0.875rem;
    font-weight: 500;
    white-space: nowrap;
    transition: color var(--fade);
}

.nav-link:hover,
.nav-link[aria-current="page"] {
    color: var(--blue);
}

/* A POST that sits in a line of links or a table cell: signing out, ending a
   session. The form takes no box of its own. */
.nav-form,
.inline-form {
    display: inline;
}

.nav-link-button {
    background: none;
    border: none;
    line-height: inherit;
    cursor: pointer;
}

/* Page */

.container {
    display: flex;
    flex-direction: column;
    width: 100%;
    max-width: var(--page-max);
    margin: 0.75rem auto;
    padding: 0 1.5rem;
}

.login-container {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
}

.login-card {
    width: 100%;
    max-width: var(--login-max);
    padding: 3rem;
    background: var(--surface);
    border: var(--edge);
    border-radius: var(--r-tile);
    text-align: center;
}

.profile-card {
    width: 100%;
    margin-bottom: 0.85rem;
    padding: 0.9rem 1.1rem;
    background: var(--surface);
    border: var(--edge);
    border-radius: var(--r-card);
}

h1 {
    margin-bottom: 0.6rem;
    color: var(--ink);
    font-size: 19px;
    font-weight: 500;
}

h2 {
    margin: 0 0 0.55rem;
    color: var(--ink);
    font-size: 16px;
    font-weight: 500;
}

h3 {
    margin: 0.85rem 0 0.25rem;
    color: var(--muted);
    font-size: 0.875rem;
    font-weight: 500;
}

.profile-card > h3:first-child {
    margin-top: 0;
}

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

.disabled {
    opacity: 0.7;
}

.margin-top-1 {
    margin-top: 1rem;
}

.margin-top-2 {
    margin-top: 2rem;
}

/* A card heading with a control at its right, on one baseline. */
.card-heading {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.75rem;
}

/* Two cards read as one comparison. Subgrid puts both cards' heading, intro
   and body on the same three rows, so the two tables start at one height.
   Each card holds exactly those three children. */
.card-pair {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
    grid-template-rows: auto auto auto;
    align-items: start;
    gap: 0.85rem;
    margin-bottom: 0.85rem;
}

.card-pair > .profile-card {
    display: grid;
    grid-row: span 3;
    grid-template-rows: subgrid;
    margin-bottom: 0;
}

/* Forms */

.input-group {
    margin-bottom: 0.55rem;
    text-align: left;
}

label {
    display: block;
    margin-bottom: 0.2rem;
    color: var(--muted);
    font-size: 0.78rem;
    font-weight: 500;
}

input,
select {
    width: 100%;
    padding: 0.4rem 0.55rem;
    border: var(--edge);
    border-radius: var(--r-control);
    outline: none;
    font-size: 0.875rem;
    font-weight: 400;
    transition: border var(--fade);
}

textarea {
    width: 100%;
    min-height: 5em;
    padding: 0.4rem 0.55rem;
    border: var(--edge);
    border-radius: var(--r-control);
    outline: none;
    font-size: 0.875rem;
    resize: vertical;
}

/* Focus draws the edge at 2px in blue. */
:is(input, textarea):focus {
    border: 2px solid var(--blue);
}

:is(input, select, textarea)[aria-invalid="true"] {
    background: var(--red-tint);
    border-color: var(--red);
}

/* The error under its field: location is the field, so the line is cause and
   fix. */
.input-error {
    display: block;
    margin-top: 0.2rem;
    color: var(--red-deep);
    font-size: 0.78rem;
}

.input-group small {
    display: block;
    margin-top: 0.2rem;
    color: var(--muted);
    font-size: 0.78rem;
    font-style: normal;
}

.password-input {
    width: 320px;
    max-width: 100%;
}

/* A checkbox or radio with its label on one line. */
.check {
    display: flex;
    align-items: center;
    gap: 0.45rem;
    margin-bottom: 0;
    color: var(--muted);
    font-size: 0.82rem;
}

.check input {
    width: auto;
    margin: 0;
}

/* A filter above a list: a wrapping row of fields, labels above. */
.filter-form {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: 0.5rem 0.75rem;
    margin-bottom: 0.85rem;
}

.filter-form .input-group {
    margin-bottom: 0;
}

/* A filter on one line: labels inline, fields as wide as their content. */
.filters {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem;
    margin-bottom: 0.85rem;
    font-size: 0.875rem;
}

.filters label {
    display: inline;
    margin: 0;
}

.filters :is(input, select) {
    width: auto;
}

/* The filter the page is narrowed by, with its clear control. */
.active-filter {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    margin-bottom: 0.85rem;
    padding: 0.25rem 0.7rem;
    border: var(--edge);
    border-radius: var(--r-pill);
    color: var(--muted);
    font-size: 0.8125rem;
}

.active-filter-clear {
    color: var(--muted);
    font-weight: 600;
    line-height: 1;
}

.active-filter-clear:hover {
    color: var(--red);
}

/* Buttons */

.btn {
    display: inline-block;
    padding: 0.45rem 0.9rem;
    background: var(--green);
    border: none;
    border-radius: var(--r-control);
    color: var(--surface);
    font-size: 0.8rem;
    font-weight: 500;
    cursor: pointer;
    transition: background var(--fade);
}

.btn:hover {
    background: var(--green-deep);
}

a.btn,
a.btn:hover {
    color: var(--surface);
}

.btn:disabled,
.btn[aria-disabled="true"],
.btn-disabled {
    opacity: 0.5;
    pointer-events: none;
}

.btn-block {
    width: 100%;
}

.btn-small {
    padding: 0.3rem 0.6rem;
    font-size: 0.78rem;
}

.btn-danger,
.logout-btn {
    background: var(--red);
}

.logout-btn {
    margin-top: 1.5rem;
}

.btn-danger:hover,
.logout-btn:hover {
    background: var(--red-hover);
}

/* The quiet action beside a green one, or a row of them where a badge
   already carries the state: surface fill, line edge, muted text. */
.btn-quiet {
    background: var(--surface);
    border: var(--edge);
    color: var(--muted);
}

.btn-quiet:hover {
    background: var(--slate);
    color: var(--ink);
}

.btn-quiet-danger {
    color: var(--red);
}

.btn-quiet-danger:hover {
    background: var(--red-tint);
    color: var(--red);
}

/* A form that is one row: fields side by side, labels above, the button on
   the fields' baseline. A field grows from 12rem; .input-group-compact
   holds the lone always-visible field to 20rem; .input-group-full takes a
   row of its own. */
.form-row {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: 0.4rem 0.75rem;
    margin-bottom: 0.85rem;
}

.form-row .input-group {
    flex: 1 1 12rem;
    min-width: 9rem;
    margin-bottom: 0;
}

.form-row .btn {
    flex: 0 0 auto;
}

.form-row .input-group-full {
    flex: 1 1 100%;
}

.form-row .input-group-compact {
    flex: 0 1 20rem;
}

/* Optional fields folded under the row: a muted summary, a narrow stack. */
.form-more {
    flex: 1 1 100%;
}

.form-more summary {
    width: fit-content;
    color: var(--muted);
    font-size: 0.8rem;
    font-weight: 500;
    cursor: pointer;
    user-select: none;
}

.form-more-fields {
    display: grid;
    gap: 0.6rem;
    max-width: 26rem;
    margin-top: 0.6rem;
}

.links {
    margin-top: 1.5rem;
    font-size: 0.875rem;
}

.links a {
    color: var(--blue);
    font-weight: 500;
}

.forgot-password-link {
    margin-bottom: 1em;
    text-align: right;
}

.forgot-password-link a {
    color: var(--blue);
    font-size: 0.95em;
    transition: color var(--fade);
}

.admin-links {
    display: flex;
    flex-wrap: wrap;
    gap: 1rem;
}

.pagination {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.75rem;
    margin-top: 0.85rem;
}

/* Messages */

.error-message,
.warning-message,
.info-message,
.success-message {
    margin-bottom: 1rem;
    padding: 0.75rem;
    border: 1px solid;
    border-radius: var(--r-control);
    font-size: 0.875rem;
}

.error-message {
    background-color: var(--red-tint);
    border-color: var(--red);
    color: var(--red);
    text-align: center;
}

.info-message {
    background-color: var(--blue-tint);
    border-color: var(--blue);
    color: var(--blue);
}

.success-message {
    background-color: var(--green-tint);
    border-color: var(--green);
    color: var(--green);
}

.warning-message {
    background-color: var(--amber-tint);
    border-color: var(--amber);
    color: var(--amber-deep);
}

/* Facts */

.facts {
    display: grid;
    grid-template-columns: max-content 1fr;
    gap: 0.3rem 1.5rem;
}

.facts dt {
    color: var(--muted);
    font-weight: 600;
}

.fact-links {
    display: flex;
    flex-wrap: wrap;
    gap: 0 1rem;
}

.profile-info {
    margin: 1rem 0;
    font-size: 0.875rem;
    text-align: left;
}

.profile-info p {
    margin: 0.75rem 0;
    color: var(--muted);
}

.profile-info strong {
    display: inline-block;
    width: var(--label-col);
    color: var(--ink);
}

.change-password {
    padding-top: 1.5rem;
    border-top: var(--edge);
    text-align: left;
}

/* Events */

.events-list {
    margin-top: 0.5rem;
    list-style: none;
}

.event-item {
    display: flex;
    align-items: flex-start;
    gap: 0.85rem;
    padding: 0.45rem 0;
    border-bottom: var(--edge);
}

.event-item:last-child {
    border-bottom: none;
}

.event-time {
    color: var(--muted);
    font-size: 0.75rem;
    white-space: nowrap;
}

.event-content {
    flex: 1;
}

.event-action {
    margin-bottom: 0.25rem;
    color: var(--ink);
    font-weight: 500;
}

.event-details {
    color: var(--muted);
    font-size: 0.875rem;
    font-weight: 400;
}

/* A timeline: one thing's events in order, a time right-aligned in its
   column, a dot on a thread, the event. The dot's hue is the event's state;
   an unmarked dot is an event with no state to report. */
.timeline {
    margin: 0.4rem 0 0;
    list-style: none;
}

.timeline-event {
    display: flex;
    align-items: baseline;
    gap: 0.6rem;
    padding: 0.12rem 0;
    font-size: 0.875rem;
}

.timeline-time {
    flex: 0 0 8.5rem;
    color: var(--muted);
    font-size: 0.72rem;
    line-height: 1.35;
    text-align: right;
    white-space: nowrap;
    opacity: 0.85;
}

.timeline-mark {
    position: relative;
    flex: 0 0 auto;
    align-self: flex-start;
    width: 0.5rem;
    height: 0.5rem;
    margin-top: 0.3rem;
    background: var(--line);
    border-radius: 50%;
}

/* The thread from each dot down to the next. */
.timeline-mark::before {
    content: "";
    position: absolute;
    top: 0.5rem;
    bottom: -0.44rem;
    left: 50%;
    width: 1px;
    background: var(--line);
    transform: translateX(-50%);
}

.timeline-event:last-child .timeline-mark::before {
    display: none;
}

.timeline-body {
    flex: 1;
    color: var(--ink);
    line-height: 1.35;
}

.timeline-ok .timeline-mark {
    background: var(--green);
}

.timeline-info .timeline-mark {
    background: var(--blue);
}

.timeline-data .timeline-mark {
    background: var(--data);
}

.timeline-end .timeline-mark {
    background: var(--muted);
}

/* Still going: a hollow dot, the line muted and italic. */
.timeline-ongoing .timeline-mark {
    background: transparent;
    border: 2px solid var(--data);
}

.timeline-ongoing .timeline-body {
    color: var(--muted);
    font-style: italic;
}

/* Tables */

.table-container {
    margin-top: 0.5rem;
    overflow-x: auto;
}

.data-table {
    width: 100%;
    background-color: var(--surface);
    border-collapse: collapse;
    border-radius: var(--r-control);
    box-shadow: var(--shadow-lift);
    font-size: 0.8rem;
}

/* Fixed layout: a column keeps its width across filters and pages instead
   of following the longest cell on the page. Set the fixed columns' widths
   on the th; the unsized columns share the rest. */
.data-table-fixed {
    table-layout: fixed;
}

.data-table tr:hover {
    background-color: rgb(0 0 0 / 2%);
}

/* Ten columns of small numbers: the band keeps the eye on one row across the
   width. nth-child follows DOM order, so a script that sorts by moving the
   rows keeps the bands alternating. */
.data-table-zebra tbody tr:nth-child(even) {
    background: rgb(0 0 0 / 3%);
}

/* One line per row whatever the widths work out to. Top alignment keeps a
   row menu's button on that line while its panel is open in flow. */
.data-table-compact td {
    white-space: nowrap;
    vertical-align: top;
}

.data-table th,
.data-table td {
    padding: 0.4rem 0.6rem;
    border-bottom: var(--edge);
    text-align: left;
}

.data-table th {
    background-color: var(--slate);
    color: var(--muted);
    font-weight: 500;
    white-space: nowrap;
}

/* A sortable column: aria-sort carries the direction, the arrow draws it. */
.data-table th[data-sort] {
    cursor: pointer;
    user-select: none;
}

.data-table th[data-sort]:hover {
    color: var(--ink);
}

.data-table th[aria-sort="ascending"]::after {
    content: " ▲";
    font-size: 0.6rem;
}

.data-table th[aria-sort="descending"]::after {
    content: " ▼";
    font-size: 0.6rem;
}

.data-table tr:last-child td {
    border-bottom: none;
}

/* A row that is a link: tabindex="0" and the script follows its data-href. */
.data-table tr[role="link"] {
    cursor: pointer;
}

.data-table tr[role="link"]:focus-visible {
    outline: 2px solid var(--blue);
    outline-offset: -2px;
}

.data-table :is(td, th).num {
    font-variant-numeric: tabular-nums;
    text-align: right;
}

.data-table td.row-actions {
    text-align: right;
}

.row-actions form {
    display: block;
    margin: 0;
}

/* A badge and the figure it qualifies, on one baseline. The flex box is this
   span, not the cell: a flex td stops giving its width to the column. */
.status-line {
    display: inline-flex;
    align-items: baseline;
    gap: 0.35rem;
    white-space: nowrap;
}

/* A row's actions behind one ⋯: <details class="row-menu">. It opens in
   flow and pushes the row taller; a script that adds .row-menu-floating
   floats it over the table instead, out of the scrolling box's clip. */
.row-menu > summary {
    list-style: none;
}

.row-menu > summary::-webkit-details-marker {
    display: none;
}

.row-menu-items {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
    width: fit-content;
    margin-top: 0.3rem;
    margin-left: auto;
}

.row-menu-floating[open] > .row-menu-items {
    position: fixed;
    z-index: 30;
    margin: 0;
    padding: 0.3rem;
    background: var(--surface);
    border: var(--edge);
    border-radius: var(--r-card);
    box-shadow: var(--shadow-menu);
}

.row-menu-items .btn {
    width: 100%;
    text-align: left;
    white-space: nowrap;
}

/* A card that folds: <details class="profile-card card-fold">, its h2 in the
   summary. display stays on the summary, since flex on it drops the
   browser's disclosure marker and the row reads as dead text. */
.card-fold > summary {
    cursor: pointer;
    user-select: none;
}

.card-fold > summary h2 {
    display: inline;
    margin: 0;
}

.card-fold > summary .text-muted {
    margin-left: 0.4rem;
}

.card-fold[open] > summary {
    margin-bottom: 0.55rem;
}

/* Two or three links that pick a window, 7d or All time. The current one is
   aria-current and filled; each reloads the page, so no script. */
.range-switch {
    display: inline-flex;
    overflow: hidden;
    border: var(--edge);
    border-radius: var(--r-pill);
    font-size: 0.8125rem;
}

.range-switch a {
    padding: 0.15rem 0.7rem;
    color: var(--muted);
}

.range-switch a:hover {
    background: var(--slate);
}

.range-switch a[aria-current] {
    background: var(--blue);
    color: var(--surface);
}

.forgot-password-link a:hover {
    color: var(--blue-deep);
}

/* Badges */

/* The state a row is in: offline, idle. nowrap: a pill is sized by its
   text, and a wrapped one is a blob. */
.badge {
    display: inline-block;
    padding: 0.15rem 0.6rem;
    background: var(--slate);
    border: var(--edge);
    border-radius: var(--r-pill);
    color: var(--muted);
    font-size: 0.75rem;
    font-weight: 600;
    letter-spacing: 0.03em;
    white-space: nowrap;
}

/* The one state a page is watching for, "live": solid, so it reads first. */
.badge-solid {
    background: var(--data);
    border: none;
    color: var(--surface);
}

/* A note on a row rather than its state: a size down, closer tracking. The
   tinted and quiet badges are notes too. */
.badge-sm,
.badge-ok,
.badge-info,
.badge-warn,
.badge-danger,
.badge-data,
.badge-quiet {
    font-size: 0.7rem;
    letter-spacing: 0.02em;
}

.badge-ok {
    background: rgb(from var(--green) r g b / 12%);
    border-color: rgb(from var(--green) r g b / 30%);
    color: var(--green-deep);
}

.badge-info {
    background: rgb(from var(--blue) r g b / 12%);
    border-color: rgb(from var(--blue) r g b / 30%);
    color: var(--blue-deep);
}

.badge-warn {
    background: rgb(from var(--amber) r g b / 12%);
    border-color: rgb(from var(--amber) r g b / 32%);
    color: var(--amber-deep);
}

.badge-danger {
    background: rgb(from var(--red) r g b / 12%);
    border-color: rgb(from var(--red) r g b / 30%);
    color: var(--red-deep);
}

.badge-data {
    background: rgb(from var(--data) r g b / 16%);
    border-color: rgb(from var(--data) r g b / 35%);
    color: var(--data-deep);
}

/* Present but set aside by hand: the quietest mark there is. */
.badge-quiet {
    border-style: dashed;
}

/* A one-letter mark: D, N, @. Square, since a one-letter pill reads as a
   bullet; the word it stands for is its title. */
.badge-initial {
    min-width: 1.35rem;
    padding: 0.1rem 0.25rem;
    border-radius: var(--r-control);
    font-size: 0.68rem;
    letter-spacing: 0;
    text-align: center;
}

a.badge:hover {
    border-color: var(--blue);
    color: var(--blue);
}

/* Free-form tags: a wrapping row of small neutral pills. */
.tag-chips {
    display: flex;
    flex-wrap: wrap;
    gap: 0.25rem 0.35rem;
    margin: 0.3rem 0;
}

.tag-chip {
    display: inline-block;
    padding: 0.05rem 0.45rem;
    background: var(--slate);
    border: var(--edge);
    border-radius: var(--r-pill);
    color: var(--muted);
    font-size: 0.7rem;
}

/* Stats: a figure over its label over a muted line, on a slate tile. */
.stats,
.stat-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(var(--tile-min), 1fr));
    gap: 0.7rem;
    margin-top: 0.7rem;
}

.stat,
.stat-tile {
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    padding: 0.7rem 0.8rem;
    background: var(--slate);
    border: var(--edge);
    border-radius: var(--r-card);
}

/* The figure leads whatever order the markup gives it. */
.stat-value {
    order: -1;
    color: var(--ink);
    font-size: 1.5rem;
    font-weight: 600;
    line-height: 1.15;
}

.stat-label {
    color: var(--ink);
    font-size: 0.8rem;
    font-weight: 500;
}

.stat-sub {
    color: var(--muted);
    font-size: 0.72rem;
    font-style: italic;
}

/* Many tiles read as two calm rows of four, not one crowded strip. */
@media (width >= 768px) {
    .stat-grid-hero {
        grid-template-columns: repeat(4, 1fr);
    }
}

/* Charts: every mark is the data hue on the track, and every distinction a
   chart draws has a key under it. */

/* A bar chart: label, proportional track, value; every track starts at one x. */
.bar-chart {
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
}

.bar-row {
    display: grid;
    grid-template-columns: minmax(7rem, 12rem) 1fr auto;
    align-items: center;
    gap: 0.6rem;
}

.bar-label {
    overflow: hidden;
    font-size: 0.82rem;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.bar-track {
    height: 14px;
    overflow: hidden;
    background: var(--track);
    border-radius: var(--r-bar);
}

.bar-fill {
    display: block;
    min-width: 2px;
    height: 100%;
    background: var(--data);
    border-radius: var(--r-bar);
}

.bar-value {
    color: var(--muted);
    font-size: 0.78rem;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

/* A share: proportional segments in one track, each keyed in the legend. A
   segment names its step, so leaving out an empty one moves no colour; the
   track is what is left. */
.segment-bar {
    display: flex;
    max-width: 480px;
    height: 16px;
    overflow: hidden;
    background: var(--track);
    border-radius: var(--r-bar);
}

.segment {
    height: 100%;
    background: var(--data);
}

/* The key under a share bar: a swatch, then its name and count. */
.legend {
    display: flex;
    gap: 1rem;
    margin-top: 0.45rem;
    font-size: 0.75rem;
}

.swatch {
    display: inline-block;
    width: 11px;
    height: 11px;
    margin-right: 0.15rem;
    background: var(--data);
    border-radius: var(--r-mark);
    vertical-align: -0.05em;
}

.segment-2,
.swatch-2 {
    background: var(--data-2);
}

.segment-3,
.swatch-3 {
    background: var(--data-1);
}

.swatch-track {
    background: var(--track);
}

/* A sparkline: one series, no axes, no label; the line of text beside it
   states the figure. A link, so the hit area hugs the drawing, and the ring
   clears the stroke the SVG lets overflow. */
.sparkline {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    width: fit-content;
    margin: 0.3rem 0;
    outline-offset: 3px;
}

.sparkline svg {
    width: 120px;
    height: 28px;
    overflow: visible;
}

.sparkline polyline {
    fill: none;
    stroke: var(--data);
    stroke-width: 1.5;
    stroke-linecap: round;
    stroke-linejoin: round;
    vector-effect: non-scaling-stroke;
}

/* A 1.5px line has no room for an underline; hover thickens the stroke. */
.sparkline:is(:hover, :focus-visible) polyline {
    stroke: var(--data-deep);
    stroke-width: 2.5;
}

/* A chart: an SVG on a fixed viewBox that scales to the card. The labels are
   in viewBox units, so they scale with the drawing; strokes do not. */
.chart {
    margin: 1rem 0 0;
}

.chart svg {
    width: 100%;
    height: auto;
}

.chart-grid,
.chart-axis {
    stroke: var(--line);
    stroke-width: 1;
    vector-effect: non-scaling-stroke;
}

.chart-area {
    fill: var(--data);
    fill-opacity: 0.12;
    stroke: none;
}

.chart-line {
    fill: none;
    stroke: var(--data);
    stroke-width: 2;
    stroke-linecap: round;
    stroke-linejoin: round;
    vector-effect: non-scaling-stroke;
}

/* Reference lines, each keyed in the legend: the peak solid red, the average
   dashed muted. */
.chart-peak {
    stroke: var(--red);
    stroke-width: 1;
    vector-effect: non-scaling-stroke;
}

.chart-average {
    stroke: var(--muted);
    stroke-width: 1;
    stroke-dasharray: 4 3;
    vector-effect: non-scaling-stroke;
}

/* A point's hover target: invisible until pointed at, so a dense series
   reads as a curve rather than a string of beads. */
.chart-point {
    fill: var(--data);
    fill-opacity: 0;
}

.chart-point:hover {
    fill-opacity: 1;
}

/* An axis label: text-anchor on the element places it. */
.chart-label {
    fill: var(--muted);
    font-size: 11px;
    font-variant-numeric: tabular-nums;
}

.chart-legend {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.35rem;
    margin-top: 0.4rem;
    font-size: 0.85rem;
}

.chart-key {
    display: inline-block;
    width: 14px;
    height: 0;
    border-top: 2px solid var(--red);
}

.chart-key-average {
    border-top-style: dashed;
    border-top-color: var(--muted);
}

/* A heatmap: a day by hour grid, each cell a step of the data hue. */
.heatmap-scroll {
    padding-bottom: 0.25rem;
    overflow-x: auto;
}

.heatmap {
    display: grid;
    grid-template-columns: auto repeat(24, 16px);
    align-items: center;
    gap: 3px;
    width: max-content;
}

.heatmap-hour {
    color: var(--muted);
    font-size: 0.62rem;
    line-height: 1;
    text-align: left;
}

.heatmap-day {
    padding-right: 0.4rem;
    color: var(--muted);
    font-size: 0.68rem;
    text-align: right;
    white-space: nowrap;
}

.heatmap-cell {
    width: 16px;
    height: 16px;
    background: var(--track);
    border-radius: var(--r-mark);
}

.heatmap-l1 {
    background: var(--data-1);
}

.heatmap-l2 {
    background: var(--data-2);
}

.heatmap-l3 {
    background: var(--data-3);
}

.heatmap-l4 {
    background: var(--data);
}

.heatmap-legend {
    display: flex;
    align-items: center;
    gap: 4px;
    margin-top: 0.6rem;
    font-size: 0.7rem;
}

.heatmap-legend .heatmap-cell {
    width: 13px;
    height: 13px;
}

/* Cards in a grid: one entry each, a thumbnail over a body whose foot is
   pinned, so every card's last line sits at one height. */
.cards {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(var(--card-min), 1fr));
    gap: 1rem;
    margin-top: 0.85rem;
}

.card {
    display: flex;
    flex-direction: column;
    overflow: hidden;
    background: var(--surface);
    border: var(--edge);
    border-radius: var(--r-tile);
    transition:
        box-shadow 0.15s,
        transform 0.15s;
}

.card:hover {
    box-shadow: var(--shadow-hover);
    transform: translateY(-2px);
}

/* 16:9, with the data hue behind it for the moment an image is missing. */
.card-media {
    position: relative;
    display: block;
    aspect-ratio: 16 / 9;
    overflow: hidden;
    background: var(--data);
}

.card-media img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

/* A figure over the thumbnail's corner: a duration, a count. */
.card-overlay {
    position: absolute;
    right: 0.5rem;
    bottom: 0.5rem;
    padding: 0.1rem 0.45rem;
    background: var(--scrim);
    border-radius: var(--r-control);
    color: var(--surface);
    font-size: 0.72rem;
    font-weight: 600;
}

.card-body {
    display: flex;
    flex: 1;
    flex-direction: column;
    gap: 0.35rem;
    padding: 0.7rem 0.8rem 0.85rem;
}

.card-head {
    display: flex;
    align-items: center;
    gap: 0.55rem;
}

.avatar {
    flex: 0 0 auto;
    width: 38px;
    height: 38px;
    object-fit: cover;
    border-radius: 50%;
}

.card-title {
    overflow: hidden;
    color: var(--ink);
    font-size: 0.95rem;
    font-weight: 600;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.card-title:hover {
    color: var(--blue);
}

.card-sub {
    font-size: 0.78rem;
}

/* Two lines, clamped and held open, so every card's rows line up. */
.card-text {
    display: -webkit-box;
    min-height: 2.6em;
    overflow: hidden;
    color: var(--muted);
    font-size: 0.82rem;
    line-height: 1.3;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    line-clamp: 2;
}

.card-foot {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.5rem;
    margin-top: auto;
    padding-top: 0.4rem;
}

/* Code block: scrolls at 30rem rather than stretching the page. */
.code-block {
    max-height: 30rem;
    padding: 0.75rem;
    overflow: auto;
    background: var(--slate);
    border: var(--edge);
    border-radius: var(--r-control);
    font-size: 0.8rem;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}

@media (width <= 768px) {
    .nav-container {
        gap: 0.75rem;
        padding: 0.5rem 0.9rem;
    }

    .nav-logo a {
        font-size: 1.05rem;
    }

    .nav-links {
        gap: 0.9rem;
    }

    .nav-link {
        font-size: 0.8rem;
    }

    .container {
        padding: 0 0.7rem;
    }

    .profile-card {
        padding: 0.8rem 0.75rem;
    }

    .profile-info strong {
        display: block;
        width: auto;
    }

    /* A table whose cells carry data-mobile-label becomes a list of small
       cards: the first cell is the card's title, full width over a line, and
       each labelled cell is a label/value pair. A head with sortable columns
       stays as a row of sort chips; any other head is clipped from view but
       read aloud. A table without the attribute keeps its shape and scrolls
       sideways. */
    .data-table:has(td[data-mobile-label]) {
        display: block;
        background: transparent;
        box-shadow: none;
    }

    .data-table:has(td[data-mobile-label]) thead {
        display: block;
    }

    .data-table:has(td[data-mobile-label]) thead:not(:has(th[data-sort])) {
        position: absolute;
        width: 1px;
        height: 1px;
        overflow: hidden;
        clip-path: inset(50%);
    }

    .data-table:has(td[data-mobile-label]) thead tr {
        display: flex;
        flex-wrap: wrap;
        align-items: center;
        gap: 0.3rem;
        margin-bottom: 0.55rem;
    }

    .data-table:has(td[data-mobile-label]) thead tr::before {
        content: "Sort by";
        color: var(--muted);
        font-size: 0.7rem;
    }

    /* Roomier than the desktop head: these are the only tap targets that
       replace clicking a column heading. */
    .data-table:has(td[data-mobile-label]) thead th {
        padding: 0.4rem 0.7rem;
        background: var(--surface);
        border: var(--edge);
        border-radius: var(--r-pill);
        font-size: 0.72rem;
    }

    .data-table:has(td[data-mobile-label]) thead th:not([data-sort]) {
        display: none;
    }

    .data-table:has(td[data-mobile-label]) thead th[aria-sort] {
        background: var(--slate);
        border-color: var(--muted);
        color: var(--ink);
    }

    .data-table:has(td[data-mobile-label]) :is(tbody, tbody tr, tbody td) {
        display: block;
    }

    .data-table:has(td[data-mobile-label]) tbody tr {
        margin-bottom: 0.5rem;
        padding: 0.55rem 0.7rem;
        background: var(--surface);
        border: var(--edge);
        border-radius: var(--r-card);
    }

    .data-table-zebra:has(td[data-mobile-label]) tbody tr:nth-child(even) {
        background: var(--surface);
    }

    .data-table:has(td[data-mobile-label]) tbody tr:last-child {
        margin-bottom: 0;
    }

    .data-table:has(td[data-mobile-label]) tbody td {
        padding: 0.12rem 0;
        border-bottom: none;
    }

    .data-table:has(td[data-mobile-label]) tbody td:first-child {
        margin-bottom: 0.3rem;
        padding-bottom: 0.4rem;
        border-bottom: var(--edge);
        font-size: 0.9rem;
    }

    .data-table:has(td[data-mobile-label]) tbody td[data-mobile-label] {
        display: flex;
        align-items: baseline;
        justify-content: space-between;
        gap: 0.75rem;
    }

    .data-table:has(td[data-mobile-label]) tbody td[data-mobile-label]::before {
        content: attr(data-mobile-label);
        flex: 0 0 auto;
        color: var(--muted);
        font-family: var(--sans);
        font-weight: 500;
    }

    .data-table:has(td[data-mobile-label]) td.row-actions {
        margin-top: 0.55rem;
        text-align: left;
    }

    .data-table:has(td[data-mobile-label]) .row-menu-items {
        margin-left: 0;
    }
}

/* The heatmap's fixed columns need about 490px. Below 560px they go fluid
   and the cells stay square, so all 24 hours stay on screen: iOS draws no
   resting scrollbar, and a clipped grid reads as broken. minmax(0, 16px),
   not 1fr, so a column never grows past the desktop cell. */
@media (width <= 560px) {
    .heatmap {
        grid-template-columns: auto repeat(24, minmax(0, 16px));
        gap: 2px;
        width: 100%;
    }

    .heatmap .heatmap-cell {
        width: auto;
        height: auto;
        aspect-ratio: 1;
    }

    .heatmap-hour {
        font-size: 0.55rem;
    }

    .heatmap-day {
        padding-right: 0.3rem;
        font-size: 0.62rem;
    }
}
