/* Hexux theme layer.
   Overrides Bootstrap 5.3's own CSS variables rather than adding rules that
   fight it, so components pick the theme up without extra classes. */

:root,
[data-bs-theme="light"] {
    --bs-primary: #7048e8;
    --bs-primary-rgb: 112, 72, 232;
    --bs-link-color: #5f3dc4;
    --bs-link-color-rgb: 95, 61, 196;
    --bs-link-hover-color: #4c2fa8;

    /* Warm neutrals rather than Bootstrap's cool greys. */
    --bs-body-bg: #f7f5f2;
    --bs-body-color: #2b2431;
    --bs-secondary-color: #6b6577;
    --bs-border-color: #e6e1dc;

    --hx-surface: #ffffff;
    --hx-surface-muted: #f2eeea;
    --hx-shadow: 0 1px 2px rgba(28, 20, 40, .04), 0 8px 24px rgba(28, 20, 40, .06);
    --hx-shadow-hover: 0 2px 4px rgba(28, 20, 40, .06), 0 12px 32px rgba(28, 20, 40, .10);
    --hx-radius: 14px;
}

[data-bs-theme="dark"] {
    --bs-primary: #9775fa;
    --bs-primary-rgb: 151, 117, 250;
    --bs-link-color: #b197fc;
    --bs-link-color-rgb: 177, 151, 252;
    --bs-link-hover-color: #d0bfff;

    /* Warm-tinted dark, not blue-black. */
    --bs-body-bg: #17151a;
    --bs-body-color: #ece9f0;
    --bs-secondary-color: #a49eb0;
    --bs-border-color: #302b38;

    --hx-surface: #211e26;
    --hx-surface-muted: #2a2530;
    --hx-shadow: 0 1px 2px rgba(0, 0, 0, .3), 0 8px 24px rgba(0, 0, 0, .25);
    --hx-shadow-hover: 0 2px 4px rgba(0, 0, 0, .35), 0 12px 32px rgba(0, 0, 0, .35);
}

body {
    background: var(--bs-body-bg);
    color: var(--bs-body-color);
    -webkit-font-smoothing: antialiased;
}

/* ---------- layout ---------- */

.hx-main {
    max-width: 42rem;
}
.hx-main-wide {
    max-width: 64rem;
}

/* ---------- cards ---------- */

.hx-card {
    background: var(--hx-surface);
    border: 1px solid var(--bs-border-color);
    border-radius: var(--hx-radius);
    box-shadow: var(--hx-shadow);
    padding: 1rem 1.15rem;
    margin-bottom: .9rem;
    transition: box-shadow .18s ease, transform .18s ease;
}
.hx-card:hover {
    box-shadow: var(--hx-shadow-hover);
}

/* ---------- composer ---------- */

.hx-composer-input {
    border: 0;
    background: transparent;
    padding: .35rem 0;
    resize: vertical;
    min-height: 3rem;
    font-size: 1.02rem;
}
.hx-composer-input:focus {
    background: transparent;
    box-shadow: none;
    border: 0;
}
.hx-composer:focus-within {
    border-color: rgba(var(--bs-primary-rgb), .45);
}

/* ---------- dropdown ---------- */

.dropdown-menu {
    background: var(--hx-surface);
    border: 1px solid var(--bs-border-color);
    border-radius: 12px;
    box-shadow: var(--hx-shadow);
    padding: .35rem;
}
.dropdown-item {
    border-radius: 8px;
    padding: .45rem .7rem;
}
.dropdown-item:hover, .dropdown-item:focus {
    background: var(--hx-surface-muted);
    color: var(--bs-body-color);
}

/* ---------- avatars ---------- */

.hx-avatar {
    border-radius: 50%;
    object-fit: cover;
    flex: 0 0 auto;
    background: var(--hx-surface-muted);
}
.hx-avatar-sm { width: 34px; height: 34px; }
.hx-avatar-md { width: 46px; height: 46px; }
.hx-avatar-lg { width: 104px; height: 104px; }

/* ---------- posts ---------- */

.hx-post-head {
    display: flex;
    align-items: center;
    gap: .65rem;
    margin-bottom: .5rem;
}
.hx-post-author {
    font-weight: 600;
    text-decoration: none;
    color: var(--bs-body-color);
}
.hx-post-author:hover { color: var(--bs-link-color); }
.hx-post-meta {
    font-size: .82rem;
    color: var(--bs-secondary-color);
}
.hx-post-body {
    white-space: pre-wrap;
    overflow-wrap: anywhere;
    line-height: 1.55;
}
.hx-post-actions {
    display: flex;
    align-items: center;
    gap: .4rem;
    flex-wrap: wrap;
    margin-top: .75rem;
}

/* Quiet action buttons that only gain weight on hover. */
.hx-action {
    --bs-btn-border-color: transparent;
    --bs-btn-hover-border-color: transparent;
    border-radius: 999px;
    font-size: .84rem;
    padding: .3rem .7rem;
    color: var(--bs-secondary-color);
    background: transparent;
    border: 1px solid transparent;
    text-decoration: none;
    display: inline-flex;
    align-items: center;
    gap: .35rem;
}
.hx-action:hover {
    background: var(--hx-surface-muted);
    color: var(--bs-body-color);
}
.hx-action-like.is-liked,
.hx-action-like.is-liked:hover {
    color: #e8590c;
}
.hx-action-danger:hover {
    color: #c92a2a;
}

