/* Dark is the default palette. The surface/border tokens exist so the many
   translucent overlays across the site flip as one set rather than being
   re-tuned individually per theme. */
:root {
    --bg: #0b0f1a;
    --panel: rgba(18, 24, 38, 0.72);
    --surface-1: rgba(255, 255, 255, 0.05);
    --surface-2: rgba(255, 255, 255, 0.07);
    --surface-3: rgba(255, 255, 255, 0.09);
    --border-1: rgba(255, 255, 255, 0.12);
    --border-2: rgba(255, 255, 255, 0.16);
    --panel-border: var(--border-1);
    --text: #eef2fb;
    --muted: #97a2bd;
    --accent: #ffae57;
    --accent-2: #ff6b6b;
    /* A lift off the page rather than a colour of its own - white in both
       themes, at very different strengths against #0b0f1a and #eef1f7. */
    --topbar-bg: rgba(255, 255, 255, 0.055);
    /* Room the fixed burger needs inside the bar. Zeroed above 900px,
       where the button moves out into the gutter instead. */
    --topbar-reserve: 52px;
    --shadow: 0 18px 50px rgba(0, 0, 0, 0.35);
    --glow-warm: rgba(255, 140, 90, 0.16);
    --glow-cool: rgba(120, 140, 255, 0.12);
    --drawer-bg: #12172a;

    /* The Moon's own colours, which cannot be drawn from the surface tokens.
       Those are translucent washes meant to sit on a card, and at 7% they
       made the unlit disc invisible on a light one - a crescent needs a
       shadowed side dark enough to be an object, in either theme. */
    --moon-dark: #2b3247;
    --moon-edge: rgba(255, 255, 255, 0.22);
    --scrim: rgba(4, 6, 12, 0.55);
    --lightbox-scrim: rgba(4, 6, 12, 0.7);
    --lightbox-caption-bg: rgba(18, 24, 38, 0.94);
    --nav-btn-bg: rgba(12, 16, 26, 0.55);
    /* Panels and menus, not buttons - those carry their own 14px and are
       meant to read as things you press. Twenty-four on a card was enough
       curve to look like a toy next to the type, and the whole page is
       cards, so it compounded. */
    --radius-lg: 16px;
    --radius-md: 12px;

    /* score 1 (worst) -> 5 (best) */
    --score-1: #6b7690;
    --score-2: #8894ad;
    --score-3: #f2c94c;
    --score-4: #ffa94d;
    --score-5: #ff6b4a;

    --tl-golden: #ffae57;
    --tl-blue: #4a7fd4;
    --tl-blue-text: #6f9bea;
    --tl-aurora: #4fd6a0;
    --tl-moon: #b9c4dd;
    --tl-now: #ffd84d;
    --positive: #41d18d;
    --negative: #e26d6d;
    --own-vote-border: rgba(255, 174, 87, 0.35);
}

/* Light palette. Applied when the user pins light, or when they're on
   "match device" (no data-theme attribute) and the device asks for light. */
@media (prefers-color-scheme: light) {
    :root:not([data-theme="dark"]) {
        --bg: #eef1f7;
        --panel: rgba(255, 255, 255, 0.86);
        --surface-1: rgba(16, 24, 44, 0.045);
        --surface-2: rgba(16, 24, 44, 0.075);
        --surface-3: rgba(16, 24, 44, 0.10);
        --border-1: rgba(16, 24, 44, 0.14);
        --border-2: rgba(16, 24, 44, 0.20);
        --text: #161c2c;
        --muted: #5b6680;
        --topbar-bg: rgba(255, 255, 255, 0.78);
        --shadow: 0 14px 38px rgba(20, 30, 60, 0.12);
        --glow-warm: rgba(255, 150, 90, 0.22);
        --glow-cool: rgba(120, 150, 255, 0.18);
        --drawer-bg: #ffffff;

        /* Dark enough to read as the Moon's shadowed side against a white
           card - see the note in the dark palette. */
        --moon-dark: #b4bccd;
        --moon-edge: rgba(16, 24, 44, 0.30);
        --scrim: rgba(20, 28, 48, 0.35);
        --lightbox-scrim: rgba(255, 255, 255, 0.45);
        --lightbox-caption-bg: rgba(255, 255, 255, 0.96);
        --nav-btn-bg: rgba(255, 255, 255, 0.85);

        /* The two lowest scores are near-white on dark; they need real
           contrast against a light ground to stay readable. */
        --score-1: #77839c;
        --score-2: #5d6b88;
        --score-3: #b8880c;
        --score-4: #d2681a;
        --score-5: #d2401f;
        --tl-golden: #c9741a;
        --tl-blue: #3a63b8;
        --tl-blue-text: #2f56a8;
        --tl-aurora: #148a63;
        --tl-moon: #64708c;
        --tl-now: #b8860b;
        --positive: #17835a;
        --negative: #c0392b;
        --own-vote-border: rgba(201, 116, 26, 0.45);
    }
}

:root[data-theme="light"] {
    --bg: #eef1f7;
    --panel: rgba(255, 255, 255, 0.86);
    --surface-1: rgba(16, 24, 44, 0.045);
    --surface-2: rgba(16, 24, 44, 0.075);
    --surface-3: rgba(16, 24, 44, 0.10);
    --border-1: rgba(16, 24, 44, 0.14);
    --border-2: rgba(16, 24, 44, 0.20);
    --panel-border: var(--border-1);
    --text: #161c2c;
    --muted: #5b6680;
    --topbar-bg: rgba(255, 255, 255, 0.78);
    --shadow: 0 14px 38px rgba(20, 30, 60, 0.12);
    --glow-warm: rgba(255, 150, 90, 0.22);
    --glow-cool: rgba(120, 150, 255, 0.18);
    --drawer-bg: #ffffff;

    /* Dark enough to read as the Moon's shadowed side against a white card -
       see the note in the dark palette. */
    --moon-dark: #b4bccd;
    --moon-edge: rgba(16, 24, 44, 0.30);
    --scrim: rgba(20, 28, 48, 0.35);
    --lightbox-scrim: rgba(255, 255, 255, 0.45);
    --lightbox-caption-bg: rgba(255, 255, 255, 0.96);
    --nav-btn-bg: rgba(255, 255, 255, 0.85);
    --score-1: #77839c;
    --score-2: #5d6b88;
    --score-3: #b8880c;
    --score-4: #d2681a;
    --score-5: #d2401f;
    --tl-golden: #c9741a;
    --tl-blue: #3a63b8;
    --tl-blue-text: #2f56a8;
    --tl-aurora: #148a63;
    --tl-moon: #64708c;
    --tl-now: #b8860b;
    --positive: #17835a;
    --negative: #c0392b;
    --own-vote-border: rgba(201, 116, 26, 0.45);
}

/* The browser's own rule is a bare attribute selector, [hidden] { display:
   none }, so any class that sets display beats it on specificity and the
   element stays on screen with the attribute set and doing nothing. Five
   components had already worked around this one at a time. Said once here,
   with the weight to actually hold. */
[hidden] {
    display: none !important;
}

* {
    box-sizing: border-box;
}

body {
    margin: 0;
    overflow-x: hidden;
    font-family: Inter, system-ui, Arial, sans-serif;
    color: var(--text);
    background:
        radial-gradient(circle at 15% -10%, var(--glow-warm), transparent 40%),
        radial-gradient(circle at 85% 0%, var(--glow-cool), transparent 45%),
        var(--bg);
    min-height: 100vh;
}

a {
    color: inherit;
}

.shell {
    max-width: 760px;
    margin: 0 auto;
    padding: 24px 20px 56px;
}

.glass {
    background: var(--panel);
    backdrop-filter: blur(14px);
    border: 1px solid var(--panel-border);
    box-shadow: var(--shadow);
    border-radius: var(--radius-lg);
}

/* ---------- topbar ---------- */

/* Packed from the left rather than space-between: the dashboard's topbar
   has a third item (the search box) and the subpages' does not, so
   distributing the free space put the logo mid-row on one and hard right on
   the other. Left-packing pins the logo just after the hamburger on every
   page, and the search box pushes itself to the far right instead. */
/* A full-bleed header bar, not a card: the surface runs edge to edge and
   flush to the top, while the logo inside it still lines up with the content
   column below.
 *
 * The margin pulls the box out to the viewport edges and the matching padding
 * pushes its contents back in - 50% resolves against the centred shell, so
 * the two cancel exactly at any width. margin-top cancels the shell's own
 * padding so the bar touches the top rather than floating below a strip of
 * page. */
.topbar {
    display: flex;
    align-items: center;
    justify-content: flex-start;
    gap: 16px;
    flex-wrap: wrap;
    margin: -24px calc(50% - 50vw) 28px;
    padding: 14px calc(50vw - 50%);
    background: var(--topbar-bg);
    border-bottom: 1px solid var(--border-1);
}

/* .page-shell (settings/login/signup pages) has no max-width of its own -
   only the .dashboard/.card content nested inside it does - so a bare
   .topbar there would span the full page edge to edge instead of lining up
   with the centered content below it. Matches .shell's width so the logo
   sits in the same place across every page. */
.page-shell > .topbar {
    max-width: none;
    margin-left: -20px;
    margin-right: -20px;
    padding-left: calc(max(20px, 50vw - 380px) + var(--topbar-reserve));
    padding-right: max(20px, calc(50vw - 380px));
}

.brand {
    text-decoration: none;
    font-weight: 700;
    font-size: 18px;
    letter-spacing: -0.01em;
    display: flex;
    align-items: center;
}

/* A transparent wordmark whose letters are filled with a warm sunset photo -
   mid-toned enough to read on both themes without any per-theme treatment.
   Height-driven so the intrinsic 1300x300 ratio picks the width; the
   width/height attributes on the <img> reserve the box so the topbar doesn't
   jump while it loads. */
.brand-logo {
    height: 34px;
    width: auto;
    display: block;
}

/* Which wordmark is painted, following the same three-state rule the palette
   uses: dark unless the device asks for light, and an explicit choice beating
   the device either way. Higher specificity than .brand-logo's own display so
   the order of these rules does not decide it. */
.brand-logo.brand-light,
.nav-drawer-title-mark.brand-light {
    display: none;
}

@media (prefers-color-scheme: light) {
    :root:not([data-theme="dark"]) .brand-logo.brand-light,
    :root:not([data-theme="dark"]) .nav-drawer-title-mark.brand-light {
        display: block;
    }

    :root:not([data-theme="dark"]) .brand-logo.brand-dark,
    :root:not([data-theme="dark"]) .nav-drawer-title-mark.brand-dark {
        display: none;
    }
}

:root[data-theme="light"] .brand-logo.brand-light,
:root[data-theme="light"] .nav-drawer-title-mark.brand-light {
    display: block;
}

:root[data-theme="light"] .brand-logo.brand-dark,
:root[data-theme="light"] .nav-drawer-title-mark.brand-dark {
    display: none;
}

/* At 6:1 the wordmark would otherwise crowd the hamburger off the row on the
   narrowest phones. It has grown twice at the same height - 1300 to 1600, and
   then to 1800 when the name gained its space - so this matters more each
   time. The rendered size follows the file's own ratio, so the only thing
   pinned here is the height. */
@media (max-width: 420px) {
    .brand-logo {
        height: 28px;
    }
}

/* ---------- search autocomplete ---------- */

/* The suggestions hang off the form, which has to become a positioning
   context for them. */
.quick-search {
    position: relative;
}

.quick-search-results {
    position: absolute;
    top: calc(100% + 6px);
    left: 0;
    right: 0;
    z-index: 40;
    margin: 0;
    padding: 4px;
    list-style: none;
    border-radius: var(--radius-md);
    border: 1px solid var(--panel-border);
    background: var(--drawer-bg, var(--panel));
    box-shadow: var(--shadow);
    max-height: 320px;
    overflow-y: auto;
}

.quick-search-item {
    display: flex;
    align-items: baseline;
    gap: 8px;
    padding: 8px 10px;
    border-radius: 8px;
    cursor: pointer;
    font-size: 14px;
}

.quick-search-item.is-active {
    background: var(--surface-1);
}

.quick-search-name {
    font-weight: 600;
}

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

/* Marks the results that are more than a live lookup - somewhere with
   history, photos and a permanent address. */
.quick-search-badge {
    margin-left: auto;
    flex-shrink: 0;
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--accent);
    border: 1px solid var(--accent);
    border-radius: 999px;
    padding: 1px 7px;
}

.quick-search {
    display: flex;
    gap: 8px;
    flex: 1 1 260px;
    max-width: 360px;
    margin-left: auto;
}

/* Below this the search can no longer share the row, so give it one of its
   own at full width - otherwise margin-left:auto would strand it against
   the right edge with dead space beside it. */
@media (max-width: 700px) {
    .quick-search {
        flex-basis: 100%;
        max-width: none;
        margin-left: 0;
    }
}

.quick-search input {
    flex: 1;
    border-radius: 12px;
    border: 1px solid var(--panel-border);
    background: var(--surface-1);
    color: var(--text);
    padding: 9px 12px;
    font-size: 14px;
}

.quick-search button {
    border-radius: 12px;
    border: 1px solid var(--panel-border);
    background: var(--surface-2);
    color: var(--text);
    padding: 9px 14px;
    cursor: pointer;
    font-size: 14px;
    flex-shrink: 0;
}

.icon-button {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 38px;
    padding: 0 !important;
    color: var(--muted);
}

.icon-button:hover {
    color: var(--text);
}

.icon-button:disabled {
    opacity: 0.5;
    cursor: default;
}

.hamburger-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 38px;
    height: 38px;
    flex-shrink: 0;
    border-radius: 12px;
    border: 1px solid var(--panel-border);
    background: var(--surface-1);
    color: var(--text);
    cursor: pointer;
    order: -1;
}

/* ---------- left-side nav drawer ---------- */

.nav-drawer-overlay {
    position: fixed;
    inset: 0;
    z-index: 70;
    background: var(--scrim);
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.2s ease, visibility 0.2s ease;
}

.nav-drawer-overlay.open {
    opacity: 1;
    visibility: visible;
}

.nav-drawer {
    position: fixed;
    top: 0;
    left: 0;
    bottom: 0;
    z-index: 71;
    width: 280px;
    max-width: 82vw;
    background: var(--drawer-bg);
    border-right: 1px solid var(--panel-border);
    box-shadow: var(--shadow);
    display: flex;
    flex-direction: column;
    padding: 20px;
    box-sizing: border-box;
    overflow-y: auto;
    transform: translateX(-100%);
    transition: transform 0.25s ease;
}

.nav-drawer.open {
    transform: translateX(0);
}

.nav-drawer-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 20px;
}

/* The mark replaces the word "Menu" in the drawer header. Sized by height so
   it lines up with the close button beside it whatever the artwork's aspect
   ratio; the intrinsic 463x236 keeps the row from reflowing before it loads. */
.nav-drawer-title-link {
    display: block;
    line-height: 0;
    border-radius: 6px;
}

.nav-drawer-title-link:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 3px;
}

.nav-drawer-title-mark {
    height: 30px;
    width: auto;
    display: block;
    transition: opacity 0.15s ease;
}

.nav-drawer-title-link:hover .nav-drawer-title-mark {
    opacity: 0.8;
}

.nav-drawer-close {
    width: 32px;
    height: 32px;
    border-radius: 50%;
    border: 1px solid var(--panel-border);
    background: var(--surface-1);
    color: var(--text);
    font-size: 20px;
    line-height: 1;
    cursor: pointer;
}

.nav-drawer-links {
    display: flex;
    flex-direction: column;
    gap: 4px;
}

.nav-drawer-links a {
    padding: 12px 10px;
    border-radius: 10px;
    text-decoration: none;
    color: var(--text);
    font-size: 15px;
}

/* Carries the same mark as the topbar shortcut, so the two are recognisable
   as the same destination. */
.nav-drawer-icon-link {
    display: flex;
    align-items: center;
    gap: 9px;
}

.nav-drawer-icon-link svg {
    flex-shrink: 0;
    color: var(--muted);
}

@media (hover: hover) and (pointer: fine) {
    .nav-drawer-links a:hover {
        background: var(--surface-2);
    }
}

.nav-drawer-divider {
    height: 1px;
    background: var(--panel-border);
    margin: 10px 0;
}

.nav-drawer-user {
    margin: 0 0 4px;
    padding: 0 10px;
    font-size: 12px;
    color: var(--muted);
    word-break: break-all;
}

.nav-drawer-heading {
    margin: 0 0 2px;
    padding: 0 10px;
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--muted);
}

/* ---------- today card (swipeable) ---------- */

.today-card {
    position: relative;
    padding: 0;
    margin-bottom: 16px;
    border-top: 4px solid var(--score-color, var(--accent));
    overflow: hidden;
}

/* A day past the forecast has no score, so it gets no score colour - the
   top border is the verdict at a glance, and a coloured one would be
   claiming something. Muted, not absent: the card still needs its edge. */
.today-card-times-only,
.today-card-edge-plain {
    border-top-color: var(--border-2, var(--border-1));
}

/* Stands where the quality word stands on a scored tile, so the eye lands on
   the fact this day actually has. */
.verdict-quality-time {
    margin: 2px 0 0;
    font-size: 22px;
    font-weight: 700;
    line-height: 1.2;
    color: var(--text);
}

.today-nav {
    display: none;
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    z-index: 2;
    width: 34px;
    height: 34px;
    border-radius: 50%;
    border: 1px solid var(--panel-border);
    background: var(--nav-btn-bg);
    backdrop-filter: blur(6px);
    color: var(--muted);
    font-size: 20px;
    line-height: 1;
    cursor: pointer;
    align-items: center;
    justify-content: center;
    transition: opacity 0.15s ease, color 0.15s ease;
    opacity: 0.55;
}

.today-nav:hover {
    opacity: 1;
    color: var(--text);
}

.today-nav.hidden {
    visibility: hidden;
    pointer-events: none;
}

.today-nav-prev {
    left: 10px;
}

.today-nav-next {
    right: 10px;
}

/* Only show click arrows on devices with a real pointer - touch devices
   already have native swipe, no need to clutter the card with buttons. */
@media (hover: hover) and (pointer: fine) {
    .today-nav {
        display: flex;
    }
}

.today-pager {
    display: flex;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
}

.today-pager::-webkit-scrollbar {
    display: none;
}

.today-page {
    flex: 0 0 100%;
    min-width: 0;
    scroll-snap-align: start;
    padding: 32px 28px;
    box-sizing: border-box;
}

/* Wherever the click arrows are shown they float over the card, reaching
   44px in from each edge (10px offset + 34px wide), so the page needs to
   clear that or they sit on top of the content - most visibly over the two
   ends of the timeline arc, which spans the full content width. Must come
   after the shorthand padding above, which would otherwise win on equal
   specificity regardless of the media query. */
@media (hover: hover) and (pointer: fine) {
    .today-page {
        padding-left: 56px;
        padding-right: 56px;
    }
}

.today-page-verdict {
    text-align: center;
}

/* The page a shared link opens on. The photograph is the whole point of the
   visit, so it gets the width the verdict text does not need, and the label
   above it is deliberately quiet - it explains why this page is first, and
   then gets out of the way.

   The inset is cut right back, including the wider one the other pages take
   on pointer devices to keep the click arrows off the timeline arc. Here the
   arrows land on the picture instead, which is where every photo viewer puts
   them - they are translucent discs over a corner, not something the eye has
   to work around. */
.today-page-photo {
    text-align: center;
    padding: 20px 12px 24px;
}

.today-page-photo .verdict-location {
    margin-bottom: 12px;
}

.today-page-photo .verdict-capture {
    margin-top: 0;
}

/* Full width and its own aspect ratio, against the 264x176 crop the tile
   uses. That size is right for a thumbnail sitting under a verdict, and
   wrong for the one thing somebody was sent here to look at: it threw most
   of the frame away and left the rest floating in the middle of the panel.
   Capped against the viewport so a tall frame cannot push the swipe hint
   off the bottom of the screen, which is the only cue that there is more
   here than the photograph. */
.today-page-photo .verdict-thumb {
    width: 100%;
}

.today-page-photo .verdict-thumb img {
    width: 100%;
    height: auto;
    max-height: 68vh;
    object-fit: contain;
    border-radius: 12px;
}

.today-dots {
    display: flex;
    justify-content: center;
    gap: 6px;
    padding: 0 0 16px;
}

.today-dots .dot {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--panel-border);
}

.today-dots .dot.active {
    background: var(--accent);
}

.verdict-location {
    margin: 0 0 6px;
    color: var(--muted);
    text-transform: uppercase;
    letter-spacing: 0.1em;
    font-size: 12px;
}

/* Sits above the pager, so it stays put on whichever tile is showing -
   which tile that is varies with the time of day. */
/* The card itself has no padding, so this was pressed against the coloured
   top border and ran under the home button in the corner. It is now a header
   block in its own right: the place reads first at a normal size and normal
   case, with the date as a caption under it rather than fighting for the
   same line. The horizontal padding keeps a long name clear of the button. */
.today-location {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 3px;
    margin: 0;
    /* Clear of the date on the left as well as the controls on the right,
       so the centred name stays centred between two things of about the
       same width rather than shouldering into one of them. */
    padding: 12px 84px 8px;
    text-align: center;
}

.today-location-name {
    font-size: 15px;
    font-weight: 700;
    line-height: 1.3;
    color: var(--text);
}

.today-location-date {
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--muted);
}

/* Long place names get the space back on a narrow screen, where the button
   is the only thing they can collide with. */
@media (max-width: 420px) {
    .today-location {
        padding: 10px 46px 6px;
    }

    .today-location-name {
        font-size: 14px;
    }
}

.verdict-main {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 18px;
}

.verdict-icon {
    font-size: 56px;
    line-height: 1;
    flex-shrink: 0;
}

.verdict-headline {
    margin: 0;
    font-size: 30px;
    font-weight: 800;
    letter-spacing: -0.01em;
}

/* Event name leads, its quality sits beneath in slightly smaller text -
   the same shape the Earlier-today rows use. */
.verdict-quality {
    margin: 2px 0 0;
    font-size: 22px;
    font-weight: 700;
    line-height: 1.2;
}

/* The time to be outside. This is what the site is for, so it is the same
   on every tile that has one - centred, bold, a step up from the words
   around it - whether it is a sunset window, a liftoff or a pass rising.
   Not the headline's size: the headline says what, this says when, and
   when is read after what. */
.verdict-time {
    margin: 8px 0 0;
    color: var(--text);
    font-size: 18px;
    font-weight: 700;
    text-align: center;
    font-variant-numeric: tabular-nums;
}

.verdict-note {
    margin: 14px auto 0;
    max-width: 46ch;
    color: var(--muted);
    font-size: 13px;
    line-height: 1.5;
}

.score-1 { --score-color: var(--score-1); }
.score-2 { --score-color: var(--score-2); }
.score-3 { --score-color: var(--score-3); }
.score-4 { --score-color: var(--score-4); }
.score-5 { --score-color: var(--score-5); }

/* ---------- sunset/sunrise curved timeline ---------- */

.sky-timeline {
    margin: 26px 0 4px;
    text-align: left;
}

/* Height follows from the width so the aspect ratio holds - a fixed height
   would reintroduce the stretch. Only min-height is safe here: the SVG must
   always be limited by width, since a height-limited one would be drawn
   narrower than its container and the percentage-positioned labels below
   would no longer line up with the dots above them. */
.sky-timeline-svg {
    width: 100%;
    height: auto;
    min-height: 52px;
    display: block;
    overflow: visible;
}

.sky-timeline-curve {
    fill: none;
    stroke: var(--border-2);
    stroke-width: 2;
}

.sky-timeline-band {
    fill: none;
    stroke-width: 4;
    stroke-linecap: round;
}

.sky-timeline-band-golden {
    stroke: var(--tl-golden);
}

.sky-timeline-band-blue {
    stroke: var(--tl-blue);
}

/* The aurora band spans the whole darkness window rather than a phase of it,
   so it reads as a backdrop the dots sit on - thinner and dimmer than the
   golden/blue bands, which each mark one specific stretch worth noticing. */
.sky-timeline-band-aurora {
    stroke: var(--tl-aurora);
    stroke-width: 3;
    opacity: 0.55;
}

.sky-timeline-dot {
    fill: var(--muted);
    stroke: var(--bg);
    stroke-width: 2;
}

.sky-timeline-dot.dot-is-golden {
    fill: var(--tl-golden);
}

.sky-timeline-dot.dot-is-blue {
    fill: var(--tl-blue);
}

.sky-timeline-dot.dot-is-peak {
    fill: var(--score-5);
}

.sky-timeline-dot.dot-is-now {
    fill: var(--tl-now);
}

/* Which way to face, over the mark it belongs to. A short row above the
   curve rather than a third row beneath it: the two below already carry a
   time and a label each, and a compass point sitting over the arc reads as
   belonging to that point on it. */
.sky-timeline-above {
    position: relative;
    /* Room for the name and the dial stacked under it. */
    height: 34px;
    margin-bottom: 2px;
}

.sky-timeline-above-label {
    position: absolute;
    top: 0;
    transform: translateX(-50%);
    /* Name over dial, so the column reads downward into the mark it belongs
       to rather than sideways away from it. */
    display: inline-flex;
    flex-direction: column;
    align-items: center;
    gap: 1px;
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.04em;
    color: var(--muted);
    white-space: nowrap;
}

/* Matches how the labels below handle the ends, so a mark near the edge is
   not left hanging half off the panel. */
/* Tinted like the mark it names, so the pairing is visible rather than
   inferred - the golden 4 degree label below, the golden compass above. */
.sky-timeline-above-label.is-golden {
    color: var(--tl-golden);
}

.sky-timeline-above-label.is-blue {
    color: var(--tl-blue);
}

/* At the ends the box is pinned rather than centred, so the stack has to
   line up with the edge it is pinned to or the dial drifts off the mark. */
.sky-timeline-above-label.align-start {
    transform: none;
    align-items: flex-start;
}

.sky-timeline-above-label.align-end {
    right: 0;
    left: auto !important;
    transform: none;
    align-items: flex-end;
}

.sky-timeline-labels {
    position: relative;
    height: 62px;
    margin-top: 6px;
}

.sky-timeline-label {
    position: absolute;
    top: 0;
    display: flex;
    flex-direction: column;
    white-space: nowrap;
}

/* Two staggered rows: adjacent markers can sit only minutes apart (blue
   hour starts ~9 min after golden hour ends at low latitudes), so
   alternating rows keeps neighbouring labels from overlapping. */
.sky-timeline-label.row-2 {
    top: 32px;
}

