/* ─────────────────────────────────────────────────────────────────────────
   W GROUP — module + standalone-shell styles.
   Panel/SOS/marker rules EXTRACTED from weathergpx css/main.css, every rule
   prefixed .wgroup (panel container, overlay wrapper and marker elements all
   carry the class). The "design-system primitives" section is COPIED from
   weathergpx alien.css/main.css (same values) so the standalone shell needs
   no host CSS — embedded, the duplicates resolve to the identical look.
   .wgroup-shell rules are standalone-only chrome (index.html).
   ───────────────────────────────────────────────────────────────────────── */

/* ── Design-system primitives (copied from weathergpx, scoped) ──────────── */

.wgroup .hide { display: none !important; }

.wgroup .td-btn {
    display: flex;
    align-items: center;
    gap: 0.35rem;
    padding: 0.45rem 0.75rem;
    border-radius: 0.4rem;
    border: 1px solid var(--blue6);
    background: var(--blue7);
    color: var(--blue2);
    cursor: pointer;
    font-size: var(--font-xs);
    font-family: inherit;
    transition: background 120ms, color 120ms, border-color 120ms;
    white-space: nowrap;
}
.wgroup .td-btn:disabled { opacity: 0.35; cursor: default; }
.wgroup .td-btn:hover:not(:disabled) { background: var(--blue7); color: var(--color-primary-ultralight); border-color: var(--color-primary-ultralight); }

.wgroup .btn-secondary-hztal {
    font-size: var(--font-md);
    padding: 0.45rem 1rem;
    text-align: center;
    border: 1px solid var(--blue6);
    border-radius: 0.4rem;
    background: var(--blue7);
    color: var(--blue3);
    cursor: pointer;
    font-family: inherit;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.4rem;
    transition: background 120ms, color 120ms, border-color 120ms;
}
.wgroup .btn-secondary-hztal:hover { background: var(--blue7); color: var(--blue3); border-color: var(--color-primary-ultralight); }
.wgroup .btn-secondary-hztal:disabled { opacity: 0.35; cursor: default; }
.wgroup .btn-secondary-hztal .wg-ico { color: var(--color-secondary-dark); transition: color 120ms; }
.wgroup .btn-secondary-hztal:hover .wg-ico { color: var(--color-secondary); }

.wgroup .wg-ico { width: 1.4rem; height: 1.4rem; flex-shrink: 0; pointer-events: none; }

.wgroup .nav-opt-lbl {
    font-size: var(--font-xs);
    color: var(--blue3);
    text-align: center;
    padding: 0.3rem 0.1rem 0;
    line-height: 1.25;
}

.wgroup .toggle-label {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    cursor: pointer;
    font-size: var(--font-xs);
    color: var(--blue4);
    user-select: none;
    white-space: nowrap;
    transition: color 120ms;
}
.wgroup .toggle-label:has(input:checked) { color: var(--blue1); }
.wgroup .toggle-label:has(input:disabled) { opacity: 0.4; cursor: default; pointer-events: none; }
.wgroup .toggle-label input[type="checkbox"] { position: absolute; opacity: 0; width: 0; height: 0; }
.wgroup .toggle-check-box {
    display: inline-block;
    width: 36px;
    height: 22px;
    border-radius: 999px;
    background: var(--blue6);
    border: 1px solid var(--grey4T);
    box-sizing: border-box;
    position: relative;
    flex-shrink: 0;
    transition: background 200ms, border-color 200ms;
}
.wgroup .toggle-check-box::after {
    content: '';
    position: absolute;
    width: 16px;
    height: 16px;
    border-radius: 50%;
    background: var(--blue4-5);
    top: 2px;
    left: 2px;
    transition: transform 200ms, background 200ms;
    box-shadow: 0 1px 3px rgba(0,0,0,0.35);
}
/* EMBEDDED-ONLY BUG: weathergpx's alien.css prints the word "on"/"off" inside
   the switch with .toggle-check-box::before. Its switch is 62px wide and has
   room; ours is 36px with a 16px knob, i.e. 16px of free track, so the word
   lands under the knob and reads "ff" when off and "O" when on. group.css never
   declared a ::before, so nothing was overriding it. The module owns its own
   compact switch, so drop the word. Standalone never saw this (no alien.css).
   ⚠ TWO rules to beat, not one: alien.css also sets content:"on" under
   .toggle-label:has(input:checked) .toggle-check-box::before, which scores
   (0,2,1) and outranks a plain .wgroup .toggle-check-box::before at (0,2,0).
   Killing only the first leaves the CHECKED switch still showing "on". */
.wgroup .toggle-check-box::before,
.wgroup .toggle-label:has(input:checked) .toggle-check-box::before { content: none; }
.wgroup .toggle-label:not(:has(input:checked)):not(:has(input:disabled)):hover .toggle-check-box { background: var(--blue5); }
.wgroup .toggle-label:has(input:checked) .toggle-check-box { background: var(--color-primary-deep); border-color: var(--color-primary-lightT); }
.wgroup .toggle-label:has(input:checked) .toggle-check-box::after {
    background: var(--color-primary-bright);
    transform: translateX(15px);
}
.wgroup .td-btn:focus-visible,
.wgroup .toggle-label input:focus-visible ~ .toggle-check-box { outline: 2px solid var(--color-primary-ultralight); outline-offset: 2px; }

/* ── Group panel (extracted from weathergpx main.css) ───────────────────── */

.wgroup #group-options {
    display: grid;
    grid-template-columns: 1fr 1fr;
    align-items: center;
    gap: 0.15rem 0.5rem;
    margin-bottom: 0.5rem;
}
.wgroup #group-options .nav-opt-lbl {
    font-size: var(--font-sm);
    color: var(--blue3);
}
.wgroup #group-options .toggle-label { justify-self: center; }

/* SOS buttons */
.wgroup #btn-sos-group {
    width: 100%;
    background: var(--color-sos);
    color: var(--grey0);
    border: none;
    border-radius: 0.4rem;
    font-size: var(--font-md);
    font-weight: 700;
    letter-spacing: 0.08em;
    cursor: pointer;
    min-height: 44px;
    margin-top: 0.5rem;
    margin-bottom: 0.5rem;
}
.wgroup #btn-sos-group:hover { background: var(--color-sos-strong); }
.wgroup #btn-sos-group.sos-active { background: var(--color-sos-dark); }