/* ---------- navbar ---------- */

.navbar.hx-nav {
    background: var(--hx-surface);
    border-bottom: 1px solid var(--bs-border-color);
    backdrop-filter: saturate(1.4) blur(6px);
}
.hx-brand {
    font-weight: 700;
    letter-spacing: -.02em;
    color: var(--bs-body-color) !important;
}
.hx-brand-accent { color: var(--bs-primary); }
.hx-nav .nav-link {
    border-radius: 999px;
    padding: .4rem .8rem !important;
    color: var(--bs-secondary-color);
}
.hx-nav .nav-link:hover {
    background: var(--hx-surface-muted);
    color: var(--bs-body-color);
}
.hx-nav .nav-link.active {
    background: rgba(var(--bs-primary-rgb), .12);
    color: var(--bs-primary);
    font-weight: 600;
}

/* ---------- forms ---------- */

.form-control, .form-select {
    background: var(--hx-surface);
    border-color: var(--bs-border-color);
    border-radius: 10px;
    padding: .55rem .8rem;
}
.form-control:focus, .form-select:focus {
    border-color: var(--bs-primary);
    box-shadow: 0 0 0 .2rem rgba(var(--bs-primary-rgb), .18);
}
.btn { border-radius: 10px; }
.btn-primary {
    --bs-btn-bg: var(--bs-primary);
    --bs-btn-border-color: var(--bs-primary);
    --bs-btn-hover-bg: #5f3dc4;
    --bs-btn-hover-border-color: #5f3dc4;
}

/* ---------- chat ---------- */

.hx-thread {
    background: var(--hx-surface);
    border: 1px solid var(--bs-border-color);
    border-radius: var(--hx-radius);
    /* Viewport-relative with a floor and ceiling, so it fits a phone in
       landscape as well as a desktop. */
    height: clamp(16rem, 55vh, 34rem);
    overflow-y: auto;
    /* Stop a swipe that reaches the top or bottom of the thread from carrying
       on into the page behind it. Everywhere, not only on a phone: the same
       chaining makes a desktop trackpad jump the whole window. */
    overscroll-behavior: contain;
    -webkit-overflow-scrolling: touch;
    padding: 1rem;
}
.hx-bubble {
    display: inline-block;
    max-width: min(80%, 34rem);
    padding: .5rem .8rem;
    border-radius: 16px;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
    text-align: left;
    line-height: 1.45;
}
.hx-bubble-mine {
    background: var(--bs-primary);
    color: #fff;
    border-bottom-right-radius: 5px;
}
.hx-bubble-theirs {
    background: var(--hx-surface-muted);
    color: var(--bs-body-color);
    border-bottom-left-radius: 5px;
}

/* ---------- chat media ---------- */

.hx-media { display: block; }
.hx-media-img,
.hx-media-video {
    max-width: min(100%, 20rem);
    max-height: 20rem;
    border-radius: 12px;
    border: 1px solid var(--bs-border-color);
    display: inline-block;
    background: var(--hx-surface-muted);
}
.hx-media-img { cursor: zoom-in; object-fit: cover; }
.hx-media-video { width: min(100%, 20rem); }

@media (max-width: 576px) {
    .hx-media-img, .hx-media-video { max-width: 100%; max-height: 15rem; }
}

/* ---------- map ---------- */

#map {
    height: clamp(18rem, 60vh, 40rem);
    border-radius: var(--hx-radius);
    border: 1px solid var(--bs-border-color);
}
.cell-label {
    background: transparent;
    border: 0;
    box-shadow: none;
    font-weight: 600;
    color: var(--bs-body-color);
}
.cell-label::before { display: none; }

/* ---------- hover card ---------- */

/* Bootstrap's popover reads these, so the card follows light/dark with the
   rest of the theme instead of staying stock white. */
.popover {
    --bs-popover-bg: var(--hx-surface);
    --bs-popover-border-color: var(--bs-border-color);
    --bs-popover-body-color: var(--bs-body-color);
    --bs-popover-max-width: 20rem;
    --bs-popover-border-radius: 12px;
    --bs-popover-inner-border-radius: 12px;
    box-shadow: var(--hx-shadow-hover);
}
.popover-body { padding: .8rem .9rem; }

.hx-popup {
    /* Bounded so a long bio or username cannot stretch the card. */
    min-width: 14rem;
    max-width: 18rem;
}
.hx-popup img { flex: 0 0 auto; }
.hx-popup-name {
    display: block;
    font-weight: 600;
    text-decoration: none;
    color: var(--bs-body-color);
    overflow-wrap: anywhere;
}

/* ---------- search suggestions ---------- */