/* A third row, only when a label fits neither of the first two (a phone,
   with Sunset, Best color and Blue hour ends minutes apart); the strip
   grows to hold it. */
.sky-timeline-label.row-3 {
    top: 64px;
}

.sky-timeline-labels.has-row-3 {
    height: 94px;
}

.sky-timeline-label.align-center {
    left: 50%;
    transform: translateX(-50%);
    align-items: center;
    text-align: center;
}

.sky-timeline-label.align-start {
    align-items: flex-start;
    text-align: left;
}

.sky-timeline-label.align-end {
    right: 0;
    left: auto !important;
    align-items: flex-end;
    text-align: right;
}

.sky-timeline-label-time {
    font-size: 11px;
    font-weight: 700;
}

.sky-timeline-label-text {
    font-size: 10px;
    color: var(--muted);
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

.sky-timeline-label.is-golden .sky-timeline-label-time,
.sky-timeline-label.is-golden .sky-timeline-label-text {
    color: var(--tl-golden);
}

.sky-timeline-label.is-blue .sky-timeline-label-time,
.sky-timeline-label.is-blue .sky-timeline-label-text {
    color: var(--tl-blue-text);
}

.sky-timeline-label.is-peak .sky-timeline-label-time,
.sky-timeline-label.is-peak .sky-timeline-label-text {
    color: var(--score-5);
}

/* Ties the reading below the arch to the yellow marker sitting on it. */
.sky-timeline-now-dot {
    display: inline-block;
    width: 9px;
    height: 9px;
    border-radius: 50%;
    background: var(--tl-now);
    margin-right: 7px;
    vertical-align: baseline;
}

.sky-timeline-current-time {
    margin: 10px 0 0;
    text-align: center;
    font-size: 13px;
    color: var(--muted);
}

/* Level with the words rather than sitting on their baseline: on its own at
   the front of the line, a dot hung from the baseline reads as a bullet
   point. */
.sky-timeline-current-time .sky-timeline-now-dot {
    vertical-align: middle;
    position: relative;
    top: -1px;
}

/* The timeline's own footnote sits under a centred curve and a centred
   current-time line, so left-aligning it left the whole block looking
   knocked out of true. Only the one inside a timeline: elsewhere the class
   explains a paragraph and belongs ranged left with it. */
.sky-timeline .why-footnote {
    text-align: center;
}

.sky-timeline-current-time strong {
    color: var(--text);
}

/* ---------- day-at-a-glance page ---------- */

.day-segments {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 10px;
}

.day-segment {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    padding: 14px 10px;
    border-radius: var(--radius-md);
    background: var(--surface-1);
}

.day-segment-label {
    color: var(--muted);
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    margin-bottom: 8px;
}

.day-segment-main {
    display: flex;
    flex-direction: column;
    align-items: center;
}

.day-segment-icon {
    font-size: 26px;
    line-height: 1;
    margin-bottom: 6px;
}

.day-segment-temp {
    font-weight: 700;
    font-size: 16px;
}

.day-segment-info {
    display: flex;
    flex-direction: column;
    align-items: center;
}

.day-segment-desc {
    font-size: 12px;
    margin-top: 2px;
}

.day-segment-meta {
    color: var(--muted);
    font-size: 10px;
    margin-top: 6px;
}

/* ---------- light-times page ---------- */

/* ---------- meter ---------- */

.meter {
    max-width: 320px;
    margin: 18px auto 0;
}

.meter-track {
    height: 8px;
    border-radius: 6px;
    background: var(--surface-3);
    position: relative;
    overflow: hidden;
}

.meter-fill {
    position: absolute;
    inset: 0 auto 0 0;
    background: var(--score-color, var(--accent));
    border-radius: 6px;
}

.meter-scale {
    display: flex;
    justify-content: space-between;
    margin-top: 6px;
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--muted);
}

.meter-compact {
    max-width: 220px;
    margin-top: 12px;
}

.meter-compact .meter-track {
    height: 5px;
}

.meter-mini {
    max-width: 120px;
    margin: 8px auto 0;
}

.meter-mini .meter-track {
    height: 4px;
}

.why-footnote {
    margin: 4px 0 0;
    color: var(--muted);
    font-size: 12px;
    font-style: italic;
}

/* ---------- day strip ---------- */

.section-heading {
    margin: 0 0 12px;
    font-size: 13px;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--muted);
}

.section-heading-row {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
}

.section-heading-link {
    margin-bottom: 12px;
    font-size: 13px;
    color: var(--accent);
    text-decoration: none;
}

.section-heading-link:hover {
    text-decoration: underline;
}

/* ---------- the tuning page's charts ---------- */

.tuning-subhead {
    margin: 22px 0 8px;
    font-size: 13px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.07em;
    color: var(--muted);
}

/* Two across where there is room, one on a phone. Six small charts read as
   a set to compare against each other; one big one at a time does not. */
.tuning-chart-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
    gap: 14px;
}

.tuning-chart {
    margin: 14px 0 0;
    padding: 10px 12px 6px;
    border: 1px solid var(--border-1);
    border-radius: var(--radius-md);
    background: var(--surface-1);
    color: var(--text);
}

.tuning-chart figcaption {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    justify-content: space-between;
    gap: 8px;
    margin-bottom: 4px;
    font-size: 12px;
}

.tuning-chart figcaption span {
    font-size: 11px;
    color: var(--muted);
}

.tuning-chart svg {
    display: block;
    width: 100%;
    height: auto;
}

.tuning-chart-key {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px 14px;
    margin: 6px 0 2px;
    font-size: 11px;
    color: var(--muted);
}

/* The key's marks are drawn the way the lines are, so the legend is read
   against the chart rather than against a colour name. */
.tuning-chart-key span {
    display: inline-block;
    width: 16px;
    height: 0;
    border-top-width: 2px;
    border-top-style: solid;
}

.tuning-key-observed {
    border-top-color: var(--positive);
}

.tuning-key-predicted {
    border-top-color: var(--accent);
    border-top-style: dashed !important;
}

.tuning-key-bars {
    height: 9px;
    border: 0 !important;
    background: currentColor;
    opacity: 0.15;
}

/* ---------- the sky over a place ---------- */

.sky-view {
    position: fixed;
    /* Below the site's own bar, not over it. The sky is a whole screen and
       looks like somewhere you have gone, and the way out of somewhere you
       have gone should be the bar that is on every other page rather than a
       control you have to find. --sky-top is the bar's real height, measured
       when the sky opens; the fallback is only for the instant before. */
    top: var(--sky-top, 0px);
    right: 0;
    bottom: 0;
    left: 0;
    z-index: 200;
    display: flex;
    flex-direction: column;
    /* Its own near-black in both themes. A star map is a picture of the
       dark, and putting it on a light page surface would be like printing
       one on white paper. */
    background: #05070d;
}

/* While the sky is up the bar is pinned to the top of the window and given
   a solid background - it is normally translucent over the page, and the
   page is no longer behind it. The search row folds away: reaching another
   place is not what somebody is doing here, and the row it takes is sky. */
body.sky-view-open .topbar {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    z-index: 210;
    margin: 0;
    padding-top: 10px;
    padding-bottom: 10px;
    background: var(--drawer-bg);
    max-width: none;
}

body.sky-view-open .topbar .quick-search {
    display: none;
}

/* Everything the bar can open has to come out above the sky, or pressing
   the burger slides the menu in behind a star map. On a phone the burger
   itself is a fixed button of its own rather than part of the row, so it
   needs lifting too. */
body.sky-view-open .hamburger-btn {
    z-index: 215;
}

body.sky-view-open .nav-drawer-overlay {
    z-index: 220;
}

body.sky-view-open .nav-drawer {
    z-index: 221;
}

.sky-view[hidden] {
    display: none;
}

body.sky-view-open {
    overflow: hidden;
}

.sky-view-canvas {
    flex: 1 1 auto;
    width: 100%;
    min-height: 0;
    display: block;
    cursor: grab;
    touch-action: none;
}

.sky-view-canvas:active {
    cursor: grabbing;
}

.sky-view-canvas:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: -2px;
}

/* Which way you are facing, over the top-left of the sky, because a star
   map with no compass on it is a picture rather than a direction. */
.sky-view-facing {
    position: absolute;
    top: 12px;
    left: 14px;
    font-size: 13px;
    font-weight: 600;
    color: rgba(255, 255, 255, 0.85);
    text-shadow: 0 0 6px rgba(0, 0, 0, 0.9);
    pointer-events: none;
}

.sky-view-layers {
    position: absolute;
    top: 10px;
    right: 12px;
    display: flex;
    gap: 8px;
}

.sky-view-chip {
    padding: 6px 12px;
    border: 1px solid rgba(255, 255, 255, 0.22);
    border-radius: 999px;
    background: rgba(11, 15, 26, 0.72);
    color: rgba(255, 255, 255, 0.9);
    font-size: 12px;
    cursor: pointer;
}

.sky-view-chip.is-off {
    color: rgba(255, 255, 255, 0.45);
    border-color: rgba(255, 255, 255, 0.12);
}

.sky-view-credit {
    position: absolute;
    left: 14px;
    bottom: 96px;
    max-width: 260px;
    margin: 0;
    font-size: 10px;
    line-height: 1.5;
    color: rgba(255, 255, 255, 0.45);
    text-shadow: 0 0 4px rgba(0, 0, 0, 0.9);
    pointer-events: none;
}

.sky-view-bar {
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 12px 16px;
    background: rgba(5, 7, 13, 0.96);
    border-top: 1px solid rgba(255, 255, 255, 0.12);
}

.sky-view-when {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 120px;
}

.sky-view-when strong {
    font-size: 15px;
    color: #fff;
}

.sky-view-when span {
    font-size: 12px;
    color: rgba(255, 255, 255, 0.55);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.sky-view-scrub {
    flex: 1 1 auto;
    min-width: 0;
}

.sky-view-scrub input[type="range"] {
    width: 100%;
    accent-color: var(--accent);
}

.sky-view-actions {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-shrink: 0;
}

.sky-view-back {
    width: 44px;
    height: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: 1px solid rgba(255, 255, 255, 0.22);
    border-radius: 14px;
    background: rgba(255, 255, 255, 0.06);
    color: #fff;
    cursor: pointer;
}

.sky-view-back:hover {
    background: rgba(255, 255, 255, 0.12);
}

.sky-view-bar .button.ghost {
    color: #fff;
    border-color: rgba(255, 255, 255, 0.22);
    background: rgba(255, 255, 255, 0.06);
    padding: 10px 14px;
}

@media (max-width: 560px) {
    /* The clock and the place go above the scrubber, which needs the width
       more than they do. */
    .sky-view-bar {
        flex-wrap: wrap;
        gap: 10px;
    }

    .sky-view-when {
        flex: 1 1 auto;
    }

    .sky-view-scrub {
        order: 3;
        flex-basis: 100%;
    }

    .sky-view-credit {
        bottom: 132px;
        max-width: 200px;
    }
}

/* ---------- choosing a spot on a map ---------- */

/* The whole screen, because this is one task and nothing else on the page
   is part of it. The app gives it a screen of its own for the same reason. */
.map-pick {
    position: fixed;
    inset: 0;
    z-index: 200;
    display: flex;
    flex-direction: column;
    background: var(--bg);
}

/* The rule above would otherwise beat the browser's own [hidden] display:
   none, and the picker would be open across the whole page on arrival. A
   class-and-attribute selector outranks the class alone. */
.map-pick[hidden] {
    display: none;
}

/* The page behind must not scroll while the map is up: a drag that started
   on the map and wandered off it would otherwise take the page with it. */
body.map-pick-open {
    overflow: hidden;
}

/* touch-action: none, or the browser claims the drag for scrolling and the
   map only moves when the page has nowhere left to go. */
.map-pick-surface {
    position: relative;
    flex: 1 1 auto;
    overflow: hidden;
    cursor: grab;
    touch-action: none;
    background: var(--surface-2);
    user-select: none;
}

.map-pick-surface:active {
    cursor: grabbing;
}

.map-pick-surface:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: -2px;
}

.map-pick-tiles {
    position: absolute;
    inset: 0;
}

/* Positioned by transform rather than left/top: the browser moves a
   transformed layer without laying the page out again, which on a drag is
   the difference between a map that follows your finger and one that
   catches up with it. */
.map-tile {
    position: absolute;
    top: 0;
    left: 0;
    width: 256px;
    height: 256px;
    pointer-events: none;
    /* The squares are drawn for a light background. Dimmed a little in dark
       mode so they sit with the rest of the page instead of glaring out of
       it - not inverted, which turns land into an x-ray and is worse. */
    filter: none;
}

/* A sharp square fades in over the blurred one under it rather than
   popping on top, so the map sharpens instead of flickering. Opacity only:
   a transition on transform would make every drag lag behind the finger. */
.map-tile {
    transition: opacity 0.25s ease;
}

.map-tile.is-loading {
    opacity: 0;
}

:root[data-theme="dark"] .map-tile {
    filter: brightness(0.82) saturate(0.85);
}

@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) .map-tile {
        filter: brightness(0.82) saturate(0.85);
    }
}

/* The cross. Fixed at the middle, never moves: the map moves under it, and
   what is under it is what is kept. */
.map-pick-cross {
    position: absolute;
    top: 50%;
    left: 50%;
    width: 0;
    height: 0;
    pointer-events: none;
}

.map-pick-cross span {
    position: absolute;
    /* Every piece is drawn twice over, in a dark halo and then the accent,
       so the cross stays visible over a snowfield and over a forest. */
    filter: drop-shadow(0 0 2px rgba(0, 0, 0, 0.85));
}

.map-pick-cross-ring {
    top: -13px;
    left: -13px;
    width: 26px;
    height: 26px;
    border: 2px solid var(--accent);
    border-radius: 50%;
}

.map-pick-cross-arm {
    background: var(--accent);
}

.map-pick-cross-n { top: -22px; left: -1px; width: 2px; height: 7px; }
.map-pick-cross-s { top: 15px; left: -1px; width: 2px; height: 7px; }
.map-pick-cross-w { top: -1px; left: -22px; width: 7px; height: 2px; }
.map-pick-cross-e { top: -1px; left: 15px; width: 7px; height: 2px; }

.map-pick-cross-dot {
    top: -2px;
    left: -2px;
    width: 4px;
    height: 4px;
    border-radius: 50%;
    background: var(--accent);
}

.map-pick-controls {
    position: absolute;
    right: 12px;
    bottom: 12px;
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.map-pick-btn {
    width: 40px;
    height: 40px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--border-2);
    border-radius: 12px;
    /* Its own dark glass rather than a surface token: these sit on a map,
       not on the page, and a translucent wash of the page colour over
       whatever ground is underneath is neither one thing nor the other. */
    background: rgba(11, 15, 26, 0.82);
    color: var(--accent);
    font-size: 19px;
    line-height: 1;
    cursor: pointer;
}

.map-pick-btn:hover {
    background: rgba(11, 15, 26, 0.94);
}

.map-pick-btn:disabled {
    opacity: 0.5;
    cursor: default;
}

.map-pick-foot {
    position: absolute;
    left: 12px;
    bottom: 12px;
    display: flex;
    flex-direction: column;
    gap: 4px;
    font-size: 11px;
    color: #fff;
    text-shadow: 0 0 4px rgba(0, 0, 0, 0.9);
    pointer-events: none;
}

.map-pick-scale {
    width: 120px;
    padding-bottom: 3px;
    border-bottom: 2px solid rgba(255, 255, 255, 0.85);
    border-left: 2px solid rgba(255, 255, 255, 0.85);
    border-right: 2px solid rgba(255, 255, 255, 0.85);
    text-align: center;
}

.map-pick-keep {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
    padding: 12px 16px;
    border-top: 1px solid var(--border-1);
}

.map-pick-what {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
}

.map-pick-what strong {
    font-size: 15px;
    color: var(--text);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.map-pick-coords {
    font-size: 12px;
    color: var(--muted);
    font-variant-numeric: tabular-nums;
}

/* The way out and the way on, together. Leaving is the rarer of the two and
   is drawn as the quieter of the two, but it sits on the same row: the map
   fills the screen and there is nowhere else for it to be. */
.map-pick-actions {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-shrink: 0;
}

.map-pick-back {
    flex-shrink: 0;
    width: 44px;
    height: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--border-2);
    border-radius: 14px;
    background: var(--surface-1);
    color: var(--text);
    cursor: pointer;
}

.map-pick-back:hover {
    background: var(--surface-2);
}

.map-pick-back:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

@media (max-width: 520px) {
    /* The name on its own line, then the two buttons across the bottom -
       the arrow keeping its square and the primary taking the rest, which
       is where a thumb already is. */
    .map-pick-keep {
        flex-wrap: wrap;
    }

    .map-pick-actions {
        width: 100%;
    }

    .map-pick-actions .button.primary {
        flex: 1 1 auto;
    }
}

/* ---------- pick a date ---------- */

/* The calendar that opens opposite "Next few days". It sits where "View
   more" sits under the strip below it, so the two section headings carry
   their control in the same place and the eye only has to learn it once. */
.date-jump-pop {
    position: relative;
    z-index: 30;
}

/* Only in a section heading row, where it has to sit on the same baseline as
   the heading beside it, which carries that margin of its own. In a page
   title bar it is one of two things centred on a row and the margin was
   nudging it half a line above the link next to it. */
.section-heading-row .date-jump-pop {
    margin-bottom: 12px;
}

/* A control, drawn as one.
   This was accent-coloured text with a small mark beside it, which read
   clearly enough on the dark home page and all but vanished in light mode
   on the day page's pale title bar - thin orange on near-white, next to a
   heading four times its size. It is the thing on that row you are meant to
   press, so it is given an edge and a surface to sit on and stops competing
   with the date above it on type weight alone. */
.date-jump-summary {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    padding: 8px 14px;
    border: 1px solid var(--border-2);
    border-radius: 999px;
    background: var(--surface-1);
    font-size: 13px;
    font-weight: 600;
    color: var(--accent);
    cursor: pointer;
    list-style: none;
    user-select: none;
}

.date-jump-summary:hover {
    background: var(--surface-2);
    border-color: var(--accent);
}

/* Open, it belongs to the panel hanging off it. */
.date-jump-pop[open] .date-jump-summary {
    background: var(--surface-2);
    border-color: var(--accent);
}

/* Both spellings: the triangle is furniture from another century and the
   calendar mark already says what this opens. */
.date-jump-summary::-webkit-details-marker {
    display: none;
}

.date-jump-summary::marker {
    content: '';
}



.date-jump-summary:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 3px;
    border-radius: 6px;
}

/* Hung from the right edge of the trigger, because the trigger is itself
   hard right in the heading row and a panel opening leftward is the only
   one that stays on a phone screen. */
.date-jump-panel {
    position: absolute;
    z-index: 30;
    top: calc(100% + 8px);
    right: 0;
    padding: 12px;
    min-width: 268px;
    /* Opaque, unlike every other .glass surface on the site.
       Those sit on the page background and the translucency is what makes
       them feel like glass over a sky. This one lands on top of whatever
       happens to be underneath it - a card, a photograph, the title bar -
       and at 86% white in light mode the page showed through the grid
       enough to make a row of numbers hard to read. A thing you are asked
       to read a date off has to be legible wherever it opens, so it takes
       the drawer's solid surface and keeps the border and the shadow. */
    background: var(--drawer-bg);
    backdrop-filter: none;
    box-shadow: 0 16px 40px rgba(8, 12, 24, 0.28);
}

.date-jump-cal-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    margin-bottom: 8px;
}

.date-jump-cal-month {
    font-size: 13px;
    font-weight: 600;
    color: var(--text);
}

.date-jump-cal-step {
    width: 28px;
    height: 28px;
    border-radius: 8px;
    border: 1px solid var(--panel-border);
    background: var(--surface-1);
    color: var(--text);
    font-size: 15px;
    line-height: 1;
    cursor: pointer;
}

.date-jump-cal-step:hover {
    background: var(--surface-2);
}

.date-jump-cal-names,
.date-jump-cal-days {
    display: grid;
    grid-template-columns: repeat(7, 1fr);
    gap: 2px;
}

.date-jump-cal-names {
    margin-bottom: 4px;
}

.date-jump-cal-names span {
    text-align: center;
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--muted);
}

.date-jump-day {
    aspect-ratio: 1;
    min-height: 32px;
    border: 0;
    border-radius: 8px;
    background: transparent;
    color: var(--text);
    font-size: 13px;
    cursor: pointer;
}

.date-jump-day:hover:not(:disabled) {
    background: var(--surface-2);
}

.date-jump-day:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: -2px;
}

/* Out of range days stay in place rather than being blanked out, so the
   month keeps its shape and it is obvious the grid ends rather than that
   the calendar is broken. */
.date-jump-day:disabled {
    color: var(--muted);
    opacity: 0.35;
    cursor: default;
}

/* Today is a ring; the day being looked at is filled. Both at once is the
   ordinary case on the home page, and they have to stay distinguishable. */
.date-jump-day.is-today {
    box-shadow: inset 0 0 0 1px var(--accent);
    color: var(--accent);
}

.date-jump-day.is-selected {
    background: var(--accent);
    color: #1a1205;
    font-weight: 600;
}

.date-jump-day.is-selected:hover {
    background: var(--accent);
}

/* Phones: a 268px panel hanging off the right of a 360px screen still
   fits, but only just, and the narrowest phones are narrower than that.
   Allowed to shrink to whatever the screen leaves rather than pushing the
   page sideways - which is the one thing a popover must never do. */
@media (max-width: 420px) {
    .date-jump-panel {
        min-width: 0;
        width: calc(100vw - 32px);
        max-width: 268px;
    }
}

/* The search that replaces the chips. The glass is one more chip until it
   is pressed; the box is the whole row once it is. */
.event-kind-search svg {
    display: block;
}

.event-search {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-top: 14px;
    padding: 0 12px;
    border: 1px solid var(--border-2);
    /* The same pill the chips are, so the row does not change shape when
       one becomes the other. */
    border-radius: 999px;
    background: var(--surface-1);
}

.event-search[hidden] {
    display: none;
}

.event-search-mark {
    display: flex;
    flex-shrink: 0;
    color: var(--muted);
}

.event-search-field {
    flex: 1 1 auto;
    min-width: 0;
    padding: 10px 0;
    border: 0;
    background: none;
    color: var(--text);
    font-size: 14px;
}

.event-search-field:focus {
    outline: none;
}

/* Chrome draws its own clear cross in a search field, in the middle of the
   row and a different shape from ours. One way out is enough. */
.event-search-field::-webkit-search-cancel-button {
    -webkit-appearance: none;
    appearance: none;
}

.event-search-close {
    flex-shrink: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 30px;
    height: 30px;
    padding: 0;
    border: 0;
    border-radius: 50%;
    background: none;
    color: var(--muted);
    cursor: pointer;
}

.event-search-close:hover {
    color: var(--text);
    background: var(--surface-2);
}

/* The feature switches, grouped by where the feature shows up. The heading
   carries the whole answer, so the rows under it need no tag of their own. */
.feature-group {
    margin: 26px 0 0;
    padding-top: 16px;
    border-top: 1px solid var(--border-1);
    font-size: 13px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--accent);
}

/* The first group sits right under the section's own text, which is
   already a rule's worth of separation. */
.feature-group:first-of-type {
    margin-top: 18px;
    padding-top: 0;
    border-top: 0;
}

.feature-group-note {
    margin: 4px 0 0;
    font-size: 12px;
    color: var(--muted);
}

.day-card[data-day-href] {
    cursor: pointer;
    transition: background 0.15s ease;
}

@media (hover: hover) and (pointer: fine) {
    .day-card[data-day-href]:hover {
        background: var(--surface-2);
    }
}

.day-strip {
    margin-bottom: 32px;
}

/* ---------- swipeable day/history tile strips ---------- */
/* Same scroll-snap + click-arrow pattern as .today-pager/.today-nav,
   generalized so "Next few days" and "Last few days" can share one set of
   rules instead of each getting a copy. */

.tile-strip {
    position: relative;
}

.tile-pager {
    display: flex;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
}

.tile-pager::-webkit-scrollbar {
    display: none;
}

.tile-page {
    flex: 0 0 100%;
    min-width: 0;
    scroll-snap-align: start;
    box-sizing: border-box;
}

.tile-nav {
    display: none;
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    z-index: 2;
    width: 34px;
    height: 34px;
    border-radius: 50%;
    border: 1px solid var(--panel-border);
    background: var(--nav-btn-bg);
    backdrop-filter: blur(6px);
    color: var(--muted);
    font-size: 20px;
    line-height: 1;
    cursor: pointer;
    align-items: center;
    justify-content: center;
    transition: opacity 0.15s ease, color 0.15s ease;
    opacity: 0.55;
}

.tile-nav:hover {
    opacity: 1;
    color: var(--text);
}

.tile-nav.hidden {
    visibility: hidden;
    pointer-events: none;
}

.tile-nav-prev {
    left: -4px;
}

.tile-nav-next {
    right: -4px;
}

@media (hover: hover) and (pointer: fine) {
    .tile-nav {
        display: flex;
    }
}

.tile-dots {
    display: flex;
    justify-content: center;
    gap: 6px;
    padding-top: 12px;
}

.tile-dots .dot {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--panel-border);
}

.tile-dots .dot.active {
    background: var(--accent);
}

.day-card {
    padding: 16px 18px;
    border-radius: var(--radius-md);
}

.day-card .day-name {
    display: block;
    color: var(--muted);
    font-size: 12px;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    margin-bottom: 12px;
}

.day-events {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 16px;
}

.day-event {
    text-align: center;
}

.day-event-icon {
    display: block;
    font-size: 26px;
    line-height: 1;
    margin-bottom: 6px;
}

/* The whole tile is the link - role and keyboard handling are set on it in
   index.php - so it takes the pointer rather than any one thing inside it. */
.day-card[data-day-href] {
    cursor: pointer;
}

.day-event-thumb {
    display: block;
    width: 100%;
    aspect-ratio: 4 / 3;
    border-radius: 10px;
    overflow: hidden;
    border: none;
    padding: 0;
    margin: 0 0 8px;
    /* The card underneath is the link, so the photo shows the same pointer
       the rest of it does rather than offering a zoom it no longer has. */
    cursor: inherit;
    background: var(--surface-1);
}

.day-event-thumb img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

.day-event-label {
    display: block;
    font-weight: 700;
    font-size: 15px;
}

.day-event-time {
    display: block;
    color: var(--muted);
    font-size: 11px;
    margin-top: 2px;
}

.score-text-1 { color: var(--score-1); }
.score-text-2 { color: var(--score-2); }
.score-text-3 { color: var(--score-3); }
.score-text-4 { color: var(--score-4); }
.score-text-5 { color: var(--score-5); }