/* SOS alert banner — position:fixed top, shown when another rider triggers SOS */
.wgroup #sos-alert {
    position: fixed;
    top: 0; left: 0; right: 0;
    z-index: 9999;
    background: var(--color-sos);
    color: var(--grey0);
    flex-direction: column;
    gap: 0.2rem;
    padding: 0.5rem 1rem;
}
.wgroup .sos-alert-item {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    cursor: pointer;
    font-size: var(--font-md);
    font-weight: 600;
    min-height: 36px;
}
.wgroup .sos-group-name { font-size: 0.8em; font-weight: 400; opacity: 0.8; }
.wgroup .sos-cancel-admin {
    margin-left: auto;
    background: rgba(0,0,0,0.25);
    color: #fff;
    border: none;
    border-radius: 50%;
    width: 24px; height: 24px;
    font-size: var(--font-md);
    line-height: 1;
    cursor: pointer;
    flex-shrink: 0;
    display: flex; align-items: center; justify-content: center;
}
.wgroup .sos-cancel-admin:hover { background: rgba(0,0,0,0.45); }
.wgroup .sos-alert-dot {
    width: 24px; height: 24px; border-radius: 50%;
    display: flex; align-items: center; justify-content: center;
    color: #fff; font-size: var(--font-sm); font-weight: 700;
    flex-shrink: 0;
    border: 2px solid rgba(255,255,255,0.7);
}

/* Help button on each banner row: the one control on the red strip that is not
   the row itself. White on red, so it reads as "the way out", not as more alarm.
   Opens the SOS emergency card below. */
.wgroup .sos-help-btn {
    margin-left: auto;
    display: inline-flex; align-items: center; gap: 0.3rem;
    background: var(--grey0);
    color: var(--color-sos-dark);
    border: none;
    border-radius: 999px;
    padding: 0.3rem 0.7rem;
    font-size: var(--font-sm);
    font-weight: 700;
    line-height: 1;
    cursor: pointer;
    flex-shrink: 0;
}
.wgroup .sos-help-btn svg { width: 14px; height: 14px; }
.wgroup .sos-help-btn + .sos-cancel-admin { margin-left: 0; }

/* SOS emergency card: position in big type + Copy, then the host's emergency
   block (.emg-* rules above apply as-is). Same skin as the confirm dialog. */
.wgroup #sos-help-dialog {
    border: none; border-radius: 0.75rem; padding: 1.25rem;
    width: min(92vw, 340px); box-sizing: border-box;
    background: var(--blue8); color: var(--blue1);
}
.wgroup #sos-help-dialog::backdrop { background: rgba(0,0,0,0.55); }
.wgroup #sos-help-dialog:not([open]) { display: none; }
.wgroup .sos-help-head { display: flex; align-items: center; gap: 0.5rem; font-size: var(--font-md); }
.wgroup .sos-help-lbl { margin: 0.9rem 0 0.25rem; font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.06em; opacity: 0.65; }
.wgroup .sos-help-pos {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 1.4rem; font-weight: 700; letter-spacing: 0.02em;
    user-select: all; -webkit-user-select: all;
    word-break: break-all;
}
.wgroup #btn-sos-help-copy { margin-top: 0.5rem; }
.wgroup #btn-sos-help-copy.is-copied { background: var(--color-success); color: var(--color-success-content); border-color: var(--color-success-bright); }
.wgroup .sos-help-note { font-size: var(--font-sm); opacity: 0.8; margin: 0.9rem 0 0.8rem; }
.wgroup #btn-sos-help-close { display: block; margin-left: auto; }

/* SOS confirm dialog */
.wgroup #sos-confirm-dialog { border: none; border-radius: 0.75rem; padding: 1.5rem; max-width: 260px; text-align: center; background: var(--blue8); color: var(--blue1); }
.wgroup #sos-confirm-dialog[open] { display: flex; flex-direction: column; align-items: center; }
.wgroup #sos-confirm-dialog::backdrop { background: rgba(0,0,0,0.55); }
.wgroup #sos-confirm-dialog:not([open]) { display: none; }
.wgroup .sos-confirm-text { font-size: var(--font-lg); font-weight: 600; margin: 0 0 1.2rem; }
.wgroup .sos-confirm-btns { display: flex; gap: 0.5rem; justify-content: center; }
.wgroup #btn-sos-go { background: var(--color-sos); color: var(--grey0); border: none; border-radius: 0.4rem; font-weight: 700; font-size: var(--font-md); letter-spacing: 0.06em; padding: 0.5rem 1.5rem; min-height: 44px; cursor: pointer; }
.wgroup #btn-sos-go:hover { background: var(--color-sos-strong); }

/* Admin delete. The panel button stays quiet — outlined, muted, sitting last —
   because it must not compete with Leave, which riders tap all the time. The
   commitment colour appears only on the confirm button inside the modal, where
   there is nothing else to mistake it for. */
/* Centred rather than full width: everything else in this section is a
   full-width row, so a left-hugging button reads as a layout slip instead of
   as restraint. Centred and small reads as deliberate — a lesser action. */
/* The identity line on the join-choice screen. Looks like a quiet info row,
   behaves like a button, because tapping it must lead somewhere (setup). */
.wgroup #group-choice-identity {
    display: flex; align-items: center; gap: 0.5rem; width: 100%;
    margin-top: 0.6rem; padding: 0.45rem 0.6rem;
    background: transparent; border: 1px dashed var(--blue6); border-radius: 0.5rem;
    color: var(--blue2); font-size: var(--font-sm); cursor: pointer; text-align: left;
}
.wgroup #group-choice-identity:hover { border-color: var(--color-primary); }
.wgroup #group-choice-identity .rider-dot { width: 24px; height: 24px; font-size: var(--font-xs); flex: 0 0 auto; }
.wgroup #group-choice-identity u { margin-left: auto; opacity: 0.8; }

.wgroup #btn-group-delete { display: block; margin: 0.5rem auto 0; color: var(--color-sos); border-color: var(--color-sos); opacity: 0.75; }
.wgroup #btn-group-delete:hover { opacity: 1; }
.wgroup #delete-confirm-dialog { border: none; border-radius: 0.75rem; padding: 1.5rem; max-width: 280px; text-align: center; background: var(--blue8); color: var(--blue1); }
.wgroup #delete-confirm-dialog[open] { display: flex; flex-direction: column; align-items: center; }
.wgroup #delete-confirm-dialog::backdrop { background: rgba(0,0,0,0.55); }
.wgroup #delete-confirm-dialog:not([open]) { display: none; }
.wgroup .group-delete-note { font-size: var(--font-sm); opacity: 0.8; margin: 0 0 1.2rem; }
.wgroup #btn-delete-go { background: var(--color-sos); color: var(--grey0); border: none; border-radius: 0.4rem; font-weight: 700; font-size: var(--font-md); padding: 0.5rem 1.2rem; min-height: 44px; cursor: pointer; }
.wgroup #btn-delete-go:hover { background: var(--color-sos-strong); }
.wgroup #btn-delete-go[disabled] { opacity: 0.5; cursor: default; }

/* Pulsing SOS ring on rider map marker */
@keyframes wgroup-sos-pulse {
    0%   { box-shadow: 0 0 0 0   var(--color-sos-90); }
    70%  { box-shadow: 0 0 0 10px var(--color-sos-0);  }
    100% { box-shadow: 0 0 0 0   var(--color-sos-0);   }
}
.wgroup .rider-dot.rider-sos { animation: wgroup-sos-pulse 1.2s ease-out infinite; }

