:root {
    /* Whole-app black theme - the map viewport is the one deliberate
       exception (it keeps its light basemap tiles, see map.js), so none of
       the map/overlay-on-map rules below (#map, .zoom-hint,
       .run-filter-banner) pull from these. */
    --color-bg: #000000;
    --color-panel: #000000;
    --color-border: #333333;
    --color-text: #e6e6e6;
    --color-text-muted: #9a9a9a;
    --color-primary: #8b5cf6;
    --color-primary-dark: #6d28d9;
    --color-input-bg: #0d0d0d;
    --color-hover-bg: #1a1a1a;
    /* Plasma colormap samples (yellow -> orange -> violet, low -> high
       threshold), same color language as the map's hex fill scale (see
       PLASMA_STOPS in map.js). */
    --color-explored-bg: #f6e423;
    --color-claimed-bg: #f89640;
    --color-locked-bg: #7d07a6;
    --color-stat-text: #ffffff;
    --radius: 10px;
}

* {
    box-sizing: border-box;
}

html, body {
    height: 100%;
}

body {
    margin: 0;
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
    background: var(--color-bg);
    color: var(--color-text);
    /* Lets supporting browsers render native controls (scrollbars, date
       picker popups, checkboxes, the default look of a <select>) in their
       dark variant by default, instead of a light control floating on this
       otherwise-black theme. */
    color-scheme: dark;
}

a {
    color: var(--color-primary);
}

h1, h2 {
    margin: 0 0 8px;
}

/* Auth pages */
.auth-body {
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 100vh;
    padding: 16px;
}

.auth-card {
    background: var(--color-panel);
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
    padding: 32px;
    width: 100%;
    max-width: 380px;
    box-shadow: 0 4px 20px rgba(255, 255, 255, 0.04);
}

.auth-subtitle {
    color: var(--color-text-muted);
    margin: 0 0 20px;
    font-size: 14px;
}

.auth-footer {
    margin-top: 16px;
    font-size: 14px;
    text-align: center;
    color: var(--color-text-muted);
}

form label {
    display: block;
    font-size: 13px;
    font-weight: 600;
    margin: 12px 0 4px;
}

form input[type="text"],
form input[type="email"],
form input[type="password"],
form input[type="file"] {
    width: 100%;
    padding: 9px 10px;
    border: 1px solid var(--color-border);
    border-radius: 6px;
    font-size: 14px;
    background: var(--color-input-bg);
    color: var(--color-text);
}

.btn {
    display: inline-block;
    border: none;
    border-radius: 6px;
    padding: 10px 16px;
    font-size: 14px;
    font-weight: 600;
    cursor: pointer;
    text-decoration: none;
    text-align: center;
}

.btn-primary {
    background: var(--color-primary);
    color: #fff;
    width: 100%;
    margin-top: 16px;
}

.btn-primary:hover {
    background: var(--color-primary-dark);
}

.btn-secondary {
    background: var(--color-hover-bg);
    color: var(--color-text);
    width: 100%;
    margin-top: 10px;
}

.btn-ghost {
    background: transparent;
    color: var(--color-text-muted);
    border: 1px solid var(--color-border);
    padding: 6px 12px;
}

.alert {
    border-radius: 6px;
    padding: 10px 12px;
    margin-bottom: 12px;
    font-size: 13px;
}

.alert-error {
    background: #3a1414;
    color: #ff8080;
    border: 1px solid #5c2020;
}

.alert-success {
    background: #123420;
    color: #7ee6a0;
    border: 1px solid #1e5c38;
}

/* App shell */
.app-header {
    height: 56px;
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 0 20px;
    background: var(--color-panel);
    border-bottom: 1px solid var(--color-border);
}

.app-header-title {
    font-weight: 700;
    font-size: 18px;
}

.app-header-user {
    display: flex;
    align-items: center;
    gap: 12px;
    font-size: 14px;
    color: var(--color-text-muted);
}

