.view {
    display: flex;
    flex-direction: column;
    gap: var(--gap);
    max-width: 1400px;
}

.view-header {
    display: flex;
    flex-direction: column;
    gap: 4px;
}

.view-title {
    margin: 0;
    font-size: 20px;
    font-weight: 600;
}

.view-note {
    margin: 0;
    max-width: 70ch;
    color: var(--text-secondary);
    font-size: 13px;
}

.section-title {
    margin: 0 0 10px;
    font-size: 15px;
    font-weight: 600;
}

.state-message,
.empty-message {
    margin: 0;
    padding: 16px 0;
    color: var(--text-muted);
}

/* --- monthly history controls --------------------------------------------- */

.controls {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    gap: var(--gap);
    padding: 14px;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--surface-1);
}

.control {
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.control-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 10px;
}

.control-label {
    color: var(--text-secondary);
    font-size: 12px;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

.control-body {
    display: flex;
    gap: 10px;
}

.control-display,
.control-metric,
.control-total,
.control-size,
.control-run {
    justify-content: flex-start;
}

.control-search {
    flex: 0 1 220px;
}

.control-mods {
    flex: 1 1 320px;
    min-width: 260px;
}

/* Nothing here feeds the chart while the total is plotted on its own. */
.control-mods.is-disabled {
    opacity: 0.5;
}

.control-mods.is-disabled .checklist-label {
    cursor: default;
}

.control-mods.is-disabled .checklist-label:hover {
    background: none;
}

.checklist-counter {
    color: var(--text-muted);
    font-size: 12px;
    font-variant-numeric: tabular-nums;
}

.checklist-tools {
    display: flex;
    gap: 6px;
}

.checklist-filter {
    flex: 1;
    min-width: 0;
}

.checklist-list {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
    gap: 2px 12px;
    max-height: 190px;
    overflow-y: auto;
    margin: 0;
    padding: 6px;
    list-style: none;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--surface-2);
}

.checklist-label {
    display: flex;
    align-items: baseline;
    gap: 6px;
    padding: 2px 4px;
    border-radius: 4px;
    cursor: pointer;
}

.checklist-label:hover {
    background: var(--surface-1);
}

.checklist-name {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.checklist-code {
    margin-left: auto;
    color: var(--text-muted);
    font-family: var(--font-mono);
    font-size: 11px;
}

.monthly-output {
    display: flex;
    flex-direction: column;
    gap: var(--gap);
}

.timing-strip,
.crowded-message {
    margin: 0;
    padding: 8px 12px;
    border: 1px solid var(--border);
    border-left: 3px solid var(--accent);
    border-radius: var(--radius);
    background: var(--surface-1);
    color: var(--text-secondary);
    font-size: 13px;
}

/* Wraps and scrolls, so "All" with sixty mods stays a legend rather than a wall. */
.chart-legend {
    display: flex;
    flex-wrap: wrap;
    gap: 4px 16px;
    max-height: 108px;
    overflow-y: auto;
    margin: 0;
    padding: 0;
    list-style: none;
}

.legend-item {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 12px;
}

.legend-swatch {
    flex: 0 0 auto;
    width: 18px;
    height: 3px;
    border-radius: 1px;
}

.legend-label {
    color: var(--text-secondary);
}

.monthly-table .data-table {
    min-width: max-content;
}

/* --- admin ---------------------------------------------------------------- */

.admin-create {
    padding: 14px;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--surface-1);
}

.admin-form {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: 12px;
}

.admin-form .button {
    align-self: flex-end;
}

.form-message {
    flex-basis: 100%;
    margin: 0;
    color: var(--danger);
    font-size: 13px;
}

.form-message.is-success {
    color: var(--success);
}

/* --- user data ------------------------------------------------------------ */

.user-output {
    display: flex;
    flex-direction: column;
    gap: var(--gap);
}

/* The elapsed counter ticks ten times a second; fixed-width digits keep it still. */
.timing-strip {
    font-variant-numeric: tabular-nums;
}

/* A query runs for seconds: the table it is replacing stays readable but recedes. */
.userdata-table.is-stale {
    opacity: 0.45;
}

.view-userdata.is-busy .controls {
    cursor: progress;
}

.data-table th.sortable {
    padding: 0;
}

.sort-button {
    display: flex;
    align-items: center;
    gap: 4px;
    width: 100%;
    padding: 7px 12px;
    border: 0;
    background: none;
    color: inherit;
    font: inherit;
    letter-spacing: inherit;
    text-transform: inherit;
    cursor: pointer;
}

.data-table th.numeric .sort-button {
    justify-content: flex-end;
}

.sort-button:hover {
    color: var(--text-primary);
}