.wgroup .group-join-btns { display: flex; gap: 0.5rem; justify-content: center; }
.wgroup .group-join-btns .btn-secondary-hztal { flex: 1 1 0; white-space: normal; line-height: 1.25; }

.wgroup #group-paste-area { display: flex; flex-direction: column; gap: 0.5rem; margin-top: 0.5rem; }
.wgroup .group-paste-btns { display: flex; gap: 0.5rem; }
.wgroup .group-paste-btns .td-btn,
.wgroup .group-paste-btns .group-btn-primary { flex: 1; min-height: 36px; text-align: center; justify-content: center; }

/* the member-panel hint about background behaviour: quiet, not an error */
.wgroup .group-screen-hint { font-size: var(--font-xs); color: var(--blue3); text-align: center; margin: 0.35rem 0 0; line-height: 1.3; }
.wgroup .group-msg { font-size: var(--font-xs); color: var(--color-error-content); text-align: center; margin: 0.4rem 0 0; }

/* Group inputs — consistent focus style, no browser outline overflow */
.wgroup #group-paste-input,
.wgroup #group-rename-input,
.wgroup #group-voice-input,
.wgroup #group-tracker-input,
.wgroup #group-tracker-pw,
.wgroup #group-add-follow-input {
    width: 100%; box-sizing: border-box; min-height: 44px; padding: 0.5rem 0.6rem;
    background: var(--blue7); border: 1px solid var(--blue6); border-radius: 0.4rem;
    color: var(--blue2); font-size: var(--font-sm); outline: none; display: block;
    font-family: inherit;
}
.wgroup #group-paste-input:focus,
.wgroup #group-rename-input:focus,
.wgroup #group-voice-input:focus,
.wgroup #group-tracker-input:focus,
.wgroup #group-tracker-pw:focus,
.wgroup #group-add-follow-input:focus { border-color: var(--color-primary-light); }

/* Member / follower choice */
.wgroup #group-choice { display: flex; flex-direction: column; gap: 0.5rem; margin-bottom: 0.5rem; }
.wgroup .group-choice-btns { display: flex; flex-direction: column; gap: 0.4rem; }
.wgroup .group-choice-btns button { min-height: 40px; text-align: center; justify-content: center; }
.wgroup .group-choice-cancel { min-height: 36px; font-size: var(--font-sm); text-align: center; justify-content: center; }

/* Sub-titles: MEMBER / WATCH */
.wgroup .group-sub-title {
    font-size: var(--font-xs); font-weight: 700; text-transform: uppercase;
    letter-spacing: 0.07em; color: var(--blue4);
    margin: 0.8rem 0 0.3rem; padding: 0;
}
.wgroup .group-watch-title { margin-top: 1rem; }

/* Name rows (group name + rename btn / watch row) */
.wgroup .group-name-row {
    display: flex; align-items: center; gap: 0.5rem;
    padding: 0.45rem 0.5rem 0.45rem 0.7rem;
    background: var(--blue7); border: 1px solid var(--blue6);
    border-left: 3px solid transparent;
    border-radius: 0.4rem; margin-bottom: 0.3rem;
    cursor: pointer; user-select: none;
}
.wgroup .group-name-row:hover { background: var(--blue6); }
.wgroup .group-name-row.active { border-left-color: var(--color-primary-light); }
.wgroup .group-view-name { flex: 1; font-size: var(--font-md); font-weight: 600; color: var(--blue1); }
.wgroup .group-view-count { font-size: var(--font-xs); color: var(--blue4); flex-shrink: 0; }
.wgroup .group-followed-unfollow {
    min-height: 28px; min-width: 28px; padding: 0 6px; font-size: var(--font-md); line-height: 1;
    text-align: center; justify-content: center; flex-shrink: 0;
}
/* Voice for a group you only watch. Same footprint as the × beside it so the row
   keeps its shape whether or not that group has a room, but tinted like the
   member Voice button — it does the same thing, it is not a row control. */
.wgroup .group-followed-voice {
    display: flex; align-items: center; justify-content: center;
    min-height: 28px; min-width: 28px; padding: 0 6px; flex-shrink: 0;
    color: var(--color-primary-light);
}
.wgroup .group-followed-voice:hover { color: var(--color-primary); }
.wgroup .group-followed-voice .wg-ico { width: 16px; height: 16px; }

/* Admin rename "…" button */
.wgroup .group-rename-btn {
    background: none; border: none; cursor: pointer; padding: 0 2px;
    color: var(--blue4); font-size: var(--font-md); line-height: 1; flex-shrink: 0;
}
.wgroup .group-rename-btn:hover { color: var(--color-primary-light); }

/* Rename inline area */
.wgroup #group-rename-area { display: flex; flex-direction: column; gap: 0.5rem; margin-bottom: 0.4rem; }
.wgroup #group-rename-input { min-height: 40px; }

/* Add followed group */
.wgroup .group-add-follow-btn { width: 100%; min-height: 36px; margin-top: 0.4rem; font-size: var(--font-sm); text-align: center; justify-content: center; }
.wgroup #group-add-follow-area { display: flex; flex-direction: column; gap: 0.5rem; margin-top: 0.4rem; }

/* Identity setup */
.wgroup .group-setup-identity {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin-bottom: 0.6rem;
}
.wgroup #group-name-input {
    flex: 1;
    min-width: 0; /* inputs refuse to shrink below intrinsic width otherwise */
    min-height: 36px;
    padding: 0 0.5rem;
    background: var(--blue7);
    border: 1px solid var(--blue6);
    border-radius: 0.3rem;
    color: var(--blue1);
    font-size: var(--font-sm);
    box-sizing: border-box;
    position: relative;
    font-family: inherit;
}
.wgroup #group-name-input:focus {
    outline: none;
    border-color: var(--color-primary-light);
    z-index: 1;
}
.wgroup input[type="color"]#group-color-input {
    width: 36px; height: 36px; flex-shrink: 0;
    border: 1px solid var(--blue6); border-radius: 0.3rem;
    background: none; cursor: pointer; padding: 2px;
}
.wgroup .group-setup-btns {
    display: flex; gap: 0.5rem; margin-top: 0.4rem;
}
/* "With voice" row in the create form — same label/toggle pairing as #group-options */
.wgroup #group-setup-voice {
    display: flex; align-items: center; justify-content: space-between;
    gap: 0.5rem; margin-top: 0.6rem;
}
.wgroup #group-setup-voice .nav-opt-lbl { font-size: var(--font-sm); color: var(--blue3); }
.wgroup .group-btn-primary {
    flex: 1; min-height: 36px;
    background: var(--color-primary-light); border: none;
    border-radius: 0.4rem; color: #fff;
    font-size: var(--font-sm); font-weight: 600; cursor: pointer;
    text-align: center;
    font-family: inherit;
    transition: background 120ms;
}
.wgroup .group-btn-primary:hover { background: var(--color-primary); }
.wgroup .group-setup-btns .td-btn { flex: 1; min-height: 36px; text-align: center; justify-content: center; }