/* The list opens over the page under the search box, drawn by
   place-suggest.js. Bounded in height so it fits above a phone keyboard,
   and scrolling inside itself rather than pushing the page. Nothing here
   depends on hover: the highlighted row is aria-selected, set by keyboard
   or by whatever is pointing at it. */
.hx-suggest-host { position: relative; }
.hx-suggest {
    position: absolute;
    left: 0;
    right: 0;
    z-index: 1030;
    margin: .25rem 0 0;
    padding: .3rem 0;
    list-style: none;
    max-height: 18rem;
    overflow-y: auto;
    background: var(--hx-surface);
    border: 1px solid var(--bs-border-color);
    border-radius: 12px;
    box-shadow: var(--hx-shadow-hover);
}
.hx-suggest-item,
.hx-suggest-none {
    display: flex;
    flex-direction: column;
    padding: .5rem .8rem;
    cursor: pointer;
}
.hx-suggest-none {
    cursor: default;
    color: var(--bs-secondary-color);
    font-size: .9rem;
}
.hx-suggest-item[aria-selected="true"],
.hx-suggest-item:hover {
    background: var(--hx-surface-muted);
}
.hx-suggest-name {
    font-weight: 600;
    overflow-wrap: anywhere;
}
.hx-suggest-meta {
    font-size: .82rem;
    color: var(--bs-secondary-color);
    overflow-wrap: anywhere;
}
.hx-popup-name:hover { color: var(--bs-link-color); }
.hx-popup-meta {
    font-size: .78rem;
    color: var(--bs-secondary-color);
    line-height: 1.35;
}
.hx-popup-bio {
    font-size: .86rem;
    margin: 0 0 .5rem;
    /* Trim a long bio rather than letting it grow the card. */
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
    overflow-wrap: anywhere;
}
.hx-popup-stats {
    display: flex;
    flex-wrap: wrap;
    gap: .75rem;
    font-size: .82rem;
    color: var(--bs-secondary-color);
}

/* ---------- misc ---------- */

.hx-stat {
    background: var(--hx-surface);
    border: 1px solid var(--bs-border-color);
    border-radius: var(--hx-radius);
    padding: 1rem;
    text-align: center;
}
.hx-stat-value {
    font-size: 1.6rem;
    font-weight: 700;
    line-height: 1.1;
}
.list-group-item { background: var(--hx-surface); }

/* ---------- mobile ---------- */

@media (max-width: 576px) {
    .hx-card { padding: .85rem .9rem; border-radius: 12px; }
    .hx-avatar-lg { width: 72px; height: 72px; }
    /* Comfortable tap targets. */
    .hx-action { padding: .45rem .75rem; }
    .hx-nav .nav-link { padding: .55rem .8rem !important; }
    h1 { font-size: 1.5rem; }
}

/* Moved out of a <style> block in map.html so no page carries inline CSS. */
.cell-users { margin: 0; padding-left: 1rem; }

/* The withdraw control on your own chat messages. Deliberately quiet: it sits
   beside every message you sent, so it must not compete with the text.
   Quiet, but always there - see the note on hover-to-reveal below. */
.hx-message-delete {
    padding: 0 .35rem;
    line-height: 1;
    color: var(--bs-secondary-color);
    text-decoration: none;
}
.hx-message-delete:hover { color: var(--bs-danger); }

/* Hide-until-hover, for pointers that can actually hover.
   
   This site is used on phones, and a touch screen has no hover: the browser
   fakes one on first tap, so a control hidden this way needs tapping twice -
   once to reveal it and once to press it. That reads as a broken button, and
   it is how Corey found the delete control apparently missing.
   
   `hover: hover` is true only where a pointer can rest on something without
   pressing it. Everywhere else these stay visible, which is the behaviour a
   thread on a phone wants anyway. */
@media (hover: hover) and (pointer: fine) {
    .hx-message-delete,
    .hx-message-report {
        opacity: 0;
        transition: opacity .12s ease-in-out;
    }
    .hx-thread > div:hover .hx-message-delete,
    .hx-thread > div:hover .hx-message-report,
    .hx-message-delete:focus-visible,
    .hx-message-report:focus-visible {
        opacity: 1;
    }
}

.hx-footer hr { opacity: .12; }

/* The privacy policy reads as a document, not as a feed. */
.hx-prose { max-width: 46rem; }
.hx-prose h2 { font-size: 1.15rem; margin-top: 2rem; }
.hx-prose h2:first-of-type { margin-top: 1rem; }
.hx-prose li { margin-bottom: .35rem; }

/* Moved out of a <style> block in edit_profile.html, so no page carries
   inline CSS either. */

/* Hidden by edit-profile.js rather than by default, so the fields stay
   usable if the script does not run. */
#password-fields.is-hidden { display: none; }

/* The preview sits beside its controls rather than above them, so the photo
   and the setting for who may see it read as one thing. Wraps on a narrow
   screen. .picture-icon used to live here as well - an overlay for a camera
   button that no template ever rendered. */