.app-layout {
    display: flex;
    height: calc(100vh - 56px);
}

.sidebar {
    width: 432px; /* 320px + 35% */
    flex-shrink: 0;
    overflow-y: auto;
    padding: 16px;
    border-right: 1px solid var(--color-border);
    background: var(--color-panel);
}

.panel {
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
    padding: 14px;
    margin-bottom: 16px;
    background: var(--color-panel);
}

.panel h2 {
    font-size: 14px;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    color: var(--color-text-muted);
}

.panel-hint {
    font-size: 12px;
    color: var(--color-text-muted);
    margin: 0 0 8px;
}

.stats-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 8px;
    margin-bottom: 8px;
}

.stat-tile {
    display: flex;
    flex-direction: column;
    align-items: center;
    padding: 10px 4px;
    border-radius: 8px;
}

.stat-value {
    font-size: 20px;
    font-weight: 700;
}

.stat-label {
    font-size: 11px;
    margin-top: 2px;
}

.stat-explored { background: var(--color-explored-bg); }
.stat-claimed { background: var(--color-claimed-bg); }
.stat-locked { background: var(--color-locked-bg); }

.stat-tile .stat-value, .stat-tile .stat-label { color: var(--color-stat-text); }

.opacity-control {
    display: flex;
    align-items: center;
    gap: 12px;
}

.opacity-control input[type="range"] {
    flex: 1;
    width: auto;
}

.opacity-value {
    font-size: 13px;
    font-weight: 600;
    color: var(--color-text-muted);
    min-width: 36px;
    text-align: right;
}

.upload-feedback {
    margin-top: 10px;
    padding: 10px;
    border-radius: 8px;
    background: var(--color-hover-bg);
    font-size: 13px;
}

.upload-feedback.is-error {
    background: #3a1414;
    color: #ff8080;
}

.upload-feedback dl {
    display: grid;
    grid-template-columns: auto 1fr;
    gap: 2px 8px;
    margin: 6px 0 0;
}

.upload-feedback dt {
    color: var(--color-text-muted);
}

.upload-feedback dd {
    margin: 0;
    font-weight: 600;
    text-align: right;
}

.runs-list {
    list-style: none;
    margin: 0;
    padding: 0;
    font-size: 13px;
}

.runs-list li {
    border-bottom: 1px solid var(--color-border);
}

.runs-list li:last-child {
    border-bottom: none;
}

.runs-list li.runs-empty {
    padding: 6px 0;
    color: var(--color-text-muted);
}

.run-row {
    display: flex;
    align-items: center;
    gap: 2px;
}

.run-item-btn {
    display: flex;
    flex-direction: column;
    gap: 2px;
    flex: 1;
    min-width: 0;
    padding: 6px 4px;
    border: none;
    background: none;
    font: inherit;
    text-align: left;
    cursor: pointer;
    border-radius: 6px;
}

.run-item-btn:hover {
    background: var(--color-hover-bg);
}

.run-item-btn.run-item-active {
    background: #14213d;
}

.run-edit-btn {
    flex-shrink: 0;
    border: none;
    background: none;
    cursor: pointer;
    font-size: 14px;
    line-height: 1;
    padding: 6px;
    border-radius: 6px;
    color: var(--color-text-muted);
}

.run-edit-btn:hover {
    background: var(--color-hover-bg);
    color: var(--color-text);
}

.run-link-btn {
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    text-decoration: none;
    font-size: 14px;
    line-height: 1;
    padding: 6px;
    border-radius: 6px;
    color: var(--color-text-muted);
}

.run-link-btn:hover {
    background: var(--color-hover-bg);
    color: var(--color-text);
}

.run-edit-row {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 6px 4px;
}

.run-edit-row-expanded {
    flex-direction: column;
    align-items: stretch;
    gap: 6px;
    padding: 8px 4px;
    background: var(--color-input-bg);
    border-radius: 6px;
}