/* Active group */
.wgroup #group-members-list { margin-bottom: 0.5rem; }
.wgroup .group-member-item {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.3rem 0;
    font-size: var(--font-sm);
    color: var(--blue2);
}
.wgroup .group-member-me { opacity: 0.7; cursor: pointer; }
.wgroup .group-member-me:hover { opacity: 1; }
.wgroup .group-member-edit-ico { font-size: var(--font-xs); opacity: 0.45; margin-left: auto; }
.wgroup .group-member-dot {
    width: 24px; height: 24px; border-radius: 50%;
    display: flex; align-items: center; justify-content: center;
    color: #fff; font-size: var(--font-sm); font-weight: 700;
    flex-shrink: 0;
    box-shadow: 0 1px 3px rgba(0,0,0,0.25);
}
.wgroup .group-member-info { flex: 1; display: flex; flex-direction: column; }
.wgroup .group-member-name em { font-style: normal; opacity: 0.5; font-size: var(--font-xs); }
.wgroup .group-member-speed { font-size: var(--font-xs); color: var(--blue4); }
.wgroup .group-active-btns { display: flex; gap: 0.5rem; margin-bottom: 0.5rem; }
.wgroup .group-active-btns .td-btn { flex: 1; min-height: 36px; font-size: var(--font-sm); text-align: center; justify-content: center; }

/* Voice (Discord L0) — an action, so rounded; full width because it is the one
   thing a member does here besides SOS. The edit row below is admin-only. */
.wgroup .group-btn-voice {
    display: flex; align-items: center; justify-content: center; gap: 0.4rem;
    width: 100%; min-height: 40px; margin-bottom: 0.5rem;
    background: var(--color-primary-light); border: none;
    border-radius: 999px; color: #fff;
    font-size: var(--font-sm); font-weight: 600; font-family: inherit;
    cursor: pointer; transition: background 120ms;
}
.wgroup .group-btn-voice:hover { background: var(--color-primary); }
.wgroup .group-btn-voice .wg-ico { width: 18px; height: 18px; }
.wgroup .group-voice-edit { width: 100%; min-height: 32px; font-size: var(--font-xs); text-align: center; justify-content: center; margin-bottom: 0.5rem; }
.wgroup #group-voice-area { display: flex; flex-direction: column; gap: 0.5rem; margin-bottom: 0.5rem; }
.wgroup #group-tracker-area { display: flex; flex-direction: column; gap: 0.5rem; margin-bottom: 0.5rem; }
.wgroup .group-tracker-help { margin: 0; font-size: var(--font-xs); color: var(--blue3); line-height: 1.35; }
.wgroup #btn-group-tracker.is-linked { border-color: var(--color-success-bright); color: var(--color-success-content); }
/* Satellite fix badges: on the dot (under it, next to the age) and in the members list */
.wgroup .rider-sat, .wgroup .group-member-sat {
    display: inline-block; font-size: 9px; line-height: 1; font-weight: 700; letter-spacing: 0.04em;
    text-transform: uppercase; padding: 2px 4px; border-radius: 3px;
    background: var(--blue2); color: var(--blue8); vertical-align: middle;
}
.wgroup .rider-sat { position: absolute; top: -7px; right: -10px; }
.wgroup .group-member-sat { margin-left: 0.35rem; }
/* Help link under the paste field: quiet, it must not compete with Save. */
.wgroup .group-voice-help { font-size: var(--font-xs); color: var(--blue3); text-decoration: underline; align-self: flex-start; margin-top: -0.15rem; }
.wgroup .group-voice-help:hover { color: var(--color-primary); }

/* Map rider markers (marker element itself carries .wgroup).
   The z-index below is only ever compared against other things INSIDE the map
   container — #map is a stacking context in both faces (see `.wgroup-shell
   #map`, and the host's own `#map`). Do not read 3000 as "above the panel": if
   it ever behaves that way again, the map lost its stacking context. */
.wgroup.rider-marker {
    display: flex;
    flex-direction: column;
    align-items: center;
    pointer-events: none;
    z-index: 3000;
}
.wgroup .rider-dot {
    width: 32px; height: 32px;
    border-radius: 50%;
    display: flex; align-items: center; justify-content: center;
    color: #fff;
    font-size: var(--font-lg);
    font-weight: 700;
    border: 2px solid rgba(255,255,255,0.85);
    box-shadow: 0 2px 6px rgba(0,0,0,0.35);
}
.wgroup #group-identity-preview.rider-dot {
    width: 40px; height: 40px;
    font-size: var(--font-xxl);
    flex-shrink: 0;
    cursor: text;
    user-select: text;
}
.wgroup #group-identity-preview.rider-dot:focus {
    outline: 2px solid rgba(255,255,255,0.6);
    outline-offset: 2px;
}
.wgroup #group-identity-preview.rider-dot:empty::before {
    content: '?';
    opacity: 0.45;
}

/* ── Dot size vs map zoom — three steps ───────────────────────────────────────
   The dots are fixed-pixel ON PURPOSE: a rider pin is an identity marker, not a
   real-world extent, so it stays legible and thumb-sized at riding zoom. What
   fixed-pixel does badly is the zoomed-out view, where a dozen riders a few km
   apart become overlapping blobs that hide the map underneath. Hence steps, not
   continuous scaling — scaling proportionally would shrink the letters exactly
   when riders bunch up, which is backwards.

   The classes sit on the MAP CONTAINER (set by _zoom_class_apply in group.js),
   so one class write restyles every dot. #group-identity-preview lives in the
   panel, not in the map, so it never matches these. */
.wgroup-zoom-mid .wgroup .rider-dot {
    width: 24px; height: 24px;
    font-size: var(--font-sm);
    border-width: 1.5px;
}
.wgroup-zoom-far .wgroup .rider-dot {
    width: 16px; height: 16px;
    font-size: 0;                 /* one letter in 16px is mush — drop it */
    border-width: 1.5px;
}
/* An age pill under a 16px dot is bigger than the dot. Same reasoning as the
   letter: at country zoom the dot answers "where", not "who" or "how old".
   An SOS marker is the exception, exactly as in rider_update_marker(): the age
   is what tells you how old the last known position of someone in trouble is,
   so it survives every zoom step. If :has() is unsupported the whole rule drops
   and every age label shows — the pre-existing behaviour, not a broken one. */
.wgroup-zoom-far .wgroup:not(:has(.rider-sos)) .rider-age { display: none; }

/* SOS never shrinks. Same rule as .rider-old above skipping SOS dots: someone in
   trouble must stay loud at every zoom, including the one you zoom out to when
   you are working out how far away they are. */
.wgroup-zoom-mid .wgroup .rider-dot.rider-sos,
.wgroup-zoom-far .wgroup .rider-dot.rider-sos {
    width: 32px; height: 32px;
    font-size: var(--font-lg);
    border-width: 2px;
}