.hx-photo-edit {
    display: flex;
    flex-wrap: wrap;
    gap: 1rem;
    align-items: flex-start;
}
.hx-photo-controls { flex: 1 1 16rem; min-width: 0; }
.profile-picture {
    width: clamp(88px, 22vw, 140px);
    height: clamp(88px, 22vw, 140px);
    border-radius: 50%;
    object-fit: cover;
    border: 1px solid var(--bs-border-color);
}

/* The terms shown on the acceptance gate: scrollable so the Accept and
   Decline buttons stay reachable without paging through the whole document,
   while the text itself stays fully readable. */
.hx-terms-scroll {
    max-height: 24rem;
    overflow-y: auto;
    background-color: var(--bs-tertiary-bg);
}
.hx-terms-scroll h2:first-of-type { margin-top: 1rem; }

/* ---------------------------------------------------------------- reactions */
.hx-reactions { display: inline-flex; flex-wrap: wrap; gap: .25rem; align-items: center; }
.hx-reaction {
    border: 1px solid transparent;
    background: var(--bs-tertiary-bg);
    border-radius: 999px;
    padding: .1rem .5rem;
    font-size: .9rem;
    line-height: 1.4;
    cursor: pointer;
}
.hx-reaction:hover { border-color: var(--bs-border-color); }
.hx-reaction.is-mine {
    border-color: var(--bs-primary);
    background: color-mix(in srgb, var(--bs-primary) 12%, transparent);
}
.hx-reaction-count { font-size: .8rem; color: var(--bs-secondary-color); margin-left: .15rem; }
.hx-reaction.is-mine .hx-reaction-count { color: var(--bs-primary); }
.hx-reaction-add { color: var(--bs-secondary-color); font-weight: 600; }
.hx-reaction-menu { min-width: auto; padding: .35rem; }

/* -------------------------------------------------------------------- polls */
.hx-poll { margin-top: .75rem; display: flex; flex-direction: column; gap: .35rem; }
.hx-poll-option {
    display: block;
    width: 100%;
    text-align: left;
    border: 1px solid var(--bs-border-color);
    background: transparent;
    border-radius: .5rem;
    padding: .4rem .75rem;
    cursor: pointer;
}
.hx-poll-option:hover { border-color: var(--bs-primary); }
/* The bar sits behind the label rather than beside it, so a long option does
   not squash the result out of view on a narrow screen. */
.hx-poll-result {
    position: relative;
    display: flex;
    justify-content: space-between;
    gap: .5rem;
    border-radius: .5rem;
    padding: .4rem .75rem;
    overflow: hidden;
    background: var(--bs-tertiary-bg);
}
.hx-poll-fill {
    position: absolute;
    inset: 0 auto 0 0;
    background: color-mix(in srgb, var(--bs-primary) 18%, transparent);
}
.hx-poll-result.is-mine .hx-poll-fill {
    background: color-mix(in srgb, var(--bs-primary) 32%, transparent);
}
.hx-poll-label, .hx-poll-share { position: relative; }
.hx-poll-share { color: var(--bs-secondary-color); font-variant-numeric: tabular-nums; }
.hx-poll-meta { font-size: .8rem; color: var(--bs-secondary-color); margin: .25rem 0 0; }
.hx-poll-fields.is-hidden { display: none; }

/* ------------------------------------------------------- question of the day */
/* Used on an <a> in the feed and a <div> on the prompt page, so everything it
   needs to look right lives here rather than in utility classes on the tag.
   It previously carried .d-block in the feed, which is display:block
   !important and quietly beat the flex column below - the three lines ran
   together into one, and only on that page. */
.hx-prompt-card {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: .3rem;
    text-decoration: none;
    /* Marks it out from the ordinary post cards it sits above. */
    border-left: 3px solid var(--bs-primary);
}
a.hx-prompt-card .hx-prompt-body { transition: color .15s ease-in-out; }
a.hx-prompt-card:hover .hx-prompt-body { color: var(--bs-primary); }
.hx-prompt-kicker {
    font-size: .7rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: .06em;
    color: var(--bs-primary);
}
.hx-prompt-body {
    font-size: 1.15rem;
    font-weight: 600;
    line-height: 1.35;
    color: var(--bs-body-color);
    overflow-wrap: anywhere;
}
.hx-prompt-tag {
    display: inline-block;
    font-size: .75rem;
    color: var(--bs-secondary-color);
    text-decoration: none;
    margin-bottom: .35rem;
}
.hx-prompt-tag:hover { color: var(--bs-primary); }

/* --------------------------------------------------------- profile questions */
.hx-profile-answers {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
    gap: .75rem;
    margin-top: 1rem;
}
.hx-profile-answer {
    background: var(--bs-tertiary-bg);
    border-radius: .5rem;
    padding: .6rem .75rem;
}
.hx-profile-question {
    display: block;
    font-size: .75rem;
    color: var(--bs-secondary-color);
    margin-bottom: .15rem;
}

/* ------------------------------------------------------------------ reports */
.hx-action-report { color: var(--bs-secondary-color); }
.hx-action-report:hover { color: var(--bs-danger); }
/* Quiet, like the delete control beside it: a Report link on every incoming
   message must not read as an invitation. The hide-until-hover half is with
   that rule, scoped to pointers that can hover. */