.sort-button:disabled {
    cursor: default;
}

.data-table th.is-active .sort-button {
    color: var(--accent);
}

.sort-arrow {
    font-size: 11px;
}

/* An id with no nickname is named by its id, which is not the name of a person. */
.no-nickname {
    color: var(--text-muted);
    font-variant-numeric: tabular-nums;
}

.pager {
    display: flex;
    align-items: center;
    gap: 10px;
}

.pager-status {
    color: var(--text-secondary);
    font-variant-numeric: tabular-nums;
}

/* --- per-user transactions ------------------------------------------------- */

/* The toggle column carries one button and should not claim table width. */
.data-table th.detail-head,
.data-table td.detail-toggle {
    width: 1%;
    padding: 0;
}

.detail-toggle-button {
    width: 28px;
    height: 28px;
    border: 0;
    background: none;
    color: var(--text-muted);
    font: inherit;
    line-height: 1;
    cursor: pointer;
}

.detail-toggle-button:hover:not(:disabled) {
    color: var(--accent);
}

.detail-toggle-button:disabled {
    cursor: default;
    opacity: 0.5;
}

.data-table tbody tr.is-expanded {
    background: var(--surface-2);
}

/* The detail row is a container, not a cell: its padding lives inside the panel.
   The table's own row hover would otherwise light the whole panel up. */
.transaction-detail > td,
.data-table tbody tr.transaction-detail:hover {
    padding: 0;
    background: var(--surface-0);
}

.transaction-panel {
    display: flex;
    flex-direction: column;
    gap: 10px;
    padding: 12px;
    border-left: 3px solid var(--accent);
}

.transaction-panel.is-busy {
    cursor: progress;
}

.transaction-head {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 4px var(--gap);
    color: var(--text-secondary);
    font-size: 13px;
}

.transaction-status {
    font-variant-numeric: tabular-nums;
}

/* A hundred rows inside a table row scroll in their own box rather than pushing
   every other user off the screen. The shared sticky header sticks to this. */
.transaction-scroll {
    max-height: 420px;
    overflow-y: auto;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--surface-1);
}

.transaction-table .transaction-time {
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
}

/* Game and channel are free text from the modules: they wrap rather than
   stretching the panel to the width of the longest one. */
.transaction-table .transaction-text {
    max-width: 22ch;
    overflow-wrap: anywhere;
}

/* Tinted, never tinted alone -- the word is in the cell either way. */
.transaction-table .type-in {
    color: var(--success);
}

.transaction-table .type-out {
    color: var(--danger);
}

.transaction-pager {
    display: flex;
    gap: 8px;
}

/* --- purchases -------------------------------------------------------------- */

/* Panels are hidden rather than destroyed, so switching tabs keeps their state. */
.panel[hidden] {
    display: none;
}

.purchase-output {
    display: flex;
    flex-direction: column;
    gap: 12px;
    min-height: 0;
}

/* --- grouped checkbox list (Verlauf) --- */

.control-purchase-series {
    flex: 1 1 420px;
    min-width: 280px;
}

.checklist-groups {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
    gap: 8px;
}

.checklist-group {
    display: flex;
    flex-direction: column;
    gap: 4px;
    min-width: 0;
}

.checklist-group-head {
    display: flex;
    align-items: center;
    gap: 6px;
}