/* ── Online-required policy: stale dots + reconnect banner + mini toast ── */

/* Positions freeze while offline — dim so nobody rides to a stale dot */
.wgroup .rider-dot.rider-stale { filter: grayscale(60%); opacity: 0.55; }

/* ── Parked riders: last known position, kept on the map ──────────────────────
   .rider-old is PER RIDER (their own fix is old); .rider-stale above is GLOBAL
   (my connection is down). Deliberately two classes: _stale_set() toggles the
   global one wholesale, which would otherwise wipe per-rider ageing on every
   reconnect. They stack harmlessly if both apply. */
.wgroup .rider-dot.rider-old { filter: grayscale(70%); opacity: 0.45; }

/* Age chip under the dot. The marker is already a centred flex column, so this
   just flows underneath it. Dark pill so it stays readable over any map. */
.wgroup .rider-age {
    margin-top: 2px;
    padding: 1px 5px;
    border-radius: 0.6rem;
    background: rgba(0,0,0,0.6);
    color: #fff;
    font-size: 0.65rem;
    font-weight: 700;
    line-height: 1.4;
    white-space: nowrap;
    pointer-events: none;
}
.wgroup .rider-age.hide { display: none; }

/* Same story in the members list */
.wgroup .group-member-stale .group-member-dot { filter: grayscale(70%); opacity: 0.45; }
.wgroup .group-member-stale .group-member-name { opacity: 0.6; }
/* Past RIDER_DROP_S the dot is off the map, so this row is the ONLY remaining
   trace of that rider. Fade it further than "parked" to say so, but never hide
   it — an unaccounted-for rider is exactly what this list is for. */
.wgroup .group-member-gone { opacity: 0.55; }
.wgroup .group-member-gone .group-member-age { font-style: italic; }
/* Mirrors .group-member-speed exactly (it replaces it), so a stale row keeps the
   same shape as a live one — no margin-left:auto, the speed does not use it. */
.wgroup .group-member-age {
    font-size: var(--font-xs);
    color: var(--blue4);
    white-space: nowrap;
}

/* Geolocation refused. Same pill shape and the SAME warning colour as the
   offline pill — deliberately NOT red: red is SOS here, and a red pill at the
   top of the screen would be read as an SOS alert.
   Both pills can be true at once (no signal AND no location), so this one drops
   below the offline pill when that one is showing rather than covering it. */
.wgroup #wgroup-offline,
.wgroup #wgroup-geo-denied {
    position: fixed;
    top: 3.4rem; left: 50%;
    transform: translateX(-50%);
    z-index: 2500;
    background: var(--color-warning-bright);
    color: var(--grey0);
    font-size: var(--font-sm);
    font-weight: 600;
    padding: 0.4rem 0.9rem;
    border-radius: 999px;
    box-shadow: 0 2px 8px rgba(0,0,0,0.35);
    white-space: nowrap;
}

/* Fallback toast (hosts with their own toast never show this) */
.wgroup #wgroup-toast {
    position: fixed;
    bottom: 1.5rem; left: 50%;
    transform: translateX(-50%);
    z-index: 2600;
    background: var(--blue7);
    color: var(--blue1);
    border: 1px solid var(--blue6);
    font-size: var(--font-sm);
    padding: 0.5rem 1rem;
    border-radius: 0.5rem;
    box-shadow: 0 2px 10px rgba(0,0,0,0.4);
    white-space: nowrap;
}

/* ── Standalone shell chrome (index.html only) ──────────────────────────── */

/* One source of truth for the bar. Everything that has to sit clear of it —
   both map stacks, the toast — offsets from --wg-bar-space, so the bar's height
   and the iPhone home-indicator inset are never re-typed.
   ⚠ Declared on BODY, not on .wgroup-shell, because the toast is one of the
   things that offsets from it and the overlays are appended to body as a
   SIBLING of the shell — on .wgroup-shell the variable simply does not reach
   them, and calc() with an undefined var is invalid at computed-value time, so
   the toast silently kept its unshifted position. */
body:has(.wgroup-shell) {
    --wg-bar-h: 52px;
    --wg-bar-space: calc(var(--wg-bar-h) + env(safe-area-inset-bottom, 0px));
}
.wgroup-shell {
    position: fixed;
    inset: 0;
    display: flex;
    flex-direction: column;
    background: var(--blue8);
    color: var(--blue1);
    font-family: system-ui, -apple-system, sans-serif;
    font-size: 16px;
}
.wgroup-shell #bottombar {
    position: relative;
    z-index: 700;   /* above #panel (600): the sheet slides down BEHIND the bar */
    display: flex;
    align-items: center;
    gap: 0.6rem;
    padding: 0 0.8rem;
    /* Height is the bar itself; the inset is added as padding so the content
       stays 52px tall and only the background grows into the home-indicator
       strip. app.html already ships viewport-fit=cover, which is what makes
       env() resolve to anything but 0. */
    height: var(--wg-bar-space);
    padding-bottom: env(safe-area-inset-bottom, 0px);
    box-sizing: border-box;
    flex-shrink: 0;
    background: var(--blue8);
    border-top: 1px solid var(--blue6);
}
.wgroup-shell .brand { font-weight: 800; letter-spacing: 0.04em; font-size: var(--font-lg); white-space: nowrap; }
.wgroup-shell .brand .brand-module { color: var(--color-primary-light); }
/* Status slot — a button so it is focusable and tappable without ARIA glue,
   styled as text because it is state first and a control second. */
.wgroup-shell #group-status {
    flex: 1;
    min-width: 0;                 /* let it ellipse instead of shoving the bar */
    background: none;
    border: none;
    padding: 0;
    /* Centred, not left-aligned: with a full wordmark on one side and two
       buttons on the other, the status reads as the middle of the bar rather
       than as something trailing the brand. */
    text-align: center;
    font: inherit;
    font-size: var(--font-sm);
    color: var(--blue2);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    cursor: pointer;
}
/* The wordmark STAYS at full length in every state — it is the app's name and the
   bar is where a name belongs. An earlier pass collapsed it to `W` when a group
   went live to buy room for the status; measured, that room is not needed:
   ~158px is left for the status at 360px, and the group line is short ("G8 · 1",
   "Stelvio Run · 5"). A long name ellipses, which is the right failure. */
/* Bar voice shortcut. Same 44px target as the person button beside it, but
   quieter: it is a jump to Discord, not the app's own main action. */
.wgroup-shell #btn-bar-voice {
    flex-shrink: 0;
    background: none;
    border: 1px solid transparent;
    border-radius: 0.4rem;
    color: var(--blue2);
    min-width: 44px; min-height: 40px;
    display: flex; align-items: center; justify-content: center;
    cursor: pointer;
}
.wgroup-shell #btn-bar-voice:hover { border-color: var(--blue6); color: var(--color-primary-light); }
.wgroup-shell #btn-bar-voice .wg-ico { width: 1.25rem; height: 1.25rem; }
.wgroup-shell #group-status:empty { cursor: default; }
.wgroup-shell #btn-panel-toggle {
    background: var(--blue7);
    border: 1px solid var(--blue6);
    border-radius: 0.4rem;
    color: var(--blue2);
    min-width: 44px; min-height: 40px;
    display: flex; align-items: center; justify-content: center;
    cursor: pointer;
}
.wgroup-shell #btn-panel-toggle:hover { border-color: var(--color-primary-ultralight); color: var(--color-primary-ultralight); }
.wgroup-shell #btn-panel-toggle .wg-ico { width: 1.5rem; height: 1.5rem; }