.run-edit-input,
.run-edit-link-input,
.run-edit-shoe-select {
    width: 100%;
    padding: 5px 8px;
    border: 1px solid var(--color-border);
    border-radius: 6px;
    font-size: 13px;
    font: inherit;
    background: var(--color-input-bg);
    color: var(--color-text);
}

.run-edit-actions {
    display: flex;
    justify-content: flex-end;
    gap: 10px;
}

/* Pushed to the opposite end from Save/Cancel (still packed together via
   the parent's justify-content: flex-end) since it's a distinct, dangerous
   action rather than part of the same save-or-discard choice. */
.run-edit-delete {
    margin-right: auto;
}

.run-name {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.run-details {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 8px;
}

.run-date {
    color: var(--color-text-muted);
    font-size: 12px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.run-meta {
    color: var(--color-text-muted);
    flex-shrink: 0;
}

.runs-pagination {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-top: 8px;
    padding-top: 8px;
    border-top: 1px solid var(--color-border);
}

/* Same `display` vs. `[hidden]` gotcha as .modal-overlay/.run-filter-banner
   elsewhere in this file: without this, this element ignored being hidden
   while a hex filter was active (see app.js). */
.runs-pagination[hidden] {
    display: none;
}

.runs-pagination .btn-link:disabled {
    color: var(--color-text-muted);
    cursor: default;
}

.runs-page-info {
    font-size: 12px;
    color: var(--color-text-muted);
}

.runs-filters {
    display: flex;
    flex-direction: column;
    gap: 8px;
    margin-bottom: 10px;
    padding-bottom: 10px;
    border-bottom: 1px solid var(--color-border);
}

.runs-filters[hidden] {
    display: none;
}

.runs-filters input[type="text"] {
    width: 100%;
    padding: 6px 8px;
    border: 1px solid var(--color-border);
    border-radius: 6px;
    font-size: 13px;
    background: var(--color-input-bg);
    color: var(--color-text);
}

/* Shared by both the date-range and length-range filters (#date-range-filter
   and #length-range-filter) - same two-overlapping-range-inputs slider
   mechanic, just different data and units behind it. */
.dual-range-filter {
    display: flex;
    flex-direction: column;
    gap: 4px;
}

/* Same `display` vs. `[hidden]` gotcha as elsewhere in this file: without
   this, the unconditional `display: flex` above beats the browser's
   built-in `[hidden] { display: none }` rule. */
.dual-range-filter[hidden] {
    display: none;
}

.dual-range-labels {
    display: flex;
    justify-content: space-between;
    font-size: 11px;
    color: var(--color-text-muted);
}

.dual-range-label-wrap {
    display: inline-flex;
}

/* Idle state looks like plain text - only on hover/focus does it hint that
   clicking opens an exact date picker (see .dual-range-label-picker). */
.dual-range-label-text {
    cursor: pointer;
}

.dual-range-label-text:hover,
.dual-range-label-text:focus {
    color: var(--color-primary);
    text-decoration: underline;
}

.dual-range-label-picker {
    font-size: 11px;
    border: 1px solid var(--color-border);
    border-radius: 4px;
    padding: 0 2px;
    background: var(--color-input-bg);
    color: var(--color-text);
}

/* Two overlapping type="range" inputs sharing one visual track - the
   standard vanilla-JS dual-handle slider technique, since there's no native
   two-thumb range input. Each input is fully transparent/trackless and
   pointer-events: none except over its own thumb, so clicking/dragging
   lands on whichever thumb is actually under the cursor rather than
   whichever input element happens to be stacked on top. */
.dual-range-slider {
    position: relative;
    height: 20px;
    margin-top: 4px;
}

.dual-range-track,
.dual-range-track-active {
    position: absolute;
    top: 50%;
    height: 4px;
    transform: translateY(-50%);
    border-radius: 2px;
}

.dual-range-track {
    left: 0;
    right: 0;
    background: var(--color-border);
}

.dual-range-track-active {
    background: var(--color-primary);
}

.dual-range-input {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 20px;
    margin: 0;
    background: transparent;
    pointer-events: none;
    -webkit-appearance: none;
    appearance: none;
}

.dual-range-input::-webkit-slider-runnable-track {
    -webkit-appearance: none;
    background: transparent;
}

.dual-range-input::-moz-range-track {
    background: transparent;
    border: none;
}

.dual-range-input::-webkit-slider-thumb {
    -webkit-appearance: none;
    pointer-events: auto;
    width: 14px;
    height: 14px;
    border-radius: 50%;
    background: var(--color-primary);
    border: 2px solid #fff;
    box-shadow: 0 0 0 1px var(--color-primary);
    cursor: pointer;
    margin-top: 3px;
}

.dual-range-input::-moz-range-thumb {
    pointer-events: auto;
    width: 14px;
    height: 14px;
    border-radius: 50%;
    background: var(--color-primary);
    border: 2px solid #fff;
    box-shadow: 0 0 0 1px var(--color-primary);
    cursor: pointer;
}

.hex-filter-banner {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    background: var(--color-hover-bg);
    border-radius: 6px;
    padding: 8px 10px;
    margin-bottom: 10px;
    font-size: 12px;
}

.hex-filter-banner[hidden] {
    display: none;
}

.hex-filter-banner .btn-link {
    font-size: 12px;
    flex-shrink: 0;
}

.map-wrap {
    position: relative;
    flex: 1;
}

#map {
    height: 100%;
    width: 100%;
    background: #dfe6ea;
}

/* Vignette framing the map: fully transparent over the middle of each axis,
   graduating to solid black over the outer edge on each side - 10% on the
   top/bottom, 6% on the left/right. Four linear gradients (one per edge)
   rather than a single radial one, so the dark band is a straight-edged
   rectangular frame instead of an ellipse - each gradient is opaque black at
   its own edge and fully transparent by its own stop, and corners (where two
   edge-bands overlap) end up no darker than plain opaque black since
   compositing black-over-black is still just black. Sits above the map/hex
   canvas but below the zoom hint and run-filter banner (z-index 1000) so
   their text stays fully legible; pointer-events: none so it never
   intercepts map drag/zoom/click.
*/
.map-vignette {
    position: absolute;
    inset: 0;
    pointer-events: none;
    z-index: 500;
    background:
        linear-gradient(to right, rgba(0, 0, 0, 1) 0%, transparent 6%),
        linear-gradient(to left, rgba(0, 0, 0, 1) 0%, transparent 6%),
        linear-gradient(to bottom, rgba(0, 0, 0, 1) 0%, transparent 10%),
        linear-gradient(to top, rgba(0, 0, 0, 1) 0%, transparent 10%);
}

.zoom-hint {
    position: absolute;
    top: 12px;
    left: 50%;
    transform: translateX(-50%);
    background: rgba(34, 41, 47, 0.85);
    color: #fff;
    padding: 6px 14px;
    border-radius: 20px;
    font-size: 13px;
    z-index: 1000;
}

/* Deliberately hardcoded rather than var(--color-text): this banner floats
   over the map, which (unlike the rest of the app) stays in its own light
   color language, so it needs a light background with dark text regardless
   of the app's black theme. */
.run-filter-banner {
    position: absolute;
    top: 12px;
    left: 50%;
    transform: translateX(-50%);
    display: flex;
    align-items: center;
    gap: 12px;
    background: #fff;
    color: #22292f;
    padding: 8px 14px;
    border-radius: 20px;
    font-size: 13px;
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.15);
    z-index: 1000;
}