/* ---------- location search / grid ---------- */

.location-section {
    margin-bottom: 28px;
}

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

.location-card {
    padding: 10px 12px;
    border-radius: var(--radius-md);
    text-decoration: none;
    display: block;
}

.location-card .location-card-name {
    display: block;
    font-weight: 600;
    font-size: 13px;
}

.location-card .location-card-region {
    display: block;
    color: var(--muted);
    font-size: 11px;
    margin-top: 1px;
}

.location-card .location-card-next {
    display: block;
    color: var(--text);
    font-size: 11px;
    margin-top: 6px;
    padding-top: 6px;
    border-top: 1px solid var(--panel-border);
}

.location-card.active {
    border: 1px solid var(--accent);
}

/* ---------- error / empty states ---------- */

.state-card {
    padding: 28px;
    text-align: center;
    margin-bottom: 16px;
}

.state-card h1 {
    font-size: 22px;
    margin: 0 0 8px;
}

.state-card p {
    color: var(--muted);
    margin: 0;
}

/* ---------- footer ---------- */

footer {
    margin-top: 40px;
    text-align: center;
    color: var(--muted);
    font-size: 13px;
    line-height: 1.8;
}

footer a {
    color: var(--muted);
    text-decoration: underline;
}

/* ---------- settings/login pages reuse a subset of the above ---------- */

/* Top padding and content width match .shell so the topbar - and with it
   the SkyTime logo - lands in exactly the same place on every page. */
.page-shell {
    min-height: 100vh;
    padding: 24px 20px 48px;
}

.dashboard {
    max-width: 680px;
    margin: 0 auto;
    display: grid;
    gap: 20px;
}

/* A grid item's minimum width is its content's, and a table set to nowrap
   has a wide one. That minimum climbed straight up through the card and the
   scroll wrapper - a scroll container does not shrink its own intrinsic size
   - and set the width of the page, so on a phone the admin tables rendered
   the whole site at three screens wide and zoomed out to fit. Zero here, and
   the wide thing scrolls inside its wrapper instead, which is what the
   wrapper was for. */
.dashboard > * {
    min-width: 0;
}

.card {
    border-radius: var(--radius-lg);
    padding: 24px;
}

.eyebrow {
    margin: 0 0 8px;
    text-transform: uppercase;
    letter-spacing: 0.12em;
    font-size: 12px;
    color: var(--muted);
}

h1, h2, h3 {
    margin: 0 0 10px;
}

.subtitle {
    margin: 0;
    color: var(--muted);
}


.button {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    padding: 12px 16px;
    border-radius: 14px;
    text-decoration: none;
    border: 1px solid var(--border-2);
    /* Stated rather than left alone, because this class goes on both <a> and
       <button>. An anchor has no background of its own and looked right; a
       button takes the browser's, which is near-white, and the light text
       this sets on top of it disappeared in dark mode. */
    background: transparent;
    color: var(--text);
    cursor: pointer;
}

.button.primary {
    background: linear-gradient(135deg, var(--accent), var(--accent-2));
    border: none;
}

.button.ghost {
    background: var(--surface-1);
}

.settings-form {
    display: grid;
    gap: 20px;
}

.settings-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 14px;
}

.compact-grid {
    grid-template-columns: repeat(4, 1fr);
}

label {
    display: grid;
    gap: 8px;
    color: var(--muted);
    font-size: 14px;
}

/* Checkbox rows opt out of the stacked grid above, which would otherwise
   drop the box onto its own line under the text. The markup is text-then-
   input, so row-reverse puts the box first without reordering the DOM. */
.checkbox-label {
    display: flex;
    flex-direction: row-reverse;
    justify-content: flex-end;
    align-items: center;
    gap: 10px;
    cursor: pointer;
}

.checkbox-label input[type="checkbox"] {
    width: 18px;
    height: 18px;
    flex-shrink: 0;
    margin: 0;
    accent-color: var(--accent);
    cursor: pointer;
}

input[type="number"], input[type="text"], input[type="email"], input[type="password"], input[type="search"], input[type="time"], select {
    width: 100%;
    border-radius: 12px;
    border: 1px solid var(--border-1);
    padding: 12px 14px;
    background: var(--surface-1);
    color: var(--text);
}

select {
    color-scheme: dark;
}

/* The dropdown popup itself often ignores color-scheme/background and
   renders as the OS's light list regardless - force readable text there
   explicitly rather than relying on the near-white --text color, which is
   invisible against that light background until an item is hovered. */
select option {
    color: #14161f;
    background-color: #ffffff;
}

.obs-filter-bar {
    grid-template-columns: repeat(4, 1fr);
    align-items: end;
}

.obs-filter-bar .save-row {
    grid-column: 1 / -1;
}

.obs-table {
    display: flex;
    flex-direction: column;
    gap: 8px;
    margin-top: 16px;
}

.obs-table-head, .obs-row > summary {
    display: grid;
    /* Fixed first column so the thumbnails line up down the page whether or
       not a given event actually caught one. */
    grid-template-columns: 72px 1.3fr 0.8fr 1.1fr 1.3fr 1.1fr;
    gap: 12px;
    align-items: center;
}

.obs-thumb {
    width: 64px;
    height: 42px;
    border-radius: 8px;
    overflow: hidden;
    background: var(--surface-2);
    display: block;
}

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

.obs-table-head {
    padding: 0 14px 0 28px;
    font-size: 12px;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--muted);
}

.obs-row {
    background: var(--surface-1);
    border: 1px solid var(--border-1);
    border-radius: 12px;
}

.obs-row > summary {
    position: relative;
    padding: 14px 14px 14px 28px;
    cursor: pointer;
    list-style: none;
    font-size: 14px;
}

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

.obs-row > summary::before {
    content: '\25B8';
    position: absolute;
    left: 12px;
    top: 50%;
    transform: translateY(-50%);
    color: var(--muted);
}

.obs-row[open] > summary::before {
    content: '\25BE';
}

.obs-row-detail {
    padding: 0 14px 16px 28px;
    border-top: 1px solid var(--surface-3);
}

.obs-row-detail .settings-form {
    margin-top: 14px;
    gap: 14px;
}

.obs-photo-link {
    display: block;
    max-width: 260px;
    margin: 14px 0 4px;
    border-radius: 10px;
    overflow: hidden;
}

.obs-photo-link img {
    display: block;
    width: 100%;
    height: auto;
}

.vote-list {
    display: grid;
    gap: 6px;
    margin: 10px 0 4px;
}

.vote-row {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 90px minmax(0, 1.6fr);
    gap: 10px;
    align-items: baseline;
    padding: 8px 10px;
    border-radius: 10px;
    background: var(--surface-1);
    font-size: 13px;
}

.vote-row.is-own {
    border: 1px solid var(--own-vote-border);
}

.vote-who {
    color: var(--muted);
    overflow-wrap: anywhere;
}

.vote-score {
    font-weight: 700;
}

.vote-meta {
    color: var(--muted);
    overflow-wrap: anywhere;
}

@media (max-width: 640px) {
    .vote-row {
        grid-template-columns: 1fr;
        gap: 2px;
    }
}

.obs-status {
    font-weight: 600;
    font-size: 13px;
}

.obs-status.is-pending {
    color: var(--accent);
}

.obs-status.is-reviewed {
    color: var(--positive);
}

.save-row {
    display: flex;
    justify-content: flex-end;
}

.score-picker {
    display: flex;
    gap: 8px;
    flex-wrap: wrap;
}

.score-choice {
    position: relative;
    cursor: pointer;
}

.score-choice input {
    position: absolute;
    opacity: 0;
    width: 1px;
    height: 1px;
}

.score-choice span {
    display: inline-flex;
    padding: 10px 14px;
    border-radius: 12px;
    border: 1px solid var(--border-1);
    background: var(--surface-1);
    color: var(--muted);
    font-size: 13px;
    transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}

.score-choice input:checked + span {
    background: linear-gradient(135deg, var(--accent), var(--accent-2));
    color: var(--text);
    border-color: transparent;
}

.score-choice input:focus-visible + span {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

.error-card {
    border-left: 4px solid var(--negative);
}

.success-card {
    border-left: 4px solid var(--positive);
}

code {
    background: var(--surface-3);
    padding: 2px 6px;
    border-radius: 8px;
}

@media (max-width: 640px) {
    .settings-grid, .compact-grid {
        grid-template-columns: 1fr;
    }

    .obs-table-head {
        display: none;
    }

    .obs-row > summary {
        display: flex;
        flex-direction: column;
        gap: 4px;
    }

    .obs-status {
        margin-top: 2px;
    }

    .location-grid {
        grid-template-columns: 1fr 1fr;
    }

    .day-event-time {
        font-size: 10px;
    }

    .verdict-main {
        flex-direction: column;
        gap: 10px;
    }
}

@media (max-width: 420px) {
    .location-grid {
        grid-template-columns: 1fr;
    }

    .day-event-icon {
        font-size: 22px;
    }

    .day-segments {
        grid-template-columns: 1fr;
    }

    .day-segment {
        flex-direction: row;
        align-items: center;
        justify-content: space-between;
        text-align: left;
        padding: 12px 14px;
        gap: 10px;
    }

    .day-segment-label {
        margin-bottom: 0;
        width: 60px;
        flex-shrink: 0;
    }

    .day-segment-main {
        flex-direction: row;
        align-items: center;
        gap: 8px;
        flex-shrink: 0;
    }

    .day-segment-icon {
        margin-bottom: 0;
    }

    .day-segment-info {
        align-items: flex-end;
        text-align: right;
        flex: 1;
        min-width: 0;
    }

    .day-segment-desc {
        margin-top: 0;
        white-space: nowrap;
    }

    .day-segment-meta {
        margin-top: 2px;
    }
}

/* ---------- admin section ---------- */

.admin-nav {
    display: flex;
    gap: 8px;
    flex-wrap: wrap;
    margin: -8px 0 4px;
}

.admin-nav-link {
    padding: 9px 16px;
    border-radius: 999px;
    border: 1px solid var(--panel-border);
    background: var(--surface-1);
    color: var(--muted);
    text-decoration: none;
    font-size: 14px;
}

.admin-nav-link.active {
    background: linear-gradient(135deg, var(--accent), var(--accent-2));
    border-color: transparent;
    color: var(--text);
    font-weight: 600;
}

@media (hover: hover) and (pointer: fine) {
    .admin-nav-link:not(.active):hover {
        color: var(--text);
        background: var(--surface-2);
    }
}

/* Compact clickable list for admin records - the row is the link, so the
   whole width is a target rather than just the name. */
.admin-table {
    display: flex;
    flex-direction: column;
    gap: 6px;
    margin-top: 14px;
}

.admin-table-head,
.admin-table-row {
    display: grid;
    grid-template-columns: 2fr 0.8fr 1.6fr 1fr;
    gap: 12px;
    align-items: center;
}

.admin-table-head {
    padding: 0 14px;
    font-size: 12px;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--muted);
}

.admin-table-row {
    padding: 13px 14px;
    border-radius: var(--radius-md);
    border: 1px solid var(--panel-border);
    background: var(--surface-1);
    text-decoration: none;
    color: var(--muted);
    font-size: 14px;
    transition: background 0.15s ease;
}

@media (hover: hover) and (pointer: fine) {
    .admin-table-row:hover {
        background: var(--surface-2);
    }
}

.admin-table-strong {
    color: var(--text);
    font-weight: 600;
    overflow-wrap: anywhere;
}

.field-note {
    display: block;
    color: var(--muted);
    font-size: 14px;
    margin-bottom: 8px;
}

@media (max-width: 640px) {
    .admin-table-head {
        display: none;
    }

    .admin-table-row {
        grid-template-columns: 1fr;
        gap: 3px;
    }
}

.location-admin-head {
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    gap: 16px;
    flex-wrap: wrap;
}

.camera-group {
    margin: 14px 0 0;
    padding: 14px;
    border: 1px solid var(--panel-border);
    border-radius: var(--radius-md);
    background: var(--surface-1);
}

.camera-group legend {
    padding: 0 8px;
    font-size: 12px;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--muted);
}

/* ---------- captured-photo lightbox ---------- */

.lightbox[hidden] {
    display: none;
}

/* Its own scrim rather than the shared --scrim (which the nav drawer also
   uses): that one is deliberately light, and the page showing through it
   left the lightbox's own caption competing with the timeline labels behind
   it. Kept to a light wash - white on light, dark on dark - so the page is
   still visibly there; the blur does the separating, and the caption's own
   panel handles legibility. */
.lightbox {
    position: fixed;
    inset: 0;
    z-index: 100;
    background: var(--lightbox-scrim);
    backdrop-filter: blur(3px);
    display: flex;
    align-items: center;
    justify-content: center;
    /* Just enough to keep the card off the edges. Anything more is screen
       spent on scrim, on the one view whose whole purpose is the photo. */
    padding: 12px;
}

/* Built as one of the feed's tiles rather than a bare image with a caption
   floating under it: same surface, same corner radius, the photograph flush
   to the top edge and its details in an attached strip below. Two views of
   the same photograph looked like two different things, and this is the one
   people arrive at from the other.

   Width comes from the picture, not from the viewport - the card wraps the
   image, so the caption strip is exactly as wide as what it describes and a
   portrait frame does not sit in a letterboxed slab. */
.lightbox-content {
    position: relative;
    max-width: calc(100vw - 24px);
    max-height: calc(100vh - 24px);
    display: flex;
    flex-direction: column;
    align-items: stretch;
    overflow: hidden;
    border-radius: var(--radius-md);
    background: var(--panel);
    border: 1px solid var(--panel-border);
    box-shadow: var(--shadow);
}

/* Under the photo, not over it. Floating the caption bought the image about a
   quarter more height and spent it covering the part of the photo people had
   just clicked to see - which is the wrong trade on a page whose only job is
   showing a photograph. It sits below again, and the image keeps every pixel
   of height the strip does not need.

   The strip's height is reserved rather than measured, since the image has to
   be sized in CSS before anything has laid the caption out. Generous enough
   for two lines on a narrow screen; the card's own cap and overflow catch a
   third rather than letting it push off the bottom. */
.lightbox-content img {
    display: block;
    width: auto;
    height: auto;
    max-width: calc(100vw - 26px);
    max-height: calc(100vh - 26px - 74px);
    border-radius: 0;
    object-fit: contain;
}

/* Part of the card now, not a pill floating under it: no margin, no corners
   of its own, seated against the photo by a hairline the way the feed tile's
   figcaption is. It no longer needs a panel of its own for legibility - it is
   on the card's own surface, over nothing. */
.lightbox-caption {
    margin: 0;
    max-width: none;
    text-align: left;
    padding: 10px 14px;
    border-radius: 0;
    border: 0;
    border-top: 1px solid var(--panel-border);
    background: transparent;
    box-shadow: none;
}

/* The feed tile's proportions, a step up in size because this one is being
   looked at rather than scrolled past. */
.lightbox-rating {
    display: block;
    font-size: 15px;
    font-weight: 600;
}

.lightbox-meta {
    display: block;
    color: var(--muted);
    font-size: 12px;
    margin-top: 3px;
}

/* Above the card, which now reaches the edges it used to sit clear of. It is
   positioned against the overlay rather than the card, so it stays in the
   screen's corner whatever shape the photograph is. */
.lightbox-close {
    position: absolute;
    z-index: 2;
    top: 18px;
    right: 22px;
    width: 40px;
    height: 40px;
    border-radius: 50%;
    border: 1px solid var(--panel-border);
    background: var(--surface-2);
    color: var(--text);
    font-size: 24px;
    line-height: 1;
    cursor: pointer;
}

/* ---------- history table page ---------- */

.history-table {
    display: flex;
    flex-direction: column;
    gap: 10px;
    margin-top: 16px;
}

.history-row-link {
    display: flex;
    align-items: center;
    gap: 16px;
    padding: 14px;
    border-radius: var(--radius-md);
    background: var(--surface-1);
    border: 1px solid var(--surface-3);
    text-decoration: none;
    color: var(--text);
    transition: background 0.15s ease;
}

@media (hover: hover) and (pointer: fine) {
    .history-row-link:hover {
        background: var(--surface-2);
    }
}

.history-row-thumb {
    flex: 0 0 96px;
    width: 96px;
    height: 72px;
    border-radius: 10px;
    overflow: hidden;
    background: var(--surface-1);
    display: flex;
    align-items: center;
    justify-content: center;
}

.history-row-thumb img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

.history-row-icon {
    font-size: 30px;
}

.history-row-info {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 4px;
}

.history-row-date {
    font-weight: 600;
    font-size: 15px;
}

.history-row-meta {
    color: var(--muted);
    font-size: 13px;
}

.history-row-rating {
    font-weight: 700;
    font-size: 15px;
    flex-shrink: 0;
    white-space: nowrap;
}

/* ---------- dedicated day page ---------- */

.day-adjacent-nav {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 12px;
    margin-bottom: 28px;
}

.day-adjacent-link {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 12px;
    border-radius: var(--radius-md);
    background: var(--surface-1);
    border: 1px solid var(--surface-3);
    text-decoration: none;
    color: var(--text);
}

.day-adjacent-nav a:last-child {
    flex-direction: row-reverse;
    text-align: right;
}

.day-adjacent-thumb {
    flex: 0 0 56px;
    width: 56px;
    height: 56px;
    border-radius: 8px;
    overflow: hidden;
    background: var(--surface-1);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 22px;
}

.day-adjacent-thumb img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

.day-adjacent-label {
    display: block;
    color: var(--muted);
    font-size: 12px;
}

/* The date as the card's badge draws it, smaller, with the weekday beside. */
.day-adjacent-when {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-top: 4px;
}

/* Number then month on one line, sharing a baseline and nearly touching -
   the card's mark turned on its side, at the tile's scale. */
.day-adjacent-badge {
    display: flex;
    flex-direction: row;
    align-items: baseline;
    gap: 2px;
    line-height: 0.9;
    font-weight: 900;
    letter-spacing: -0.02em;
}

.day-adjacent-badge .today-date-day {
    font-size: 26px;
    margin-left: 0;
}

.day-adjacent-badge .today-date-month {
    font-size: 15px;
    margin-top: 0;
}

.day-adjacent-weekday {
    font-weight: 600;
    font-size: 15px;
}

.day-adjacent-nav a:last-child .day-adjacent-when {
    flex-direction: row-reverse;
}


@media (max-width: 640px) {

    .day-adjacent-nav {
        grid-template-columns: 1fr;
    }

    .day-adjacent-nav a:last-child {
        flex-direction: row;
        text-align: left;
    }

    .day-adjacent-nav a:last-child .day-adjacent-when {
        flex-direction: row;
    }

}

/* ---------- admin image gallery ---------- */

/* Tiles size themselves to the grid rather than to a fixed column count, so
   the same markup reads as one column on a phone and four on a desktop. */
.gallery-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
    gap: 16px;
    margin-top: 18px;
}

.gallery-tile {
    /* The favourite heart is positioned against this. */
    position: relative;
    display: flex;
    flex-direction: column;
    overflow: hidden;
    padding: 0;
    border-radius: var(--radius-lg);
    margin: 0;
}

/* A link through to that day's page, not a lightbox trigger - the day page
   is where the times, the timeline and both of that day's events live. */
.gallery-thumb {
    display: block;
    width: 100%;
    padding: 0;
    border: 0;
    background: var(--surface-2);
    line-height: 0;
}

a.gallery-thumb {
    cursor: pointer;
}

@media (hover: hover) and (pointer: fine) {
    a.gallery-thumb img {
        transition: opacity 0.15s ease;
    }

    a.gallery-thumb:hover img {
        opacity: 0.82;
    }
}

/* Webcam stills arrive in whatever aspect the camera happens to use; a fixed
   box with object-fit keeps the grid from turning ragged. */
.gallery-thumb img {
    width: 100%;
    aspect-ratio: 16 / 9;
    object-fit: cover;
    display: block;
}

.gallery-body {
    display: flex;
    flex-direction: column;
    gap: 10px;
    padding: 14px;
}

.gallery-head {
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.gallery-when {
    font-size: 13px;
    color: var(--muted);
}

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

.gallery-scores dt {
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--muted);
}

.gallery-scores dd {
    margin: 2px 0 0;
    font-size: 14px;
    font-weight: 600;
}

.gallery-votecount {
    display: block;
    font-size: 11px;
    font-weight: 400;
    color: var(--muted);
}

.gallery-colors {
    margin: 0;
    font-size: 12px;
    color: var(--muted);
}

.gallery-votes {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 3px;
    font-size: 12px;
}

.gallery-votes li {
    display: flex;
    gap: 8px;
    align-items: baseline;
}

.gallery-voter {
    color: var(--muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.gallery-rate {
    display: flex;
    flex-direction: column;
    gap: 8px;
    padding-top: 10px;
    border-top: 1px solid var(--panel-border);
}

/* ---------- tuning: score explainer + capture frequency ---------- */

.explainer {
    margin: 14px 0 0;
    padding-left: 20px;
    display: flex;
    flex-direction: column;
    gap: 10px;
    color: var(--muted);
    line-height: 1.55;
}

.explainer strong {
    color: var(--text);
}

.explainer code {
    font-size: 0.92em;
    padding: 1px 5px;
    border-radius: 5px;
    background: var(--surface-2);
}

.cron-interval {
    margin-top: 16px;
}

.source-list {
    display: flex;
    flex-direction: column;
    gap: 14px;
    margin-top: 18px;
}

/* Subtle group boxes rather than nested cards: these are one page of related
   settings, not seven separate panels competing for attention. */
.source-group,
.metric-group {
    border: 1px solid var(--panel-border);
    border-radius: var(--radius-md);
    background: var(--surface-1);
    padding: 14px 16px 16px;
    margin: 0 0 14px;
    display: flex;
    flex-direction: column;
    gap: 10px;
}

.source-group > legend,
.metric-group > legend {
    padding: 0 8px;
    font-weight: 600;
    display: flex;
    align-items: baseline;
    gap: 10px;
    flex-wrap: wrap;
}

.source-group.is-over {
    border-color: var(--negative);
}

.source-provider,
.metric-share {
    font-size: 12px;
    font-weight: 400;
    color: var(--muted);
}

.source-facts {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));
    gap: 10px;
}

.source-facts dt {
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--muted);
}

.source-facts dd {
    margin: 2px 0 0;
    font-weight: 600;
    font-size: 14px;
}

.source-uses {
    margin: 0;
    font-size: 13px;
    line-height: 1.55;
    color: var(--muted);
}

/* Warnings are advisory, not errors - a left rule reads as "mind this"
   without turning every group box into an alarm. */
.source-warning {
    margin: 0;
    padding-left: 10px;
    border-left: 2px solid var(--accent);
    font-size: 13px;
    line-height: 1.5;
    color: var(--muted);
}

.budget-warn {
    margin: 14px 0 0;
    padding: 10px 12px;
    border-radius: var(--radius-md);
    border: 1px solid var(--negative);
    background: var(--surface-1);
    font-size: 13px;
    line-height: 1.6;
}

.override-box {
    margin-top: 16px;
    padding: 14px 16px;
    border: 1px dashed var(--panel-border);
    border-radius: var(--radius-md);
    background: var(--surface-1);
}

/* ---------- shared site footer + legal page ---------- */

.site-footer {
    max-width: 640px;
    margin-left: auto;
    margin-right: auto;
}

.site-footer p {
    margin: 0 0 6px;
}

/* Long-form prose, unlike the rest of the site's dense cards - wider line
   height and real spacing between headings so the terms are actually
   readable rather than technically present. */
.legal h2 {
    margin: 26px 0 8px;
    font-size: 16px;
}

.legal h2:first-child {
    margin-top: 0;
}

.legal p,
.legal li {
    margin: 0 0 10px;
    color: var(--muted);
    line-height: 1.65;
    font-size: 14px;
}

.legal strong {
    color: var(--text);
}

.legal ul {
    margin: 0 0 10px;
    padding-left: 20px;
}

.legal a {
    color: var(--text);
    text-decoration: underline;
}

/* ---------- captured-photo thumbnail on an event tile ---------- */

/* Deliberately small and secondary: the tile's job is still the verdict and
   the time. The photo is "here's how it actually turned out", one tap from
   full size in the shared lightbox. Centred on its own row, image only -
   the picture says what it is without a caption. */
/* Figure wrapper: the frame plus the time it was taken. */
.verdict-capture {
    margin: 14px 0 0;
    text-align: center;
}

/* Slightly greyed, and small - it labels the photo rather than competing
   with the tile's own times. */
.verdict-capture-time {
    margin-top: 6px;
    color: var(--muted);
    font-size: 12px;
}

/* No frame around the photo - the rounded corners are enough to seat it on
   the tile, and a border plus its padding was a second rectangle competing
   with the picture inside it. The corner radius is unchanged. */
.verdict-thumb {
    display: block;
    margin: 0 auto;
    padding: 0;
    border: 0;
    background: none;
    line-height: 0;
    cursor: pointer;
}

/* Twice the size it was. A webcam frame at 132px was a stamp - big enough to
   prove a photo exists, too small to see what the sky did, which is the only
   reason it is on the tile. Capped by the tile's own width on a phone. */
.verdict-thumb img {
    width: 264px;
    max-width: 100%;
    height: 176px;
    object-fit: cover;
    border-radius: 10px;
    display: block;
}

/* Rests flat, lifts on hover. A cursor change says "this does something"
   only once the pointer is already on it and only to someone watching the
   cursor; a photograph that rises off the page says it about the thing being
   pointed at. The transition is on the resting state so it eases back down
   as well as up, and the shadow is what sells the lift - without it the
   image only looks slightly bigger. */
.verdict-thumb img {
    transition: transform 0.16s ease, box-shadow 0.16s ease;
}

@media (hover: hover) and (pointer: fine) {
    .verdict-thumb:hover img,
    .verdict-thumb:focus-visible img {
        transform: translateY(-3px);
        box-shadow: 0 10px 22px rgba(0, 0, 0, 0.42);
    }
}

/* Nothing moves for anyone who has asked the system for less of it. The
   photograph still brightens, so the hover is not lost entirely. */
@media (prefers-reduced-motion: reduce) {
    .verdict-thumb img {
        transition: none;
    }

    .verdict-thumb:hover img {
        transform: none;
        filter: brightness(1.06);
    }
}

/* Public gallery tile: one rating word in place of the admin view's
   predicted / colour-analysis / observed columns. */
.gallery-rating {
    margin: 0;
    font-size: 17px;
    font-weight: 700;
}