.wgroup-shell #map-wrap { position: relative; flex: 1; min-height: 0; }
/* ⚠ z-index:0 is LOAD-BEARING, not decoration. `position:absolute` with
   z-index:auto creates NO stacking context, so rider markers — which carry
   z-index 3000 — escaped into the root context and floated ON TOP of #panel
   (600) and #bottombar (700): the panel opened and the dots stayed over it.
   z-index:0 makes #map a stacking context and traps everything inside it,
   which is exactly what the weathergpx host has always done (`#map { z-index:
   0 }` in its main.css). Standalone was simply missing the same line, so the
   bug could only ever show up here. Found on two phones 2026-09-02. */
.wgroup-shell #map { position: absolute; inset: 0; z-index: 0; }

/* Every map control lives here, bottom-left, in one stack — zoom and locate
   included since 2026-08-31. The bottom-RIGHT corner is left to MapLibre's
   attribution ⓘ, which is the whole reason the controls are not there: a stack
   in that corner sits directly on top of the credit.

   A COLUMN in both orientations — no direction switch, no media query for it.
   Two earlier passes tried to pick a direction per orientation from occlusion
   arithmetic (column 60/390 = 15% of portrait width vs row 44/792 = 5.5% of its
   height, and the reverse in landscape). On the device that maths lost to two
   things it does not measure: reading the run bottom-up is simply nicer to use,
   and a portrait screen is tall enough that a 60px strip down the left edge
   leaves plenty of map either way. Same order, same place, nothing to relearn
   when the phone turns.
   Bottom-up: SOS, phone, locate, −, +. DOM is written zoom-first so a plain
   `column` produces that, with SOS — the one wide button — at the end of the run
   nearest the bar rather than marooned between four 44px squares.

   ⚠ SOS is display:none for non-members (.hide), so the stack simply starts at
   the emergency button. Deliberate: no hole where SOS would have been. */
.wgroup-shell #map-btns {
    position: absolute;
    left: 0.75rem;
    bottom: 1rem;
    z-index: 500;
    display: flex;
    flex-direction: column;
    align-items: flex-start;   /* SOS widens to "Cancel SOS" — let it grow right */
    gap: 0.5rem;
}
.wgroup-shell #map-btns button,
.wgroup-shell #map-btns .maplibregl-ctrl-group button {
    width: 44px; height: 44px;
    display: flex; align-items: center; justify-content: center;
    background: rgba(12, 24, 41, 0.85);
    color: var(--blue1);
    border: 2px solid var(--blue5);
    border-radius: 0.5rem;
    cursor: pointer;
}
.wgroup-shell #map-btns button:hover:not(:disabled) { background: var(--blue6); }
/* At min/max zoom. Dimmed rather than hidden: a button that vanishes at the end
   of the range moves its neighbour under your thumb mid-ride. */
.wgroup-shell #map-btns button:disabled {
    color: var(--blue4);
    border-color: var(--blue6);
    cursor: not-allowed;
}
.wgroup-shell #map-btns .wg-ico { width: 22px; height: 22px; }
/* The relocated geolocate control still carries the margin MapLibre gives a
   child of a corner container. Zero it — the flex gap spaces this stack. */
.wgroup-shell #map-btns .maplibregl-ctrl { margin: 0; }

/* The attribution ⓘ needs no nudging: standing up, the stack is 60px wide on the
   LEFT (136px at its widest, with "Cancel SOS"), and the credit sits bottom-right
   at x≈294. Nothing can reach it. It was lifted while the stack was a row, which
   is the only shape that could grow into that corner. */
/* ⚠ Two ids on every one of these, deliberately. The stack's own
   `.wgroup-shell #map-btns button` is (1,1,1) and would otherwise flatten SOS
   into generic dark chrome — and its `:hover:not(:disabled)` is (1,3,1), which
   beats even `.wgroup-shell #btn-sos-map:hover` (1,2,0). Scoping through the
   container keeps SOS red without an !important anywhere.
   SOS is also the one control in the stack that is NOT a 44px square: the label
   swaps to "Cancel SOS" / "SOS abbrechen" while an SOS is live, so it has to be
   free to grow. align-items:flex-start on the stack lets it grow rightwards
   without dragging the other four off the edge. */
.wgroup-shell #map-btns #btn-sos-map {
    width: auto;
    min-width: 60px; height: 44px;
    padding: 0 0.6rem;
    color: var(--color-sos);
    border-color: var(--color-sos-strong);
    font-size: var(--font-md);
    font-weight: 700;
    letter-spacing: 0.06em;
}
.wgroup-shell #map-btns #btn-sos-map:hover { background: var(--color-sos-strong); color: var(--grey0); }
.wgroup-shell #map-btns #btn-sos-map.sos-active { background: var(--color-sos-dark); color: var(--grey0); border-color: var(--color-sos-dark); }

/* Emergency numbers button — directly under SOS in the stack. Neutral, not red:
   SOS owns the alarm colour, this is reference you read rather than an alarm you
   raise. Always visible, unlike SOS which is member-only.
   It needs no rule of its own any more: the stack's generic button style is
   exactly this. Its old min-width:60px is gone with it — that was sized for the
   horizontal row and left the column ragged against the 44px icon buttons. */

/* ── GeolocateControl — MapLibre's chrome dragged into ours ─────────────────
   This is the one control we do not build: maplibregl.GeolocateControl ships
   its own white rounded box with a #333 icon, which is a light-theme widget
   sitting in a dark app. On our surface that icon is very nearly invisible —
   MapLibre's only alternate fills live under @media (forced-colors:active),
   NOT prefers-color-scheme, so a dark OS does not rescue it either.

   So: the container loses its white box and the button is restyled to match
   #btn-emg-map exactly (same fill, same border, same radius, same 44px target).
   The icon has to be REPLACED rather than recoloured — MapLibre bakes the
   colour into a background-image data URI, and a filter that turned #333 into
   our light blue would also turn the active state's #33b5e5 into orange.

   ⚠ The hexes below are the theme tokens written out longhand, because a
   var() cannot be read from inside a url(). Keep them in step by hand:
     #E4F0F8 --blue1   #6A8FAA --blue4   #38BDF8 --color-primary-light
     #EF4444 --color-sos
   The path data is MapLibre 4's own, copied verbatim so the glyph still
   matches every other maplibre install and survives a library restyle.

   Scoped to .wgroup-shell: embedded, this control belongs to WeatherGPX and
   these rules must not reach it. */