.run-filter-banner .btn-link {
    font-size: 13px;
}

/* Without this, the unconditional `display: flex` above beats the browser's
   built-in `[hidden] { display: none }` rule (author styles win over
   user-agent styles regardless of selector specificity), so this banner
   would ignore its `hidden` attribute and show up empty on every page load -
   same underlying bug as .modal-overlay had (see its own fix above). */
.run-filter-banner[hidden] {
    display: none;
}

/* Batch upload preview modal */
.modal-overlay {
    position: fixed;
    inset: 0;
    background: rgba(20, 24, 28, 0.55);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 2000;
    padding: 24px;
}

/* The `display: flex` above would otherwise beat the browser's built-in
   `[hidden] { display: none }` rule (author styles win over user-agent
   styles regardless of selector specificity), which made this overlay
   ignore its `hidden` attribute and show up empty on every page load. */
.modal-overlay[hidden] {
    display: none;
}

.modal-panel {
    background: var(--color-panel);
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
    width: 100%;
    max-width: 980px;
    max-height: 88vh;
    display: flex;
    flex-direction: column;
    overflow: hidden;
    box-shadow: 0 10px 40px rgba(255, 255, 255, 0.06);
}

.modal-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 16px 20px;
    border-bottom: 1px solid var(--color-border);
    flex-shrink: 0;
}