.hx-report-snapshot {
    background: var(--bs-tertiary-bg);
    border-left: 3px solid var(--bs-border-color);
    border-radius: .35rem;
    padding: .6rem .75rem;
    overflow-wrap: anywhere;
}

/* ------------------------------------------------------------------ beacons */
/* The dot marks something live and time-limited, so it pulses rather than
   sitting still - but only for people who have not asked for less motion. */
.hx-beacon-dot {
    display: inline-block;
    width: .5rem;
    height: .5rem;
    border-radius: 50%;
    background: #e03131;
    margin-right: .3rem;
    vertical-align: baseline;
}
@media (prefers-reduced-motion: no-preference) {
    .hx-beacon-dot { animation: hx-pulse 2s ease-in-out infinite; }
}
@keyframes hx-pulse {
    0%, 100% { opacity: 1; }
    50% { opacity: .35; }
}
.hx-beacon { border-left: 3px solid #e03131; }
.hx-beacon-mine { border-left: 3px solid var(--bs-primary); }
.hx-beacon-body { overflow-wrap: anywhere; }
.hx-beacon-inline {
    font-size: .9rem;
    margin-bottom: .35rem;
    overflow-wrap: anywhere;
}

/* A range, not a number - so it should not look like a precise readout. */
.hx-distance { white-space: nowrap; }

/* ------------------------------------------------------------- looking now */
.hx-filters {
    display: flex;
    flex-wrap: wrap;
    gap: .5rem;
    align-items: center;
    margin-bottom: .9rem;
}
.hx-filters .form-select { width: auto; }
.hx-nearby { padding: .75rem .9rem; margin-bottom: .6rem; }
/* Someone who has said they are around now is the reason to open this page,
   so they carry the same accent the beacon list uses. */
.hx-nearby-beacon { border-left: 3px solid #e03131; }
.hx-nearby-about { font-size: .9rem; overflow-wrap: anywhere; }

/* ------------------------------------------------------- profile detail chips */
.hx-profile-details { display: flex; flex-wrap: wrap; gap: .35rem; }
.hx-chip {
    display: inline-block;
    background: var(--bs-tertiary-bg);
    border-radius: 999px;
    padding: .1rem .6rem;
    font-size: .82rem;
}
.hx-chip-strong {
    background: color-mix(in srgb, var(--bs-primary) 14%, transparent);
    color: var(--bs-primary);
}

/* -------------------------------------------------------------- private album */
.hx-album-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
    gap: .6rem;
}
.hx-album-item { margin: 0; }
.hx-album-item img {
    width: 100%;
    aspect-ratio: 1 / 1;
    object-fit: cover;
    border-radius: .5rem;
    display: block;
}
.hx-album-item figcaption { text-align: center; margin-top: .2rem; }

/* Health details are shown apart from the other chips: not colour-coded by
   status, which would sort people by it visually, just marked as its own
   thing. */
.hx-chip-health {
    background: var(--bs-tertiary-bg);
    border: 1px solid var(--bs-border-color);
}
.hx-health-fieldset {
    border-left: 3px solid var(--bs-border-color);
    padding-left: .9rem;
}

/* ------------------------------------------------------------- admin panel */
/* Deliberately unlike the member controls above it. Suspend and Delete sitting
   in the same visual language as Follow and Message is how the wrong one gets
   pressed. */
.hx-admin-panel {
    margin-top: 1rem;
    padding: .75rem .9rem;
    border: 1px dashed var(--bs-border-color);
    border-radius: .5rem;
    background: var(--bs-tertiary-bg);
}
.hx-admin-kicker {
    font-size: .7rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: .06em;
    color: var(--bs-secondary-color);
}

/* ------------------------------------------------------- people in search */
.hx-people-results {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
    gap: .5rem;
}
.hx-person-card {
    display: flex;
    align-items: center;
    gap: .5rem;
    padding: .5rem .6rem;
    border: 1px solid var(--bs-border-color);
    border-radius: .5rem;
    text-decoration: none;
    color: inherit;
}
.hx-person-card:hover { border-color: var(--bs-primary); }

/* The walkthrough reads as one thing at a time, so it stays narrow. */
.hx-onboarding { max-width: 34rem; }

/* Multi-select fields rendered as a checkbox grid. A native <select multiple>
   needs ctrl-click to add a second choice, which on a phone means it quietly
   behaves like a single select - so the one control people were most likely
   to use on a phone was the one that worked worst there. */
.hx-tag-grid {
    display: flex;
    flex-wrap: wrap;
    gap: .375rem;
}

.hx-tag-option {
    cursor: pointer;
    margin: 0;
}

.hx-tag-option input {
    position: absolute;
    opacity: 0;
    width: 0;
    height: 0;
}

.hx-tag-option span {
    display: inline-block;
    padding: .3rem .7rem;
    border: 1px solid var(--bs-border-color);
    border-radius: 2rem;
    font-size: .875rem;
    line-height: 1.2;
    background: var(--bs-body-bg);
    color: var(--bs-body-color);
    transition: background-color .12s ease, border-color .12s ease;
}

.hx-tag-option input:checked + span {
    background: var(--bs-primary);
    border-color: var(--bs-primary);
    color: #fff;
}

/* Keyboard users get the same affordance as the pointer: without this the
   focus ring is on the hidden input and lands nowhere visible. */
.hx-tag-option input:focus-visible + span {
    outline: 2px solid var(--bs-primary);
    outline-offset: 2px;
}

/* The profile carousel manager. */
.hx-photo-grid {
    display: flex;
    flex-wrap: wrap;
    gap: .5rem;
    margin-bottom: 1rem;
}

.hx-photo-tile {
    position: relative;
    width: 120px;
    height: 120px;
    border-radius: .5rem;
    overflow: hidden;
    cursor: grab;
}

.hx-photo-tile img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

.hx-photo-tile.is-dragging {
    opacity: .4;
}

.hx-photo-badge {
    position: absolute;
    left: .25rem;
    bottom: .25rem;
    padding: .1rem .4rem;
    border-radius: .25rem;
    font-size: .7rem;
    background: rgba(0, 0, 0, .65);
    color: #fff;
}

.hx-photo-remove {
    position: absolute;
    top: .25rem;
    right: .25rem;
    width: 1.5rem;
    height: 1.5rem;
    line-height: 1;
    border: 0;
    border-radius: 50%;
    background: rgba(0, 0, 0, .65);
    color: #fff;
    cursor: pointer;
}

/* The carousel as shown on a profile. Horizontal scroll rather than a
   JavaScript slider: it works with a thumb, a trackpad and a keyboard without
   any of them being special-cased. */
.hx-photo-strip {
    display: flex;
    gap: .5rem;
    overflow-x: auto;
    padding-bottom: .5rem;
    scroll-snap-type: x mandatory;
}

.hx-photo-strip a {
    flex: 0 0 auto;
    scroll-snap-align: start;
}

.hx-photo-strip img {
    width: 160px;
    height: 160px;
    object-fit: cover;
    border-radius: .5rem;
    display: block;
}

/* The lock screen. Narrow and centred: it is the only thing on the page, and
   it should not look like the site with the content missing. */
.hx-lock-screen {
    max-width: 22rem;
    margin: 3rem auto;
}

.hx-narrow-form {
    max-width: 24rem;
}

/* ---------- places ---------- */

.hx-place-map {
    height: clamp(14rem, 40vh, 24rem);
    border-radius: var(--hx-radius);
    border: 1px solid var(--bs-border-color);
}
.hx-place-here {
    border-left: 3px solid var(--bs-success);
}
.hx-place-coming {
    border-left: 3px solid var(--bs-warning);
}
.hx-place-faces {
    display: flex;
    flex-wrap: wrap;
    gap: .5rem;
}
.hx-place-face {
    display: flex;
    align-items: center;
    gap: .4rem;
    padding: .25rem .6rem .25rem .25rem;
    border: 1px solid var(--bs-border-color);
    border-radius: 999px;
    font-size: .875rem;
    text-decoration: none;
}
.hx-place-face img {
    border-radius: 50%;
}

/* Place pins on the map. An emoji in a disc rather than an image sprite:
   nothing to licence, nothing to keep in step with the category list, and a
   new category is drawable the moment it is added. */
.hx-place-pin {
    width: 34px;
    height: 34px;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 1.05rem;
    line-height: 1;
    border-radius: 50%;
    background: var(--bs-body-bg);
    border: 2px solid var(--bs-border-color);
    box-shadow: 0 1px 4px rgb(0 0 0 / 25%);
    position: relative;
}
/* Somewhere with people in it earns a ring, so a busy venue reads at a
   glance without having to click every pin. */
.hx-place-pin.is-busy {
    border-color: var(--bs-success);
}
.hx-place-pin-count {
    position: absolute;
    top: -4px;
    right: -4px;
    min-width: 17px;
    height: 17px;
    padding: 0 4px;
    border-radius: 999px;
    background: var(--bs-success);
    color: #fff;
    font-size: .65rem;
    font-weight: 700;
    line-height: 17px;
    text-align: center;
}
.hx-place-pin-legend {
    width: 22px;
    height: 22px;
    font-size: .8rem;
    display: inline-flex;
    vertical-align: middle;
    box-shadow: none;
}

/* An event that has been called off is still listed until its time passes:
   removing it from the page is how somebody turns up anyway. */
.hx-event-off h2 a {
    text-decoration: line-through;
}
.hx-event-off {
    opacity: .75;
}

/* The follow-me control on the map. */
.hx-follow-icon {
    vertical-align: -2px;
    margin-right: .15rem;
}
#follow.active {
    background: var(--bs-primary);
    border-color: var(--bs-primary);
    color: #fff;
}