.checklist-group-label {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    color: var(--text-secondary);
    font-size: 12px;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* A box switched off because its metric would be a third axis. It stays readable --
   the point is that it is unavailable, not that it is gone. */
.checklist-item.is-disabled .checklist-label {
    cursor: default;
    opacity: 0.45;
}

.checklist-item.is-disabled .checklist-label:hover {
    background: none;
}

/* --- Kaeufe --- */

.purchase-results,
.retention-results {
    display: flex;
    flex-direction: column;
    gap: 2px;
    max-height: 280px;
    margin: 0;
    padding: 0;
    overflow-y: auto;
    list-style: none;
}

.purchase-result,
.retention-result {
    display: flex;
    align-items: baseline;
    gap: 12px;
    width: 100%;
    padding: 6px 8px;
    border: 1px solid transparent;
    border-radius: var(--radius);
    background: none;
    color: inherit;
    font: inherit;
    text-align: left;
    cursor: pointer;
}

.purchase-result:hover,
.retention-result:hover {
    border-color: var(--border);
    background: var(--surface-2);
}

.purchase-result-name,
.retention-result-name {
    font-weight: 600;
}

.purchase-result-meta,
.retention-result-meta {
    color: var(--text-muted);
    font-size: 12px;
    font-variant-numeric: tabular-nums;
}

.purchase-detail {
    display: flex;
    flex-direction: column;
    gap: 10px;
    min-height: 0;
}

.purchase-summary {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 12px;
}

.purchase-summary-id {
    color: var(--text-muted);
    font-size: 12px;
    font-variant-numeric: tabular-nums;
}

.purchase-summary-figures {
    color: var(--text-secondary);
    font-variant-numeric: tabular-nums;
}

.purchase-note {
    margin: 0;
    color: var(--text-muted);
    font-size: 12px;
}

.purchase-status {
    color: var(--text-muted);
    font-size: 12px;
    font-variant-numeric: tabular-nums;
}

/* The transaction id is long and nobody reads it in full; it is there to be copied. */
.purchase-table td:last-child {
    max-width: 260px;
    overflow: hidden;
    color: var(--text-muted);
    font-family: var(--font-mono);
    font-size: 12px;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* --- Verteilung --- */

.distribution-split {
    display: grid;
    grid-template-columns: minmax(280px, 1fr) minmax(280px, 1fr);
    gap: 24px;
    align-items: start;
}

@media (max-width: 900px) {
    .distribution-split {
        grid-template-columns: 1fr;
    }
}

/* A doughnut has no long axis to fill, so it is squarer than the line chart. */
.chart-frame-pie {
    height: 320px;
}

.distribution-table .is-total th,
.distribution-table .is-total td {
    border-top: 2px solid var(--border-strong);
    font-weight: 600;
}

/* --- Heatmap --- */

.heatmap-summary {
    margin: 0;
    color: var(--text-secondary);
    font-variant-numeric: tabular-nums;
}

.heatmap-grid {
    display: grid;
    /* Seven columns: days 1-28 make four full rows and 29-31 a short fifth. Not a
       calendar -- the columns are not weekdays. */
    grid-template-columns: repeat(7, minmax(0, 1fr));
    gap: 4px;
    max-width: 760px;
}

.heatmap-cell {
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    aspect-ratio: 1.4;
    padding: 6px 8px;
    border-radius: var(--radius);
    /* The default before a step paints over it: a cell that never got a value must
       not inherit the previous render's colour. */
    background: var(--surface-2);
    color: var(--text-primary);
    font-variant-numeric: tabular-nums;
}

/* A day the month does not have. Not a zero -- there is nothing there to count. */
.heatmap-cell.is-absent {
    background: none;
    box-shadow: none;
}

/* A day whose purchases all predate price reporting. */
.heatmap-cell.is-unknown {
    background: var(--surface-2);
    border: 1px dashed var(--border);
    color: var(--text-muted);
}

.heatmap-day {
    font-size: 11px;
    opacity: 0.75;
}

.heatmap-value {
    font-size: 13px;
    font-weight: 600;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* --- freeplay -------------------------------------------------------------- */

/*
 * The filter bar. Data selection is the first group and stays first at every width:
 * the month range and the payin threshold are what everything else is conditional on,
 * so they are read before the game and the amount are chosen.
 */
.freeplay-bar {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    gap: var(--gap);
}

.freeplay-group {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: 12px;
    margin: 0;
    padding: 10px 14px 14px;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--surface-1);
}

.freeplay-group > legend {
    padding: 0 6px;
    color: var(--text-secondary);
    font-size: 12px;
    font-weight: 600;
    letter-spacing: 0.03em;
    text-transform: uppercase;
}

.freeplay-data-host {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    gap: 12px;
}

.field-narrow .field-input {
    width: 72px;
}

.freeplay-group .game-input {
    width: 110px;
}

.field-check {
    flex-direction: row;
    align-items: center;
    gap: 6px;
}

/*
 * The notes stack. `.view-note` carries margin: 0 because most views place one note
 * on its own; here there are up to three in a row -- the ordering rule, how stale the
 * nickname snapshot is, and whether the chosen game is known to `moduls` -- and run
 * together they read as one paragraph nobody finishes.
 */
.panel-freeplay-prepare > .view-note {
    margin: 4px 0;
}

/* The note that states the one rule of the feature: highest amount first. */
.order-note {
    max-width: 78ch;
}

/* The block this sits above is the cohort; give it room to read as a heading for it. */
.panel-freeplay-prepare > .summary-strip {
    margin-top: 10px;
}

/*
 * Nothing on this page is colour-only. The warning box is tinted AND says what it
 * means in words, because the thing it is warning about -- users permanently stranded
 * on a smaller amount -- cannot be undone by writing again.
 */
.overlap-box {
    padding: 12px 14px;
    border: 1px solid var(--border);
    border-left: 3px solid var(--border-strong);
    border-radius: var(--radius);
    background: var(--surface-1);
}

.overlap-box.is-warning {
    border-left-color: var(--danger);
}

.overlap-head {
    margin: 0;
    font-weight: 600;
}

.overlap-warning {
    margin: 6px 0 0;
    color: var(--danger);
}

.overlap-note,
.game-note {
    margin: 6px 0 0;
    color: var(--text-secondary);
}

.summary-strip {
    font-variant-numeric: tabular-nums;
}

/* Same recede-while-querying treatment the User Data table gets. */
.freeplay-table.is-stale {
    opacity: 0.45;
}

.view-freeplay.is-busy .freeplay-bar {
    cursor: progress;
}

/* A user with no knuddels_nicks row is in the cohort all the same -- quieter, not absent. */
.data-table td.is-muted {
    color: var(--text-muted);
}

/* --- freeplay log ---------------------------------------------------------- */

.log-panel {
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.log-title {
    margin: 0;
    color: var(--text-secondary);
    font-size: 12px;
    font-weight: 600;
    letter-spacing: 0.03em;
    text-transform: uppercase;
}

/*
 * Fixed height and its own scroll: the log is a running account read at a glance
 * while a query runs, not a document. Letting it grow would push the table it is
 * describing off the screen.
 */
.log-list {
    height: 160px;
    margin: 0;
    padding: 8px 10px;
    overflow-y: auto;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--surface-2);
    list-style: none;
    font-family: var(--font-mono);
    font-size: 12px;
    line-height: 1.6;
}

.log-line {
    display: flex;
    gap: 8px;
}

/* The elapsed counter redraws ten times a second; fixed-width digits keep it still. */
.log-stamp {
    flex: none;
    color: var(--text-muted);
    font-variant-numeric: tabular-nums;
}

.log-text {
    font-variant-numeric: tabular-nums;
    overflow-wrap: anywhere;
}

.log-good .log-text {
    color: var(--success);
}

.log-bad .log-text {
    color: var(--danger);
}

/* --- retention -------------------------------------------------------------- */

/*
 * The filter bar. Same fieldset grouping Freeplay uses, and for the same reason: the two
 * windows are separate questions and grouping them stops "Tage" from appearing twice in a
 * row with nothing saying which is which.
 */
.retention-bar {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    gap: var(--gap);
}

.retention-group {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: 12px;
    margin: 0;
    padding: 10px 14px 14px;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--surface-1);
}

.retention-group > legend {
    padding: 0 6px;
    color: var(--text-secondary);
    font-size: 12px;
    font-weight: 600;
    letter-spacing: 0.03em;
    text-transform: uppercase;
}

.retention-data-host {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: 12px;
}

/*
 * The unit, printed beside the threshold rather than left in the dropdown. On Kaeufe a
 * threshold of 1000 is a thousand euro under Preis and a thousand PACKAGES under Knuddel,
 * and those two readings are nothing like each other.
 */
.retention-unit {
    padding-bottom: 7px;
    color: var(--text-secondary);
    font-size: 12px;
}

/* The count is the answer this page exists to give; the list under it is the evidence. */
.retention-count {
    margin: 0;
    font-size: 20px;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
}

.retention-output {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 12px;
}

/* Same recede-while-stale treatment the User Data and Freeplay tables get. */
.retention-output.is-stale {
    opacity: 0.45;
}

.panel-retention.is-busy .retention-bar {
    cursor: progress;
}

/* Both windows named by their real dates: "die letzten 14 Tage" of WHEN is the one thing
   a reader cannot check for themselves, and is where an off-by-one would hide. */
.retention-windows {
    display: flex;
    flex-wrap: wrap;
    gap: var(--gap);
}

.retention-window {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 200px;
    padding: 8px 12px;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--surface-1);
}

.retention-window-name {
    color: var(--text-secondary);
    font-size: 12px;
    font-weight: 600;
    letter-spacing: 0.03em;
    text-transform: uppercase;
}

.retention-window-dates {
    color: var(--text-muted);
    font-size: 12px;
    font-variant-numeric: tabular-nums;
}

.retention-window-total {
    font-variant-numeric: tabular-nums;
}

.retention-results {
    width: 100%;
    max-width: 640px;
}

/* A user with no knuddels_nicks row is in the cohort all the same -- quieter, not absent. */
.retention-result-name.is-muted {
    color: var(--text-muted);
    font-weight: 400;
}

.retention-detail {
    display: flex;
    flex-direction: column;
    gap: 8px;
    width: 100%;
    max-width: 640px;
}

.retention-detail-who {
    margin: 0;
    font-weight: 600;
}

.retention-detail-id {
    color: var(--text-muted);
    font-weight: 400;
    font-variant-numeric: tabular-nums;
}