.gallery-body > .gallery-when {
    margin: -6px 0 0;
}

/* ---------- gallery toolbar ---------- */

/* One bar instead of a header card plus a filter card plus a count line -
   those three stacked pushed the photos most of the way down the screen. */
/* Square, like every other page's title bar. It names the page and carries
   its controls; it is not one of the things on the page. */
.gallery-bar {
    padding: 16px 18px;
    border-radius: 0;
    background: var(--surface-1);
    border: 1px solid var(--border-1);
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: 12px 20px;
}

.gallery-bar-title {
    flex: 1 1 220px;
    min-width: 0;
}

.gallery-bar-title h1 {
    margin: 0;
    font-size: 22px;
    line-height: 1.2;
}

.gallery-bar-title p {
    margin: 3px 0 0;
    color: var(--muted);
    font-size: 12px;
    line-height: 1.45;
}

.gallery-filters {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: 8px 10px;
}

/* Overrides the global `label { display: grid; gap: 8px }` - at this size
   the 8px gap between caption and control was most of the row's height. */
.gallery-filters label {
    display: flex;
    flex-direction: column;
    gap: 3px;
    min-width: 0;
}

.gallery-filters label > span {
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.07em;
    color: var(--muted);
}

.gallery-filters select,
.gallery-filters input[type="date"] {
    padding: 6px 8px;
    font-size: 13px;
    border-radius: 9px;
    border: 1px solid var(--panel-border);
    background: var(--surface-1);
    color: var(--text);
    max-width: 150px;
}

.gallery-filters .button {
    padding: 6px 14px;
    font-size: 13px;
}

.gallery-count {
    color: var(--muted);
    font-size: 12px;
    padding-bottom: 7px;
    white-space: nowrap;
}

/* Below this the bar is stacking anyway, so let the title have its own row
   and the controls spread across the full width rather than squeezing. */
@media (max-width: 620px) {
    .gallery-bar-title {
        flex-basis: 100%;
    }

    .gallery-filters {
        width: 100%;
    }

    .gallery-filters label {
        flex: 1 1 130px;
    }

    .gallery-filters select,
    .gallery-filters input[type="date"] {
        max-width: none;
        width: 100%;
    }
}

/* Quick link to the photo gallery, sitting beside the wordmark. Sized and
   bordered like the hamburger so the two read as one row of controls. */
.topbar-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 38px;
    height: 38px;
    flex-shrink: 0;
    border-radius: 12px;
    border: 1px solid var(--panel-border);
    background: var(--surface-1);
    color: var(--muted);
    text-decoration: none;
}

@media (hover: hover) and (pointer: fine) {
    .topbar-icon:hover {
        color: var(--text);
        background: var(--surface-2);
    }
}

/* The date field is toggled with the `hidden` attribute, which the flex
   display above would otherwise win against. */
.gallery-filters label[hidden] {
    display: none;
}

/* ---------- search results page ---------- */

.search-heading {
    margin: 0 0 18px;
    font-size: 22px;
    line-height: 1.25;
}

.search-empty {
    color: var(--muted);
    font-size: 14px;
    margin: 0;
}

/* The export page swaps a whole column list per dataset; the `hidden`
   attribute has to beat the grid/flex display these would otherwise get. */
.settings-grid[hidden],
.settings-form label[hidden],
p.dataset-blurb[hidden] {
    display: none;
}

/* ---------- place search on the admin location form ---------- */

.place-search {
    position: relative;
    margin-bottom: 16px;
}

.place-search-results {
    list-style: none;
    margin: 6px 0 0;
    padding: 4px;
    position: absolute;
    z-index: 20;
    width: 100%;
    max-height: 300px;
    overflow-y: auto;
    border: 1px solid var(--panel-border);
    border-radius: var(--radius-md);
    background: var(--drawer-bg);
    box-shadow: var(--shadow);
}

.place-search-results[hidden] {
    display: none;
}

.place-search-option {
    display: flex;
    flex-direction: column;
    gap: 2px;
    width: 100%;
    padding: 9px 10px;
    border: 0;
    border-radius: 9px;
    background: none;
    color: var(--text);
    font: inherit;
    text-align: left;
    cursor: pointer;
}

.place-search-option span {
    font-size: 12px;
    color: var(--muted);
}

@media (hover: hover) and (pointer: fine) {
    .place-search-option:hover {
        background: var(--surface-2);
    }
}

/* ---------- collapsible admin location list ---------- */

.location-details {
    padding: 0;
}

.location-details > summary {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 4px 12px;
    padding: 16px 20px;
    cursor: pointer;
    border-radius: var(--radius-lg);
    list-style: none;
}

/* The default disclosure triangle sits oddly against a flex row, so it is
   replaced with one that rotates on open. */
.location-details > summary::-webkit-details-marker {
    display: none;
}

.location-details > summary::before {
    content: "›";
    display: inline-block;
    width: 12px;
    color: var(--muted);
    transform: rotate(0deg);
    transition: transform 0.15s ease;
}

.location-details[open] > summary::before {
    transform: rotate(90deg);
}

.location-details[open] > summary {
    border-bottom: 1px solid var(--panel-border);
    border-radius: var(--radius-lg) var(--radius-lg) 0 0;
}

@media (hover: hover) and (pointer: fine) {
    .location-details > summary:hover {
        background: var(--surface-1);
    }
}

.location-summary-name {
    font-weight: 600;
    font-size: 16px;
}

/* Everything after the summary is the expanded body. */
.location-details > *:not(summary) {
    margin-left: 20px;
    margin-right: 20px;
}

.location-details > *:last-child {
    margin-bottom: 20px;
}

.location-details > .location-admin-head {
    margin-top: 16px;
}

.aurora-reason {
    margin-top: 4px;
}

/* Inline save feedback on the gallery rating picker, in place of the Save
   button the script hides. */
.gallery-save-status {
    font-size: 12px;
    color: var(--muted);
    min-height: 1em;
}

.gallery-save-status.is-error {
    color: var(--negative);
}

/* Camera control frames on the observation log - admin only. */
.obs-controls {
    margin: 12px 0 4px;
}

.obs-control-strip {
    display: flex;
    gap: 10px;
    margin-top: 6px;
}

.obs-control-strip figure {
    margin: 0;
    width: 110px;
}

.obs-control-strip img {
    width: 100%;
    aspect-ratio: 4 / 3;
    object-fit: cover;
    border-radius: 8px;
    display: block;
    border: 1px solid var(--panel-border);
}

.obs-control-strip figcaption {
    font-size: 11px;
    color: var(--muted);
    margin-top: 3px;
}

/* Correlation table on the tuning page - four columns rather than the
   admin table's default five. */
.correlation-table .admin-table-head,
.correlation-table .admin-table-row {
    grid-template-columns: 2fr 1fr 1fr 1.2fr;
}

/* ---------- observation log: view toggle and tile extras ---------- */

.obs-view-bar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    margin-top: 16px;
}

.obs-view-bar .subtitle {
    margin: 0;
}

.view-toggle {
    display: inline-flex;
    border: 1px solid var(--panel-border);
    border-radius: 10px;
    overflow: hidden;
}

.view-toggle a {
    padding: 6px 14px;
    font-size: 13px;
    text-decoration: none;
    color: var(--muted);
}

.view-toggle a.active {
    background: var(--surface-2);
    color: var(--text);
    font-weight: 600;
}

@media (hover: hover) and (pointer: fine) {
    .view-toggle a:not(.active):hover {
        background: var(--surface-1);
        color: var(--text);
    }
}

/* Time and notes are the uncommon case, so they stay folded away behind the
   rating, which is the whole interaction most of the time. */
.obs-tile-more > summary {
    cursor: pointer;
    font-size: 12px;
    color: var(--muted);
    padding-top: 8px;
}

.obs-tile-more .settings-form {
    gap: 8px;
    margin-top: 8px;
}

.obs-tile-more .settings-form input {
    font-size: 13px;
    padding: 6px 8px;
}

/* An event with no captured photo still needs to hold its place in the grid
   so the tiles stay aligned. */
.gallery-tile > span.gallery-thumb {
    aspect-ratio: 16 / 9;
}

/* Control frames inside a tile are narrower than in an expanded table row,
   so they size to the tile rather than to a fixed width. */
.gallery-tile .obs-controls {
    margin: 4px 0 0;
}

.gallery-tile .obs-control-strip figure {
    width: auto;
    flex: 1 1 0;
    min-width: 0;
}

/* Whether a captured frame may train the model. Deliberately quieter than
   the rating picker beside it: rating is the everyday action, this is the
   exception you reach for when a photo is wrong. */
.obs-validity {
    display: flex;
    flex-direction: column;
    gap: 6px;
    margin: 10px 0 0;
    padding: 10px;
    border: 1px solid var(--border-1);
    border-radius: 12px;
    background: var(--surface-1);
}

.validity-picker {
    display: flex;
    gap: 6px;
    flex-wrap: wrap;
}

.validity-choice {
    position: relative;
    cursor: pointer;
}

.validity-choice input {
    position: absolute;
    opacity: 0;
    width: 1px;
    height: 1px;
}

.validity-choice span {
    display: inline-flex;
    padding: 6px 12px;
    border-radius: 999px;
    border: 1px solid var(--border-1);
    background: var(--surface-2, var(--surface-1));
    color: var(--muted);
    font-size: 12px;
    transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}

.validity-choice input:checked + span {
    color: var(--text);
    border-color: transparent;
}

.validity-choice-auto input:checked + span {
    background: var(--border-1);
}

/* The two overrides are colour-coded because they are a standing decision
   somebody made, not a default - they should not read the same as Auto. */
.validity-choice-valid input:checked + span {
    background: rgba(64, 160, 96, 0.85);
}

.validity-choice-invalid input:checked + span {
    background: rgba(190, 70, 70, 0.85);
}

.validity-choice input:focus-visible + span {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

.validity-status {
    margin: 0;
    font-size: 12px;
    line-height: 1.4;
    color: var(--muted);
}

.validity-status.is-excluded {
    color: rgb(224, 138, 138);
}

/* An account that has not confirmed its address yet. A nudge, not a wall -
   the wall is require_verified() on the actions that save. */
.verify-banner {
    margin: 0 0 16px;
    padding: 12px 14px;
    border-radius: 12px;
    border: 1px solid rgba(224, 122, 63, 0.45);
    background: rgba(224, 122, 63, 0.12);
    font-size: 14px;
    line-height: 1.5;
}

.verify-banner a {
    white-space: nowrap;
}

/* Google sign-in. Google's brand rules require their mark unmodified on a
   plain white or plain dark button, so this deliberately does not take the
   site's gradient treatment. */
.auth-divider {
    display: flex;
    align-items: center;
    gap: 12px;
    margin: 20px 0 16px;
    color: var(--muted);
    font-size: 12px;
    text-transform: uppercase;
    letter-spacing: 0.08em;
}

.auth-divider::before,
.auth-divider::after {
    content: "";
    flex: 1;
    height: 1px;
    background: var(--border-1);
}

.google-button {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 10px;
    width: 100%;
    padding: 11px 16px;
    background: #fff;
    color: #1f1f1f;
    border: 1px solid #dadce0;
    border-radius: 12px;
    font-weight: 600;
    text-decoration: none;
}

.google-button:hover {
    background: #f7f8f8;
    color: #1f1f1f;
}

.google-button svg {
    flex: none;
}

/* ---------- more skies / recent skies feed ---------- */

/* The reveal button sits in the grid as the sixth cell rather than below it,
   so the first view is two clean rows of three and the button reads as the
   continuation of the set instead of a control bolted underneath. */
.more-skies-card {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    font: inherit;
    font-weight: 600;
    font-size: 13px;
    color: var(--text);
    cursor: pointer;
    border: 1px dashed var(--panel-border);
    background: transparent;
    text-align: center;
    transition: border-color 0.15s ease, color 0.15s ease;
}

.more-skies-card:hover {
    border-color: var(--accent);
    color: var(--accent);
}

.more-skies-card:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

.more-skies-arrows {
    font-size: 15px;
    line-height: 1;
    transition: transform 0.15s ease;
}

.more-skies-card:hover .more-skies-arrows {
    transform: translateX(2px);
}

.sky-feed-section {
    margin-top: 24px;
}

/* One column, the full width of the content area - the photo is the point,
   and at three-up it was a thumbnail of a sky rather than a sky. */
.sky-feed {
    display: grid;
    grid-template-columns: 1fr;
    gap: 16px;
}

.sky-feed-tile {
    margin: 0;
    border-radius: var(--radius-md);
    overflow: hidden;
    display: flex;
    flex-direction: column;
}

.sky-feed-frame {
    position: relative;
}

.sky-feed-thumb {
    display: block;
    aspect-ratio: 16 / 10;
    background: var(--surface-1);
}

.sky-feed-thumb img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

.sky-feed-tile figcaption {
    padding: 10px 12px 12px;
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.sky-feed-head,
.sky-feed-foot {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
}

.sky-feed-tile figcaption strong {
    font-size: 14px;
    line-height: 1.3;
}

.sky-feed-rating {
    font-size: 12px;
    font-weight: 600;
}

.sky-feed-when {
    font-size: 11px;
    color: var(--muted);
}

.sky-feed-status {
    text-align: center;
    color: var(--muted);
    font-size: 12px;
    min-height: 18px;
    margin-top: 10px;
}

.sky-feed-sentinel {
    height: 1px;
}

/* ---------- per-person controls ---------- */

/* Shown to everyone, signed in or not. A control that only appears once you
   have an account hides the reason to make one; pressing this signed out
   sends you to sign in and brings you back. */
.pin-btn {
    position: absolute;
    top: 6px;
    right: 6px;
    width: 24px;
    height: 24px;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    border-radius: 50%;
    border: 1px solid var(--border-1);
    background: var(--surface-2, rgba(255, 255, 255, 0.07));
    color: var(--muted);
    font-size: 15px;
    line-height: 1;
    cursor: pointer;
    transition: color 0.15s ease, border-color 0.15s ease, background 0.15s ease;
}

.location-card {
    position: relative;
}

/* Room for the button so a long place name never runs under it. */
.location-card .location-card-name {
    padding-right: 26px;
}

.pin-btn:hover:not(:disabled) {
    color: var(--accent);
    border-color: var(--accent);
}

.pin-btn.is-pinned {
    color: var(--accent);
    border-color: var(--accent);
}

.pin-btn:disabled {
    opacity: 0.5;
    cursor: default;
}

.pin-btn-card {
    top: 12px;
    right: 12px;
    width: 28px;
    height: 28px;
    font-size: 17px;
}

.today-card {
    position: relative;
}

/* My Skies gives each card two controls rather than one, and .pin-btn pins
   itself to the corner - two of them would sit on top of each other. The row
   takes the corner instead and the buttons sit inside it normally. */
.today-card-controls {
    position: absolute;
    top: 12px;
    right: 12px;
    display: flex;
    /* Stacked, not in a row. Three buttons across reached far enough into the
       card to sit on top of the location name, which is centred and is the
       first thing anyone reads on the panel. Down the edge they are clear of
       it however long the name is - and "Tampa, Florida, United States"
       wrapping to two lines was already close. */
    flex-direction: column;
    align-items: center;
    gap: 6px;
    z-index: 2;
}

.today-card-controls .pin-btn,
.today-card-controls .share-btn {
    position: static;
}

/* The telescope hovers to the accent like the rest of the cluster, and its
   drawing is strokes rather than a glyph, so the colour has to reach the
   stroke rather than the text. */
.today-card-controls [data-sky-open] svg {
    display: block;
}

.today-card-controls [data-sky-open]:hover {
    color: var(--accent);
    border-color: var(--accent);
}

/* Share sits with home and the minus, so it wears their circle rather than
   the translucent disc it uses over a photograph. Three controls in a column
   that do not agree on whether they have an outline read as two controls and
   a decoration. */
.today-card-controls .share-btn {
    width: 28px;
    height: 28px;
    border-radius: 50%;
    border: 1px solid var(--border-1);
    background: var(--surface-2, rgba(255, 255, 255, 0.07));
    backdrop-filter: none;
    color: var(--muted);
    transition: color 0.15s ease, border-color 0.15s ease, background 0.15s ease;
}

.today-card-controls .share-btn:hover {
    color: var(--accent, #ff8c42);
    border-color: var(--accent, #ff8c42);
    transform: none;
}

.today-card-controls .share-btn.is-shared {
    color: #5fc97f;
}

/* The heart. One icon filled by CSS rather than two, so the states cannot
   drift apart or shift by a pixel when toggled. */
.fav-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 4px;
    border: none;
    background: none;
    cursor: pointer;
    color: var(--muted);
    transition: color 0.15s ease, transform 0.12s ease;
}

.fav-btn svg path {
    fill: none;
}

.fav-btn.is-favorited {
    color: #ff6b6b;
}

.fav-btn.is-favorited svg path {
    fill: currentColor;
}

.fav-btn:hover:not(:disabled) {
    color: #ff6b6b;
    transform: scale(1.08);
}

.fav-btn-overlay {
    position: absolute;
    top: 8px;
    right: 8px;
    color: #fff;
    background: rgba(0, 0, 0, 0.35);
    border-radius: 50%;
    backdrop-filter: blur(4px);
}

.fav-btn:disabled {
    opacity: 0.5;
    cursor: default;
}

/* Briefly flashed when a save fails, so a dead tap is visible. */
.interact-error {
    color: #e07a7a !important;
    animation: interact-shake 0.3s;
}

@keyframes interact-shake {
    0%, 100% { transform: translateX(0); }
    25% { transform: translateX(-3px); }
    75% { transform: translateX(3px); }
}

/* The gallery's heart sits on the photo, like the feed's. */
.gallery-tile > .fav-btn {
    position: absolute;
    top: 8px;
    right: 8px;
    z-index: 2;
    color: #fff;
    background: rgba(0, 0, 0, 0.35);
    border-radius: 50%;
    backdrop-filter: blur(4px);
}

/* Share sits beside the heart, inboard of it, so the corner control people
   already know keeps the corner. Same disc treatment: both are laid over a
   photograph and need to stay legible against whatever the sky was doing. */
.share-btn {
    position: absolute;
    top: 8px;
    right: 44px;
    z-index: 2;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    padding: 0;
    border: none;
    border-radius: 50%;
    cursor: pointer;
    color: #fff;
    background: rgba(0, 0, 0, 0.35);
    backdrop-filter: blur(4px);
    transition: color 0.15s ease, transform 0.12s ease;
}

.share-btn:hover {
    color: var(--accent, #ffb26b);
    transform: scale(1.08);
}

/* Confirmation for the copy fallback. The tooltip says what happened, but a
   tooltip on a tap target nobody hovers is invisible on a phone, so the
   colour has to carry it too. */
.share-btn.is-shared {
    color: #5fc97f;
}

.gallery-fav-filter .button.ghost {
    padding: 7px 10px;
    font-size: 12px;
    white-space: nowrap;
}

/* The "make this home" state of the corner control. Shares .pin-btn's shape
   so the slot does not move when a card goes from unpinned to pinned. */
.home-btn svg path {
    fill: none;
}

.home-btn.is-primary {
    color: var(--accent);
    border-color: var(--accent);
    cursor: default;
}

.home-btn.is-primary svg path {
    fill: currentColor;
}

.home-btn:not(.is-primary):hover {
    color: var(--accent);
    border-color: var(--accent);
}

/* Add/remove sits in the opposite corner from "home", so the two controls on
   a card never trade places or meanings as its state changes. */
.pin-btn-corner {
    top: auto;
    bottom: 6px;
    right: 6px;
}

/* The main verdict card has no bottom-right slot of its own, so its + stays
   where the house would be. */
.pin-btn-top {
    top: 12px;
    bottom: auto;
}

/* Room at the bottom of a card so the control never sits on the text. */
.location-card .location-card-next {
    padding-right: 26px;
}

/* Fixed at every width, so the menu can be reached from any scroll position.
   It is the same button that was in the topbar, lifted out of the flow rather
   than duplicated - so there is never a second one to tap by mistake.

   At rest it sits exactly where it always did: top 24px / left 20px matches
   the shell's own padding. The topbar reserves the width it vacated so the
   logo does not slide underneath it. */
.hamburger-btn {
    position: fixed;
    top: 12px;
    left: 20px;
    z-index: 60;

    /* Opaque, and the drawer's own colour since that is what it opens.
       It used to be surface-1 at 90% opacity - a five-percent white wash -
       which on a narrow screen, where there is no gutter and the button
       necessarily sits over content, let the heading underneath read
       straight through it. A translucent button over text does not look
       like a button over text; it looks like the layout has gone wrong.
       The shadow is what says it is floating above rather than set into
       the page. */
    background: var(--drawer-bg);
    box-shadow: var(--shadow);
}

.topbar {
    padding-left: calc(50vw - 50% + var(--topbar-reserve));
}

/* Wide enough that the button clears the content column at the viewport's
   own left edge - 760px of shell leaves 70px of gutter at 900px, and more
   above that - so it stays in the corner rather than tracking the column
   inward. Tied to the column it drifted toward the middle of a wide monitor,
   which is nowhere anyone looks for a menu. The topbar also stops reserving
   room, since nothing overlaps it out here. */
@media (min-width: 900px) {
    /* The corner position itself is inherited unchanged from the base rule -
       the button does not move at any width, only what it sits over does. */
    .hamburger-btn {
        /* Out here it sits over the gutter and covers nothing, so it needs no
           lift off the page. */
        box-shadow: none;
    }

    /* The burger is out in the gutter here, so nothing needs reserving.
       Set on :root so every rule that reads it follows, however specific. */
    :root {
        --topbar-reserve: 0px;
    }
}


/* The end-of-list marker the scroller watches. Reserves a little height so
   the observer has something to intersect with before anything is in it. */
.gallery-more {
    min-height: 40px;
    padding: 18px 0;
    text-align: center;
    color: var(--muted);
    font-size: 13px;
}

.gallery-more.is-done {
    opacity: 0.6;
}


/* ---------- gallery filter panel ---------- */

.gallery-filters-panel {
    width: 100%;
}

.gallery-filters-panel > summary {
    display: flex;
    align-items: center;
    gap: 12px;
    cursor: pointer;
    list-style: none;
    padding: 4px 0;
    color: var(--muted);
    font-size: 13px;
}

/* The default triangle is a different shape in every browser, so it is
   replaced with one chevron that rotates - the arrow is the only hint that
   the row opens, so it should at least be the same arrow everywhere. */
.gallery-filters-panel > summary::-webkit-details-marker {
    display: none;
}

.gallery-filters-label::before {
    content: '';
    display: inline-block;
    width: 6px;
    height: 6px;
    margin-right: 8px;
    border-right: 1.5px solid currentColor;
    border-bottom: 1.5px solid currentColor;
    transform: translateY(-2px) rotate(45deg);
    transition: transform 0.15s ease;
}

.gallery-filters-panel[open] .gallery-filters-label::before {
    transform: translateY(1px) rotate(-135deg);
}

.gallery-filters-panel > summary:hover {
    color: var(--text);
}

.gallery-filters-panel .gallery-count {
    margin-left: auto;
}

.gallery-filters-panel > .gallery-filters {
    margin-top: 12px;
}


/* ---------- stacked curves on the glance panel ---------- */

/* Every arc of the day under one another. Divided rather than boxed - they
   are one sequence read top to bottom, not four separate things. */
.glance-curves {
    margin-top: 18px;
    border-top: 1px solid var(--border-1);
}

/* On a past day the panel is only the arcs - no weather segments above them
   to be divided from. */
.glance-curves-alone {
    margin-top: 6px;
    border-top: 0;
}

.glance-curve {
    padding-top: 6px;
}

.glance-curve + .glance-curve {
    border-top: 1px solid var(--border-1);
    margin-top: 10px;
}

.glance-curve-label {
    margin: 0;
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--muted);
}

/* Tighter than a curve standing on its own in every dimension - four of them
   at full spacing set the height of every page in the pager, which is what
   left the sunset tile with a screen of nothing under its own curve. The arc
   itself is flatter (see render_timeline_curve); this is the space around
   it. */
.sky-timeline-compact {
    margin: 4px 0 2px;
}

.sky-timeline-compact .sky-timeline-labels {
    height: 40px;
    margin-top: 2px;
}

.sky-timeline-compact .sky-timeline-label.row-2 {
    top: 21px;
}

.sky-timeline-compact .sky-timeline-label-time {
    font-size: 10px;
}

.sky-timeline-compact .sky-timeline-label-text {
    font-size: 9px;
}

.glance-curve .sky-timeline {
    margin-top: 0;
}


/* Saved-as-you-go feedback on the preferences page. Sits where the save
   button used to, so the row does not collapse when there is nothing to
   say. */
.save-status {
    font-size: 13px;
    color: var(--muted);
    min-height: 20px;
}

.save-status.is-error {
    color: var(--score-1);
}


/* The ownership notice, quieter than the attributions above it - it needs to
   be present and legible, not to be the loudest thing on the page. */
.site-footer-legal {
    opacity: 0.75;
}


/* The one card on the site that does something irreversible. Marked, not
   shouted: a red panel people scroll past every visit stops registering. */
.danger-card {
    border-color: color-mix(in srgb, var(--score-1) 45%, var(--panel-border));
}

/* The same marking as .danger-card, for a destructive block sitting inside a
   card rather than being one. */
.danger-inset {
    margin-top: 20px;
    padding: 16px;
    border-radius: var(--radius-md);
    border: 1px solid color-mix(in srgb, var(--score-1) 45%, var(--panel-border));
}

.danger-inset h3 {
    margin: 0 0 6px;
}

/* The confirmation page. Roomy on purpose - it is the whole screen's job to
   be read once, not skimmed. */
.confirm-card {
    max-width: 34rem;
    margin: 0 auto;
}

.confirm-subject {
    margin: 2px 0 14px;
    font-size: 20px;
    font-weight: 700;
    word-break: break-word;
}

.confirm-list {
    margin: 8px 0 18px;
    padding-left: 20px;
    color: var(--muted);
    font-size: 14px;
}

.confirm-list li {
    margin-bottom: 4px;
}

/* Cancel first, and the destructive one is not the one a thumb lands on by
   default. */
.confirm-actions {
    display: flex;
    gap: 12px;
    flex-wrap: wrap;
    align-items: center;
    margin-top: 18px;
}

.button.danger {
    background: var(--score-1);
    border-color: var(--score-1);
    color: #fff;
}

.button.danger:hover {
    filter: brightness(1.08);
}


/* ---------- cookie choice ---------- */

/* A bar, not a modal. Nothing on this site tracks anyone, so blocking the
   page to say so would be theatre - and the one thing being asked about is
   already held back until the answer arrives. */
.cookie-notice {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 80;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 18px;
    flex-wrap: wrap;
    padding: 14px 18px;
    background: var(--drawer-bg, var(--panel));
    border-top: 1px solid var(--border-1);
    box-shadow: var(--shadow);
}

.cookie-notice-body {
    max-width: 70ch;
    font-size: 13px;
}

.cookie-notice-body p {
    margin: 0;
}

.cookie-notice-links {
    margin-top: 4px !important;
    font-size: 12px;
    color: var(--muted);
}

.cookie-notice-actions {
    display: flex;
    gap: 10px;
    flex-shrink: 0;
}

.aurora-map.is-blocked img {
    display: none;
}

/* ---------- page title bar ---------- */

/* A square bar rather than one of the site's rounded cards. The cards are
   things on the page; this names the page, and giving it the same bubble
   treatment made it read as the first item in the list rather than the
   heading above it. */
.page-head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 16px;
    flex-wrap: wrap;
    padding: 16px 18px;
    background: var(--surface-1);
    border: 1px solid var(--border-1);
    border-radius: 0;
    margin-bottom: 20px;
}

.page-head h1 {
    margin: 2px 0 0;
    font-size: 22px;
}

/* Anything sitting beside the title - a date field, a button - lines up with
   it rather than with the subtitle underneath. */
.page-head > :last-child:not(:first-child) {
    margin-top: 2px;
}

.page-head .subtitle {
    margin-bottom: 0;
}

.date-jump {
    display: flex;
    align-items: center;
    gap: 12px;
    flex-wrap: wrap;
}

/* The link beside the calendar - "All history", "Back to today". It used to
   be a ghost button, which made two things of equal weight out of a control
   people press and a way out they mostly do not. */
.date-jump-aside {
    font-size: 13px;
    /* Underlined and in the body colour rather than the muted grey it was.
       Grey with no underline on a grey bar is what a disabled control looks
       like, and this one works. */
    color: var(--text);
    text-decoration: underline;
    text-underline-offset: 3px;
    text-decoration-color: var(--border-2);
    white-space: nowrap;
}

.date-jump-aside:hover {
    text-decoration-color: currentColor;
}

/* The panel's fallback, for a page with no script: the plain date field this
   control was made of before. */
.date-jump-form {
    display: flex;
    align-items: center;
    gap: 8px;
}

.date-jump-label {
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--muted);
}

.date-jump-panel input[type="date"] {
    font-size: 13px;
    padding: 7px 10px;
    border-radius: 8px;
    border: 1px solid var(--panel-border);
    background: var(--surface-1);
    color: var(--text);
    /* Safari puts a native inner shadow on date fields that reads as a hole
       in a dark palette. */
    -webkit-appearance: none;
    appearance: none;
}

/* On a phone the title bar was taking a third of the first screen before the
   forecast started. It is navigation furniture, not content, and it was
   outweighing the thing people opened the page for. Two rows: the place and
   the date, then the calendar and the way out, both small. */
@media (max-width: 640px) {
    .page-head {
        padding: 10px 14px 12px;
        gap: 8px;
        margin-bottom: 14px;
    }

    .page-head h1 {
        font-size: 18px;
    }

    .page-head .date-jump {
        width: 100%;
        justify-content: space-between;
        gap: 8px;
    }

    /* The calendar sits at the left end of that row here, not the right, so
       a panel hung from its right edge would open off the side of the
       screen. Hung from the left instead. */
    .page-head .date-jump-panel {
        left: 0;
        right: auto;
    }

    .date-jump-aside {
        font-size: 12px;
    }
}

/* The location name doubles as the way back to today, so it has to look like
   the heading it already was rather than sprouting link styling. */
a.today-location-name {
    color: inherit;
    text-decoration: none;
}

a.today-location-name:hover,
a.today-location-name:focus-visible {
    text-decoration: underline;
}


/* ---------- admin data grid ---------- */

/* The locations table is a working surface, not a reading one: gridlines on
   every cell, no padding to speak of, and as many rows on screen as will fit.
   Deliberately unlike the rest of the site - this is the one page where the
   job is scanning a hundred rows for the odd one out, and the card-per-item
   layout it replaced could show four. */

/* Only the pages that hold a grid go wide. The topbar keeps the narrow
   column's alignment, so the logo stays where it is on every other page. */
.dashboard-wide {
    max-width: 1500px;
}

.grid-card {
    padding: 14px;
}

.grid-toolbar {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: 14px;
    flex-wrap: wrap;
    margin-bottom: 12px;
}

.grid-filters {
    display: flex;
    gap: 8px;
    flex-wrap: wrap;
}

.grid-filters label {
    display: flex;
    flex-direction: column;
    gap: 3px;
}

.grid-filters label span {
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--muted);
}