/* Voice notes. */
.hx-media-voice {
    max-width: 100%;
    width: 16rem;
}

#voice-record.is-recording {
    background-color: var(--bs-danger);
    border-color: var(--bs-danger);
    color: #fff;
}

/* A view-once photo, shown as something to press rather than a picture: an
   <img> would be fetched by the browser on its own and spend the one look
   before the recipient had chosen to open anything. */
.hx-once {
    display: inline-block;
    padding: .6rem .75rem;
    border: 1px dashed var(--bs-border-color);
    border-radius: .5rem;
    max-width: 16rem;
}

.hx-once.is-spent {
    border-style: solid;
    color: var(--bs-secondary-color);
    font-size: .875rem;
}

/* The screenshot notice. Quiet rather than alarming: it is a standing fact
   about how screens work, not a warning about the person you are talking to,
   and something shouty would be ignored within a week. */
.hx-screenshot-notice {
    font-size: .8125rem;
    color: var(--bs-secondary-color);
    margin: .5rem 0 0;
}

/* Landing page. The only page seen before signing in, so it is wider than the
   signed-in column and reads as a page rather than a feed. Mobile first: the
   grid is one column until there is room for two, and nothing here depends on
   hover. */
.hx-landing {
    max-width: 46rem;
}

.hx-landing-hero {
    padding: 2.5rem 0 1.75rem;
}