.modal-header h2 {
    font-size: 16px;
}

.modal-header-actions {
    display: flex;
    gap: 12px;
}

.btn-link {
    background: none;
    border: none;
    color: var(--color-primary);
    font-size: 13px;
    font-weight: 600;
    cursor: pointer;
    padding: 0;
}

.preview-grid {
    flex: 1;
    overflow-y: auto;
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 14px;
    padding: 20px;
}

@media (max-width: 900px) {
    .preview-grid {
        grid-template-columns: repeat(2, 1fr);
    }
}

.preview-card {
    position: relative;
    border: 1px solid var(--color-border);
    border-radius: 8px;
    padding: 10px;
    display: flex;
    flex-direction: column;
    gap: 6px;
    background: var(--color-input-bg);
}

.preview-card.is-error {
    background: #3a1414;
    border-color: #5c2020;
}

.preview-card.is-duplicate {
    background: #3a2f0a;
    border-color: #6b5416;
}

.preview-duplicate-badge {
    display: inline-block;
    align-self: flex-start;
    background: #f2994a;
    color: #fff;
    font-size: 11px;
    font-weight: 600;
    padding: 2px 6px;
    border-radius: 4px;
}

.preview-thumb {
    width: 100%;
    aspect-ratio: 1 / 1;
    background: var(--color-hover-bg);
    border-radius: 6px;
    display: block;
}