.grid-filters select {
    font-size: 12px;
    padding: 5px 8px;
    border-radius: 6px;
    border: 1px solid var(--panel-border);
    background: var(--surface-1);
    color: var(--text);
}

.grid-search {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-left: auto;
}

.grid-search input {
    width: 280px;
    max-width: 46vw;
    font-size: 13px;
    padding: 7px 10px;
    border-radius: 6px;
    border: 1px solid var(--panel-border);
    background: var(--surface-1);
    color: var(--text);
}

.grid-count {
    font-size: 11px;
    color: var(--muted);
    white-space: nowrap;
}

/* Scrolls in both directions inside its own box, so the header row and the
   page around it stay put while a wide table moves. */
.grid-scroll {
    overflow: auto;
    max-height: 70vh;
    border: 1px solid var(--border-1);
    border-radius: 4px;
}

.grid-table {
    border-collapse: collapse;
    width: 100%;
    font-size: 12px;
    line-height: 1.35;
    white-space: nowrap;
}

.grid-table th,
.grid-table td {
    border: 1px solid var(--border-1);
    padding: 3px 8px;
    text-align: left;
    font-weight: 400;
}

.grid-table thead th {
    position: sticky;
    top: 0;
    z-index: 2;
    background: var(--surface-2);
    font-weight: 600;
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--muted);
    cursor: pointer;
    user-select: none;
}

.grid-table thead th:hover {
    color: var(--text);
}

.grid-table thead th.is-sorted::after {
    content: ' ▲';
    font-size: 9px;
}

.grid-table thead th.is-sorted.is-desc::after {
    content: ' ▼';
}

.grid-expander-head,
.grid-table thead th.grid-expander-head {
    cursor: default;
    width: 22px;
}

.grid-row:hover > * {
    background: var(--surface-1);
}

.grid-name {
    font-weight: 600;
}

.grid-num {
    text-align: right;
    font-variant-numeric: tabular-nums;
}

.grid-dim {
    color: var(--muted);
}

/* Retired rows stay in the table - they are still real locations with
   history - but read as switched off. */
.grid-row.is-retired > * {
    opacity: 0.55;
}

.grid-expander {
    padding: 0 !important;
    text-align: center;
}

/* The reorder arrows, shown only while the table is in sort-order - see
   data-grid.js. Out of flow so the column does not change width when they
   appear. */
.grid-sort-cell {
    white-space: nowrap;
}

.grid-sort-arrows {
    display: none;
    margin-left: 6px;
}

.grid-table.sorted-by-sort .grid-sort-arrows {
    display: inline-flex;
    gap: 2px;
    vertical-align: middle;
}

.grid-sort-arrows form {
    display: inline;
}

.grid-sort-arrows button {
    border: 1px solid var(--border-1);
    background: var(--surface-1);
    color: var(--muted);
    border-radius: 4px;
    cursor: pointer;
    font-size: 10px;
    line-height: 1;
    padding: 2px 4px;
}

/* A brief tint on the row that just moved - long enough to follow it to its
   new position, short enough not to be a decoration. */
@keyframes grid-row-moved {
    from { background: var(--surface-2); }
    to { background: transparent; }
}

.grid-row.just-moved > * {
    animation: grid-row-moved 0.9s ease-out;
}

@media (prefers-reduced-motion: reduce) {
    .grid-row.just-moved > * {
        animation: none;
    }
}

.grid-sort-arrows button:hover {
    color: var(--text);
    border-color: var(--border-2);
}

.grid-expand-btn {
    width: 100%;
    border: 0;
    background: none;
    color: var(--muted);
    cursor: pointer;
    font-size: 14px;
    line-height: 1;
    padding: 3px 4px;
    transition: transform 0.12s ease, color 0.12s ease;
}

.grid-expand-btn:hover {
    color: var(--text);
}

.grid-expand-btn[aria-expanded="true"] {
    transform: rotate(90deg);
    color: var(--accent);
}

/* The editor drops out of the grid's density entirely - it is a form, and
   forms need room. */
.grid-editor > td {
    padding: 16px 18px 20px;
    background: var(--surface-1);
    white-space: normal;
}

.grid-pill {
    display: inline-block;
    padding: 1px 7px;
    border-radius: 999px;
    font-size: 10px;
    border: 1px solid var(--border-1);
}

.grid-pill.is-on {
    color: var(--tl-aurora);
    border-color: var(--tl-aurora);
}

.grid-pill.is-off {
    color: var(--muted);
}

.visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
}


/* ---------- special events listing ---------- */

/* One event per row, closed. The places it is visible from are the second
   question and only ever asked of one event at a time, so they sit behind the
   row - same disclosure as the locations table. */
.event-row {
    padding: 0;
}

.event-row > summary {
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 16px 20px;
    cursor: pointer;
    border-radius: var(--radius-lg);
    list-style: none;
    /* A flex item refuses to shrink below its content by default, so one long
       unwrappable string anywhere in here widens the row, and a row wider
       than the screen widens every card beside it. */
    min-width: 0;
}

.event-row > summary > * {
    min-width: 0;
}

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

.event-row > summary::after {
    content: "›";
    margin-left: 2px;
    color: var(--muted);
    transform: rotate(0deg);
    transition: transform 0.15s ease;
}

.event-row[open] > summary::after {
    transform: rotate(90deg);
}

.event-row[open] > summary {
    border-bottom: 1px solid var(--panel-border);
    border-radius: var(--radius-lg) var(--radius-lg) 0 0;
}

@media (hover: hover) and (pointer: fine) {
    .event-row > summary:hover {
        background: var(--surface-1);
    }
}

.event-row-head {
    display: flex;
    flex-direction: column;
    gap: 2px;
    flex: 1 1 auto;
    min-width: 0;
}

/* The eyebrow carries a bottom margin for use above a heading; stacked in a
   gapped column it just doubles the gap. */
.event-row-head .eyebrow {
    margin: 0;
}

.event-row-head .subtitle {
    font-size: 13px;
}

.event-row-name {
    font-size: 21px;
    font-weight: 700;
    line-height: 1.2;
}

/* Drawn, not typed. As emoji the platform picked the colours - a red comet
   and a blue-grey moon in a palette built around one warm orange - so they
   read as pasted in. In currentColor they take the site's own. */
/* Icon with the date stacked under it, the badge centred on the icon. */
.event-row-lead {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 8px;
    flex-shrink: 0;
    align-self: flex-start;
}

.event-row-badge {
    display: flex;
    flex-direction: column;
    align-items: center;
    line-height: 0.9;
    font-weight: 900;
    letter-spacing: -0.02em;
}

.event-row-badge .today-date-day {
    font-size: 22px;
    margin-left: 0;
}

.event-row-badge .today-date-month {
    font-size: 12px;
    margin-top: -2px;
}

.event-row-icon {
    display: flex;
    align-items: center;
    flex-shrink: 0;
    color: var(--accent, #ff8c42);
    line-height: 0;
    /* Aligned to the top of the text block rather than centred against it.
       A two-line title on a narrow screen pushed a vertically centred icon
       down past the eyebrow it belongs beside. */
    align-self: flex-start;
    margin-top: 2px;
}

/* How many places can see it - the one thing about the places worth saying
   before the row is opened. */
/* The activity window, set apart from the peak night it follows so the two
   are not read as one date. */
.event-row-range {
    display: inline-block;
    margin-left: 2px;
    padding: 1px 7px;
    border-radius: 999px;
    background: var(--surface-1);
    font-size: 11px;
    letter-spacing: 0.02em;
    /* Right for a date range: "active 8 Oct" must not be left hanging from
       "2 Nov". See .event-row-from for why it is not right for everything
       that happens to use this pill. */
    white-space: nowrap;
}

/* A launch site, in the same pill but allowed to wrap.
   
   "from Taiyuan Satellite Launch Center, People's Republic of China" held on
   one line is wider than a phone, and because it sat inside a flex row it did
   not clip - it pushed every card on the page out with it, so the headings,
   the filter row and the event names were all cut off at the right edge. A
   date range is four words and a dash; a place name is not, and inheriting
   nowrap from one to the other was the whole bug. */
.event-row-from {
    display: inline;
    margin-left: 2px;
    padding: 1px 7px;
    border-radius: 999px;
    background: var(--surface-1);
    font-size: 11px;
    letter-spacing: 0.02em;
    white-space: normal;
    /* Last line of defence: a single unbroken token - some pads have one -
       breaks rather than taking the page with it. */
    overflow-wrap: anywhere;
}

/* Off by default, and clearly on when it is on - a filter that silently
   hides most of the page has to say so. */
.events-filter {
    align-self: flex-start;
    flex-shrink: 0;

    /* Pushed right whether or not it shares the line with the heading. The
       page head wraps on a narrow screen, and without this the button drops
       to a line of its own and goes back to the left margin, which reads as
       a stray control rather than as the heading's own switch. */
    margin-left: auto;
}

.events-filter.is-on {
    border-color: var(--accent);
    color: var(--accent);
}

.event-row-count {
    flex-shrink: 0;
    font-size: 12px;
    color: var(--muted);
    white-space: nowrap;
}

.event-row-body {
    padding: 14px 20px 18px;
}

/* One row per place, each its own link into that night. */
.event-places {
    display: grid;
    gap: 6px;
}

.event-place {
    display: flex;
    align-items: baseline;
    gap: 12px;
    flex-wrap: wrap;
    padding: 10px 12px;
    border-radius: var(--radius-md);
    background: var(--surface-1);
    border: 1px solid transparent;
    text-decoration: none;
    color: inherit;
}

.event-place:hover,
.event-place:focus-visible {
    border-color: var(--accent);
}

.event-place-name {
    font-weight: 700;
    font-size: 14px;
}

.event-place-headline {
    flex: 1;
    min-width: 140px;
    font-size: 13px;
    color: var(--accent);
}

.event-place-when {
    font-size: 12px;
    color: var(--muted);
    white-space: nowrap;
}


/* ---------- occasional events ---------- */

/* The lead tile when something unusual is happening. Warmer than the verdict
   tiles it sits in front of, because it is there to interrupt. */
.today-page-event .verdict-headline {
    font-size: 26px;
}

.event-headline {
    margin: 2px 0 0;
    font-size: 19px;
    font-weight: 700;
    color: var(--accent);
    line-height: 1.25;
}

.event-facts {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
    gap: 8px;
    margin: 18px 0 0;
    text-align: center;
}

.event-facts > div {
    padding: 8px 6px;
    border-radius: var(--radius-md);
    background: var(--surface-1);
}

.event-facts dt {
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--muted);
}

.event-facts dd {
    margin: 3px 0 0;
    font-size: 16px;
    font-weight: 700;
}

.event-phases {
    margin-top: 14px;
    display: grid;
    gap: 2px;
}

.event-phase {
    display: flex;
    align-items: baseline;
    gap: 10px;
    padding: 5px 8px;
    border-radius: 6px;
    background: var(--surface-1);
    font-size: 13px;
}

.event-phase-time {
    font-weight: 700;
    min-width: 72px;
}

.event-phase-name {
    flex: 1;
    text-align: left;
}

/* A contact that happens while the Moon is below the horizon here. Dimmed
   rather than hidden: knowing the eclipse began before moonrise is the
   difference between "I missed it" and "I was never going to see that part". */
.event-phase.is-below {
    opacity: 0.5;
}

.event-phase-alt {
    color: var(--muted);
    font-size: 11px;
}

/* Not a footnote. Looking at a partly eclipsed Sun through anything that is
   not a solar filter does permanent damage, and this is the one place on the
   site where the text has to be impossible to skim past. */
.event-warning {
    margin: 16px 0 0;
    padding: 10px 12px;
    border-radius: var(--radius-md);
    border: 1px solid var(--score-1);
    background: color-mix(in srgb, var(--score-1) 12%, transparent);
    font-size: 13px;
    font-weight: 600;
    text-align: left;
}


/* ---------- moon page ---------- */

/* Sits where the verdict tiles put their emoji, so the row lines up with
   every other tile in the pager. */
.moon-disc {
    flex-shrink: 0;
    display: block;
}

.moon-disc-dark {
    fill: var(--moon-dark);
}

.moon-disc-lit {
    /* Warm rather than pure white - a full moon photographs closer to
       daylight-balanced sunlight than to paper. */
    fill: #f0e9d8;
}

/* Keeps the unlit limb readable against the card at a thin crescent, where
   there is otherwise almost nothing to see. */
.moon-disc-edge {
    fill: none;
    stroke: var(--moon-edge);
    stroke-width: 2;
}

.moon-illumination {
    margin: 2px 0 0;
    font-size: 18px;
    font-weight: 600;
    color: var(--tl-moon);
}

/* Dimmer than the aurora band: the arc is the whole point of the Moon's
   timeline rather than a stretch worth highlighting within it. */
.sky-timeline-band-moon {
    stroke: var(--tl-moon);
    stroke-width: 3;
    opacity: 0.5;
}


/* The OVATION map. Square, so it is sized by width and left to find its own
   height, and capped well below the card so it stays a supporting picture
   rather than the tile's subject. */
.aurora-map {
    margin: 14px 0 0;
    text-align: center;
}

.aurora-map img {
    width: 260px;
    max-width: 100%;
    height: auto;
    border-radius: 12px;
    border: 1px solid var(--panel-border);
    display: block;
    margin: 0 auto;
    /* The map is drawn on black. On the light theme that is a hard edge, so
       it gets the same quiet frame the photos have. */
    background: #000;
}

.aurora-map figcaption {
    margin-top: 6px;
    font-size: 12px;
    color: var(--muted);
}


/* ---------- aurora hour strip ---------- */

/* Hour by hour under the aurora curve. auto-fit rather than a fixed count:
   a long polar night is nine or ten columns, which fits one row on a desktop
   and wraps to two on a phone, and neither needs its own breakpoint. It must
   not scroll sideways - it lives inside the swipe pager, which owns that
   gesture. */
.aurora-hours {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(52px, 1fr));
    gap: 6px;
    margin-top: 14px;
}

.aurora-hour {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 2px;
    padding: 7px 4px;
    border-radius: var(--radius-md);
    background: var(--surface-1);
    /* Transparent rather than absent, so the best hour's border does not make
       that column a pixel taller than its neighbours. */
    border: 1px solid transparent;
}

/* The hour the night was scored on - the same moment the curve marks. */
.aurora-hour.is-best {
    border-color: var(--tl-aurora);
    background: var(--surface-2);
}

.aurora-hour-time {
    font-size: 10px;
    color: var(--muted);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    white-space: nowrap;
}

.aurora-hour-cloud {
    font-size: 12px;
    font-weight: 700;
    white-space: nowrap;
}

/* A quarter of the sky or less: enough of a gap to see through, which is the
   whole question this strip answers. */
.aurora-hour-cloud.is-clear {
    color: var(--tl-aurora);
}

.aurora-hour-kp {
    font-size: 10px;
    color: var(--muted);
    white-space: nowrap;
}

/* Rating the photograph, inside the training panel. Deliberately smaller than
   the observed-rating picker: it is the narrower question, and it sits in a
   panel rather than standing on its own. */
.img-rate {
    display: flex;
    flex-direction: column;
    gap: 5px;
    margin-top: 10px;
    padding-top: 10px;
    border-top: 1px solid var(--border-1);
}

.img-rate-row {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
}

.img-rate-btn {
    padding: 4px 9px;
    font-size: 11px;
    font-weight: 600;
    line-height: 1.3;
    border-radius: 999px;
    border: 1px solid var(--border-1);
    background: var(--surface-1);
    color: var(--muted);
    cursor: pointer;
    transition: color 0.15s ease, border-color 0.15s ease, background 0.15s ease;
}

.img-rate-btn:hover:not(:disabled) {
    color: var(--text);
    border-color: var(--border-2);
}

.img-rate-btn.is-active {
    color: #fff;
    border-color: transparent;
}

.img-rate-btn:disabled {
    opacity: 0.5;
    cursor: default;
}

.score-bg-1 { background: var(--score-1); }
.score-bg-2 { background: var(--score-2); }
.score-bg-3 { background: var(--score-3); }
.score-bg-4 { background: var(--score-4); }
.score-bg-5 { background: var(--score-5); }


/* ---------- site controls ---------- */

/* A switch and its current state, side by side, so the state is read before
   the button that changes it. */
.control-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 14px;
    margin-top: 16px;
    padding: 14px 16px;
    background: var(--surface-1);
    border: 1px solid var(--border-1);
}

.control-state {
    margin: 0;
    font-size: 15px;
}

.state-on {
    color: var(--accent);
}

/* Not an error colour - off is a deliberate setting, not a fault - but it
   has to be obvious at a glance which way the switch is thrown. */
.state-off {
    color: #d98b4a;
}

.control-detail {
    margin-top: 18px;
    font-size: 13px;
    color: var(--muted);
}

.control-detail-heading {
    margin: 12px 0 4px;
    font-weight: 600;
}

.control-detail ul {
    margin: 0;
    padding-left: 20px;
}

.control-detail li {
    margin: 2px 0;
}


/* A checkbox living in a dense numeric column: centred like the numbers it
   sits among, and big enough to hit without being the loudest thing in the
   row. */
.grid-check {
    margin: 0;
    cursor: pointer;
    accent-color: var(--accent);
    width: 15px;
    height: 15px;
    vertical-align: middle;
}

.grid-check:disabled {
    opacity: 0.45;
    cursor: progress;
}


/* ---------- upcoming events on a day page ---------- */

/* One panel, rows divided by a line, rather than a pill per row with a gap
   between. A pill each made a list of six read as six things; this reads
   as one list, which is what it is. */
.upcoming-events {
    display: grid;
    margin-top: 14px;
    background: var(--panel);
    border: 1px solid var(--panel-border);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow);
    overflow: hidden;
}

/* Name on the left, date hard right, so a column of them can be read as
   dates without tracking across ragged text. Roomier than before - the
   rows carry a bar now and needed the air. */
.upcoming-event {
    display: flex;
    align-items: flex-start;
    gap: 14px;
    padding: 14px 16px;
    text-decoration: none;
    color: inherit;
    border-top: 1px solid var(--panel-border);
}

.upcoming-event:first-child {
    border-top: 0;
}

.upcoming-event:hover,
.upcoming-event:focus-visible {
    background: var(--surface-2);
}

.upcoming-event-icon {
    display: flex;
    align-items: center;
    flex-shrink: 0;
    line-height: 0;
    color: var(--accent, #ff8c42);
    /* Level with the name's first line, now the row is top-aligned. */
    margin-top: 2px;
}

.upcoming-event-main {
    display: flex;
    flex-direction: column;
    gap: 3px;
    flex: 1 1 auto;
    min-width: 0;
}

/* The bar under the note: short, so it reads as a rating beside the words
   rather than a ruler across the row. */
.upcoming-event-meter {
    display: block;
    width: 110px;
    max-width: 100%;
    margin-top: 4px;
}

.upcoming-event-meter .meter-mini {
    margin: 0;
}

.upcoming-event-name {
    font-weight: 700;
    font-size: 14px;
}

/* Muted rather than accent. On a dark ground the orange read as a highlight;
   on a light one it was washed-out text on pale lavender, and the name is
   what should be carrying the emphasis in either. */
.upcoming-event-note {
    font-size: 12px;
    color: var(--muted);
}

.upcoming-event-date {
    flex-shrink: 0;
    font-size: 12px;
    color: var(--muted);
    white-space: nowrap;

    /* The time sits under the date, and both are ragged-left against a
       right edge otherwise. Aligning right puts the two on the same margin
       as each other and as the rows above and below, which is what makes a
       column of dates scannable. The name on the left keeps its own edge. */
    text-align: right;
}


/* The expander sits in the list rather than beside it, so it reads as the
   next row rather than as a control floating under one. */
/* Dashed and unfilled, so it reads as the end of the list rather than as
   another event in it - the one row that is a control, not a destination. */
.upcoming-events-more {
    padding: 12px 16px;
    border: 0;
    border-top: 1px solid var(--panel-border);
    background: transparent;
    color: var(--text);
    font: inherit;
    font-size: 14px;
    font-weight: 600;
    cursor: pointer;
}

.upcoming-events-more:hover {
    color: var(--accent);
    border-color: var(--accent);
}

/* Sits on the text's own colour, so it dims and lifts with the label rather
   than being a separate thing that happens to be nearby. */
.compass-rose {
    flex-shrink: 0;
    display: block;
}

/* ---------- reminders ---------- */

/* Bottom left of a card, diagonally opposite the home and pin controls.
   Same round shape and size as those, so the two corners read as the same
   kind of thing rather than as a button and an ornament. */
.today-card-footer-control {
    position: absolute;
    left: 12px;
    bottom: 12px;
    z-index: 2;
}

.reminder-bell {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    padding: 0;
    border-radius: 50%;
    border: 1px solid var(--border-1);
    background: var(--surface-2);
    color: var(--muted);
    cursor: pointer;
    transition: color 0.15s ease, border-color 0.15s ease, background 0.15s ease;
}

@media (hover: hover) and (pointer: fine) {
    .reminder-bell:hover {
        color: var(--accent);
        border-color: var(--accent);
    }
}

/* Set: the site's own accent, which is the logo's orange in both themes, and
   the bell fills in. One path doing both states rather than two drawings, so
   they cannot drift apart as it toggles. */
.reminder-bell.is-on {
    color: var(--accent);
    border-color: var(--accent);
}

.reminder-bell.is-on .bell-icon-body {
    fill: currentColor;
}

.reminder-bell:disabled {
    opacity: 0.5;
    cursor: progress;
}

.reminder-list {
    display: grid;
    gap: 8px;
    margin-top: 14px;
}

.reminder-row {
    display: flex;
    align-items: baseline;
    gap: 12px;
    flex-wrap: wrap;
    padding: 10px 12px;
    border-radius: var(--radius-md);
    background: var(--surface-1);
}

.reminder-row-main {
    flex: 1 1 auto;
    min-width: 0;
}

.reminder-row-label {
    display: block;
    font-weight: 600;
    font-size: 14px;
}

.reminder-row-where {
    display: block;
    font-size: 12px;
    color: var(--muted);
}

.reminder-row-when {
    font-size: 12px;
    color: var(--muted);
    white-space: nowrap;
}

/* Past its date, or already sent: kept in the list so nothing looks lost,
   but clearly no longer pending. */
.reminder-row.is-done .reminder-row-label {
    color: var(--muted);
    font-weight: 500;
}

/* A label that only a screen reader needs. The select beside it is obvious
   in context and a visible caption on every row would be six words of
   furniture repeated down the list. */
.sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
    border: 0;
}