.hx-landing-title {
    font-weight: 700;
    letter-spacing: -.02em;
    font-size: clamp(2.25rem, 9vw, 3.25rem);
    margin-bottom: .5rem;
}

.hx-landing-lede {
    font-size: clamp(1.05rem, 3.6vw, 1.3rem);
    color: var(--bs-secondary-color);
    max-width: 30rem;
    margin: 0 auto 1.5rem;
    text-wrap: balance;
}

.hx-landing-cta {
    display: flex;
    flex-direction: column;
    gap: .625rem;
    max-width: 20rem;
    margin: 0 auto;
}

@media (min-width: 30rem) {
    .hx-landing-cta {
        flex-direction: row;
        justify-content: center;
        max-width: none;
    }
    .hx-landing-cta .btn {
        min-width: 11rem;
    }
}

.hx-landing-fineprint {
    margin-top: 1rem;
    font-size: .85rem;
    color: var(--bs-secondary-color);
}

/* The location model is the reason to choose this over anything else, so it
   gets its own card above the grid rather than a quarter of it. */
.hx-landing-lead {
    border-left: 3px solid var(--bs-primary);
}

.hx-landing-grid {
    margin-top: 1rem;
}

.hx-landing-foot {
    margin-top: 1rem;
}

.hx-landing-foot {
    padding: 1.5rem 0 .5rem;
}

/* The install offer. Quiet on purpose - it is an offer, not news, and a
   banner that shouts gets dismissed on reflex by the people who would
   otherwise have taken it. */
.hx-install {
    align-items: flex-start;
}

.hx-install p:last-child {
    margin-bottom: 0;
}

/* A message and its controls on one line.
 *
 * They were a row underneath, which read as a separate thing rather than as
 * belonging to the message - and before that they were loose inline elements
 * that wrapped differently on every message. Beside the bubble, on its right,
 * is where somebody looks for them.
 */
.hx-msg-line {
    display: flex;
    align-items: center;
    gap: .35rem;
    min-width: 0;
}

/* Own messages hug the right, so the group does too. */
.text-end > .hx-msg-line {
    justify-content: flex-end;
}

.hx-msg-tools {
    display: inline-flex;
    align-items: center;
    gap: .1rem;
    flex-shrink: 0;
}

.hx-msg-tools form {
    display: inline-flex;
}

/* Icons rather than words: four labels beside every message is more text than
   the messages. The accessible name lives on the button, because a control
   whose label is a picture still has to say what it does. */
.hx-icon-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    /* 32px: comfortably past the 24px minimum for a touch target, without
       making four of them wider than the message they sit beside. */
    width: 2rem;
    height: 2rem;
    padding: 0;
    border: 0;
    border-radius: 999px;
    background: none;
    color: var(--bs-secondary-color);
    cursor: pointer;
    text-decoration: none;
    transition: color .12s ease-in-out, background-color .12s ease-in-out;
}

.hx-icon-btn:hover,
.hx-icon-btn:focus-visible {
    background: var(--hx-surface-muted);
    color: var(--bs-body-color);
}

.hx-icon-btn.is-danger:hover,
.hx-icon-btn.is-danger:focus-visible,
.hx-message-report:hover,
.hx-message-report:focus-visible {
    color: var(--bs-danger);
}

.hx-icon {
    display: block;
    pointer-events: none;   /* clicks belong to the button, not the path */
}

/* Time and the edited marker, quiet, on their own line under the message. */
.hx-msg-meta {
    display: flex;
    align-items: center;
    gap: .4rem;
    margin-top: .1rem;
    font-size: .72rem;
    line-height: 1.4;
    color: var(--bs-secondary-color);
}

.text-end > .hx-msg-meta {
    justify-content: flex-end;
}

/* Editing a message in place.
 *
 * Stands where the bubble was, so the thread does not jump and it is obvious
 * which message is being changed. Replaced window.prompt, which was a browser
 * dialog in the middle of a themed page, single-line for a message that may
 * not be, and silently inert in a browser set to suppress dialogs.
 */