.wgroup-shell .maplibregl-ctrl-group {
    background: none;
    border-radius: 0.5rem;
}
.wgroup-shell .maplibregl-ctrl-group:not(:empty) { box-shadow: none; }
.wgroup-shell .maplibregl-ctrl-group button {
    width: 44px; height: 44px;
    background: rgba(12, 24, 41, 0.85);
    border: 2px solid var(--blue5);
    border-radius: 0.5rem;
}
.wgroup-shell .maplibregl-ctrl-group button:not(:disabled):hover { background: var(--blue6); }
/* Rounded on every corner, not just the first/last of a stack: this control is
   alone today, and step 2 gives zoom its own buttons rather than adding them
   here. Also kills the 1px divider a second button would inherit. */
.wgroup-shell .maplibregl-ctrl-group button + button { border-top: 2px solid var(--blue5); }
.wgroup-shell .maplibregl-ctrl-group button:focus { box-shadow: 0 0 0 2px var(--color-primary-light); }

.wgroup-shell .maplibregl-ctrl button.maplibregl-ctrl-geolocate .maplibregl-ctrl-icon {
    background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='29' height='29' fill='%23E4F0F8' viewBox='0 0 20 20'%3E%3Cpath d='M10 4C9 4 9 5 9 5v.1A5 5 0 0 0 5.1 9H5s-1 0-1 1 1 1 1 1h.1A5 5 0 0 0 9 14.9v.1s0 1 1 1 1-1 1-1v-.1a5 5 0 0 0 3.9-3.9h.1s1 0 1-1-1-1-1-1h-.1A5 5 0 0 0 11 5.1V5s0-1-1-1m0 2.5a3.5 3.5 0 1 1 0 7 3.5 3.5 0 1 1 0-7'/%3E%3Ccircle cx='10' cy='10' r='2'/%3E%3C/svg%3E");
}
.wgroup-shell .maplibregl-ctrl button.maplibregl-ctrl-geolocate:disabled .maplibregl-ctrl-icon {
    background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='29' height='29' fill='%236A8FAA' viewBox='0 0 20 20'%3E%3Cpath d='M10 4C9 4 9 5 9 5v.1A5 5 0 0 0 5.1 9H5s-1 0-1 1 1 1 1 1h.1A5 5 0 0 0 9 14.9v.1s0 1 1 1 1-1 1-1v-.1a5 5 0 0 0 3.9-3.9h.1s1 0 1-1-1-1-1-1h-.1A5 5 0 0 0 11 5.1V5s0-1-1-1m0 2.5a3.5 3.5 0 1 1 0 7 3.5 3.5 0 1 1 0-7'/%3E%3Ccircle cx='10' cy='10' r='2'/%3E%3C/svg%3E");
}
/* Tracking — the app's own accent, so "it is following me" reads as ours. */
.wgroup-shell .maplibregl-ctrl button.maplibregl-ctrl-geolocate.maplibregl-ctrl-geolocate-active .maplibregl-ctrl-icon,
.wgroup-shell .maplibregl-ctrl button.maplibregl-ctrl-geolocate.maplibregl-ctrl-geolocate-background .maplibregl-ctrl-icon {
    background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='29' height='29' fill='%2338BDF8' viewBox='0 0 20 20'%3E%3Cpath d='M10 4C9 4 9 5 9 5v.1A5 5 0 0 0 5.1 9H5s-1 0-1 1 1 1 1 1h.1A5 5 0 0 0 9 14.9v.1s0 1 1 1 1-1 1-1v-.1a5 5 0 0 0 3.9-3.9h.1s1 0 1-1-1-1-1-1h-.1A5 5 0 0 0 11 5.1V5s0-1-1-1m0 2.5a3.5 3.5 0 1 1 0 7 3.5 3.5 0 1 1 0-7'/%3E%3Ccircle cx='10' cy='10' r='2'/%3E%3C/svg%3E");
}
/* Location failed. Red, but NOT the SOS red at full strength — a GPS error is
   not an emergency, and the two must not read as the same class of event. */
.wgroup-shell .maplibregl-ctrl button.maplibregl-ctrl-geolocate.maplibregl-ctrl-geolocate-active-error .maplibregl-ctrl-icon,
.wgroup-shell .maplibregl-ctrl button.maplibregl-ctrl-geolocate.maplibregl-ctrl-geolocate-background-error .maplibregl-ctrl-icon {
    background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='29' height='29' fill='%23EF4444' viewBox='0 0 20 20'%3E%3Cpath d='M10 4C9 4 9 5 9 5v.1A5 5 0 0 0 5.1 9H5s-1 0-1 1 1 1 1 1h.1A5 5 0 0 0 9 14.9v.1s0 1 1 1 1-1 1-1v-.1a5 5 0 0 0 3.9-3.9h.1s1 0 1-1-1-1-1-1h-.1A5 5 0 0 0 11 5.1V5s0-1-1-1m0 2.5a3.5 3.5 0 1 1 0 7 3.5 3.5 0 1 1 0-7'/%3E%3Ccircle cx='10' cy='10' r='2'/%3E%3C/svg%3E");
}



#emg-dialog { display: none; }
#emg-dialog[open] {
    display: flex; flex-direction: column;
    width: min(92vw, 340px); max-height: min(80vh, 620px);
    padding: 0; overflow: hidden;
    border: 1px solid var(--blue6); border-radius: 0.85rem;
    background: var(--blue8); color: var(--blue1);
}
#emg-dialog::backdrop { background: rgba(0,0,0,0.6); }
#emg-head {
    display: flex; align-items: center; justify-content: space-between;
    padding: 0.7rem 0.85rem; border-bottom: 1px solid var(--blue6);
    font-weight: 600;
}
#btn-emg-close {
    background: none; border: none; color: var(--blue1);
    font-size: 1.4rem; line-height: 1; cursor: pointer; padding: 0 0.25rem;
}
/* min-height:0 lets this shrink inside the flex column so it scrolls instead of
   being clipped by the dialog's overflow:hidden. */
#emg-dialog #emg-mount { padding: 0.75rem 0.85rem 0.85rem; overflow-y: auto; min-height: 0; }
.emg-head { display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.5rem; }
.emg-flag { font-size: 1.25rem; line-height: 1; flex-shrink: 0; }
.emg-picker {
    flex: 1; min-width: 0; padding: 0.4rem 0.5rem;
    border: 1px solid var(--blue6); border-radius: 0.4rem;
    background: var(--blue7); color: var(--blue1); font-size: var(--font-sm);
}
/* Country note. Shown only for the six countries where it changes what you DO.
   ABOVE the numbers on purpose: it tells you which of them to trust. */