.preview-info {
    font-size: 12px;
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.preview-name {
    font-weight: 600;
    font-size: 13px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.preview-meta {
    color: var(--color-text-muted);
}

.preview-stats {
    font-weight: 600;
    margin-top: 2px;
}

.preview-error-icon {
    font-size: 22px;
    text-align: center;
}

.preview-error-text {
    text-align: center;
}

.preview-select {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 12px;
    cursor: pointer;
}

.modal-footer {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 14px 20px;
    border-top: 1px solid var(--color-border);
    flex-shrink: 0;
}

.modal-footer-info {
    font-size: 13px;
    color: var(--color-text-muted);
}

.modal-footer-buttons {
    display: flex;
    gap: 10px;
}

.modal-footer-buttons .btn {
    width: auto;
    margin-top: 0;
}

/* Admin pages */
.admin-main {
    max-width: 960px;
    margin: 0 auto;
    padding: 24px 20px 60px;
}

/* Wider variant for pages whose content needs more horizontal room than the
   default admin-style page (currently just weekly.php, so a year's ~52
   weekly bars fit without horizontal scrolling - see .bar-chart-equal). */
.admin-main-wide {
    max-width: 1200px;
}

.admin-main .panel {
    margin-bottom: 20px;
}

.inline-form {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: 10px 16px;
}

.inline-form label {
    margin: 0 0 4px;
}

.inline-form input {
    width: auto;
    min-width: 160px;
}

.inline-form input[type="number"] {
    min-width: 90px;
}

.inline-form .btn {
    width: auto;
    margin-top: 0;
    height: 37px;
}

.table-wrap {
    overflow-x: auto;
}

.admin-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 13px;
}

.admin-table th,
.admin-table td {
    text-align: left;
    padding: 8px 10px;
    border-bottom: 1px solid var(--color-border);
    white-space: nowrap;
}

.admin-table th {
    color: var(--color-text-muted);
    font-weight: 600;
    font-size: 12px;
    text-transform: uppercase;
    letter-spacing: 0.03em;
}

.admin-table-actions {
    display: flex;
    align-items: center;
    gap: 12px;
}

.admin-table-actions form {
    display: inline;
}

.btn-link-danger {
    color: #ff6b6b;
}

.admin-hash {
    font-family: monospace;
    font-size: 12px;
    color: var(--color-text-muted);
}

.badge {
    display: inline-block;
    font-size: 11px;
    font-weight: 600;
    padding: 2px 8px;
    border-radius: 10px;
    white-space: nowrap;
}

.badge-keep {
    background: #123420;
    color: #7ee6a0;
}

.badge-duplicate {
    background: #3a1414;
    color: #ff8080;
}

/* Bar chart component - shared by the "Pixel Distribution" and "Weekly
   Mileage" pages (see barchart.js). */
.bar-chart-wrap {
    margin: 16px 0 20px;
}

.bar-chart {
    position: relative;
    height: 280px;
    /* Left padding reserves the y-axis label gutter (48px) so labels never
       sit over a bar - the absolutely-positioned overlays below (axis,
       gridlines) don't inherit this padding themselves (padding only
       affects normal-flow layout), so each repeats the same 48px inset
       explicitly to line up with it. */
    padding: 8px 8px 28px 48px;
    overflow-x: auto;
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
    background: var(--color-input-bg);
}

.bar-chart-empty {
    color: var(--color-text-muted);
    font-size: 13px;
    text-align: center;
    padding: 40px 0;
}

/* The y-axis label column, to the left of the plot area (bars/gridlines)
   rather than overlaid on it, so every value stays readable regardless of
   bar heights. */
.bar-chart-axis {
    position: absolute;
    left: 0;
    top: 8px;
    bottom: 28px;
    width: 44px;
    pointer-events: none;
}

.bar-axis-label {
    position: absolute;
    left: 0;
    right: 6px;
    font-size: 11px;
    color: var(--color-text-muted);
    text-align: right;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

/* Centers the label vertically on its gridline (see the JS: `bottom: X%`
   positions the label's own bottom edge at X%, and shifting it down by half
   its own height re-centers it there) - except the baseline (0) label, which
   stays bottom-aligned so it doesn't dip into the x-axis labels below it. */
.bar-axis-label-mid {
    transform: translateY(50%);
}

.bar-chart-gridlines {
    position: absolute;
    left: 48px;
    right: 8px;
    top: 8px;
    bottom: 28px;
    pointer-events: none;
}

/* Recessive hairline gridlines (marks-and-anatomy.md: one-step-off-surface
   gray, hairline, solid, never dashed) - plain lines, since their values
   live in the axis column rather than as a label overlaid on the line
   itself. */
.bar-gridline {
    position: absolute;
    left: 0;
    right: 0;
    border-top: 1px solid var(--color-border);
}

.bar-chart-bars {
    position: relative;
    display: flex;
    align-items: flex-end;
    height: 100%;
    gap: 6px;
}

/* Default: a fixed-size series (e.g. one bar per week over an open-ended
   date range) gets a fixed bar width and scrolls horizontally rather than
   being squeezed arbitrarily thin. */
.bar-col {
    position: relative;
    flex: 0 0 auto;
    width: 14px;
    height: 100%;
    display: flex;
    align-items: flex-end;
    justify-content: center;
    outline: none;
}

/* .bar-chart-equal (set via BarChart.render()'s equalWidth flag): a
   fixed-count series (e.g. always exactly 20 buckets) shares the available
   width evenly instead, since it never needs to scroll. */
.bar-chart-equal .bar-col {
    flex: 1 1 0;
    width: auto;
    min-width: 12px;
    max-width: 32px;
}

.bar-col:focus-visible {
    outline: 2px solid var(--color-primary);
    outline-offset: 2px;
}

/* 4px rounded data-end, square at the baseline, per marks-and-anatomy.md.
   Each bar's own color is set inline (see BarChart.render()) - both pages
   use the same 20-step plasma scale the map colors hexes with, just keyed
   by a different bucket (visit-count vs. 5km bins), so a bar's color always
   matches what that bucket looks like on the map. */
.bar-fill {
    width: 100%;
    border-radius: 4px 4px 0 0;
    min-height: 1px;
}

.bar-col:hover .bar-fill,
.bar-col:focus-visible .bar-fill {
    filter: brightness(1.25);
}

.bar-label {
    position: absolute;
    bottom: -18px;
    font-size: 10px;
    color: var(--color-text-muted);
    white-space: nowrap;
}

.bar-tooltip {
    position: absolute;
    pointer-events: none;
    background: #000;
    border: 1px solid var(--color-border);
    color: var(--color-text);
    font-size: 12px;
    padding: 4px 8px;
    border-radius: 6px;
    white-space: nowrap;
    z-index: 10;
    transform: translate(-50%, calc(-100% - 8px));
}

/* Year quick-links (weekly.php): jump the date slider straight to a given
   year's ~52 weeks (see DateSlider.setRange()) instead of dragging handles
   there by hand. */
.year-quicklinks {
    display: flex;
    flex-wrap: wrap;
    gap: 4px 14px;
    margin-top: 14px;
    padding-top: 12px;
    border-top: 1px solid var(--color-border);
}

.year-quicklink {
    font-size: 13px;
}

/* Shoes page */
.shoe-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
    gap: 16px;
}

.shoe-card {
    border: 1px solid var(--color-border);
    border-radius: 8px;
    padding: 12px;
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.shoe-thumb {
    width: 66%;
    margin: 0 auto;
    aspect-ratio: 1 / 1;
    object-fit: contain;
    border-radius: 6px;
    background: #000000;
}

.shoe-thumb-placeholder {
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 40px;
}

.shoe-thumb-large {
    max-width: 200px;
    width: 100%;
    aspect-ratio: 1 / 1;
    object-fit: cover;
    border-radius: 6px;
    margin-bottom: 12px;
}

.shoe-info {
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.shoe-name {
    font-weight: 600;
    font-size: 14px;
}

.shoe-meta {
    font-size: 13px;
    color: var(--color-text);
    font-weight: 600;
}

.shoe-meta-sub {
    font-size: 12px;
    color: var(--color-text-muted);
}

.shoe-actions {
    display: flex;
    align-items: center;
    gap: 12px;
    margin-top: 4px;
}

.shoe-actions form {
    display: inline;
}

/* Live tracking page - mobile-first: a full-viewport map with floating
   controls, rather than the header+sidebar shell the rest of the app uses.
   (No dark-theme variables here on purpose - the map itself is the only
   content, and it stays in the same light basemap language as the main
   map's own map area.) */
.live-body {
    margin: 0;
    height: 100%;
    overflow: hidden;
    background: #000;
}

.live-map-wrap {
    position: fixed;
    inset: 0;
}

#live-map {
    height: 100%;
    width: 100%;
    background: #dfe6ea;
}

/* Pushed below .live-header (which floats on top of the map at the same
   top edge) rather than the shared .zoom-hint's default top:12px, which
   would otherwise land right in the header's button row. */
.live-zoom-hint {
    top: 64px;
}

.live-header {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    z-index: 1000;
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 10px;
    /* env(safe-area-inset-*) keeps controls clear of notches/status bars on
       phones that report one; falls back to a plain 10px everywhere else. */
    padding: calc(env(safe-area-inset-top, 0px) + 10px) 14px 24px;
    background: linear-gradient(to bottom, rgba(0, 0, 0, 0.6), rgba(0, 0, 0, 0));
    pointer-events: none;
}

.live-header > * {
    pointer-events: auto;
}

.live-back {
    display: inline-block;
    flex-shrink: 0;
    color: #fff;
    text-decoration: none;
    font-size: 14px;
    font-weight: 600;
    background: rgba(0, 0, 0, 0.45);
    padding: 8px 14px;
    border-radius: 20px;
}

.live-status {
    color: #fff;
    font-size: 12px;
    font-weight: 600;
    background: rgba(0, 0, 0, 0.45);
    padding: 8px 14px;
    border-radius: 20px;
    text-align: right;
    max-width: 55vw;
}

.live-locate-btn {
    position: absolute;
    right: 16px;
    bottom: calc(env(safe-area-inset-bottom, 0px) + 20px);
    z-index: 1000;
    width: 52px;
    height: 52px;
    border-radius: 50%;
    border: none;
    background: #fff;
    font-size: 22px;
    line-height: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    box-shadow: 0 2px 12px rgba(0, 0, 0, 0.35);
    cursor: pointer;
}

/* Follow mode is on (the map recenters on every GPS update) - the resting
   state, since watching the dot without it drifting off-screen is the
   whole point of this page. Off (plain white) once the user pans away
   manually, matching the "recenter" pattern any mobile map app uses. */
.live-locate-btn.is-following {
    background: var(--color-primary);
    color: #fff;
}

/* Sits just above the record button, out of the way of the header/zoom-hint
   at the top of the screen entirely. */
.live-record-stats {
    position: absolute;
    left: 50%;
    bottom: calc(env(safe-area-inset-bottom, 0px) + 96px);
    transform: translateX(-50%);
    z-index: 1000;
    display: flex;
    gap: 14px;
    background: rgba(0, 0, 0, 0.55);
    color: #fff;
    padding: 8px 16px;
    border-radius: 20px;
    font-size: 14px;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.live-record-btn {
    position: absolute;
    left: 50%;
    bottom: calc(env(safe-area-inset-bottom, 0px) + 20px);
    transform: translateX(-50%);
    z-index: 1000;
    width: 64px;
    height: 64px;
    padding: 0;
    border-radius: 50%;
    border: 3px solid #fff;
    background: rgba(0, 0, 0, 0.35);
    display: flex;
    align-items: center;
    justify-content: center;
    box-shadow: 0 2px 12px rgba(0, 0, 0, 0.35);
    cursor: pointer;
}

/* Plain record/stop iconography drawn with CSS (a filled circle vs. a
   rounded square) rather than an icon font/SVG dependency - the shape
   change on .is-recording is the same universal record-button language
   every camera/voice-memo/fitness app already uses. */
.live-record-btn-dot {
    width: 28px;
    height: 28px;
    border-radius: 50%;
    background: #ff3b30;
    transition: border-radius 0.15s ease, width 0.15s ease, height 0.15s ease;
}

.live-record-btn.is-recording .live-record-btn-dot {
    width: 24px;
    height: 24px;
    border-radius: 6px;
}

.live-record-toast {
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
    z-index: 1100;
    background: rgba(0, 0, 0, 0.85);
    color: #fff;
    padding: 16px 22px;
    border-radius: 12px;
    font-size: 14px;
    text-align: center;
    max-width: 80vw;
    box-shadow: 0 4px 20px rgba(0, 0, 0, 0.4);
    cursor: pointer;
}