.hx-edit-box {
    display: inline-block;
    width: min(100%, 26rem);
    text-align: left;
    margin: .1rem 0;
}

.hx-edit-field {
    resize: vertical;
    min-height: 2.6rem;
}

.hx-edit-buttons {
    display: flex;
    gap: .35rem;
    margin-top: .35rem;
}

.hx-edit-error {
    margin-top: .3rem;
    font-size: .78rem;
    color: var(--bs-danger);
}

/* "Seen", beside the time on your own messages. Quiet: it answers a question
   somebody had, rather than announcing itself. */
.hx-read {
    color: var(--bs-secondary-color);
}

/* Whether somebody is online. Quiet by default; the green is the only strong
   colour, and it is doing the work of a word so it also has one beside it. */
.hx-presence {
    display: inline-flex;
    align-items: center;
    gap: .3rem;
    font-size: .78rem;
    color: var(--bs-secondary-color);
}

.hx-presence-dot {
    width: .5rem;
    height: .5rem;
    border-radius: 50%;
    background: var(--bs-border-color);
    flex-shrink: 0;
}

.hx-presence.is-online {
    color: #2f9e44;
}

.hx-presence-dot.is-online,
.hx-presence.is-online .hx-presence-dot {
    background: #2f9e44;
}

/* The compact badge beside a name in a list. Small, so a row of them does not
   outweigh the names they belong to, but readable without a hover - a phone
   has none, and a bare dot there meant nothing. */
.hx-presence-inline {
    font-size: .7rem;
    margin-left: .35rem;
    white-space: nowrap;
    vertical-align: middle;
}

/* Photo opened over the page rather than navigated to. See lightbox.js: the
   links used to carry target="_blank", which on a phone - and especially in
   the installed PWA, where there is no browser chrome - left no way back to
   the conversation. */
.hx-lightbox {
    position: fixed;
    inset: 0;
    z-index: 1080;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 1rem;
    background: rgba(0, 0, 0, .92);
}
.hx-lightbox-img {
    max-width: 100%;
    /* Dynamic viewport units, so the mobile browser's collapsing address bar
       does not crop the photo. */
    max-height: 100dvh;
    object-fit: contain;
}
.hx-lightbox-close {
    position: absolute;
    top: max(.5rem, env(safe-area-inset-top));
    right: max(.5rem, env(safe-area-inset-right));
    /* Comfortably past the 44px minimum touch target: this is the way out,
       and it is the one control that must never be hard to hit. */
    width: 3rem;
    height: 3rem;
    font-size: 2rem;
    line-height: 1;
    color: #fff;
    background: rgba(0, 0, 0, .5);
    border: 1px solid rgba(255, 255, 255, .4);
    border-radius: 50%;
}
/* Stop the page behind scrolling under the photo. */
body.hx-lightbox-open { overflow: hidden; }

/* The way out of a conversation. Sized past the 44px touch minimum and given
   real padding: on a phone this is the control somebody reaches for while
   holding the device one-handed, and a cramped link is a missed tap. */
.hx-back {
    display: inline-flex;
    align-items: center;
    gap: .35rem;
    min-height: 44px;
    padding: .25rem .5rem .25rem 0;
    font-size: .95rem;
    text-decoration: none;
}
.hx-back:hover, .hx-back:focus-visible { text-decoration: underline; }

/* The chat screen fills the viewport rather than scrolling.
 *
 * Before this the page scrolled *and* the thread scrolled, which on a phone
 * meant two scrollers competing for one swipe - and the page won. The thread
 * only moved after tapping into it, which reads as a broken screen. In the
 * installed PWA it is worse: there is no address bar to scroll away, so the
 * page has nowhere to go and the gesture appears to do nothing at all.
 *
 * So on this page the body is the full viewport and cannot scroll, and the
 * thread is the only scrollable region. dvh rather than vh because vh is the
 * *largest* viewport on mobile - the one with the address bar hidden - which
 * would push the composer below the fold until the bar collapsed.
 */
.hx-chat-page {
    height: 100vh;
    height: 100dvh;
    overflow: hidden;
    display: flex;
    flex-direction: column;
}
.hx-chat-page .hx-main {
    /* min-height:0 is what lets a flex child shrink below its content and
       scroll. Without it the thread grows to fit every message and the page
       overflows again, which is the bug this is fixing. */
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    flex-direction: column;
    overflow: hidden;
}
.hx-chat-page .hx-thread {
    flex: 1 1 auto;
    height: auto;
    min-height: 0;
}
/* No room for it on a screen that does not scroll, and the footer links are
   reachable from every other page. */
.hx-chat-page .hx-footer { display: none; }

/* The contact form's honeypot. Taken off the screen rather than
   display:none - a bot that skips hidden inputs still fills this one, which
   is the point of it. Kept out of the tab order and hidden from assistive
   technology in the markup, so nobody reaches it by accident. */
.hx-trap {
    position: absolute;
    left: -9999px;
    width: 1px;
    height: 1px;
    overflow: hidden;
}