.emg-note {
    margin: 0 0 0.5rem;
    padding: 0.45rem 0.6rem;
    border-left: 3px solid var(--color-warning-bright);
    border-radius: 0 0.3rem 0.3rem 0;
    background: var(--blue7);
    font-size: var(--font-sm);
    line-height: 1.35;
}
.emg-list { display: flex; flex-direction: column; gap: 0.3rem; }
.emg-row {
    display: flex; align-items: center; justify-content: space-between; gap: 0.6rem;
    min-height: 44px; padding: 0.45rem 0.7rem;
    border: 1px solid var(--blue6); border-radius: 0.5rem;
    background: var(--blue7); color: var(--blue1); text-decoration: none;
}
.emg-row:hover { background: var(--blue6); }
.emg-label { font-size: var(--font-sm); }
.emg-num { font-size: 1.05rem; font-weight: 700; white-space: nowrap; }
.emg-none { margin: 0.3rem 0; font-size: var(--font-sm); opacity: 0.75; }
.emg-src { margin: 0.55rem 0 0; font-size: 0.72rem; opacity: 0.55; }
.emg-src a { color: inherit; }

/* Panel — floating card over the map */
/* Bottom sheet. #panel lives inside #map-wrap, which ends exactly where the bar
   begins, so `bottom: 0` rests it ON the bar with no arithmetic — the same reason
   the button stack needs no bar-height offset.
   It slides rather than appearing, and slides DOWN BEHIND the bar: #bottombar
   carries z-index 700 for exactly that. Closed state is `.sheet-closed`, not the
   global `.hide` (`display:none !important` — untransitionable). */
.wgroup-shell #panel {
    position: absolute;
    left: 50%; bottom: 0;
    transform: translate(-50%, 0);
    z-index: 600;
    width: min(480px, 100%);
    max-height: calc(100% - 1rem);
    overflow-y: auto;
    transition: transform 200ms ease;
    /* Thin, themed, and it keeps its gutter — `scrollbar-width: none` would hide
       the only hint that there is more panel below the fold. */
    scrollbar-width: thin;
    scrollbar-color: var(--blue6) transparent;
    background: var(--blue8);
    border: 1px solid var(--blue6);
    border-bottom: none;                    /* it sits ON the bar */
    border-radius: 0.75rem 0.75rem 0 0;
    padding: 0.8rem;
    box-shadow: 0 -6px 24px rgba(0,0,0,0.45);
    box-sizing: border-box;
}
.wgroup-shell #panel.sheet-closed {
    transform: translate(-50%, 100%);
    /* Flipped only after the slide finishes, so the sheet is not yanked out of
       the accessibility tree mid-animation — and IS out of it once closed. */
    visibility: hidden;
    transition: transform 200ms ease, visibility 0s linear 200ms;
}
@media (prefers-reduced-motion: reduce) {
    .wgroup-shell #panel,
    .wgroup-shell #panel.sheet-closed { transition: none; }
}
/* space-between, not flex-end: the wordmark the bottom bar gave up lives here
   now, so the head carries a title on the left and the close button on the right. */
.wgroup-shell #panel::-webkit-scrollbar { width: 6px; }
.wgroup-shell #panel::-webkit-scrollbar-track { background: transparent; }
.wgroup-shell #panel::-webkit-scrollbar-thumb { background: var(--blue6); border-radius: 3px; }
.wgroup-shell #panel::-webkit-scrollbar-thumb:hover { background: var(--blue5); }

.wgroup-shell #panel-head { display: flex; align-items: center; justify-content: space-between; margin: -0.3rem -0.3rem 0.1rem 0; }
.wgroup-shell #btn-panel-close {
    background: none; border: none; cursor: pointer;
    color: var(--blue4); font-size: var(--font-xl); line-height: 1;
    min-width: 32px; min-height: 28px;
}
.wgroup-shell #btn-panel-close:hover { color: var(--color-primary-light); }

.wgroup-shell #panel-footer {
    margin-top: 0.8rem;
    padding-top: 0.6rem;
    border-top: 1px solid var(--blue6);
    font-size: var(--font-xs);
    color: var(--blue4);
}
.wgroup-shell .pf-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
}
.wgroup-shell .pf-row + .pf-row { margin-top: 1rem; }   /* finger space between the two link rows */
/* Install hidden → weathergpx link alone on row 1, pushed right */
.wgroup-shell .pf-row #link-wgpx { margin-left: auto; }
.wgroup-shell #panel-footer a { color: var(--blue3); text-decoration: none; }
.wgroup-shell #panel-footer a:hover { color: var(--color-primary-light); }
.wgroup-shell #lang-row { display: flex; gap: 0.45rem; }
.wgroup-shell #lang-row a { cursor: pointer; text-transform: uppercase; }
.wgroup-shell #lang-row a.active { color: var(--color-primary-light); font-weight: 700; }

/* ⚠ #wgroup-offline and #wgroup-toast above are .wgroup-scoped — they belong to
   the MODULE and weathergpx renders them too, so the shell overrides them here
   instead of editing them. Both assumptions they were written under changed when
   the bar moved to the bottom (step 3):
     offline pill  top: 3.4rem  cleared a 52px topbar that no longer exists
     toast      bottom: 1.5rem  is now underneath the bar */
/* ⚠ ...but a DESCENDANT selector cannot reach them. The overlays are appended to
   BODY as <div class="wgroup wgroup-overlays">, a SIBLING of #app.wgroup-shell,
   never a child of it — so every `.wgroup-shell #wgroup-*` rule written here
   silently matched nothing. Found 2026-09-01, and both had been dead since
   step 3: the offline pill still cleared the deleted topbar, and the toast was
   still rendering underneath the bottom bar, which is the exact bug the comment
   above says was being fixed. body:has() reaches across the sibling split. */
body:has(.wgroup-shell) #wgroup-offline,
body:has(.wgroup-shell) #wgroup-geo-denied { top: 0.75rem; }
/* Stacked, not overlapping, when both conditions hold. */
.wgroup:has(#wgroup-offline:not(.hide)) #wgroup-geo-denied { top: 6rem; }
body:has(.wgroup-shell):has(#wgroup-offline:not(.hide)) #wgroup-geo-denied { top: 3.35rem; }
/* Panel block: the explanation and the Settings button, where a rider wondering
   why they are not on the map would look. */
.wgroup #group-geo-denied {
    background: var(--color-warning-bright);
    color: var(--grey0);
    border-radius: 0.4rem;
    padding: 0.6rem 0.75rem;
    margin-bottom: 0.75rem;
    font-size: var(--font-sm);
}
.wgroup #group-geo-denied p { margin: 0 0 0.5rem; }
.wgroup #group-geo-denied #btn-geo-settings { width: 100%; min-height: 36px; justify-content: center; }
body:has(.wgroup-shell) #wgroup-toast { bottom: calc(var(--wg-bar-space) + 1rem); }

/* Phone landscape. The sheet needs no override any more — it is already centred,
   width-capped and anchored to the bar in every orientation. */
@media (orientation: landscape) and (max-height: 500px) {
    .wgroup-shell { --wg-bar-h: 44px; }
    /* #map-btns needs nothing here — it is the same column in both orientations. */
}