.reminder-lead-form {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-top: 14px;
}

.reminder-lead-form label {
    display: flex;
    align-items: center;
    gap: 8px;
}

.reminder-row-lead select {
    font-size: 12px;
    padding: 5px 8px;
}

/* Wraps under the label on a narrow screen rather than squeezing the name
   into a column an inch wide. */
@media (max-width: 620px) {
    .reminder-row {
        align-items: stretch;
    }

    .reminder-row-main {
        flex: 1 1 100%;
    }
}

/* ---------- reminder email preview ---------- */

/* An iframe, not a div. The email carries its own colours and widths, and
   dropping that markup into the page would let the site's stylesheet reach
   inside it - the preview would then show something nobody will ever
   receive, which is worse than no preview. */
.email-preview {
    width: 100%;
    height: 380px;
    border: 1px solid var(--panel-border);
    border-radius: var(--radius-md);
    background: #ffffff;
    margin-bottom: 8px;
}

.email-preview-subject {
    margin: 0 0 12px;
    padding: 10px 12px;
    border-radius: var(--radius-md);
    background: var(--surface-1);
    font-weight: 600;
}

.email-preview-text {
    margin: 0 0 8px;
    padding: 12px;
    border-radius: var(--radius-md);
    background: var(--surface-1);
    color: var(--muted);
    font-size: 12px;
    line-height: 1.6;
    white-space: pre-wrap;
    word-break: break-word;
    overflow-x: auto;
}

.control-detail code {
    padding: 1px 5px;
    border-radius: 4px;
    background: var(--surface-2);
    font-size: 12px;
}

/* The heart inside the opened photo. The caption is a column of text with a
   control beside it now, so the two sit on a row with the text taking the
   slack. */
.lightbox-caption {
    display: flex;
    align-items: center;
    gap: 14px;
}

.lightbox-caption-text {
    display: flex;
    flex-direction: column;
    gap: 2px;
    flex: 1 1 auto;
    min-width: 0;
}

/* Static, unlike the hearts on gallery tiles which pin themselves to a
   corner: here it is part of the caption row rather than laid over a
   picture. */
.lightbox-fav {
    position: static;
    flex-shrink: 0;
    width: 34px;
    height: 34px;
}

/* And the same for share, which is otherwise pinned to a photo's corner. In
   the strip the two sit as a pair at the end of the row. */
.lightbox-share {
    position: static;
    flex-shrink: 0;
    width: 34px;
    height: 34px;
    color: var(--muted);
    background: none;
    backdrop-filter: none;
}

.lightbox-share:hover {
    color: var(--text);
}

/* ---------- data source switches (admin API page) ---------- */

/* A switch, not a checkbox: this changes what the site does on its next run,
   and the two states have to be readable at a glance down a column of ten. */
.source-toggle-form {
    margin: 0;
}

.source-toggle {
    display: inline-flex;
    align-items: center;
    width: 38px;
    height: 22px;
    padding: 2px;
    border: 1px solid var(--panel-border);
    border-radius: 999px;
    background: var(--surface-1);
    cursor: pointer;
    transition: background 0.15s ease, border-color 0.15s ease;
}

.source-toggle-knob {
    width: 16px;
    height: 16px;
    border-radius: 50%;
    background: var(--muted);
    transition: transform 0.15s ease, background 0.15s ease;
}

.source-toggle.is-on {
    background: var(--accent, #ff8c42);
    border-color: var(--accent, #ff8c42);
}

.source-toggle.is-on .source-toggle-knob {
    transform: translateX(16px);
    background: #fff;
}

/* Nothing can be scored without it, so there is no switch to offer - but the
   column still has to say why this row is the one without one. */
.source-required {
    font-size: 11px;
    color: var(--muted);
    text-transform: uppercase;
    letter-spacing: 0.06em;
}

.grid-row.is-off .grid-name,
.grid-row.is-off td {
    opacity: 0.55;
}

.source-state {
    display: inline-block;
    margin-left: 8px;
    padding: 2px 8px;
    border-radius: 999px;
    border: 1px solid var(--panel-border);
    font-size: 11px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--muted);
    vertical-align: middle;
}

.source-state.is-on {
    color: var(--accent, #ff8c42);
    border-color: currentColor;
}

.source-state.is-required {
    color: var(--muted);
}

/* Dimmed rather than hidden. A source that is off is still worth reading
   about - that is how you decide whether to switch it on. */
.source-card.is-off .source-uses,
.source-card.is-off .source-warning {
    opacity: 0.7;
}

.form-flash {
    margin: 0 0 14px;
    padding: 9px 13px;
    border-radius: var(--radius-md);
    border: 1px solid var(--panel-border);
    background: var(--surface-1);
    font-size: 13px;
}

.form-flash.is-ok {
    border-color: #5fc97f;
}

.form-flash.is-error {
    border-color: #e07a7a;
}

/* ---------- rocket launches ---------- */

/* Its own card rather than a page of the swipe pager. A launch is not a
   property of the sky here the way a sunset is - it is somewhere else's event
   that happens to be visible from here, and on most days there isn't one. A
   pager page that is usually missing shifts the dots under people. */
.launch-panel-heading {
    display: flex;
    align-items: center;
    gap: 8px;
    margin: 0 0 12px;
    font-size: 15px;
}

.launch-item {
    padding: 12px 0;
    border-top: 1px solid var(--panel-border);
}

.launch-item:first-of-type {
    border-top: 0;
    padding-top: 0;
}

.launch-item-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 10px;
}

.launch-name {
    font-size: 14px;
    line-height: 1.3;
}

/* Coloured by the same score scale the sunset verdict uses, so "Excellent"
   means the same strength of claim in both places. */
.launch-quality {
    flex-shrink: 0;
    font-size: 12px;
    font-weight: 600;
}

.launch-item.score-5 .launch-quality { color: var(--score-5, #ff8c42); }
.launch-item.score-4 .launch-quality { color: var(--score-4, #ffb26b); }
.launch-item.score-3 .launch-quality { color: var(--muted); }
.launch-item.score-2 .launch-quality { color: var(--muted); }

.launch-when {
    margin: 4px 0 0;
    font-size: 13px;
}

/* A time given to the nearest month is not something to stand outside for. */
.launch-rough {
    margin-left: 6px;
    padding: 1px 6px;
    border: 1px solid var(--panel-border);
    border-radius: 999px;
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--muted);
}

/* The line that actually gets somebody out of the door. */
.launch-where {
    margin: 6px 0 0;
    font-size: 13px;
    line-height: 1.45;
}

.launch-note {
    margin: 5px 0 0;
    font-size: 12px;
    color: var(--muted);
    line-height: 1.45;
}

.launch-meta {
    margin: 5px 0 0;
    font-size: 11px;
    color: var(--muted);
}

/* ---------- event category chips ---------- */

/* A row of icon pills. With the names spelled out the four of them ran off
   the side of a phone and the last was a sliver at the edge of a scroller
   nothing suggested you could scroll - which is worse than a dropdown,
   because at least a dropdown looks like it has more inside.
   
   As icons they all fit at once, which was the whole reason to show them
   rather than hide them. The names are on the buttons as labels and
   tooltips, and every row underneath prints its kind beside the same icon,
   so the four are learnable from the list they filter. */
.event-kinds {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px;
    margin: 0 0 16px;
}

.event-kind {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    /* Wider than tall, so it reads as a pill rather than a round icon
       button - those are for one action, and these are a set to choose
       between. Tall enough to be a comfortable target on a phone. */
    width: 52px;
    height: 40px;
    padding: 0;
    border-radius: 999px;
    border: 1px solid var(--panel-border);
    background: var(--surface-1);
    color: var(--muted);
    cursor: pointer;
    transition: color 0.15s ease, border-color 0.15s ease, background 0.15s ease;
}

.event-kind svg {
    display: block;
}

/* The one pill carrying a word rather than a picture, so it sizes to its
   text instead of to the square the icons sit in. */
.event-kind[data-event-kind="all"] {
    width: auto;
    padding: 0 16px;
    font-size: 12px;
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
}

.event-kind:hover {
    color: var(--text);
    border-color: var(--muted);
}

.event-kind.is-on {
    color: var(--accent, #ff8c42);
    border-color: var(--accent, #ff8c42);
    background: var(--surface-2, rgba(255, 255, 255, 0.07));
}

.event-kinds-empty {
    margin: 0 0 16px;
    color: var(--muted);
    font-size: 13px;
}

/* ---------- bulk selection (admin locations) ---------- */

/* Only there once something is ticked. A bar of buttons that do nothing yet
   is furniture above the thing people came to read. */
.bulk-bar {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 10px;
    margin: 0 0 12px;
    padding: 10px 14px;
    border-radius: var(--radius-md);
    border: 1px solid var(--accent, #ff8c42);
    background: var(--surface-1);
}

.bulk-count {
    font-size: 13px;
    color: var(--muted);
}

.bulk-count strong {
    color: var(--accent, #ff8c42);
    font-variant-numeric: tabular-nums;
}

.bulk-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    flex: 1 1 auto;
}

.bulk-actions .button {
    font-size: 12px;
    padding: 6px 10px;
}

.bulk-clear {
    font-size: 12px;
    padding: 6px 10px;
    color: var(--muted);
}

/* Narrow, because it is a column of checkboxes and nothing else. */
.grid-select,
.grid-select-head {
    width: 34px;
    padding-left: 10px;
    padding-right: 0;
    text-align: center;
}

.grid-select input,
.grid-select-head input {
    cursor: pointer;
    accent-color: var(--accent, #ff8c42);
}

/* ---------- per-panel special events (My Skies) ---------- */

/* Keeps a panel and its events together. On a page that is nothing but
   stacked cards, a loose row between two of them genuinely reads as
   belonging to either. */
.sky-block {
    display: flex;
    flex-direction: column;
    min-width: 0;
}

/* Joined to the panel, not floating under it. On a page that is nothing but
   stacked cards, a gap of any size leaves a row genuinely ambiguous about
   which card it belongs to - the one above or the one below. Flush, with the
   corners between them squared off and the shared edge overlapped, the two
   read as one object. */
.sky-block.has-events .today-card {
    border-bottom-left-radius: 0;
    border-bottom-right-radius: 0;
    /* The card carries its own bottom margin for when it stands alone. Here
       it does not - that margin was the gap that left the events row looking
       like it belonged to the card below it rather than the one above. */
    margin-bottom: 0;
}

.sky-block.has-events .sky-events {
    border-top-left-radius: 0;
    border-top-right-radius: 0;
    margin-top: -1px;
}

/* Closed by default: this page exists to show a dozen places at once, and a
   dozen expanded lists is not something anyone can scan. */
.sky-events {
    border: 1px solid var(--panel-border);
    border-radius: var(--radius-md);
    background: var(--surface-1);
}

.sky-events > summary {
    display: flex;
    align-items: center;
    gap: 9px;
    padding: 10px 14px;
    cursor: pointer;
    list-style: none;
    font-size: 13px;
}

.sky-events > summary::-webkit-details-marker {
    display: none;
}

.sky-events-chev {
    display: flex;
    align-items: center;
    margin-left: auto;
    flex-shrink: 0;
    color: var(--muted);
    line-height: 0;
    transition: transform 0.15s ease;
}

.sky-events[open] .sky-events-chev {
    transform: rotate(180deg);
}

.sky-events-icon {
    display: flex;
    align-items: center;
    line-height: 0;
    color: var(--accent, #ff8c42);
    flex-shrink: 0;
}

/* The place is named here, so this is the part that must survive a narrow
   screen - it shrinks and ellipsises rather than being pushed out. */
.sky-events-label {
    display: flex;
    align-items: center;
    gap: 7px;
    min-width: 0;
    font-weight: 600;
}

.sky-events-label {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.sky-events-count {
    flex-shrink: 0;
    padding: 1px 7px;
    border-radius: 999px;
    background: var(--surface-2, rgba(255, 255, 255, 0.07));
    font-size: 11px;
    font-weight: 500;
    font-variant-numeric: tabular-nums;
    color: var(--muted);
}

/* The soonest one, named in the closed row. For most places on most days
   this is the only part anybody reads, so it has to carry the answer. */
.sky-events-next {
    color: var(--muted);
    font-size: 12px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    min-width: 0;
}

.sky-events .upcoming-events {
    padding: 0 10px 10px;
}

/* Two things share this row on a phone and the place name is the longer;
   the summary drops the lead line rather than wrapping into three rows. */
/* Two things want this row and the place name is the one that identifies it,
   so the hint is what goes when there is not room for both. */
@media (max-width: 900px) {
    .sky-events-next {
        display: none;
    }
}

/* ---------- event icons in light mode ---------- */

/* The logo is teal on a light background and orange on a dark one, and the
   event icons were orange in both - which on the light theme left them as the
   only warm thing on a cool page, reading as a highlight rather than as part
   of the set.
   
   Defined as a token so the icons themselves keep saying "the accent colour"
   and this decides what that means. Bare :root is the light palette, the
   media query covers the system default, and the explicit attribute wins over
   both so the toggle works in either direction. */
:root {
    --event-accent: #0f766e;
}

/* No unconditional :not([data-theme="light"]) rule here: signed out, the
   page carries no attribute and follows the device, and that rule made
   the date badge orange on a light device - the one case it was meant
   to leave alone. */
@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) {
        --event-accent: var(--accent, #ff8c42);
    }
}

:root[data-theme="dark"] {
    --event-accent: var(--accent, #ff8c42);
}

:root[data-theme="light"] {
    --event-accent: #0f766e;
}

.event-row-icon,
.upcoming-event-icon,
.sky-events-icon,
.today-page-event .verdict-icon,
.event-kind.is-on {
    color: var(--event-accent);
}

.event-kind.is-on {
    border-color: var(--event-accent);
}

/* ---------- launch fact tiles that carry two lines ---------- */

/* Most facts are a label and a number. Look and Landing each carry a second
   line under the first - which way it is travelling, when the booster comes
   back - so they stack rather than sitting on one row.

   Centred like every other tile. The grid sets text-align on the whole block,
   which does nothing for a flex row: its items line up from the start edge
   unless told otherwise, so the compass and its bearing sat left under a
   centred label. */
.event-facts .fact-look dd {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 5px;
}

/* A place name is longer than a number and reads as words rather than a
   figure, so it drops a size and wraps instead of stretching the tile. */
.event-facts .fact-place {
    font-size: 13px;
    line-height: 1.3;
}

.event-facts .fact-sub {
    display: block;
    text-align: center;
    margin-top: 2px;
    font-size: 11px;
    font-weight: 400;
    color: var(--muted);
    line-height: 1.35;
}

/* The needle takes the text's colour so the dial reads as part of the
   direction rather than as decoration beside it. */
.event-facts .compass-rose {
    flex-shrink: 0;
    color: var(--muted);
}

/* ---------- event priority rows ---------- */

/* A checkbox, an icon and two lines of text. The description is the point of
   the row - somebody deciding whether a kind should lead needs to know what
   the kind is - so it gets the width and the control stays out of its way. */
.priority-row {
    display: grid;
    grid-template-columns: auto auto 1fr;
    align-items: start;
    gap: 10px;
    padding: 12px 0;
    border-top: 1px solid var(--panel-border);
    cursor: pointer;
}

.priority-row:first-of-type {
    border-top: 0;
}

.priority-row input[type="checkbox"] {
    margin-top: 2px;
    accent-color: var(--accent, #ff8c42);
    cursor: pointer;
}

.priority-icon {
    display: flex;
    align-items: center;
    line-height: 0;
    margin-top: 1px;
    color: var(--event-accent, var(--accent, #ff8c42));
}

.priority-text {
    display: flex;
    flex-direction: column;
    gap: 3px;
    min-width: 0;
}

.priority-text strong {
    font-size: 14px;
}

/* The signup form's trap field.
   Taken out of the flow rather than display:none - a form-filler that is
   careful enough to skip hidden inputs will still fill something that is
   merely somewhere else, and filling it is exactly what we want it to do.
   Nobody reading the page can reach it: it is aria-hidden and out of the
   tab order in the markup. */
.signup-website {
    position: absolute;
    left: -9999px;
    width: 1px;
    height: 1px;
    overflow: hidden;
}

/* The admin's running order for the kinds of event.
   Scoped to .priority-order on purpose: the same .priority-row markup is a
   plain checkbox row on the preferences and users pages, and those must not
   grow a grip and a pair of arrows. */
.priority-order {
    list-style: none;
    margin: 0;
    padding: 0;
    counter-reset: priority;
}

.priority-order .priority-row {
    grid-template-columns: auto auto 1fr;
    cursor: default;
    background: transparent;
    border-radius: 8px;
    transition: background 120ms ease, opacity 120ms ease;
}

/* The row being carried. Faded rather than hidden, so the gap it will land
   in is visible without the list jumping about. */
.priority-order .priority-row.is-dragging {
    opacity: 0.45;
    background: var(--panel-border);
}

.priority-order .priority-row:hover {
    background: color-mix(in srgb, var(--panel-border) 45%, transparent);
}

/* The handle. Only a hint that the row moves - the arrows beside it are what
   actually has to work. */
.priority-grip {
    align-self: center;
    padding: 0 2px;
    letter-spacing: -3px;
    font-size: 15px;
    line-height: 1;
    color: var(--muted, #8a8f98);
    cursor: grab;
    user-select: none;
}

.priority-order .priority-row.is-dragging .priority-grip {
    cursor: grabbing;
}

.priority-move {
    display: flex;
    flex-direction: column;
    gap: 2px;
    align-self: center;
}

.priority-move button {
    appearance: none;
    border: 1px solid var(--panel-border);
    background: transparent;
    color: var(--muted, #8a8f98);
    border-radius: 5px;
    width: 24px;
    height: 18px;
    font-size: 9px;
    line-height: 1;
    padding: 0;
    cursor: pointer;
}

.priority-move button:hover {
    color: var(--text, inherit);
    border-color: var(--accent, #ff8c42);
}

/* The tick and its text keep the shape they have everywhere else; the row
   around them is what gained the grip and the arrows. */
.priority-lead {
    display: grid;
    grid-template-columns: auto auto 1fr;
    align-items: start;
    gap: 10px;
    min-width: 0;
    cursor: pointer;
}

/* The position, counted off the page rather than written into the markup, so
   a row that moves takes its number with it without any script. */
.priority-order .priority-row .priority-text strong::before {
    counter-increment: priority;
    content: counter(priority) ". ";
    color: var(--muted, #8a8f98);
    font-weight: 600;
}

/* Pushed to the far end of the filter row so it reads as a way out of the
   filters rather than as one more of them. */
/* Pushed to the far end of the filter row so it reads as a way out of the
   filters rather than as one more of them - and allowed to wrap onto its own
   line rather than off the side of a phone, which is what five pills plus a
   two-word link does at 360px. */
.event-kinds-prefs {
    margin-left: auto;
    flex-shrink: 0;
    font-size: 12px;
    color: var(--muted);
    white-space: nowrap;
}

/* On its own line the auto margin still holds it right, which keeps it out of
   the pills' reading order instead of looking like a sixth one. */
@media (max-width: 520px) {
    .event-kinds-prefs {
        margin-top: 2px;
    }
}

.event-kinds-prefs:hover {
    color: var(--text);
}

/* The time under the date in an upcoming-events row. Quieter than the date
   above it: the date is what somebody scans the list for, and the time is
   what they read once one has caught their eye. */
.upcoming-event-time {
    display: block;
    margin-top: 2px;
    font-size: 11px;
    font-weight: 400;
    color: var(--muted);
    font-variant-numeric: tabular-nums;
}


/* ---- My Skies, in the preferences ---------------------------------------
   This was a grid of checkboxes. It could say which places were chosen and
   never what order they came in, and with fifty of them, finding the six you
   had picked meant reading all of them.
   Two things now: the list you have, in the order it appears on My Skies,
   and a search for adding to it. */

.sky-order {
    list-style: none;
    margin: 10px 0 0;
    padding: 0;
    display: grid;
    gap: 6px;
    /* Counting is the point - the number is the position on My Skies, not
       decoration - so the counter is drawn in the row rather than left to
       the marker, which cannot be styled or kept inside the border. */
    counter-reset: sky;
}

.sky-order-row {
    counter-increment: sky;
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 9px 10px 9px 12px;
    border-radius: var(--radius-md);
    background: var(--panel);
    border: 1px solid var(--panel-border);
}

.sky-order-row::before {
    content: counter(sky);
    flex: 0 0 auto;
    min-width: 18px;
    font-size: 12px;
    font-variant-numeric: tabular-nums;
    color: var(--muted);
}

.sky-order-name {
    flex: 1 1 auto;
    min-width: 0;
    font-size: 14px;
    font-weight: 600;
}

.sky-order-actions {
    display: flex;
    gap: 4px;
    flex-shrink: 0;
}

/* First row's up and last row's down do nothing. Rather than disable them -
   which would make the buttons jump between states as rows move - they stay
   pressable and simply have nowhere to go. */
.sky-order-row:first-child [data-sky-up],
.sky-order-row:last-child [data-sky-down] {
    opacity: 0.3;
}

.sky-add {
    margin-top: 12px;
}

.sky-add-label {
    display: block;
    font-size: 12px;
    color: var(--muted);
    margin-bottom: 4px;
}

.sky-add input[type="search"] {
    width: 100%;
    padding: 9px 12px;
    border-radius: var(--radius-md);
    border: 1px solid var(--panel-border);
    background: var(--surface-1, transparent);
    color: inherit;
    font: inherit;
    font-size: 14px;
}

/* Capped and scrollable: a search for "a" matches most of the list, and a
   page that grows by forty rows under your finger loses its place. */
.sky-add-list {
    list-style: none;
    margin: 6px 0 0;
    padding: 0;
    max-height: 220px;
    overflow-y: auto;
    border: 1px solid var(--panel-border);
    border-radius: var(--radius-md);

    /* The separators are gaps in a coloured ground rather than borders on
       the rows, because filtering hides rows rather than removing them and a
       border-top would then draw itself against the top of the list. A gap
       between flex items only exists between items that are displayed. */
    display: flex;
    flex-direction: column;
    gap: 1px;
    background: var(--panel-border);
}

/* Setting display on the class beats the browser's own rule for [hidden],
   so the attribute has to be honoured explicitly or the list never closes. */
.sky-add-list[hidden] {
    display: none;
}

.sky-add-list li {
    background: var(--panel);
}

.sky-add-list button {
    display: block;
    width: 100%;
    text-align: left;
    padding: 9px 12px;
    background: transparent;
    border: 0;
    color: inherit;
    font: inherit;
    font-size: 14px;
    cursor: pointer;
}

.sky-add-list button:hover,
.sky-add-list button:focus-visible {
    background: var(--surface-1, rgba(255, 255, 255, 0.06));
    color: var(--accent);
}


/* ---- The events list, filled in as it is scrolled ----------------------- */

/* One panel, rows divided by lines, like the upcoming-events list on the
   home page and the app: a card per event made fourteen months of them read
   as fourteen months of cards. The rows keep their glass/card classes for the
   scripts that find them by those, and are undressed here. */
.event-list {
    display: block;
    background: var(--panel);
    border: 1px solid var(--panel-border);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow);
    overflow: hidden;
    margin-bottom: 16px;
}

.event-list > .event-row.glass.card {
    background: transparent;
    border: 0;
    border-radius: 0;
    /* The divider is a shadow above the row rather than a border, so the
       topmost visible row's line falls outside the panel and is clipped -
       which matters when the filter hides the first row, where a border
       would have left a line under the panel's own edge. */
    box-shadow: 0 -1px 0 var(--panel-border);
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    margin: 0;
}

.event-list > .event-row > summary {
    border-radius: 0;
}

.event-list > .event-row > summary:hover {
    background: var(--surface-2);
}


/* Where the next page lands. Holds the sentinel the observer watches and the
   line that says what is happening, and takes up room even when empty so the
   page does not jump as rows arrive under it. */
.event-list-more {
    min-height: 40px;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 8px;
    margin-bottom: 20px;
}

.event-list-status {
    margin: 0;
    font-size: 13px;
    color: var(--muted);
}

/* The chips while the rest of the list is on its way. Filtering cannot answer
   until every row is here, and half a second of nothing looks like a chip
   that did not take the press. */
.event-kinds.is-loading {
    opacity: 0.6;
    pointer-events: none;
}


/* ---- My Skies, filled in as it is scrolled ------------------------------ */

/* The wrapper exists so the script has one place to append to. It must not
   become a layout box of its own: the panels are laid out by the page, and a
   div between them would take their spacing with it. */
.sky-blocks {
    display: contents;
}

.sky-panels-more {
    min-height: 60px;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 8px;
    margin-bottom: 20px;
}

.sky-panels-status {
    margin: 0;
    font-size: 13px;
    color: var(--muted);
}


/* ---- A celestial event on a Next few days tile -------------------------- */

/* Three columns only when there is something in the middle. The two ends stay
   equal to each other whatever sits between them - they are the pair the strip
   is comparing, and letting the middle take a third of the width would make
   two days with different events look like different-shaped days. */
.day-events.has-celestial {
    grid-template-columns: 1fr auto 1fr;
    gap: 10px;
}

/* The neighbours have a verdict word this one does not, so it is laid out to
   its extremes instead: icon level with their icons, bar level with their
   bars, and the time in the space the word would have taken. */
.day-event-celestial {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: space-between;
    height: 100%;
    min-width: 62px;
}

.day-event-celestial .day-event-icon {
    /* The site colour, like every other of these icons. On the tile it also
       keeps the event visually separate from the two verdicts, which carry
       the colour of their own score. */
    color: var(--accent, #ff8c42);
    margin-bottom: 4px;
    line-height: 0;
}

.day-event-celestial .day-event-time {
    margin-bottom: 6px;
}

/* The meter is as wide as the column allows rather than as wide as its own
   content, or a good night would draw a shorter bar than a poor one simply
   because the time above it was narrower. */
.day-event-celestial .meter,
.day-event-celestial .meter-mini {
    width: 100%;
}


/* ---- Admin: traffic ------------------------------------------------------ */

.traffic-spans {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin-bottom: 16px;
}

.traffic-span {
    padding: 6px 14px;
    border-radius: 999px;
    border: 1px solid var(--panel-border);
    color: var(--muted);
    text-decoration: none;
    font-size: 13px;
}

.traffic-span.is-on {
    border-color: var(--accent);
    color: var(--accent);
}

.traffic-totals {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
    gap: 16px;
    margin-bottom: 16px;
}

.traffic-total .eyebrow {
    margin-bottom: 4px;
}

.traffic-number {
    font-size: 40px;
    font-weight: 700;
    line-height: 1.1;
    margin: 0 0 6px;
}

.traffic-columns {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
    gap: 16px;
    margin-bottom: 16px;
}

.traffic-table {
    width: 100%;
    border-collapse: collapse;
    margin-top: 10px;
    font-size: 14px;

    /* Fixed, so the name column takes what is left after the two number
       columns rather than the other way round. Under automatic layout a long
       origin name set the width of its column and pushed the numbers out
       through the side of the card. */
    table-layout: fixed;
}

.traffic-table .num {
    width: 68px;
}

.traffic-table th {
    text-align: left;
    font-size: 11px;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--muted);
    font-weight: 600;
    padding: 0 0 8px;
}

.traffic-table td {
    padding: 7px 0;
    border-top: 1px solid var(--panel-border);
    vertical-align: middle;
}

.traffic-table .num {
    text-align: right;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
    padding-left: 12px;
}

/* The name over a bar, so the column reads as a chart without being one:
   the length says how it compares, the number says how much. */
.traffic-table td:first-child {
    position: relative;
    min-width: 0;
}

.traffic-name {
    position: relative;
    z-index: 1;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    display: block;
    max-width: 100%;
}

.traffic-bar {
    position: absolute;
    left: 0;
    bottom: 3px;
    height: 3px;
    border-radius: 2px;
    background: var(--accent);
    opacity: 0.45;
}

/* A crawler category heading inside the bots card, with its own total on
   the right so "how much of this is AI" is a number rather than a sum. */
.traffic-group {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    margin: 18px 0 0;
    font-size: 12px;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--muted);
    font-weight: 600;
}

.traffic-group-total {
    font-variant-numeric: tabular-nums;
    letter-spacing: 0;
    text-transform: none;
    font-size: 13px;
}

.traffic-table-tight {
    margin-top: 4px;
}

.traffic-table-tight thead th {
    padding-bottom: 4px;
}

/* The countries a crawler ran from, beside its name. Small and a little
   apart, so they read as a note on the row rather than as part of the name. */
.traffic-flags {
    margin-left: 6px;
    font-size: 13px;
    opacity: 0.85;
}

/* ---- Agreeing to the terms at signup -------------------------------------- */

/* A box beside a sentence, aligned on the first line of the sentence rather
   than centred on the block, so it reads as "tick to agree to this" and not
   as a stray control floating beside a paragraph. */
.consent-row {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    font-size: 13px;
    line-height: 1.45;
    color: var(--muted);
}

.consent-row input[type="checkbox"] {
    flex: 0 0 auto;
    width: 18px;
    height: 18px;
    margin: 1px 0 0;
    accent-color: var(--accent);
}

.consent-row a,
.consent-note a {
    color: var(--text);
    text-decoration: underline;
}

.consent-note {
    font-size: 12px;
    color: var(--muted);
    margin: 0 0 10px;
    text-align: center;
}

/* ---- The date, large, in the card's top-left corner ---------------------- */

/* Set in the same tight, heavy face as the logo's wordmark, so the card and
   the site read as one hand. The number in the accent, the month in the
   text colour: two colours because they are two things, and the accent
   goes on the one you are scanning for. Nothing else on the card is this
   size, which is what makes it the first thing seen. */
.today-date-badge {
    position: absolute;
    top: 10px;
    left: 14px;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    text-align: left;
    line-height: 0.9;
    font-weight: 900;
    letter-spacing: -0.02em;
    pointer-events: none;
}

/* The accent colour as the theme defines it for the site's own marks - the
   same token the event icons use, orange on dark and the logo's blue on
   light - so the date and the logo agree whichever theme is on. */
.today-date-day {
    font-size: 44px;
    color: var(--event-accent, var(--accent));
    /* Digits carry a little side bearing that letters do not; without this
       the number looks indented against the month under it. */
    margin-left: -0.03em;
}

.today-date-month {
    font-size: 30px;
    color: var(--text);
    /* Pulled up under the number so the two read as one mark: the number's
       line box has empty space under the digits that a plain gap doubled. */
    margin-top: -6px;
    text-transform: uppercase;
}

/* Today: the word carries the size, the date sits under it small - the day
   number still in the accent so the eye finds it - and the whole block is
   the same height as the number-and-month form, so a strip of cards does
   not step up and down as today goes by. */
.today-date-badge.is-today .today-date-word {
    font-size: 34px;
    color: var(--event-accent, var(--accent));
    text-transform: uppercase;
    letter-spacing: -0.01em;
}

/* Under the word, the date in the same stacked form as any other day's
   badge - number over month, touching - only smaller. */
.today-date-badge.is-today .today-date-day {
    font-size: 26px;
    margin-top: 4px;
}

.today-date-badge.is-today .today-date-month {
    font-size: 18px;
    margin-top: -4px;
}

/* On a phone the corner is a third of the card wide, and "TODAY" beside a
   centred place name is two things fighting for one line. So the name
   drops below the badge instead: the header starts under it, and keeps
   only enough side room to clear the control buttons on the right. The
   badge stays the size it is - shrinking it was the wrong trade. */
@media (max-width: 480px) {
    .today-date-day { font-size: 38px; }
    .today-date-month { font-size: 26px; margin-top: -5px; }
    .today-date-badge.is-today .today-date-word { font-size: 28px; }
    .today-date-badge.is-today .today-date-day { font-size: 22px; margin-top: 3px; }
    .today-date-badge.is-today .today-date-month { font-size: 15px; margin-top: -3px; }
    .today-location { padding: 66px 56px 12px; }
}

/* An event tile is laid out like a verdict tile - icon, name, headline,
   time, all centred - rather than the icon beside a left-aligned block, so
   the time lands in the same place on every page of the card. */
.today-page-event .verdict-main {
    flex-direction: column;
    gap: 10px;
    text-align: center;
}

.today-page-event .verdict-main > div {
    text-align: center;
}

/* ---- Rating bars on the events page ------------------------------------ */

/* Under the date on the row: the best place's bar, short, like the upcoming
   rows. Inside, each place carries its own under its headline. */
.event-row-meter {
    display: block;
    width: 110px;
    max-width: 100%;
    margin-top: 6px;
}

.event-place-headline {
    display: flex;
    flex-direction: column;
    gap: 4px;
}

.event-place-meter {
    display: block;
    width: 90px;
    max-width: 100%;
}

.event-row-meter .meter-mini,
.event-place-meter .meter-mini {
    margin: 0;
}


/* A month standing in for the day number on a row's badge. */
.event-row-badge .today-date-day-month {
    font-size: 18px;
    text-transform: uppercase;
    letter-spacing: 0;
}

/* ---- Fall colour page ---------------------------------------------------- */

/* Laid out like an event tile - icon, name, band, time, all centred - so the
   band word sits under the name rather than hanging off its left edge. */
.today-page-foliage .verdict-main {
    flex-direction: column;
    gap: 10px;
    text-align: center;
}

.foliage-note {
    margin: 12px 0 0;
    font-size: 13px;
    line-height: 1.5;
    color: var(--muted);
    text-align: center;
}

/* ---- Drawn icons ------------------------------------------------------ */

/* The sky and weather icons are SVG in the accent colour, laid out as
   boxes rather than glyphs on a text baseline. */
.verdict-icon,
.day-segment-icon,
.day-event-icon,
.history-row-icon,
.day-adjacent-thumb > span {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    line-height: 0;
    color: var(--event-accent, var(--accent));
}

.day-segment-icon,
.day-event-icon {
    margin-bottom: 6px;
}

.location-card-next svg {
    vertical-align: -3px;
    margin-right: 2px;
    color: var(--event-accent, var(--accent));
}

/* ---- Units on the preferences page ------------------------------------ */

.units-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
    gap: 16px;
    margin-top: 14px;
}

.units-grid .field-label {
    margin: 0 0 8px;
    font-size: 12px;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--muted);
}

/* ---- Kind chips over a place's own list ------------------------------- */

.event-kinds-inline {
    margin: 10px 0 4px;
}

/* A filtered-out row is out, whatever the collapsible thinks. */
.upcoming-event[hidden] {
    display: none;
}

/* ---- Top bar: the right-hand end, and the search on its own row ------ */

.topbar-right {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-left: auto;
    flex-shrink: 0;
}

.topbar-signin,
.topbar-signup {
    font-size: 13px;
    font-weight: 700;
    text-decoration: none;
    padding: 8px 14px;
    border-radius: 999px;
    color: var(--text);
}

.topbar-signup {
    background: var(--accent);
    color: #fff;
}

.topbar-avatar {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 36px;
    height: 36px;
    border-radius: 50%;
    background: var(--accent);
    color: #fff;
    font-weight: 700;
    font-size: 14px;
    text-decoration: none;
}

.topbar-avatar-guest {
    display: none;
    background: var(--surface-2);
    color: var(--text);
}

/* The search on its own full-width row, under the first. */
.topbar .quick-search {
    flex-basis: 100%;
    max-width: none;
    margin-left: 0;
    order: 2;
}

@media (max-width: 720px) {
    /* A phone's first row is the menu, the mark and who you are. */
    .topbar-signin,
    .topbar-signup,
    .topbar-desktop-only {
        display: none !important;
    }

    .topbar-avatar-guest {
        display: inline-flex;
    }

    /* Enter and the suggestions both search; the glass was a third way. */
    .topbar .quick-search button[type="submit"] {
        display: none;
    }
}

/* ---- My Skies on the front page ------------------------------------- */

/* The switcher is the card's own top: full width, joined to it, as the app
   draws it - the card's score edge becomes the line between the two. It was
   a pill floating at the right above the card, which read as something else
   on the page. Pulled up under the bar, as before. */
.my-skies-switch {
    position: relative;
    display: block;
    margin: -14px 0 0;
}

.my-skies-switch + .today-card {
    border-top-left-radius: 0;
    border-top-right-radius: 0;
}

/* Manage mode: grips and buttons appear, readings go, rows stop being links. */
.my-skies-handle,
.my-skies-btn {
    display: none;
}

.is-managing [data-my-skies-reading] {
    display: none;
}

.is-managing .my-skies-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    border-radius: 50%;
    border: 1px solid var(--panel-border);
    background: var(--panel);
    color: var(--muted);
    font: inherit;
    font-size: 17px;
    line-height: 1;
    cursor: pointer;
}

.is-managing .my-skies-btn svg {
    width: 14px;
    height: 14px;
}

.is-managing .my-skies-item.is-home .my-skies-home-btn {
    color: #fff;
    background: var(--event-accent, var(--accent));
    border-color: var(--event-accent, var(--accent));
}

.is-managing .my-skies-row-link {
    cursor: grab;
}

.is-managing [data-my-skies-item].is-dragging {
    opacity: 0.5;
}

.my-skies-manage {
    flex-shrink: 0;
    padding: 8px 14px;
    border-radius: 999px;
    border: 1px solid var(--panel-border);
    background: transparent;
    font: inherit;
    font-size: 13px;
    font-weight: 700;
    color: var(--text);
    cursor: pointer;
}

.is-managing .my-skies-manage {
    background: var(--accent);
    border-color: var(--accent);
    color: #fff;
}

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

.my-skies-empty-note {
    font-size: 13px;
    color: var(--muted);
}

.my-skies-verdict {
    font-weight: 600;
}

.my-skies-time {
    color: var(--muted);
    font-weight: 600;
}

.my-skies-add {
    border: 1px dashed var(--panel-border);
    background: transparent;
    color: var(--muted);
    cursor: pointer;
    font: inherit;
    font-size: 14px;
}

/* The phone: the switcher in the card, and the sheet. */
.my-skies-switcher {
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    min-height: 48px;
    margin: 0;
    padding: 8px 18px;
    border-radius: var(--radius-lg) var(--radius-lg) 0 0;
    border: 1px solid var(--panel-border);
    border-bottom: 0;
    background: var(--surface-1);
    color: var(--text);
    font: inherit;
    text-align: left;
    cursor: pointer;
}

.my-skies-switcher-pin {
    display: inline-flex;
    color: var(--event-accent, var(--accent));
}

.my-skies-switcher-name {
    flex: 1 1 auto;
    min-width: 0;
    font-size: 16px;
    font-weight: 700;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.my-skies-switcher-count {
    font-size: 13px;
    font-weight: 600;
    color: var(--muted);
}

.my-skies-switcher-chev {
    display: inline-flex;
    color: var(--muted);
}

.my-skies-sheet[hidden] {
    display: none;
}

.my-skies-sheet {
    position: fixed;
    inset: 0;
    z-index: 60;
}

.my-skies-sheet-backdrop {
    position: absolute;
    inset: 0;
    background: rgba(0, 0, 0, 0.5);
}

.my-skies-sheet-panel {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    max-height: 82vh;
    overflow-y: auto;
    padding: 8px 16px calc(16px + env(safe-area-inset-bottom));
    border-radius: 22px 22px 0 0;
    background: var(--bg);
    border-top: 1px solid var(--panel-border);
}

.my-skies-sheet-grab {
    display: block;
    width: 44px;
    height: 5px;
    margin: 4px auto 10px;
    border-radius: 3px;
    background: var(--panel-border);
}

.my-skies-sheet-head {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 0 0 8px;
}

.my-skies-sheet-title {
    font-weight: 700;
    font-size: 22px;
    flex: 1 1 auto;
}

.my-skies-sheet .my-skies-manage {
    margin-left: 0;
}

.my-skies-sheet-x {
    width: 44px;
    height: 44px;
    border-radius: 50%;
    border: 1px solid var(--panel-border);
    background: var(--panel);
    font-size: 22px;
    line-height: 1;
    color: var(--muted);
    cursor: pointer;
}

.my-skies-rows {
    list-style: none;
    margin: 0;
    padding: 0;
}

.my-skies-row {
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    min-height: 64px;
    padding: 8px 4px;
    border: 0;
    border-top: 1px solid var(--panel-border);
    background: none;
    color: var(--text);
    text-decoration: none;
    font: inherit;
    text-align: left;
    border-radius: 12px;
}

.my-skies-rows .my-skies-row:first-child {
    border-top: 0;
}

.my-skies-row.is-current {
    background: var(--surface-1);
}

.my-skies-row-link {
    display: flex;
    align-items: center;
    gap: 10px;
    flex: 1 1 auto;
    min-width: 0;
    color: inherit;
    text-decoration: none;
}

.my-skies-check {
    width: 20px;
    flex-shrink: 0;
    display: inline-flex;
    justify-content: center;
    color: var(--event-accent, var(--accent));
    font-weight: 700;
}

.my-skies-row-main {
    display: flex;
    flex-direction: column;
    gap: 2px;
    flex: 1 1 auto;
    min-width: 0;
}

.my-skies-row .my-skies-name {
    font-weight: 700;
    font-size: 16px;
    display: inline-flex;
    align-items: center;
    gap: 6px;
}

.my-skies-home-mark {
    display: inline-flex;
    color: var(--event-accent, var(--accent));
}

.my-skies-region {
    font-size: 13px;
    font-weight: 600;
    color: var(--muted);
}

.my-skies-row-side {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 2px;
    font-size: 14px;
}

/* The rating spine at the row's edge, in the score ramp. */
.my-skies-spine {
    width: 6px;
    height: 40px;
    border-radius: 3px;
    background: var(--score-color, var(--panel-border));
    flex-shrink: 0;
}

.my-skies-handle {
    flex-direction: column;
    justify-content: center;
    gap: 5px;
    width: 40px;
    height: 44px;
    flex-shrink: 0;
    cursor: grab;
    touch-action: none;
}

.my-skies-handle span {
    display: block;
    width: 18px;
    height: 2px;
    margin: 0 auto;
    border-radius: 2px;
    background: var(--muted);
}

.is-managing .my-skies-handle {
    display: flex;
}

.is-managing .my-skies-row .my-skies-check {
    display: none;
}

.my-skies-sheet.is-managing .my-skies-btn {
    width: 44px;
    height: 44px;
}

.my-skies-add-wide {
    display: block;
    width: 100%;
    min-height: 50px;
    margin-top: 10px;
    border-radius: 14px;
}

body.my-skies-sheet-open {
    overflow: hidden;
}

/* The toast, with its undo. */
.my-skies-toast {
    position: fixed;
    left: 50%;
    bottom: calc(24px + env(safe-area-inset-bottom));
    transform: translateX(-50%);
    z-index: 70;
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 12px 16px;
    border-radius: 14px;
    background: var(--panel);
    border: 1px solid var(--panel-border);
    box-shadow: var(--shadow);
    font-size: 14px;
    color: var(--text);
}

.my-skies-toast[hidden] {
    display: none;
}

.my-skies-toast button {
    background: none;
    border: 0;
    font: inherit;
    font-weight: 700;
    color: var(--accent);
    cursor: pointer;
}

/* A desktop: the sheet is a menu straight down from the pill - the rows
   only, no handle, head, hint, locate or add. */
@media (min-width: 721px) {
    .my-skies-sheet {
        position: absolute;
        inset: auto 0 auto auto;
        top: calc(100% + 8px);
        z-index: 50;
        width: min(440px, 100%);
    }

    .my-skies-sheet-backdrop {
        position: fixed;
        background: transparent;
    }

    .my-skies-sheet-panel {
        position: relative;
        max-height: 70vh;
        padding: 6px;
        border-radius: 14px;
        border: 1px solid var(--panel-border);
        box-shadow: var(--shadow);
    }

    .my-skies-phone-only {
        display: none !important;
    }

    /* The head - My Skies, the crosshair, Edit - on the desktop menu too,
       and Edit's drag-to-reorder with it. */
    .my-skies-sheet-panel {
        padding: 10px 12px 8px;
    }

    .my-skies-sheet-title {
        font-size: 18px;
    }

    .my-skies-sheet-head {
        padding: 2px 4px 8px;
    }

    .my-skies-sheet .my-skies-hint {
        padding: 0 4px;
        margin: 0 0 6px;
    }

    .my-skies-rows .my-skies-row {
        border-radius: 10px;
    }

    /* No scroll lock on a desktop menu - but only the vertical side is
       given back. `overflow: auto` here once undid the body's usual
       overflow-x: hidden, and the page grew a sideways scrollbar for as
       long as the menu was open. */
    body.my-skies-sheet-open {
        overflow-x: hidden;
        overflow-y: auto;
    }
}

@media (max-width: 720px) {
    .my-skies-switcher {
        min-height: 52px;
    }
}

/* ---- The account menu under the initial ------------------------------- */

.topbar-me {
    position: relative;
}

.topbar-menu {
    position: absolute;
    right: 0;
    top: calc(100% + 8px);
    z-index: 40;
    min-width: 180px;
    padding: 6px;
    border-radius: 12px;
    /* Solid: the glass panel let the search box and the location name
       behind show through the menu's own words. */
    background: var(--bg);
    border: 1px solid var(--panel-border);
    box-shadow: var(--shadow);
}

.topbar-menu[hidden] {
    display: none;
}

.topbar-menu-who {
    display: block;
    padding: 8px 10px 6px;
    font-size: 12px;
    color: var(--muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    max-width: 240px;
    border-bottom: 1px solid var(--panel-border);
    margin-bottom: 4px;
}

.topbar-menu a,
.topbar-menu-item {
    display: block;
    width: 100%;
    text-align: left;
    background: none;
    border: 0;
    cursor: pointer;
    padding: 9px 10px;
    border-radius: 8px;
    color: var(--text);
    text-decoration: none;
    font-size: 14px;
    font-weight: 600;
}

.topbar-menu a:hover,
.topbar-menu a:focus-visible,
.topbar-menu-item:hover,
.topbar-menu-item:focus-visible {
    background: var(--surface-2);
}

.my-skies-sheet-locate {
    width: 44px;
    height: 44px;
    border-radius: 50%;
    border: 1px solid var(--panel-border);
    background: var(--panel);
    color: var(--event-accent, var(--accent));
    display: inline-flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
}

/* ---- Preferences: My Skies rows, as the menu's Edit mode ---------------- */

.sky-order.is-managing .sky-order-row {
    counter-increment: none;
    gap: 10px;
    padding: 8px 8px 8px 4px;
    cursor: grab;
}

.sky-order.is-managing .sky-order-row::before {
    content: none;
}

.sky-order.is-managing .my-skies-handle {
    display: flex;
}

.sky-order-main {
    display: flex;
    flex-direction: column;
    gap: 2px;
    flex: 1 1 auto;
    min-width: 0;
}

.sky-order.is-managing .sky-order-name {
    font-weight: 700;
}

.sky-order.is-managing .sky-order-row.is-home .sky-order-name::after {
    content: '';
}

.sky-order.is-managing .my-skies-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 40px;
    height: 40px;
    border-radius: 50%;
    border: 1px solid var(--panel-border);
    background: var(--panel);
    color: var(--muted);
    font: inherit;
    font-size: 17px;
    line-height: 1;
    cursor: pointer;
}

.sky-order.is-managing .sky-order-row.is-home .my-skies-home-btn {
    color: #fff;
    background: var(--event-accent, var(--accent));
    border-color: var(--event-accent, var(--accent));
}

.sky-order.is-managing .sky-order-row.is-dragging {
    opacity: 0.5;
}

/* The language switch at the foot of the drawer. A button rather than a
   link, because it changes something; small and quiet, because it is a
   way back rather than a thing to press often. */
.nav-drawer-language {
    margin-top: 10px;
}

.nav-drawer-language-btn {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    padding: 6px 12px;
    border-radius: 999px;
    border: 1px solid var(--border-1);
    background: var(--surface-2);
    color: var(--muted);
    font: inherit;
    font-size: 0.85rem;
    cursor: pointer;
}

.nav-drawer-language-btn:hover {
    color: var(--accent);
    border-color: var(--accent);
}

/* ---------- the new look: Settings, its pages, and the menu ----------
   Behind FeatureFlags `new_look`. Everything below is scoped to classes
   only that look sets (.settings-new, .nav-drawer-new), so the long
   preferences page and the old menu are exactly as they were. Drawn to the
   app's design rules: a 4px grid, 12px corners on cards and fully round
   pills, one outline icon per row. */

.settings-hub {
    max-width: 720px;
    margin: 0 auto;
}

.settings-title {
    margin: 8px 0 12px;
    font-size: 28px;
    font-weight: 800;
    letter-spacing: -0.01em;
}

.settings-group {
    padding: 8px 0;
}

.settings-group + .settings-group {
    border-top: 6px solid var(--surface-1);
}

.settings-group-label {
    margin: 12px 4px 4px;
    font-size: 13px;
    font-weight: 600;
    color: var(--muted);
}

.settings-row {
    display: flex;
    align-items: center;
    gap: 16px;
    min-height: 56px;
    padding: 12px 8px;
    border-radius: 12px;
    color: var(--text);
    text-decoration: none;
    transition: background 0.15s ease;
}

.settings-row:hover,
.settings-row:focus-visible {
    background: var(--surface-1);
}

.settings-row-icon {
    display: inline-flex;
    flex: none;
    color: var(--text);
}

.settings-row-text {
    display: flex;
    flex: 1;
    flex-direction: column;
    min-width: 0;
}

.settings-row-title {
    font-size: 16px;
}

.settings-row-sub {
    font-size: 13px;
    color: var(--muted);
    overflow-wrap: anywhere;
}

.settings-row-value {
    flex: 0 1 auto;
    max-width: 45%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 14px;
    color: var(--muted);
}

.settings-row-chevron {
    display: inline-flex;
    flex: none;
    color: var(--muted);
}

.settings-avatar {
    display: inline-flex;
    flex: none;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    border-radius: 50%;
    background: var(--accent);
    color: #fff;
    font-size: 18px;
    font-weight: 700;
}

.settings-group-apart {
    margin-top: 12px;
}

/* A page of its own: a back arrow and the title, then the controls on the
   page itself rather than inside a panel - the app draws them flat. The
   page keeps the dashboard's own width; auto margins on a grid item would
   shrink it to its content and centre it, back arrow and all. */

.settings-sub-head {
    display: flex;
    align-items: center;
    gap: 8px;
    margin: 8px 0 16px;
}

.settings-sub-head h1 {
    margin: 0;
    font-size: 26px;
    font-weight: 800;
}

.settings-back {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    border-radius: 50%;
    color: var(--text);
}

.settings-back:hover,
.settings-back:focus-visible {
    background: var(--surface-1);
}

.settings-sub .glass.card {
    padding: 0;
    background: transparent;
    border: 0;
    box-shadow: none;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
}

.settings-sub .glass.card > h2 {
    display: none;
}

.settings-sub-events .glass.card > .subtitle,
.settings-sub-skies .glass.card > .subtitle {
    display: none;
}

.settings-sub .field-label,
.settings-sub .units-grid .field-label {
    letter-spacing: 0;
    text-transform: none;
    margin: 0 0 8px;
    font-size: 13px;
    font-weight: 600;
    color: var(--muted);
}

.settings-field,
.settings-sub .settings-list-label {
    margin-top: 24px;
}

/* Segmented: the pills become one rounded bar, the choice picked out in
   the accent rather than filled with the gradient. */
.settings-sub .score-picker {
    display: flex;
    flex-wrap: nowrap;
    gap: 0;
    border: 1px solid var(--border-2);
    border-radius: 999px;
    overflow: hidden;
}

.settings-sub .score-choice {
    flex: 1 1 0;
    min-width: 0;
}

.settings-sub .score-choice span {
    display: flex;
    justify-content: center;
    width: 100%;
    padding: 12px 10px;
    border: 0;
    border-radius: 999px;
    background: transparent;
    color: var(--text);
    font-size: 14px;
    font-weight: 600;
    text-align: center;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.settings-sub .score-choice input:checked + span {
    background: rgba(255, 174, 87, 0.14);
    box-shadow: inset 0 0 0 1.5px var(--accent);
    color: var(--accent);
}

/* Switches: one per event kind, on the right, as in the app. */
.settings-sub .priority-row {
    grid-template-columns: 28px 1fr auto;
    align-items: center;
    gap: 14px;
    padding: 14px 0;
    border-top: 0;
}

.settings-sub .priority-row > .priority-icon {
    grid-column: 1;
    grid-row: 1;
}

.settings-sub .priority-row > .priority-text {
    grid-column: 2;
    grid-row: 1;
}

.settings-sub .priority-row > input[type="checkbox"] {
    grid-column: 3;
    grid-row: 1;
    position: relative;
    width: 48px;
    height: 28px;
    margin: 0;
    border: 1px solid var(--border-2);
    border-radius: 999px;
    background: var(--surface-3);
    cursor: pointer;
    transition: background 0.15s ease, border-color 0.15s ease;
    -webkit-appearance: none;
    appearance: none;
}

.settings-sub .priority-row > input[type="checkbox"]::before {
    content: "";
    position: absolute;
    top: 50%;
    left: 4px;
    width: 18px;
    height: 18px;
    border-radius: 50%;
    background: var(--muted);
    transform: translateY(-50%);
    transition: left 0.15s ease, background 0.15s ease, width 0.15s ease, height 0.15s ease;
}

.settings-sub .priority-row > input[type="checkbox"]:checked {
    background: var(--accent);
    border-color: var(--accent);
}

.settings-sub .priority-row > input[type="checkbox"]:checked::before {
    left: 23px;
    width: 20px;
    height: 20px;
    background: #14181f;
}

.settings-sub .priority-row > input[type="checkbox"]:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

.settings-sub .priority-text strong {
    font-weight: 500;
    font-size: 16px;
}

/* A list of choices: the languages. */
.settings-choice-list {
    display: grid;
}

.settings-choice {
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 14px 4px;
    border-bottom: 1px solid var(--border-1);
    cursor: pointer;
    font-size: 16px;
}

.settings-choice input[type="radio"] {
    width: 20px;
    height: 20px;
    margin: 0;
    accent-color: var(--accent);
}

/* The menu: the places you can go, each with its icon, the page you are
   on picked out. */
.nav-drawer-new .nav-drawer-row {
    display: flex;
    align-items: center;
    gap: 16px;
    padding: 13px 14px;
    border-radius: 14px;
    font-size: 16px;
}

.nav-drawer-new .nav-drawer-row svg {
    flex: none;
}

/* The site's accent for the theme: teal on light, orange on dark (see
   --event-accent). Orange on the light menu was the one warm thing on it. */
.nav-drawer-new .nav-drawer-row.is-here {
    background: rgba(255, 174, 87, 0.12);
    background: color-mix(in srgb, var(--event-accent, var(--accent)) 12%, transparent);
    color: var(--event-accent, var(--accent));
    font-weight: 600;
}

/* ---------- the new look: the small changes across the site ----------
   All under [data-new-look], which theme_html_attr() writes on <html> only
   for viewers with FeatureFlags `new_look`. Following the app's design
   rules: 12px corners on cards, page titles without a box, dates that
   stack cleanly, and lighter row titles that wrap less. */

[data-new-look] {
    --radius-lg: 12px;
}

/* Page titles: on the page, not in a box, and the size the app's are. */
[data-new-look] .page-head {
    padding: 4px 0;
    background: none;
    border: 0;
}

[data-new-look] .page-head h1 {
    font-size: 28px;
    font-weight: 800;
    letter-spacing: -0.01em;
}

/* Dates stacked with room between number and month, rather than touching. */
[data-new-look] .event-row-badge,
[data-new-look] .today-date-badge {
    line-height: 1;
}

[data-new-look] .event-row-badge .today-date-month {
    margin-top: 3px;
}

[data-new-look] .today-date-month,
[data-new-look] .today-date-badge.is-today .today-date-month {
    margin-top: 2px;
}

/* Event rows: the name lighter and smaller, the place count up beside the
   kind label, as the app lists them. */
[data-new-look] .event-row-name {
    font-size: 18px;
    font-weight: 600;
    line-height: 1.25;
}

[data-new-look] .event-row-count,
[data-new-look] .event-row > summary::after {
    align-self: flex-start;
    margin-top: 2px;
}

/* The calendar beside "Next few days" is a round button, as in the app;
   the words stay for screen readers. */
[data-new-look] .section-heading-row .date-jump-summary {
    justify-content: center;
    width: 40px;
    height: 40px;
    padding: 0;
    border-radius: 50%;
}

[data-new-look] .section-heading-row .date-jump-summary svg {
    width: 18px;
    height: 18px;
}

[data-new-look] .section-heading-row .date-jump-summary span {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
}

/* On a phone the place name already sits under the date badge, so the
   card's buttons go in a row across the corner, as in the app, instead of
   down the edge. Wider cards keep the column: there the name is beside
   them, and a row would reach it. */
@media (max-width: 480px) {
    [data-new-look] .today-card-controls {
        flex-direction: row;
        gap: 4px;
    }
}

/* Reminders, reached from Settings: the page title now says it, so the
   panel does not say it again. */
[data-new-look] .reminders-main > h2 {
    display: none;
}

/* ---------- the new look: Reminders, in the app's layout ----------
   One row per control and per reminder, drawn flat on the page. */

.reminders-new {
    display: grid;
    gap: 4px;
}

.reminders-switch {
    display: flex;
    align-items: center;
    gap: 16px;
    min-height: 56px;
    padding: 10px 4px;
    cursor: pointer;
}

/* A switch, the same one the event kinds use on their settings page. */
.settings-switch {
    position: relative;
    flex: none;
    width: 48px;
    height: 28px;
    margin: 0;
    border: 1px solid var(--border-2);
    border-radius: 999px;
    background: var(--surface-3);
    cursor: pointer;
    transition: background 0.15s ease, border-color 0.15s ease;
    -webkit-appearance: none;
    appearance: none;
}

.settings-switch::before {
    content: "";
    position: absolute;
    top: 50%;
    left: 4px;
    width: 18px;
    height: 18px;
    border-radius: 50%;
    background: var(--muted);
    transform: translateY(-50%);
    transition: left 0.15s ease, background 0.15s ease;
}

.settings-switch:checked {
    background: var(--accent);
    border-color: var(--accent);
}

.settings-switch:checked::before {
    left: 23px;
    background: #14181f;
}

.settings-switch:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

.reminders-rows {
    margin-top: 8px;
    border-top: 1px solid var(--border-1);
}

.reminders-item {
    padding: 12px 0 10px 4px;
    border-bottom: 1px solid var(--border-1);
}

.reminders-item-top {
    display: flex;
    align-items: flex-start;
    gap: 8px;
}

.reminders-item-text {
    display: flex;
    flex: 1;
    flex-direction: column;
    min-width: 0;
    color: var(--text);
    text-decoration: none;
}

.reminders-item.is-done .settings-row-title {
    color: var(--muted);
}

/* What the day holds there beyond sunrise and sunset - the reason to have
   set the reminder, so it reads in the accent rather than as small print. */
.reminders-item-events {
    color: var(--event-accent, var(--accent));
}

.reminders-bin {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 40px;
    height: 40px;
    border: 0;
    border-radius: 50%;
    background: transparent;
    color: var(--muted);
    cursor: pointer;
}

.reminders-bin:hover,
.reminders-bin:focus-visible {
    background: var(--surface-1);
    color: var(--text);
}

/* A settings row that is a button: removing everything. */
.settings-row-danger {
    width: 100%;
    margin-top: 8px;
    border: 0;
    background: none;
    color: var(--negative);
    font: inherit;
    text-align: left;
    cursor: pointer;
}

.settings-row-danger .settings-row-icon {
    color: var(--negative);
}

/* A choice drawn as the app draws its notification times: the row says
   what is chosen, with a chevron, and opens onto chips. */
.lead-picker > summary {
    display: flex;
    align-items: center;
    gap: 12px;
    min-height: 56px;
    padding: 10px 4px;
    list-style: none;
    cursor: pointer;
}

.lead-picker > summary::-webkit-details-marker {
    display: none;
}

.lead-picker > summary .settings-row-title {
    flex: 1;
}

.lead-picker-value {
    color: var(--accent);
    font-size: 15px;
    text-align: right;
}

.lead-picker-chev {
    display: inline-flex;
    flex: none;
    color: var(--muted);
    transition: transform 0.15s ease;
}

.lead-picker[open] .lead-picker-chev {
    transform: rotate(180deg);
}

.lead-picker > summary:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
    border-radius: 8px;
}

/* Under a reminder: no title, the choice and its chevron on the left. */
.lead-picker-compact > summary {
    min-height: 0;
    padding: 4px 0;
}

.lead-picker-compact .lead-picker-value {
    text-align: left;
}

.lead-chips {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
    padding: 4px 0 14px 4px;
}

.lead-chip {
    position: relative;
    cursor: pointer;
}

.lead-chip input {
    position: absolute;
    width: 1px;
    height: 1px;
    opacity: 0;
}

.lead-chip span {
    display: inline-flex;
    padding: 10px 18px;
    border: 1px solid var(--border-2);
    border-radius: 12px;
    color: var(--text);
    font-size: 15px;
    font-weight: 600;
    transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}

.lead-chip input:checked + span {
    background: rgba(255, 174, 87, 0.16);
    border-color: transparent;
    color: var(--accent);
}

.lead-chip input:focus-visible + span {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

/* ---------- the new look in light mode ----------
   Drawn flat on the page, as the app has them, the settings rows vanish
   into the pale gradient of the light theme - the dark page gives them the
   contrast the light one does not. So in light mode each group, each
   settings page and the reminders list sit on a panel of their own. Dark
   keeps the app's flat rows. */
:root[data-theme="light"] .settings-group,
:root[data-theme="light"] .settings-sub .glass.card,
:root[data-theme="light"] .reminders-new {
    padding: 8px 12px;
    background: var(--panel);
    border: 1px solid var(--border-1);
    border-radius: 12px;
    box-shadow: 0 6px 20px rgba(20, 30, 60, 0.06);
}

:root[data-theme="light"] .settings-group + .settings-group {
    margin-top: 12px;
    border-top: 1px solid var(--border-1);
}

:root[data-theme="light"] .settings-sub .glass.card {
    padding: 16px;
}

@media (prefers-color-scheme: light) {
    :root:not([data-theme="dark"]) .settings-group,
    :root:not([data-theme="dark"]) .settings-sub .glass.card,
    :root:not([data-theme="dark"]) .reminders-new {
        padding: 8px 12px;
        background: var(--panel);
        border: 1px solid var(--border-1);
        border-radius: 12px;
        box-shadow: 0 6px 20px rgba(20, 30, 60, 0.06);
    }

    :root:not([data-theme="dark"]) .settings-group + .settings-group {
        margin-top: 12px;
        border-top: 1px solid var(--border-1);
    }

    :root:not([data-theme="dark"]) .settings-sub .glass.card {
        padding: 16px;
    }
}

/* How the day actually turned out, under a history row: the three
   readings of the day in one quiet line. */
.history-row-actuals {
    display: block;
    margin-top: 2px;
    font-size: 12px;
    color: var(--muted);
}

/* ---------- the day's high and low, under the date in the corner ----------
   Warm over cool, as the owner drew it. Their own tokens, so the pair can be
   tuned per theme without touching the accent everything else wears. */
:root {
    --temp-high: #ffae57;
    --temp-low: #4fb7c2;
}

:root[data-theme="light"] {
    --temp-high: #d2691e;
    --temp-low: #1f8a96;
}

@media (prefers-color-scheme: light) {
    :root:not([data-theme="dark"]) {
        --temp-high: #d2691e;
        --temp-low: #1f8a96;
    }
}

.today-temps {
    display: flex;
    flex-direction: column;
    gap: 2px;
    /* Set well below the date, as the owner drew it, so the pair reads as
       its own thing rather than a third line of the date. */
    margin-top: 30px;
    font-size: 17px;
    font-weight: 800;
    line-height: 1;
    letter-spacing: 0;
}

.today-temp-high {
    color: var(--temp-high);
}

.today-temp-low {
    color: var(--temp-low);
}

/* On a phone, stacked as on a wide card - the owner's mockup - with a
   smaller gap under the date so the corner does not grow too tall. The
   place name comes down to sit beside the pair, clear of it sideways. */
@media (max-width: 480px) {
    .today-temps {
        margin-top: 14px;
        font-size: 15px;
    }

    .today-card.has-temps .today-location {
        padding-top: 108px;
    }

    /* Another day's corner has no "Today" word above the date, so it is a
       line shorter and the name needs less room under it. */
    .today-card.has-temps .today-date-badge:not(.is-today) + .today-location {
        padding-top: 96px;
    }
}

/* Under a long admin table: where you are, Previous and Next, and how many
   rows a page holds. Links, since each page is its own address. */
.grid-pager {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 6px;
    margin-top: 14px;
}

.grid-pager-range {
    margin-right: auto;
    font-size: 13px;
    color: var(--muted);
}

.grid-pager-sizes {
    display: inline-flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 6px;
    margin-left: 12px;
}

.grid-pager-label {
    margin: 0 4px;
    font-size: 13px;
    color: var(--muted);
}

.grid-pager-btn {
    display: inline-block;
    min-width: 44px;
    padding: 6px 12px;
    border: 1px solid var(--border-2);
    border-radius: 999px;
    background: transparent;
    color: var(--text);
    font: inherit;
    font-size: 13px;
    font-weight: 600;
    text-align: center;
    text-decoration: none;
    cursor: pointer;
}

a.grid-pager-btn:hover {
    border-color: var(--accent);
}

.grid-pager-btn[aria-pressed="true"],
.grid-pager-btn[aria-current="true"] {
    background: rgba(255, 174, 87, 0.16);
    border-color: transparent;
    color: var(--accent);
}

.grid-pager-btn.is-disabled {
    opacity: 0.4;
    cursor: default;
}

.grid-pager-btn:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

/* A column heading that sorts by going to the sorted page. */
.grid-sort-link {
    color: inherit;
    text-decoration: none;
}

@media (max-width: 640px) {
    .grid-pager-range {
        flex-basis: 100%;
    }

    .grid-pager-sizes {
        margin-left: 0;
    }
}

/* The events on a day in history (/sky): one per line, the detail under. */
.sky-history-events {
    margin: 10px 0 0;
    padding: 0;
    list-style: none;
}

.sky-history-events li {
    padding: 10px 0;
    border-top: 1px solid var(--border-2);
    line-height: 1.45;
}

.sky-history-events li:first-child {
    border-top: 0;
}

/* ---- Traffic: the chart over time, and long tables a page at a time ---- */

.traffic-chart-wrap {
    margin-top: 12px;
    padding: 0 0 22px 44px;
}

.traffic-plot {
    position: relative;
    height: 200px;
}

.traffic-chart {
    display: block;
    width: 100%;
    height: 100%;
    overflow: visible;
}

.traffic-chart .traffic-col {
    fill: var(--event-accent, var(--accent));
    opacity: 0.55;
}

.traffic-chart g:hover .traffic-col {
    opacity: 0.9;
}

.traffic-chart .traffic-hit {
    fill: transparent;
}

.traffic-chart .traffic-line {
    fill: none;
    stroke: var(--tl-blue-text);
    stroke-width: 2;
    stroke-linejoin: round;
    vector-effect: non-scaling-stroke;
    pointer-events: none;
}

.traffic-chart .traffic-grid {
    stroke: var(--panel-border);
    stroke-width: 1;
    vector-effect: non-scaling-stroke;
}

/* The numbers and dates, laid over the stretched plot as text. */
.traffic-axis {
    position: absolute;
    font-size: 11px;
    color: var(--muted);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
    pointer-events: none;
}

.traffic-axis-y {
    right: calc(100% + 6px);
    transform: translateY(-50%);
}

.traffic-axis-x {
    top: calc(100% + 6px);
    transform: translateX(-50%);
}

/* The two swatches in the chart's caption. */
.traffic-key {
    display: inline-block;
    width: 10px;
    height: 10px;
    border-radius: 2px;
    margin: 0 4px 0 2px;
    vertical-align: -1px;
}

.traffic-key-views {
    background: var(--event-accent, var(--accent));
    opacity: 0.7;
}

.traffic-key-visitors {
    height: 3px;
    vertical-align: 3px;
    background: var(--tl-blue-text);
}

.traffic-pager {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    margin-top: 10px;
    font-size: 13px;
    color: var(--muted);
}

.traffic-page-btn {
    padding: 5px 12px;
    border-radius: 999px;
    border: 1px solid var(--panel-border);
    background: transparent;
    color: var(--text);
    font: inherit;
    cursor: pointer;
}

.traffic-page-btn:disabled {
    opacity: 0.4;
    cursor: default;
}

.traffic-page-label {
    font-variant-numeric: tabular-nums;
}

/* ---- API usage over time: Open-Meteo's day by service, other sources as lines ---- */

.usage-seg { opacity: 0.8; }
.usage-seg-forecast { fill: var(--accent); background: var(--accent); }
.usage-seg-elevation { fill: #2bb3a3; background: #2bb3a3; }
.usage-seg-archive { fill: #9b7bea; background: #9b7bea; }
.usage-seg-geocoding { fill: var(--tl-blue-text); background: var(--tl-blue-text); }
.usage-seg-timezone { fill: var(--tl-moon); background: var(--tl-moon); }
.usage-seg-other { fill: var(--muted); background: var(--muted); }

.traffic-chart g.is-today .usage-seg { opacity: 0.45; }
.traffic-chart g:hover .usage-seg { opacity: 1; }

.usage-line {
    fill: none;
    stroke-width: 2;
    stroke-linejoin: round;
    vector-effect: non-scaling-stroke;
    pointer-events: none;
}
.usage-line-0 { stroke: var(--positive); background: var(--positive); }
.usage-line-1 { stroke: var(--negative); background: var(--negative); }
.usage-line-2 { stroke: var(--tl-now); background: var(--tl-now); }

/* Where the site stops asking Open-Meteo for forecasts. */
.usage-gate {
    stroke: var(--negative);
    stroke-width: 1;
    stroke-dasharray: 6 4;
    vector-effect: non-scaling-stroke;
}

.usage-key {
    display: flex;
    flex-wrap: wrap;
    gap: 6px 14px;
    margin: 4px 0 0;
    font-size: 12px;
    color: var(--muted);
}

.usage-swatch {
    display: inline-block;
    width: 10px;
    height: 10px;
    border-radius: 2px;
    margin-right: 5px;
    vertical-align: -1px;
}

.usage-swatch-line {
    height: 3px;
    vertical-align: 3px;
}

.usage-swatch-gate {
    height: 0;
    border-top: 2px dashed var(--negative);
    border-radius: 0;
    vertical-align: 3px;
}

/* ---- API usage: one section per API ---- */

.api-card h2 {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 4px 10px;
}

.api-stats {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
    gap: 10px 16px;
    margin: 14px 0 4px;
}

.api-stat-label {
    display: block;
    font-size: 11px;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--muted);
    font-weight: 600;
}

.api-stat-value {
    display: block;
    font-size: 20px;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
}

.api-stat-small {
    font-size: 14px;
    font-weight: 600;
}

.api-stat-note {
    display: block;
    font-size: 11px;
    color: var(--muted);
}

.usage-plot {
    height: 160px;
}

.api-empty {
    margin: 12px 0;
}

.api-source {
    border-top: 1px solid var(--panel-border);
    margin-top: 14px;
    padding-top: 12px;
}

.api-source.is-off {
    opacity: 0.6;
}

.api-source-head {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 4px 12px;
}

.api-source-head .field-note {
    margin: 0;
    flex: 1 1 200px;
}

.api-source p {
    margin: 6px 0 0;
}

.usage-seg-ok { fill: var(--accent); background: var(--accent); }
.usage-seg-unchanged { fill: var(--tl-blue-text); background: var(--tl-blue-text); }
.usage-seg-failed,
.usage-seg-refused { fill: var(--negative); background: var(--negative); }

/* The provider's daily limit, solid; the site's own stop is the dashed one. */
.usage-limit {
    stroke: var(--text);
    stroke-width: 1;
    opacity: 0.6;
    vector-effect: non-scaling-stroke;
}

.usage-swatch-limit {
    height: 0;
    border-top: 2px solid var(--text);
    border-radius: 0;
    opacity: 0.6;
    vector-effect: non-scaling-stroke;
    vertical-align: 3px;
}

/* ---- API usage: choosing the weather source ---- */

.weather-source-form {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: 12px 18px;
    margin-top: 12px;
}

.weather-source-form label {
    display: flex;
    flex-direction: column;
    gap: 4px;
}

.weather-source-form select {
    min-width: 180px;
}

.weather-source-notes {
    margin: 12px 0 0;
    padding-left: 18px;
    font-size: 13px;
    color: var(--muted);
}

.weather-source-notes li {
    margin: 4px 0;
}

/* ---- The dark sky map ---- */

.dark-map {
    position: relative;
    height: 420px;
    overflow: hidden;
    border-radius: var(--radius-md);
    background: #0b1a3a;
    touch-action: none;
    cursor: grab;
}

.dark-map:active {
    cursor: grabbing;
}

.dark-map-tiles,
.dark-map-tile,
.dark-map-overlay {
    position: absolute;
    left: 0;
    top: 0;
}

.dark-map-tiles {
    inset: 0;
}

.dark-map-tile {
    width: 256px;
    height: 256px;
    user-select: none;
    -webkit-user-drag: none;
}

.dark-map-overlay {
    image-rendering: auto;
    pointer-events: none;
}

.dark-map-marker {
    position: absolute;
    transform: translate(-50%, -50%);
    pointer-events: none;
    display: grid;
    place-items: center;
    font-size: 11px;
    font-weight: 700;
}

.dark-map-here {
    width: 14px;
    height: 14px;
    border-radius: 50%;
    background: #fff;
    border: 3px solid var(--accent);
    box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.4);
}

.dark-map-dark {
    width: 22px;
    height: 22px;
    border-radius: 50%;
    background: #fff;
    color: #0b1a3a;
    box-shadow: 0 0 0 2px #0b1a3a;
}

.dark-map-zoom {
    position: absolute;
    right: 10px;
    top: 10px;
    display: flex;
    flex-direction: column;
    gap: 6px;
    z-index: 2;
}

.dark-map-zoom button {
    width: 34px;
    height: 34px;
    border-radius: 8px;
    border: 1px solid var(--panel-border);
    background: var(--panel);
    color: var(--text);
    font-size: 18px;
    cursor: pointer;
}

.dark-map-credit {
    position: absolute;
    right: 6px;
    bottom: 4px;
    margin: 0;
    padding: 1px 6px;
    font-size: 11px;
    background: rgba(255, 255, 255, 0.8);
    color: #222;
    border-radius: 4px;
    z-index: 2;
}

.dark-map-credit a {
    color: inherit;
}

.dark-map-legend {
    display: flex;
    flex-wrap: wrap;
    gap: 6px 14px;
    margin: 12px 0 0;
    padding: 0;
    list-style: none;
    font-size: 12px;
    color: var(--muted);
}

.dark-map-legend i {
    display: inline-block;
    width: 12px;
    height: 12px;
    border-radius: 3px;
    margin-right: 5px;
    vertical-align: -1px;
}

.dark-map-spots {
    list-style: none;
    margin: 10px 0 0;
    padding: 0;
}

.dark-map-spots li {
    display: flex;
    gap: 12px;
    padding: 10px 0;
    border-top: 1px solid var(--panel-border);
}

.dark-map-spot-num {
    flex: none;
    width: 24px;
    height: 24px;
    border-radius: 50%;
    display: grid;
    place-items: center;
    background: #fff;
    color: #0b1a3a;
    font-size: 12px;
    font-weight: 700;
    box-shadow: 0 0 0 2px #0b1a3a;
}

.dark-map-spot-links {
    display: flex;
    gap: 14px;
    margin-top: 4px;
    font-size: 13px;
}

.event-actions {
    margin: 12px 0 0;
}

/* The map under the card's corner buttons, for a page with one (the Milky
   Way's dark sky map): in the theme's accent - orange in the dark, teal in
   the light. The corner holds the cluster and the map in a column, so the
   cluster keeps its own layout and the map sits under it at the right. */
.today-card-corner {
    position: absolute;
    top: 12px;
    right: 12px;
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 6px;
    z-index: 2;
}

.today-card-corner > .today-card-controls {
    position: static;
}

.today-card-map {
    display: grid;
    place-items: center;
    width: 40px;
    height: 40px;
    color: var(--event-accent, var(--accent));
    text-decoration: none;
}

.today-card-map[hidden] {
    display: none;
}

.today-card-map:hover {
    opacity: 0.8;
}

/* ---- The dark sky map as the map picker: its darkness tiles over the
   squares, the darkness under the cross, and the legend ---- */

.map-pick-overlay {
    position: absolute;
    inset: 0;
    pointer-events: none;
}

.map-overlay-tile {
    position: absolute;
    left: 0;
    top: 0;
    user-select: none;
    -webkit-user-drag: none;
    image-rendering: auto;
}

/* Coordinates and, while the dark sky is on, its reading - one line, so the
   bar stays the height of its buttons; it wraps only where there is no room. */
.map-pick-sub {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 2px 10px;
    min-width: 0;
}

.map-pick-dark {
    font-size: 13px;
    font-weight: 600;
    color: var(--event-accent, var(--accent));
}

.map-pick-legend {
    display: flex;
    flex-wrap: wrap;
    gap: 4px 12px;
    margin: 0;
    padding: 0;
    list-style: none;
    font-size: 11px;
    color: var(--muted);
}

.map-pick-legend i {
    display: inline-block;
    width: 12px;
    height: 12px;
    border-radius: 2px;
    margin-right: 4px;
    vertical-align: -2px;
}

/* A legend swatch as the map shows it: OpenStreetMap's land colour, dimmed
   in the dark exactly as the squares are, with the class's colour over it
   at the opacity the overlay is drawn at. */
.map-swatch {
    position: relative;
    overflow: hidden;
}

.map-swatch::before {
    content: "";
    position: absolute;
    inset: 0;
    background: #f2efe9;
}

.map-swatch b {
    position: absolute;
    inset: 0;
}

:root[data-theme="dark"] .map-swatch::before {
    filter: brightness(0.82) saturate(0.85);
}

@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) .map-swatch::before {
        filter: brightness(0.82) saturate(0.85);
    }
}

/* The dark sky toggle above the picker's zoom: its stars dim while off, in
   full accent with an accent ring while the darkness is on the map. */
.map-pick-dark-btn svg {
    opacity: 0.5;
    transition: opacity 0.15s ease;
}

.map-pick-dark-btn.is-on {
    border-color: var(--accent);
    box-shadow: 0 0 0 1px var(--accent) inset;
}

.map-pick-dark-btn.is-on svg {
    opacity: 1;
}

.map-pick-overlay[hidden],
.map-pick-dark[hidden],
.map-pick-legend[hidden] {
    display: none;
}
