/* site.css -- Tier 2: app-wide layout + component primitives.
   Consumes the tokens from colors.css. Component-specific styles (cards, pills,
   rails) land in M2; this file establishes the global shell + scale. */

:root {
    /* Spacing scale (4px base) */
    --space-1: 0.25rem;
    --space-2: 0.5rem;
    --space-3: 0.75rem;
    --space-4: 1rem;
    --space-5: 1.5rem;
    --space-6: 2rem;
    --space-8: 3rem;

    /* Layout widths (per design spec: center 680/720, rails 220/280) */
    --container-max: 1280px;
    --center-col-lg: 680px;
    --center-col-xl: 720px;
    --rail-left: 220px;
    --rail-right: 280px;

    /* Radii */
    --radius-sm: 6px;
    --radius-md: 10px;
    /* Added for the profile cards and grid tiles (ADO #639 round 6): both read as too square at
       --radius-md, and nothing else on the scale fit. A new step beats a one-off pixel value. */
    --radius-lg: 16px;
    --radius-pill: 999px;

    /* Type */
    --font-sans: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
}

*,
*::before,
*::after {
    box-sizing: border-box;
}

html {
    font-size: 16px;
}

body {
    margin: 0;
    min-height: 100vh;
    /* Sticky footer: column layout so .page-main grows and pins the footer to the
       viewport bottom on short pages. */
    display: flex;
    flex-direction: column;
    font-family: var(--font-sans);
    background-color: var(--page-bg);
    color: var(--text);
}

a {
    color: var(--link);
}

.text-muted {
    color: var(--text-muted) !important;
}

.page-container {
    width: 100%;
    max-width: var(--container-max);
    margin-inline: auto;
    padding-inline: var(--space-4);
}

.page-main {
    /* Grow to fill the space between TopBar and Footer (sticky-footer pattern). */
    flex: 1 0 auto;
    width: 100%;
    max-width: var(--container-max);
    margin-inline: auto;
    padding: var(--space-5) var(--space-4);
}

.site-footer {
    flex-shrink: 0;
}

/* ---- Comment thread ---- */
.comment-thread {
    margin-top: var(--space-5);
}

.comment-thread-title {
    font-weight: 800;
    font-size: 1.2rem;
    margin: 0 0 var(--space-4);
}

.comment-composer {
    margin-bottom: var(--space-5);
}

.comment-input {
    width: 100%;
    resize: vertical;
    padding: var(--space-3);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    background: var(--card-bg);
    color: var(--text);
    font: inherit;
}

.comment-composer-row {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: var(--space-3);
    margin-top: var(--space-2);
}

.comment-composer-hint {
    color: var(--text-muted);
    font-size: 0.85rem;
    margin-right: auto;
}

.comment-post {
    padding: var(--space-2) var(--space-5);
}

.comment-list {
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
}

.comment-main {
    display: flex;
    gap: var(--space-3);
}

.comment-avatar {
    width: 38px;
    height: 38px;
    border-radius: 50%;
    object-fit: cover;
    flex: 0 0 auto;
}

.comment-avatar-empty {
    background: var(--surface-2);
    display: inline-block;
}

.comment-body {
    min-width: 0;
}

.comment-meta {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    font-size: 0.85rem;
    color: var(--text-muted);
}

.comment-author {
    font-weight: 700;
    color: var(--text);
}

.comment-author.is-deleted {
    color: var(--text-muted);
    font-style: italic;
}

.comment-text {
    margin: var(--space-1) 0;
    overflow-wrap: anywhere;
}

.comment-actions {
    display: flex;
    gap: var(--space-3);
    color: var(--text-muted);
    font-size: 0.85rem;
}

.comment-like {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    border: 0;
    background: transparent;
    color: var(--text-muted);
    font: inherit;
    font-size: 0.85rem;
    padding: 0;
    cursor: pointer;
}

.comment-like.is-active {
    color: var(--like-active);
}

.comment-like-count {
    color: var(--text);
}

.comment-actions {
    display: flex;
    align-items: center;
    gap: var(--space-3);
}

.comment-reply-btn {
    border: 0;
    background: transparent;
    color: var(--text-muted);
    font: inherit;
    font-size: 0.85rem;
    padding: 0;
    cursor: pointer;
}

.comment-reply-btn:hover {
    color: var(--text);
}

.comment-reply-indicator {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    font-size: 0.85rem;
    color: var(--text-muted);
    margin-bottom: var(--space-2);
}

.comment-reply-cancel {
    border: 0;
    background: transparent;
    color: var(--text-muted);
    cursor: pointer;
    font-size: 1.2rem;
    line-height: 1;
    padding: 0;
}

.comment-replies {
    margin-top: var(--space-3);
    /* Tight per-level indent (~15px, matching the app) so deep threads don't scrunch -- the
       border-left carries the visual nesting cue rather than a wide avatar-width offset. */
    margin-left: var(--space-4);
    padding-left: var(--space-3);
    border-left: 2px solid var(--border);
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
}

.comment-sentinel,
.comment-end {
    padding: var(--space-4);
    text-align: center;
    color: var(--text-muted);
    font-size: 0.85rem;
}

/* ---- Category header ---- */
.category-header {
    padding: var(--space-5) 0 var(--space-3);
}

.category-title {
    font-weight: 800;
    font-size: clamp(1.5rem, 4vw, 2rem);
    margin: 0;
}

/* ---- Question card ---- */
.feed-list {
    display: flex;
    flex-direction: column;
    gap: var(--space-5);
}

.qcard {
    background: var(--card-bg);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    overflow: hidden;
    position: relative;
}

/* Per-card "Not for me" dismiss: subtle, top-right, theme-aware. Obvious on hover/focus, quiet at rest. */
.qcard-dismiss {
    position: absolute;
    top: 8px;
    left: 50%;
    transform: translateX(-50%);
    z-index: 3;
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 4px 8px;
    border: none;
    border-radius: var(--radius-sm, 8px);
    background: color-mix(in srgb, var(--card-bg) 70%, transparent);
    color: var(--text-muted);
    font-size: 0.75rem;
    line-height: 1;
    cursor: pointer;
    opacity: 0.7;
    transition: opacity 0.15s ease, background-color 0.15s ease, color 0.15s ease;
}

.qcard-dismiss:hover,
.qcard-dismiss:focus-visible {
    opacity: 1;
    color: var(--text, inherit);
    background: color-mix(in srgb, var(--surface-2, var(--card-bg)) 90%, transparent);
    outline: none;
}

.qcard-dismiss .bi {
    font-size: 0.8rem;
}

/* Inline "Removed -- Undo" confirmation that replaces a dismissed card. */
.qcard-removed {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 14px 16px;
    background: var(--card-bg);
    border: 1px dashed var(--border);
    border-radius: var(--radius-md);
    color: var(--text-muted);
    font-size: 0.875rem;
}

.qcard-removed-undo {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 6px 12px;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm, 8px);
    background: transparent;
    color: var(--text, inherit);
    font-size: 0.8125rem;
    font-weight: 600;
    cursor: pointer;
    transition: background-color 0.15s ease;
}

.qcard-removed-undo:hover,
.qcard-removed-undo:focus-visible {
    background: var(--surface-2, var(--card-bg));
    outline: none;
}

.media-header {
    position: relative;
    width: 100%;
    aspect-ratio: 4 / 3;
    background: var(--surface-2);
    overflow: hidden;
    /* No browser double-tap zoom on media: the gesture is double-tap-to-like (ADO #562). */
    touch-action: manipulation;
}

/* Instagram-style heart burst on double-tap-to-like (ADO #562). */
.media-heart-burst {
    position: absolute;
    inset: 0;
    margin: auto;
    width: 96px;
    height: 96px;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 88px;
    color: #fff;
    filter: drop-shadow(0 2px 12px rgba(0, 0, 0, 0.45));
    pointer-events: none;
    z-index: 4;
    animation: media-heart-burst 0.8s ease-out forwards;
}

@keyframes media-heart-burst {
    0% { transform: scale(0); opacity: 0; }
    15% { transform: scale(1.15); opacity: 0.95; }
    30% { transform: scale(1); opacity: 0.95; }
    70% { transform: scale(1); opacity: 0.95; }
    100% { transform: scale(1.08); opacity: 0; }
}

.media-image {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

.media-play-badge {
    position: absolute;
    inset: 0;
    margin: auto;
    width: 56px;
    height: 56px;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--overlay);
    color: #fff;
    border: none;
    border-radius: 50%;
    font-size: 1.8rem;
    cursor: pointer;
    padding: 0;
    z-index: 3;
}

/* Image carousel: all <img> are server-rendered (crawlable); scroll-snap track + nav for humans. */
.media-track {
    display: flex;
    width: 100%;
    height: 100%;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    scrollbar-width: none;
    -ms-overflow-style: none;
}

.media-track::-webkit-scrollbar {
    display: none;
}

/* Portrait-friendly carousel: each slide shows the FULL image (object-fit: contain) over a blurred,
   zoomed copy of itself that fills the letterbox -- mirrors the app's BlurredLetterbox so tall
   portraits are not cropped at the top. Landscape video keeps the cover crop; PORTRAIT video gets
   the same contain + backdrop treatment via .media-video.is-portrait (ADO #558, applied on poster
   load because orientation is unknown server-side). */
.media-header.media-carousel {
    aspect-ratio: 4 / 5;
}

.media-slide {
    position: relative;
    flex: 0 0 100%;
    width: 100%;
    height: 100%;
    scroll-snap-align: center;
    overflow: hidden;
}

.media-slide .media-image {
    position: relative;
    z-index: 1;
    object-fit: contain;
}

.media-backdrop {
    position: absolute;
    inset: 0;
    z-index: 0;
    background-size: cover;
    background-position: center;
    filter: blur(28px);
    transform: scale(1.2);
}

.media-nav {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    z-index: 3;
    width: 40px;
    height: 40px;
    display: flex;
    align-items: center;
    justify-content: center;
    border: 1.5px solid rgba(255, 255, 255, 0.85);
    border-radius: 50%;
    background: rgba(0, 0, 0, 0.6);
    color: #fff;
    font-size: 1.35rem;
    cursor: pointer;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.45);
}

.media-nav:hover {
    background: rgba(0, 0, 0, 0.8);
}

.media-prev {
    left: var(--space-2);
}

.media-next {
    right: var(--space-2);
}

/* Boundary: hide the arrow that would go past the ends (no wrap). Prev is hidden at the start
   by default; the scroll handler toggles .is-scrolled / .is-at-end as the user moves through. */
.media-prev {
    display: none;
}

.media-carousel.is-scrolled .media-prev {
    display: flex;
}

.media-carousel.is-at-end .media-next {
    display: none;
}

.media-dots {
    position: absolute;
    bottom: var(--space-3);
    left: 50%;
    transform: translateX(-50%);
    z-index: 3;
    display: flex;
    gap: 0.4rem;
    padding: 0.3rem 0.5rem;
    border-radius: var(--radius-pill);
    background: rgba(0, 0, 0, 0.4);
}

.media-dot {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: rgba(255, 255, 255, 0.55);
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
}

.media-dot.is-active {
    background: #fff;
    transform: scale(1.15);
}

/* Video: click-to-play. Poster + badge until clicked, then the Cloudflare player iframe fills it. */
.media-video-frame {
    width: 100%;
    height: 100%;
    border: 0;
    display: block;
}

/* Portrait video previews (ADO #558): the box stays 4/3 (no CLS), but a portrait poster gets
   the image-slide treatment -- contained frame over its own blurred backdrop -- and the play
   iframe keeps the poster's column (--video-ar, set from naturalWidth/Height on load) so
   playback matches the preview. Landscape videos are untouched (cover crop, full-box iframe). */
.media-video.is-portrait .media-video-poster {
    position: relative;
    z-index: 1;
    object-fit: contain;
}

.media-video.is-portrait .media-video-frame {
    position: relative;
    z-index: 1;
    width: auto;
    height: 100%;
    aspect-ratio: var(--video-ar, 9 / 16);
    margin-inline: auto;
}

/* Feed "load more" / sign-in CTA. */
.feed-more {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-5) 0 var(--space-6);
}

.load-more-btn {
    border: 1px solid var(--brand-yellow);
    background: transparent;
    color: var(--text);
    font-weight: 700;
    padding: 0.6rem 1.6rem;
    border-radius: var(--radius-pill);
    cursor: pointer;
}

.load-more-btn:hover {
    background: var(--brand-yellow);
    color: var(--on-brand-yellow);
}

.feed-more-text {
    margin: 0;
    color: var(--text-muted);
    font-size: 0.9rem;
}

.load-more-btn[disabled] {
    opacity: 0.7;
    cursor: progress;
}

.btn-spinner {
    display: none;
    width: 1.05em;
    height: 1.05em;
    border: 2px solid currentColor;
    border-top-color: transparent;
    border-radius: 50%;
    animation: pollaroo-spin 0.6s linear infinite;
}

/* While the load-more request is in flight, swap the label for the spinner. */
.load-more-btn.htmx-request .btn-text {
    display: none;
}

.load-more-btn.htmx-request .btn-spinner {
    display: inline-block;
}

@keyframes pollaroo-spin {
    to {
        transform: rotate(360deg);
    }
}

/* Comments side-panel: right-side drawer (desktop) / bottomsheet (mobile). Closing leaves the feed
   exactly where it was -- the feed is never navigated away from. */
.comments-panel {
    position: fixed;
    inset: 0;
    z-index: 1050;
    visibility: hidden;
    pointer-events: none;
}

.comments-panel.is-open {
    visibility: visible;
    pointer-events: auto;
}

.comments-panel-backdrop {
    position: absolute;
    inset: 0;
    background: rgba(0, 0, 0, 0.5);
    opacity: 0;
    transition: opacity 0.2s ease;
}

.comments-panel.is-open .comments-panel-backdrop {
    opacity: 1;
}

.comments-panel-sheet {
    position: absolute;
    background: var(--surface-2);
    display: flex;
    flex-direction: column;
    box-shadow: 0 -4px 24px rgba(0, 0, 0, 0.45);
}

.comments-panel-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: var(--space-3) var(--space-4);
    border-bottom: 1px solid rgba(127, 127, 127, 0.2);
    flex-shrink: 0;
}

.comments-panel-title {
    font-weight: 700;
    font-size: 1.05rem;
    color: var(--text);
}

.comments-panel-x {
    width: 36px;
    height: 36px;
    display: flex;
    align-items: center;
    justify-content: center;
    border: none;
    border-radius: 50%;
    background: rgba(127, 127, 127, 0.25);
    color: var(--text);
    cursor: pointer;
}

.comments-panel-body {
    flex: 1;
    overflow-y: auto;
    /* No top padding: that strip is what scrolled comments showed through above the sticky header.
       The .panel-sticky block provides its own top spacing instead. */
    padding: 0 var(--space-4) var(--space-4);
}

/* Question + answers + composer pinned to the top of the panel; only the comment list scrolls.
   Negative left/right margins + matching padding bleed the opaque background out to the body edges,
   so comments scrolling underneath are fully covered (no sliver shows past the sticky block). */
.panel-sticky {
    position: sticky;
    top: 0;
    z-index: 2;
    background: var(--surface-2);
    margin: 0 calc(-1 * var(--space-4)) var(--space-2);
    padding: var(--space-3) var(--space-4);
    border-bottom: 1px solid rgba(127, 127, 127, 0.2);
}

.panel-question {
    margin-bottom: var(--space-3);
}

/* The panel head already says "Comments", so hide the thread's duplicate heading. */
.panel-thread .comment-thread-title {
    display: none;
}

.panel-answers {
    list-style: none;
    margin: var(--space-3) 0 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
}

.panel-answer {
    padding: 0.5rem 0.9rem;
    border: 1px solid rgba(127, 127, 127, 0.25);
    border-radius: 10px;
    font-size: 0.95rem;
}

.panel-loading {
    display: flex;
    justify-content: center;
    padding: var(--space-6);
    color: var(--text-muted);
}

body.panel-open {
    overflow: hidden;
}

/* Desktop: drawer slides in from the right. */
@media (min-width: 768px) {
    .comments-panel-sheet {
        top: 0;
        right: 0;
        height: 100%;
        width: min(440px, 100%);
        transform: translateX(100%);
        transition: transform 0.25s ease;
    }

    .comments-panel.is-open .comments-panel-sheet {
        transform: translateX(0);
    }
}

/* Mobile: bottomsheet slides up. */
@media (max-width: 767.98px) {
    .comments-panel-sheet {
        left: 0;
        right: 0;
        bottom: 0;
        height: 85vh;
        border-radius: 16px 16px 0 0;
        transform: translateY(100%);
        transition: transform 0.25s ease;
    }

    .comments-panel.is-open .comments-panel-sheet {
        transform: translateY(0);
    }
}

.author-overlay {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    font-size: 0.85rem;
}

.author-avatar {
    width: 34px;
    height: 34px;
    border-radius: 50%;
    object-fit: cover;
}

.author-handle {
    font-weight: 600;
}

.author-dot,
.author-time {
    opacity: 0.85;
}

/* Category chip, right-aligned on the author row that sits above the question text (identical on
   text/image/video cards). margin-left:auto pushes it to the far right of the row. */
.author-category {
    margin-left: auto;
    flex-shrink: 0;
    background: var(--brand-yellow);
    color: var(--on-brand-yellow);
    font-weight: 700;
    font-size: 0.72rem;
    line-height: 1.2;
    padding: 0.15rem 0.55rem;
    border-radius: var(--radius-pill);
    text-decoration: none;
    white-space: nowrap;
}

.author-category:hover {
    background: var(--brand-yellow-hover);
}

.qcard-texthead {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-4) var(--space-4) 0;
}

.qcard-texthead .author-overlay {
    flex: 1;
    min-width: 0;
    color: var(--text-muted);
}

.qcard-body {
    padding: var(--space-4);
}

.qtext {
    margin-bottom: var(--space-3);
}

.qtext-content {
    margin: 0;
    font-weight: 700;
    font-size: 1.05rem;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.qtext.is-expanded .qtext-content {
    -webkit-line-clamp: unset;
    line-clamp: unset;
}

.qtext-toggle {
    border: 0;
    background: transparent;
    color: var(--text-muted);
    font-weight: 600;
    padding: var(--space-1) 0 0;
    cursor: pointer;
}

/* Inline hashtags -- accent-colored + theme-aware. Links in question text, plain highlight in answers
   (both share this class; the <a> adds the hover/cursor affordance). */
.hashtag-link {
    color: var(--link);
    font-weight: 600;
    text-decoration: none;
}

a.hashtag-link:hover {
    text-decoration: underline;
}

/* ---- Engagement footer ---- */
.engagement-footer {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    margin-top: var(--space-3);
    padding-top: var(--space-3);
    border-top: 1px solid var(--border);
}

.engagement-answered {
    color: var(--text-muted);
    font-size: 0.85rem;
    font-weight: 600;
}

.engagement-actions {
    display: flex;
    align-items: center;
    gap: var(--space-2);
}

.engagement-btn {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    border: 0;
    background: transparent;
    color: var(--text-muted);
    font: inherit;
    font-size: 0.9rem;
    padding: var(--space-1) var(--space-2);
    border-radius: var(--radius-sm);
    cursor: pointer;
    text-decoration: none;
}

.engagement-btn:hover {
    background: var(--surface-2);
    color: var(--text);
}

.engagement-btn .bi {
    font-size: 1.05rem;
}

/* Active colors mirror the app: likes red, saves blue. The count stays white in every state. */
.engagement-btn.is-active {
    color: var(--brand-yellow);
}

.engagement-like.is-active {
    color: var(--like-active);
}

.engagement-save.is-active {
    color: var(--save-active);
}

.engagement-count {
    color: var(--text);
}

/* ---- Answer pills ---- */
.answer-stack {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
}

.answer-pill {
    position: relative;
    display: flex;
    align-items: center;
    width: 100%;
    min-height: 44px;
    padding: var(--space-2) var(--space-3);
    border-radius: var(--radius-md);
    border: 1px solid transparent;
    overflow: hidden;
    text-align: left;
    font: inherit;
    font-weight: 600;
    color: var(--pill-answered-text);
}

/* Unanswered: plain clickable gray-blue pill. */
.answer-pill.is-unanswered {
    background: var(--pill-unanswered-bg);
    color: var(--pill-unanswered-text);
    cursor: pointer;
}

.answer-pill.is-unanswered:hover {
    filter: brightness(1.05);
}

/* Answered: the "bar" is a positioned fill behind the text, width driven by --pct. */
.answer-pill.is-result,
.answer-pill.is-selected {
    background: var(--surface-2);
}

.answer-pill.is-selected {
    border-color: var(--brand-yellow);
}

.answer-pill-fill {
    position: absolute;
    inset: 0 auto 0 0;
    width: calc(var(--pct, 0) * 1%);
    background: var(--pill-bar-fill);
    z-index: 0;
}

.answer-pill.is-selected .answer-pill-fill {
    background: color-mix(in srgb, var(--brand-yellow) 35%, var(--pill-bar-fill));
}

.answer-pill-text {
    position: relative;
    z-index: 1;
    /* Reserve ~40px on the right so the percent + icon never crowd the text (design lead). */
    padding-right: 56px;
    /* Wrap to a second line when the text doesn't fit the constrained width. */
    overflow-wrap: anywhere;
}

.answer-pill-meta {
    position: absolute;
    right: var(--space-3);
    top: 50%;
    transform: translateY(-50%);
    z-index: 1;
    display: flex;
    align-items: center;
    gap: var(--space-1);
}

.answer-pill-pct {
    font-variant-numeric: tabular-nums;
    font-weight: 700;
}

.answer-pill-icon.icon-selected {
    color: var(--brand-yellow);
}

.answer-pill-icon.icon-trophy {
    color: var(--trophy-gold);
}

/* ---- Feed hero ---- */
.feed-hero {
    text-align: center;
    padding: var(--space-5) 0 var(--space-4);
    overflow: hidden;
    transition: max-height 0.3s ease, opacity 0.2s ease, padding 0.3s ease;
    max-height: 360px;
}

.feed-hero.is-collapsed {
    max-height: 0;
    opacity: 0;
    padding-block: 0;
}

.feed-hero-heading {
    font-weight: 800;
    font-size: clamp(1.6rem, 4vw, 2.4rem);
    margin: 0 0 var(--space-2);
}

.feed-hero-tagline {
    color: var(--text-muted);
    margin: 0 auto var(--space-4);
    max-width: 36rem;
}

.stats-card {
    display: flex;
    justify-content: center;
    gap: var(--space-6);
    flex-wrap: wrap;
    background: var(--card-bg);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    padding: var(--space-4) var(--space-5);
    max-width: 30rem;
    margin-inline: auto;
}

.stat {
    display: flex;
    flex-direction: column;
    align-items: center;
}

.stat-number {
    font-weight: 800;
    font-size: 1.35rem;
}

.stat-label {
    color: var(--text-muted);
    font-size: 0.8rem;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

/* ---- Mobile category chip row (< lg) ---- */
.chip-row {
    display: flex;
    gap: var(--space-2);
    overflow-x: auto;
    scroll-snap-type: x proximity;
    padding-bottom: var(--space-2);
    margin-bottom: var(--space-3);
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
}

.chip-row::-webkit-scrollbar {
    display: none;
}

@media (min-width: 1024px) {
    .chip-row {
        display: none;
    }
}

.chip {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    flex: 0 0 auto;
    scroll-snap-align: start;
    padding: var(--space-2) var(--space-3);
    border-radius: var(--radius-pill);
    border: 1px solid var(--border);
    background: var(--card-bg);
    color: var(--text);
    text-decoration: none;
    font-weight: 600;
    font-size: 0.9rem;
    white-space: nowrap;
}

/* Category emoji, shared by the chip row, the left rail and the mobile sheet. Sits where the
   Bootstrap glyph used to (ADO #680), so the surrounding flex `gap` still does the spacing.
   line-height:1 keeps a colour emoji from pushing the row taller than its text, and the fixed
   width keeps names aligned across rows when glyphs differ in advance width. */
.category-emoji {
    flex: 0 0 auto;
    width: 1.15em;
    font-size: 1.05em;
    line-height: 1;
    text-align: center;
    font-style: normal;
}

.chip.is-active {
    background: var(--brand-yellow);
    border-color: var(--brand-yellow);
    color: var(--on-brand-yellow);
}

/* ---- Feed shell (3-column responsive grid) ---- */
.feed-shell {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: var(--space-5);
    align-items: start;
}

.feed-center {
    min-width: 0;
}

/* Desktop: left categories rail appears. */
@media (min-width: 1024px) {
    .feed-shell {
        grid-template-columns: var(--rail-left) minmax(0, var(--center-col-lg));
        justify-content: center;
    }
}

/* Wide desktop: right promo rail appears + wider center. */
@media (min-width: 1280px) {
    .feed-shell {
        grid-template-columns: var(--rail-left) minmax(0, var(--center-col-xl)) var(--rail-right);
    }
}

/* A shell with NO left rail (ADO #643 -- the question page dropped its filter rails). The columns
   above are positional, so without this the centre would land in the 220px left-rail track and the
   right rail in the centre's: the page does not just lose a column, it puts the remaining content
   in the wrong ones. Below 1024px both rails are hidden anyway and the base single-column rule
   already applies, so this only needs the two wide breakpoints. */
@media (min-width: 1024px) {
    .feed-shell--no-left {
        grid-template-columns: minmax(0, var(--center-col-lg));
    }
}

@media (min-width: 1280px) {
    .feed-shell--no-left {
        /* The centre keeps a real max-width rather than absorbing the freed track, and the right
           rail keeps its own, so it cannot end up stranded against the viewport edge. */
        grid-template-columns: minmax(0, var(--center-col-xl)) var(--rail-right);
    }
}

.left-rail {
    display: none;
}

@media (min-width: 1024px) {
    .left-rail {
        display: block;
    }
}

.left-rail-sticky {
    position: sticky;
    top: calc(56px + var(--space-4));
}

.rail-section-label {
    color: var(--text-muted);
    font-size: 0.75rem;
    font-weight: 700;
    letter-spacing: 0.08em;
    margin-bottom: var(--space-2);
    padding-inline: var(--space-3);
}

.rail-categories {
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
}

.rail-category {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-2) var(--space-3);
    border-radius: var(--radius-pill);
    color: var(--text);
    text-decoration: none;
    font-weight: 600;
}

.rail-category:hover {
    background: var(--surface-2);
}

.rail-category.is-active {
    background: var(--brand-yellow);
    color: var(--on-brand-yellow);
}

.rail-category-name {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.rail-category-count {
    margin-left: auto;
    color: var(--text-muted);
    font-size: 0.75rem;
    font-weight: 600;
}

.rail-category.is-active .rail-category-count {
    color: inherit;
    opacity: 0.7;
}

/* Parenthetical "unanswered by you" count beside the available total; lighter than the primary. */
.rail-category-unanswered,
.trending-unanswered {
    opacity: 0.65;
    font-weight: 500;
}

/* "Show more" expander holding the ranked overflow categories; stacks like the visible list. */
.rail-more {
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
}

.rail-more-toggle {
    cursor: pointer;
    list-style: none;
    padding: var(--space-2) var(--space-3);
    border-radius: var(--radius-pill);
    color: var(--text-muted);
    font-weight: 600;
    font-size: 0.85rem;
}

.rail-more-toggle::-webkit-details-marker {
    display: none;
}

.rail-more-toggle:hover {
    background: var(--surface-2);
}

/* ---- Right promo rail (>= xl) ---- */
.right-rail {
    display: none;
}

@media (min-width: 1280px) {
    .right-rail {
        display: block;
    }
}

.right-rail-sticky {
    position: sticky;
    top: calc(56px + var(--space-4));
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
}

.promo-card {
    background: var(--card-bg);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    padding: var(--space-4);
}

.get-pollaroo-card {
    text-align: center;
}

.get-pollaroo-glyph {
    width: 64px;
    height: auto;
}

.get-pollaroo-title {
    font-weight: 800;
    margin-top: var(--space-2);
}

.get-pollaroo-blurb {
    color: var(--text-muted);
    font-size: 0.9rem;
    margin: var(--space-2) 0 var(--space-3);
}

.get-pollaroo-card .store-cta {
    flex-direction: column;
}

.trending-list {
    list-style: none;
    margin: var(--space-2) 0 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
}

.trending-item {
    display: flex;
    align-items: center;
    gap: var(--space-3);
}

.trending-rank {
    color: var(--text-muted);
    font-weight: 700;
    width: 1.25rem;
    text-align: right;
}

.trending-tag {
    font-weight: 600;
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

a.trending-tag {
    color: inherit;
    text-decoration: none;
}

a.trending-tag:hover {
    color: var(--link);
    text-decoration: underline;
}

.trending-count {
    color: var(--text-muted);
    font-size: 0.85rem;
}

/* ---- TopBar ---- */
.topbar {
    position: sticky;
    top: 0;
    z-index: 1030;
    background: var(--card-bg);
    border-bottom: 1px solid var(--border);
}

.topbar-inner {
    position: relative;
    display: flex;
    align-items: center;
    gap: var(--space-4);
    height: 56px;
    max-width: var(--container-max);
    margin-inline: auto;
    padding-inline: var(--space-4);
}

.topbar-brand {
    display: inline-flex;
    align-items: center;
    text-decoration: none;
}

/* Marketing wordmark (brand-kit RCL); one asset for light + dark (ADO #550). */
.topbar-wordmark {
    height: 30px;
    width: auto;
    display: block;
}

/* The wordmark and the environment badge are one unit (ADO #639 round 6), so the badge sits tight
   against the logo instead of taking the topbar's own generous gap. Done here rather than in the
   shared brand.css, which Admin.Web also consumes -- see the parity ledger. */
.topbar-brandgroup {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
}

/* Centred in the BAR, not in the space left over between the brand and the actions -- those two
   sides differ in width, so a flex-based centre would drift. Absolute keeps it on the bar's true
   midline. pointer-events:none so a decorative line never eats a click meant for something under
   it. It only renders at lg and up, where there is room for it not to collide. */
.topbar-tagline {
    position: absolute;
    left: 50%;
    transform: translateX(-50%);
    pointer-events: none;
    color: var(--text-muted);
    font-size: 0.95rem;
}

.topbar-actions {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    margin-left: auto;
}

.topbar-link {
    color: var(--text);
    text-decoration: none;
    font-weight: 600;
    font-size: 0.95rem;
}

.topbar-link:hover {
    color: var(--brand-yellow-hover);
}

.topbar-icon-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 38px;
    height: 38px;
    border: 0;
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--text);
    font-size: 1.2rem;
    cursor: pointer;
}

.topbar-icon-btn:hover {
    background: var(--surface-2);
}

.brand-avatar {
    border: 0;
    background: transparent;
    color: var(--text);
    font-size: 1.5rem;
    line-height: 1;
    cursor: pointer;
    padding: 0;
    display: inline-flex;
    align-items: center;
}

.brand-avatar-img {
    width: 2rem;
    height: 2rem;
    border-radius: 50%;
    object-fit: cover;
    border: 1px solid var(--border);
}

/* ---- Account menu (ADO #589) ---- */
.account-toggle {
    gap: var(--space-2);
    padding: var(--space-1) var(--space-2);
    border-radius: var(--radius-pill);
    max-width: 14rem;
}

.account-toggle:hover {
    background: var(--surface-2);
}

/* Bootstrap's .dropdown-toggle draws its own caret; this menu uses a bi- icon instead, so the
   toggle deliberately does NOT carry that class. */
.account-toggle-handle {
    font-size: 0.95rem;
    font-weight: 700;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.account-toggle-caret {
    font-size: 0.75rem;
    color: var(--text-muted);
}

/* Themed, not .dropdown-menu-dark. The menu used to be hardcoded dark, so on the light theme it
   was a black panel with unreadable dividers -- the feed is force-dark but overlays follow the
   visitor's theme. */
.account-menu {
    --bs-dropdown-bg: var(--card-bg);
    --bs-dropdown-color: var(--text);
    --bs-dropdown-border-color: var(--border);
    --bs-dropdown-link-color: var(--text);
    --bs-dropdown-link-hover-color: var(--text);
    --bs-dropdown-link-hover-bg: var(--surface-2);
    --bs-dropdown-link-active-color: var(--text);
    --bs-dropdown-link-active-bg: var(--surface-2);
    --bs-dropdown-divider-bg: var(--border);
    --bs-dropdown-border-radius: var(--radius-md);
    min-width: 13rem;
    /* Bootstrap ships with $enable-shadows off, so --bs-dropdown-box-shadow is declared but never
       applied -- the panel needs a real property. This is the site's floating-panel shadow; keep
       any future floating panel on the same value. */
    box-shadow: 0 8px 28px var(--overlay);
}

.account-menu .dropdown-item {
    display: flex;
    align-items: center;
    gap: var(--space-3);
}

/* Sign out is a submit button inside a form, so it has no width of its own. */
.account-menu form {
    margin: 0;
}

.account-menu form .dropdown-item {
    width: 100%;
    border: 0;
    background: transparent;
    font: inherit;
    cursor: pointer;
}

/* Sign out is the menu's one destructive action, so it takes --danger (themed: #DC3545 light,
   #F87171 dark) rather than --text. The hover/focus/active restatements are required, not
   belt-and-braces: .account-menu pins the Bootstrap link-hover/active colors to var(--text),
   which would otherwise stomp the red the moment the row is highlighted. The icon inherits. */
.account-menu .account-menu-signout,
.account-menu .account-menu-signout:hover,
.account-menu .account-menu-signout:focus,
.account-menu .account-menu-signout:active {
    color: var(--danger);
}

/* ---- Brand buttons (plain anchors/buttons -- Telerik dropped, ADO #540) ---- */
.brand-btn-fill {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: var(--brand-yellow);
    border: 1px solid var(--brand-yellow);
    color: var(--on-brand-yellow);
    font-weight: 700;
    border-radius: var(--radius-pill);
    padding-inline: var(--space-4);
    text-decoration: none;
    cursor: pointer;
}

.brand-btn-fill:hover {
    background: var(--brand-yellow-hover);
    border-color: var(--brand-yellow-hover);
    color: var(--on-brand-yellow);
}

/* SECONDARY buttons are neutral (ADO #639 round 6): grey border, soft grey fill on hover. The
   solid .brand-btn-fill CTAs keep their brand yellow and are untouched -- see the modifier below
   for the one outlined button that also keeps it. */
.brand-btn-outline {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: transparent;
    border: 1px solid var(--border);
    color: var(--text);
    font-weight: 700;
    border-radius: var(--radius-pill);
    padding-inline: var(--space-4);
    text-decoration: none;
    cursor: pointer;
}

.brand-btn-outline:hover {
    background: var(--surface-2);
    color: var(--text);
}

/* The one exception, and it is flagged rather than assumed. "Ask" was named as a button that keeps
   its yellow, but in this codebase it is an OUTLINED button, not a solid one -- so the neutral rule
   above would have taken its yellow away. This preserves it without making every secondary button
   an exception. If Ask is meant to be a solid yellow CTA instead, that is a different change. */
.brand-btn-outline--brand {
    border-color: var(--brand-yellow);
}

.brand-btn-outline--brand:hover {
    background: color-mix(in srgb, var(--brand-yellow) 18%, transparent);
}

/* Topbar CTA sizing (the kendo-button skin used to provide the block padding). */
.topbar-action {
    padding-block: var(--space-2);
}

/* ---- Mobile hamburger sheet (Bootstrap Offcanvas) ---- */
.mobile-sheet {
    background: var(--card-bg);
    color: var(--text);
    border-left: 1px solid var(--border);
    width: min(86vw, 360px);
}

.mobile-sheet-header-actions {
    display: flex;
    align-items: center;
    gap: var(--space-2);
}

.mobile-sheet-body {
    display: flex;
    flex-direction: column;
    height: 100%;
}

.mobile-sheet-section-label {
    color: var(--text-muted);
    font-size: 0.75rem;
    font-weight: 700;
    letter-spacing: 0.08em;
    margin-bottom: var(--space-2);
}

.mobile-sheet-categories {
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
}

.mobile-sheet-category {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-3);
    border-radius: var(--radius-pill);
    color: var(--text);
    text-decoration: none;
    font-weight: 600;
}

.mobile-sheet-category:hover {
    background: var(--surface-2);
}

.mobile-sheet-category.is-active {
    background: var(--brand-yellow);
    color: var(--on-brand-yellow);
}

.mobile-sheet-category-chevron {
    margin-left: auto;
}

/* Account block at the top of the sheet -- parity with the desktop account dropdown (ADO #589). */
.mobile-sheet-account {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-3);
    color: var(--text);
    font-size: 1.5rem;
    line-height: 1;
}

.mobile-sheet-account-handle {
    font-size: 1rem;
    font-weight: 700;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.mobile-sheet-account-links {
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
    margin-bottom: var(--space-5);
}

/* Share Profile is a button, not a link -- reset the UA button chrome so it sits flush with the
   anchors beside it. */
.mobile-sheet-linkbtn {
    border: 0;
    background: transparent;
    width: 100%;
    text-align: left;
    font: inherit;
    cursor: pointer;
}

.mobile-sheet-spacer {
    flex: 1 1 auto;
    min-height: var(--space-5);
}

.mobile-sheet-actions {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
}

.mobile-sheet-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    padding-block: var(--space-3);
    text-decoration: none;
    text-align: center;
}

/* ---- Auth modal ---- */
.topbar-linkbtn {
    border: 0;
    background: transparent;
    cursor: pointer;
}

.auth-modal {
    position: relative;
    background: var(--card-bg);
    color: var(--text);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
}

/* Busy overlay shown while the IdToken is exchanged with the server. */
.auth-busy {
    position: absolute;
    inset: 0;
    z-index: 5;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--space-3);
    background: color-mix(in srgb, var(--card-bg) 88%, transparent);
    border-radius: var(--radius-md);
}

.auth-busy[hidden] {
    display: none;
}

.auth-busy-text {
    color: var(--text-muted);
    font-size: 0.9rem;
}

.auth-spinner {
    width: 2rem;
    height: 2rem;
    border: 3px solid var(--border);
    border-top-color: var(--brand-yellow);
    border-radius: 50%;
    animation: auth-spin 0.7s linear infinite;
}

@keyframes auth-spin {
    to {
        transform: rotate(360deg);
    }
}

.auth-provider {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-2);
    width: 100%;
    padding: var(--space-3);
    margin-bottom: var(--space-2);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    background: var(--surface-2);
    color: var(--text);
    font-weight: 600;
    cursor: pointer;
}

.auth-provider:hover {
    border-color: var(--text-muted);
}

.auth-divider {
    display: flex;
    align-items: center;
    text-align: center;
    color: var(--text-muted);
    font-size: 0.85rem;
    margin: var(--space-3) 0;
}

.auth-divider::before,
.auth-divider::after {
    content: "";
    flex: 1;
    border-top: 1px solid var(--border);
}

.auth-divider span {
    padding: 0 var(--space-3);
}

.auth-input {
    width: 100%;
    padding: var(--space-3);
    margin-bottom: var(--space-2);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    background: var(--card-bg);
    color: var(--text);
    font: inherit;
}

.auth-error {
    color: var(--danger);
    font-size: 0.85rem;
    margin-bottom: var(--space-2);
}

.auth-submit {
    width: 100%;
    padding: var(--space-3);
}

.auth-switch {
    margin-top: var(--space-3);
    text-align: center;
    color: var(--text-muted);
    font-size: 0.9rem;
}

.auth-switch-link {
    border: 0;
    background: transparent;
    color: var(--link);
    font-weight: 600;
    cursor: pointer;
}

.auth-fineprint {
    margin: var(--space-2) 0 0;
    text-align: center;
    color: var(--text-muted);
    font-size: 0.8rem;
}

/* ---- Engagement gate ---- */
.gate-body {
    text-align: center;
}

.gate-lead {
    font-weight: 700;
    font-size: 1.1rem;
    margin: 0 0 var(--space-2);
}

.gate-sub {
    color: var(--text-muted);
    margin-bottom: var(--space-4);
}

.gate-signup {
    width: 100%;
    padding: var(--space-3);
}

.gate-body .store-cta {
    justify-content: center;
}

/* ---- Back-to-feed link (question detail) ---- */
/* The ONE back-link pattern (ADO #638). There used to be a near-identical .self-profile-back doing
   the same job on /me and /me/settings; it is gone. Render this through the _BackLink partial rather
   than hand-writing the anchor, so the feed-restore attribute stays opt-in in one place. */
.back-link {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    margin-bottom: var(--space-3);
    color: var(--text-muted);
    text-decoration: none;
    font-size: 0.9rem;
    font-weight: 600;
}

.back-link:hover {
    color: var(--text);
}

/* ---- Transient toast (engagement errors / 429) ---- */
.toast-host {
    position: fixed;
    left: 50%;
    top: var(--space-5);
    transform: translateX(-50%);
    z-index: 1080;
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    pointer-events: none;
}

.app-toast {
    max-width: min(90vw, 24rem);
    padding: var(--space-3) var(--space-4);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    background: var(--card-bg);
    color: var(--text);
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.28);
    font-size: 0.9rem;
    opacity: 0;
    /* Host is top-anchored, so enter from slightly above. */
    transform: translateY(-0.5rem);
    transition: opacity 0.2s ease, transform 0.2s ease;
}

.app-toast.is-visible {
    opacity: 1;
    transform: translateY(0);
}

/* Success variant: brand lime with dark text (the brand's high-contrast pairing --
   lime-on-white/white-on-lime would be unreadable). Used for post-redirect confirmations. */
.app-toast--success {
    background: var(--brand-yellow);
    border-color: var(--brand-yellow);
    color: var(--on-brand-yellow);
    font-weight: 600;
}

/* ---- Branded 404 ---- */
.notfound {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    gap: var(--space-4);
    max-width: 560px;
    margin: var(--space-8) auto;
    padding-inline: var(--space-4);
}

.notfound-glyph {
    width: 240px;
    max-width: 60vw;
    height: auto;
}

.notfound-headline {
    font-weight: 800;
    font-size: clamp(1.5rem, 4vw, 2rem);
    margin: 0;
}

.notfound-body {
    color: var(--text-muted);
    margin: 0;
}

.notfound-actions {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-3);
}

.notfound-cta {
    text-decoration: none;
    padding: var(--space-3) var(--space-6);
}

.notfound-back {
    color: var(--text-muted);
    text-decoration: none;
    font-size: 0.95rem;
}

.notfound-back:hover {
    color: var(--text);
}

.notfound-store {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-2);
    margin-top: var(--space-4);
}

.notfound-store-label {
    color: var(--text-muted);
    font-size: 0.85rem;
}

/* Branded 500 (Error.cshtml) reuses the notfound block; only the request id is extra. */
.notfound-requestid {
    color: var(--text-muted);
    font-size: 0.85rem;
    margin-top: var(--space-4);
}

/* ---- Get-the-app funnel page (ADO #541) ---- */
.gettheapp {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    gap: var(--space-3);
    padding: var(--space-6) var(--space-4);
}

.gettheapp-headline {
    font-size: 1.75rem;
    font-weight: 800;
    margin: 0;
}

.gettheapp-body {
    color: var(--text-muted);
    max-width: 34rem;
    margin: 0;
}

/* QR is a desktop affordance: on a phone the store badges above are the direct path. */
.gettheapp-qr {
    display: none;
}

@media (min-width: 768px) {
    .gettheapp-qr {
        display: flex;
        flex-direction: column;
        align-items: center;
        gap: var(--space-2);
        margin-top: var(--space-4);
    }
}

.gettheapp-qr-label {
    color: var(--text-muted);
    font-size: 0.85rem;
}

/* ---- Image lightbox (ADO #557): shared native <dialog> ---- */
.media-lightbox {
    width: 100vw;
    height: 100vh;
    max-width: 100vw;
    max-height: 100vh;
    padding: 0;
    border: none;
    margin: 0;
    background: transparent;
    display: none;
    align-items: center;
    justify-content: center;
}

.media-lightbox[open] {
    display: flex;
}

.media-lightbox::backdrop {
    background: rgba(0, 0, 0, 0.9);
}

.media-lightbox-img {
    max-width: 100vw;
    max-height: 100vh;
    object-fit: contain;
}

/* Close + prev/next reuse the carousel-nav look at lightbox scale. */
.media-lightbox-close,
.media-lightbox-nav {
    position: fixed;
    z-index: 2;
    width: 44px;
    height: 44px;
    display: flex;
    align-items: center;
    justify-content: center;
    border: 1.5px solid rgba(255, 255, 255, 0.85);
    border-radius: 50%;
    background: rgba(0, 0, 0, 0.6);
    color: #fff;
    cursor: pointer;
}

.media-lightbox-close:hover,
.media-lightbox-nav:hover {
    background: rgba(0, 0, 0, 0.8);
}

.media-lightbox-close {
    top: var(--space-3);
    right: var(--space-3);
}

.media-lightbox-nav {
    top: 50%;
    transform: translateY(-50%);
}

.media-lightbox-prev {
    left: var(--space-3);
}

.media-lightbox-next {
    right: var(--space-3);
}

/* Expand affordance on image media (top-right of the card media box). */
.media-expand {
    position: absolute;
    top: var(--space-2);
    right: var(--space-2);
    z-index: 3;
    width: 34px;
    height: 34px;
    display: flex;
    align-items: center;
    justify-content: center;
    border: 1.5px solid rgba(255, 255, 255, 0.85);
    border-radius: 50%;
    background: rgba(0, 0, 0, 0.6);
    color: #fff;
    cursor: pointer;
    font-size: 0.85rem;
}

.media-expand:hover {
    background: rgba(0, 0, 0, 0.8);
}

/* The image itself is clickable too. */
.media-slide .media-image {
    cursor: zoom-in;
}

/* ---- Profile activity load-more sentinel (ADO #591) ----
   Lives INSIDE the .activity-grid / .activity-list <ul>, so it is an <li>. In the grid it must span
   every column, or it would be laid out as one more tile and sit in a lonely third of a row. */
.activity-sentinel {
    grid-column: 1 / -1;
    padding: var(--space-4);
    text-align: center;
    color: var(--text-muted);
    font-size: 0.85rem;
    list-style: none;
}

/* A sentinel that failed is no longer a spinner: it stops looking busy and says so. */
.activity-sentinel--failed {
    color: var(--text-muted);
    font-style: italic;
}

/* No end-of-list message here, unlike the comment thread. A profile's activity simply stops; a
   "no more activity" line under someone's own list reads as a comment on how much they have done. */
.activity-spinner {
    display: inline-block;
}

/* Identity triggers keep their surrounding text look. */
.author-identity,
.comment-author-link {
    display: inline-flex;
    align-items: center;
    gap: inherit;
    color: inherit;
    text-decoration: none;
}

.author-identity {
    gap: var(--space-2);
}

.author-identity:hover .author-handle,
.comment-author-link:hover .comment-author {
    text-decoration: underline;
}

/* ---- Quick-follow badge (ADO #624) ----
   .followable is the positioning context. The avatar is always its first child and sits at the
   wrapper's left edge, so --avatar-size is all the badge needs to find the image's bottom-right
   corner -- the wrapper itself may be wider than the avatar (the feed byline also holds the
   handle). Each surface sets the size via a modifier rather than an inline style. */
.followable {
    position: relative;
    display: inline-flex;
    align-items: center;
}

/* Each surface declares ONLY its avatar size; the badge derives from it, so the ratio is identical
   at all three render sites by construction and cannot drift when one avatar is resized (ADO #635 --
   the first cut hardcoded a badge size per surface and ran at 50-54% on two of them). */
.followable--byline {
    --avatar-size: 34px;
}

.followable--comment {
    --avatar-size: 38px;
}

.followable--wall {
    --avatar-size: 112px;
}

.follow-badge {
    position: absolute;

    /* ADO #635 geometry, agreed with AppSession (#634):
         badge diameter = 1/3 of the avatar
         badge CENTRE sits ON the avatar's circle edge at 45deg, so ~half of it overhangs.
       A point on the circle at 45deg is at (0.5 + 0.5*cos45) = 0.8536 of the diameter; subtracting
       half the badge (1/6 of the diameter) puts the box's top-left at 0.687. Both offsets are that
       one number, which is why it is not a tweakable magic value -- change the ratio and it moves. */
    --badge-size: calc(var(--avatar-size) / 3);
    left: calc(var(--avatar-size) * 0.687);
    top: calc(var(--avatar-size) * 0.687);

    width: var(--badge-size);
    height: var(--badge-size);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    border-radius: 50%;
    font-size: calc(var(--badge-size) * 0.62);
    line-height: 1;

    /* Keeps the disc's own outline visible against an avatar of any colour. Both states need it:
       the followed veil is white-on-white on a pale photo, and the yellow plus can sit on an
       equally bright frame. Web twin of the app's badgeEdgeShadow. */
    box-shadow: 0 0 0 1px var(--follow-badge-edge), 0 1px 2px var(--follow-badge-edge);
}

/* NOT FOLLOWING -- a real control and its own hit target. Solid brand yellow so it stays legible
   over any avatar photo; the ring separates the disc from a busy image. Every token here is
   theme-constant, which is why this state needs no per-theme rule: it sits on a picture, and the
   picture does not change with the theme. */
.follow-badge--follow {
    border: 1px solid var(--follow-badge-ring);
    background: var(--brand-yellow);
    color: var(--on-brand-yellow);
    cursor: pointer;
}

.follow-badge--follow:hover {
    background: var(--brand-yellow-hover);
}

/* The visible disc is only a third of the avatar, which is a poor tap target on the smaller
   surfaces. This enlarges the HIT area without touching the look, and it is biased outward
   (down/right, into empty space) rather than symmetrically -- expanding inward would eat more of
   the avatar's own navigate-to-profile tap, which is exactly the trade AppSession flagged on #622.
   Percentages resolve against the badge box, so this scales with the avatar like everything else. */
.follow-badge--follow::after {
    content: '';
    position: absolute;
    top: -30%;
    left: -30%;
    right: -70%;
    bottom: -70%;
}

/* FOLLOWING -- status, not a control (ADO #624): translucent veil, light ring, dark check.
   pointer-events:none is LOAD-BEARING, not styling. It lets the click reach the avatar underneath,
   so where the avatar is a profile link the badge stays part of that link rather than becoming a
   dead patch on the image. Removing it silently turns the badge into a hole. */
.follow-badge--following {
    border: 1px solid var(--follow-badge-ring);
    background: var(--follow-badge-veil);
    color: var(--follow-badge-glyph);
    pointer-events: none;
}

/* ---- Feed zero-results empty state (ADO #542) ---- */
.feed-empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    gap: var(--space-2);
    padding: var(--space-6) var(--space-4);
}

.feed-empty-headline {
    font-size: 1.25rem;
    font-weight: 700;
    margin: 0;
}

.feed-empty-body {
    color: var(--text-muted);
    margin: 0;
}

.feed-empty-cta {
    margin-top: var(--space-3);
    padding: var(--space-3) var(--space-6);
}

/* ---- Store CTA badges ---- */
.store-cta {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: var(--space-3);
}

.store-badge {
    display: inline-flex;
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-2) var(--space-4);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    background: var(--surface-2);
    color: var(--text);
    text-decoration: none;
}

.store-badge:hover {
    border-color: var(--text-muted);
}

.store-badge i {
    font-size: 1.6rem;
}

.store-badge-text {
    display: flex;
    flex-direction: column;
    line-height: 1.1;
    text-align: left;
}

.store-badge-text small {
    font-size: 0.65rem;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--text-muted);
}

.store-badge-text strong {
    font-size: 1rem;
}

/* ---- Ask page (M5) ---- */
.ask-shell {
    width: 100%;
    max-width: 40rem;
    margin: var(--space-6) auto var(--space-8);
    padding: var(--space-6);
    background: var(--card-bg);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
}

.ask-title {
    margin: 0 0 var(--space-5);
    font-size: 1.75rem;
    line-height: 1.15;
}

/* Gate states (signed-out / unverified) */
.ask-gate {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-4);
}

.ask-gate p {
    margin: 0;
    color: var(--text-muted);
}

/* Labels + fields */
.ask-label {
    display: block;
    font-weight: 600;
    font-size: 0.95rem;
    margin: var(--space-5) 0 var(--space-2);
}

.ask-input {
    display: block;
    width: 100%;
    padding: var(--space-3);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    background: var(--surface-2);
    color: var(--text);
    font: inherit;
}

.ask-input::placeholder {
    color: var(--text-muted);
}

.ask-input:focus-visible {
    outline: none;
    border-color: var(--brand-yellow);
    box-shadow: 0 0 0 2px color-mix(in srgb, var(--brand-yellow) 35%, transparent);
}

textarea.ask-input {
    resize: vertical;
    min-height: 4.5rem;
}

select.ask-input {
    width: auto;
    min-width: 12rem;
}


/* Answers */
.ask-answers {
    border: none;
    margin: var(--space-5) 0 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
}

.ask-answers legend {
    padding: 0;
    margin-bottom: var(--space-1);
    font-weight: 600;
    font-size: 0.95rem;
}

#ask-add-answer {
    align-self: flex-start;
    margin-top: var(--space-1);
}

/* Mature toggle */
.ask-check {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    margin-top: var(--space-5);
    font-size: 0.95rem;
    cursor: pointer;
}

.ask-check input {
    width: 1rem;
    height: 1rem;
    accent-color: var(--brand-yellow);
}

/* Media field */
.ask-media {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-3);
    margin-top: var(--space-5);
}

input[type="file"].ask-input {
    padding: var(--space-2);
}

input[type="file"].ask-input::file-selector-button {
    margin-right: var(--space-3);
    padding: var(--space-2) var(--space-4);
    border: 1px solid var(--brand-yellow);
    border-radius: var(--radius-pill);
    background: transparent;
    color: var(--text);
    font: inherit;
    cursor: pointer;
}

input[type="file"].ask-input::file-selector-button:hover {
    background: color-mix(in srgb, var(--brand-yellow) 18%, transparent);
}

#ask-progress {
    width: 100%;
    height: 0.5rem;
}

.ask-hint {
    margin: 0;
    color: var(--text-muted);
    font-size: 0.85rem;
}

/* "Get the app" funnel */
.ask-funnel {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
    margin-top: var(--space-6);
    padding: var(--space-4);
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
}

.ask-funnel-text {
    margin: 0;
    color: var(--text-muted);
    font-size: 0.9rem;
}

.ask-funnel .store-cta {
    justify-content: flex-start;
}

/* Error fragment (htmx-swapped) */
.ask-errors:empty {
    display: none;
}

.ask-errors {
    margin-bottom: var(--space-3);
}

.ask-error {
    margin: 0;
    padding: var(--space-3);
    border-radius: var(--radius-md);
    background: color-mix(in srgb, var(--danger) 15%, transparent);
    border: 1px solid color-mix(in srgb, var(--danger) 45%, transparent);
    color: var(--text);
    font-size: 0.9rem;
}

/* Submit */
.ask-submit {
    width: 100%;
    margin-top: var(--space-6);
    padding-block: var(--space-3);
    font-weight: 700;
}

/* ---- Ask: upload thumbnail + preview-before-post (ADO #564) ---- */
.ask-media-thumb {
    position: relative;
    width: 96px;
    height: 96px;
    padding: 0;
    border: 1px solid var(--border);
    border-radius: 8px;
    overflow: hidden;
    background: var(--surface-2);
    cursor: zoom-in;
}

.ask-media-thumb img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

.ask-media-thumb-play {
    position: absolute;
    inset: 0;
    margin: auto;
    width: 32px;
    height: 32px;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 2rem;
    color: #fff;
    filter: drop-shadow(0 1px 6px rgba(0, 0, 0, 0.5));
    pointer-events: none;
}

.ask-actions {
    display: flex;
    gap: var(--space-3);
    align-items: stretch;
}

.ask-actions .brand-btn-outline {
    margin-top: var(--space-6);
    padding-block: var(--space-3);
    font-weight: 700;
    flex: 1;
}

.ask-actions .ask-submit {
    flex: 2;
}

.ask-preview {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
    margin-top: var(--space-4);
}

.ask-preview-hint {
    text-align: center;
}

/* The draft card is display-only (the element also carries the inert attribute). */
.ask-preview-card {
    opacity: 0.98;
}

.ask-preview-actions {
    display: flex;
    gap: var(--space-3);
}

.ask-preview-actions .brand-btn-outline,
.ask-preview-actions .ask-submit {
    flex: 1;
    margin-top: 0;
    padding-block: var(--space-3);
    font-weight: 700;
}

/* ---- Open-in-app module (_OpenInApp partial) ---- */
.open-in-app { display: flex; flex-direction: column; gap: var(--space-3); align-items: center; padding: var(--space-4); }
.open-in-app-stores { display: flex; gap: var(--space-3); }
.open-in-app-qr { display: none; }
@media (min-width: 768px) { .open-in-app-qr { display: block; } }

/* ---- Footer ---- */
.site-footer {
    border-top: 1px solid var(--border);
    background: var(--card-bg);
}

.site-footer-inner {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    max-width: var(--container-max);
    margin-inline: auto;
    padding: var(--space-5) var(--space-4);
}

.site-footer-brand {
    display: flex;
    align-items: baseline;
    gap: var(--space-3);
}

.footer-wordmark {
    font-weight: 800;
    color: var(--text);
}

.footer-copy {
    color: var(--text-muted);
    font-size: 0.9rem;
}

.site-footer-links {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-4);
}

.site-footer-links a {
    color: var(--text-muted);
    text-decoration: none;
    font-size: 0.9rem;
}

.site-footer-links a:hover {
    color: var(--text);
}

/* ---- Mature content gate (_MatureGate partial) ---- */
.mature-gate {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-4);
    padding: var(--space-8) var(--space-4);
    text-align: center;
}

.mature-gate-heading {
    font-size: 1.4rem;
    font-weight: 800;
    margin: 0;
}

.mature-gate-body {
    color: var(--text-muted);
    max-width: 36ch;
    margin: 0;
}

.mature-gate-cta {
    padding: var(--space-3) var(--space-6);
}

/* -- Profile hard-wall (/u/{hash}) ------------------------------------------------------------ */

.profile-wall {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-6) var(--space-4);
    text-align: center;
}

.profile-wall-avatar {
    border-radius: 50%;
}

/* Headline counts on someone else's profile (ADO #594). Same shape as the self-profile stat row so
   the two pages read as one family. */
.profile-wall-stats {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: var(--space-3);
    width: 100%;
    max-width: 30rem;
    margin: var(--space-2) 0;
    padding: var(--space-4);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    background: var(--card-bg);
}

.profile-wall-stat dt {
    color: var(--text-muted);
    font-size: 0.8rem;
    font-weight: 600;
}

.profile-wall-stat dd {
    margin: var(--space-1) 0 0;
    font-size: 1.25rem;
    font-weight: 700;
}

@media (max-width: 575.98px) {
    .profile-wall-stats {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
}

/* -- Self profile (/me) and account settings (/me/settings) -- ADO #590, #593 ------------------ */

/* Shared sign-in prompt for both private routes. No redirect: the auth flow reloads the current
   URL, so signing in from here returns the visitor to the page they asked for. */
.profile-gate {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-4);
    padding: var(--space-6) var(--space-4);
    text-align: center;
}

.self-profile,
.account-settings {
    max-width: 44rem;
    margin-inline: auto;
    padding: var(--space-5) var(--space-4);
}

/* The profile lives in a rounded bordered card (ADO #640). Several smaller spacing complaints
   traced back to this container simply not existing. */
.self-profile-card {
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    background: var(--card-bg);
    padding: var(--space-4);
}

.self-profile-header {
    display: flex;
    align-items: center;
    gap: var(--space-5);
}

/* The yellow ring is deliberate brand chrome (ADO #639 round 6, confirmed by Sean). It replaces a
   1px --border hairline, so the ring is the avatar's border rather than an outer shadow -- box-sizing
   is border-box globally, so the frame stays 96px and the picture inside it shrinks by the ring. */
.self-profile-avatar {
    width: 96px;
    height: 96px;
    border-radius: 50%;
    object-fit: cover;
    border: 3px solid var(--brand-yellow);
    flex: 0 0 auto;
}

.self-profile-avatar--empty {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: var(--surface-2);
    color: var(--text-muted);
    font-size: 3rem;
    line-height: 1;
}

/* Identity now sits BELOW the avatar+stats header (ADO #639): handle, bio, then the URL row. */
.self-profile-identity {
    min-width: 0;
    margin-top: var(--space-4);
}

.self-profile-name {
    margin: 0;
    font-size: 1.5rem;
    overflow-wrap: anywhere;
}

.self-profile-bio {
    margin: var(--space-2) 0 0;
    color: var(--text-muted);
    overflow-wrap: anywhere;
}

/* URL row (ADO #639): globe icon, bare host, and NO underline. The full URL stays in the href --
   only the visible text is shortened, and only for links that passed the scheme check. */
.self-profile-social {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    margin-top: var(--space-2);
    color: var(--link);
    overflow-wrap: anywhere;
    text-decoration: none;
}

/* Hover restores the underline: removing it permanently would leave a link with no affordance
   beyond colour, which is the one cue a colour-blind visitor may not get. */
.self-profile-social:hover {
    text-decoration: underline;
}

/* Stats: still a definition list so the number/label pairing survives a screen reader, and a 2x2
   block sitting BESIDE the avatar rather than a four-across bar underneath it (ADO #639), matching
   the app. min-width:0 lets it shrink inside the flex header instead of forcing overflow.

   IT GROWS, and that is the point: filling the space beside the avatar gives TWO WIDE COLUMNS,
   which is the mockup. Sized to its content instead, it bunches into a cluster with dead space
   beside it.

   This rule previously said the opposite -- "does NOT grow, or the numbers drift apart into a bar
   again" -- and that warning outlived the layout it described. It was true while this was four
   columns across, where growing really did spread them into a bar. With two columns, growing is
   the correct thing and the old comment caused a wrong instruction before it was caught. If the
   column count ever changes, re-read this note before trusting the flex value. */
.self-profile-stats {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--space-2) var(--space-5);
    margin: 0;
    min-width: 0;
    flex: 1 1 auto;
    text-align: center;
}

/* Number FIRST and large, label small and muted beneath (ADO #640).
   column-reverse rather than reordering the markup: inside a definition list the <dt> must come
   before its <dd>, and swapping them in the DOM to get the visual order would break both the
   validity and the pairing a screen reader announces. The DOM order stays label-then-number, which
   is also how it should be READ; only the painting order flips. */
.self-profile-stat {
    display: flex;
    flex-direction: column-reverse;
    align-items: center;
    gap: var(--space-1);
}

.self-profile-stat dt {
    color: var(--text-muted);
    font-size: 0.75rem;
    font-weight: 600;
}

.self-profile-stat dd {
    margin: 0;
    font-size: 1.5rem;
    font-weight: 700;
    line-height: 1.1;
}

/* Half and half, content centred (ADO #640). No flex-wrap: wrapping is what let them bunch to the
   left at narrow widths instead of sharing the row. */
.self-profile-actions {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    margin-top: var(--space-5);
}

.self-profile-actions > * {
    flex: 1 1 0;
    justify-content: center;
    /* flex-basis 0 makes the split even regardless of label length, so "Settings" and
       "Share Profile" get the same width rather than sizing to their text. */
    min-width: 0;
}

.self-profile-actions .brand-btn-outline,
.self-profile-actions .brand-btn-fill {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-2) var(--space-4);
}

.account-settings-header {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    margin-bottom: var(--space-5);
}

.account-settings-header h1 {
    margin: 0;
    font-size: 1.5rem;
}

.account-settings-form {
    display: flex;
    flex-direction: column;
    gap: var(--space-5);
}

.account-settings-field {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
}

/* Bootstrap's form controls default to a light palette; bind them to the theme tokens so the
   fields follow the visitor's theme rather than sitting white on a dark page. */
.account-settings-field .form-control,
.account-settings-field .form-check-input {
    background-color: var(--card-bg);
    border-color: var(--border);
    color: var(--text);
}

.account-settings-field .form-control:focus {
    background-color: var(--card-bg);
    border-color: var(--brand-yellow);
    color: var(--text);
    box-shadow: none;
}

.account-settings-field .form-check-input:checked {
    background-color: var(--brand-yellow);
    border-color: var(--brand-yellow);
}

.account-settings-field .form-check-input:disabled {
    opacity: 0.5;
}

.account-settings-field .form-label,
.account-settings-field .form-check-label {
    color: var(--text);
    font-weight: 600;
    margin: 0;
}

.account-settings-hint {
    margin: 0;
    color: var(--text-muted);
    font-size: 0.85rem;
}

.account-settings-error {
    color: var(--danger);
    font-size: 0.85rem;
}

.account-settings-error:empty {
    display: none;
}

.account-settings-saved {
    padding: var(--space-3) var(--space-4);
    border-radius: var(--radius-sm);
    background: var(--surface-2);
    color: var(--text);
    margin-bottom: var(--space-4);
}

.account-settings-actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-3);
}

.account-settings-actions .brand-btn-fill,
.account-settings-actions .brand-btn-outline {
    padding: var(--space-2) var(--space-5);
}

.account-settings-appnote {
    margin-top: var(--space-6);
    color: var(--text-muted);
    font-size: 0.85rem;
}

@media (max-width: 575.98px) {
    /* The header STAYS a row here (ADO #639). The old rule stacked it because four stats side by
       side were unreadable on a phone; a 2x2 block is half the width and sits beside a smaller
       avatar comfortably, which is also how the app arranges it. Verified by rendering at 360px.
       The stats' own 2-column rule is no longer overridden here -- it is now the default. */
    .self-profile-avatar {
        width: 72px;
        height: 72px;
    }

    .self-profile-avatar--empty {
        font-size: 2.25rem;
    }

    .self-profile-header {
        gap: var(--space-4);
    }

    .self-profile-stats {
        gap: var(--space-1) var(--space-2);
    }
}

/* ---- Profile activity tabs + lists (ADO #633, restyled #639) ---- */
/* The tab strip and whatever it is showing sit TOGETHER in a second card (ADO #640), with the
   strip flush against the card's top edge and its own bottom border acting as the internal divider.
   overflow:hidden keeps the strip's corners following the card's radius. */
.activity {
    margin-top: var(--space-5);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    background: var(--card-bg);
    overflow: hidden;
}

/* Scrolls horizontally rather than wrapping: a wrapped second row reads as two separate navs. */
.activity-tabs {
    display: flex;
    border-bottom: 1px solid var(--border);
}

/* ICON-ONLY (ADO #639). The label lives in aria-label/title rather than in the box.
   THE TAB WIDTH AND THE UNDERLINE LENGTH ARE DELIBERATELY INDEPENDENT (round 4). While the
   underline was the tab's own bottom edge, the only way to shorten it was to narrow the tab, and a
   narrow tab is exactly what bunched the five icons against the left. The two asks fought each
   other. The underline is now its own element (below), so:
     tab   = an equal fifth of the strip, which spreads the icons across its full width;
     bar   = sized to the icon, centred under it.
   Do not put a border-bottom back on this rule. It re-couples them and the loop starts again. */
.activity-tab {
    flex: 1 1 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: var(--space-3) var(--space-2);
    font-size: 1.4rem;
    color: var(--text-muted);
    text-decoration: none;
    position: relative;
}

/* Hover and focus are the SAME treatment on purpose -- a keyboard user gets what a mouse user gets.
   Bolder and darker, but NO underline: that belongs to the selected tab alone, so the two stay
   distinguishable when someone hovers a tab that is not the current one. */
.activity-tab:hover,
.activity-tab:focus-visible {
    color: var(--text);
    -webkit-text-stroke: 0.5px currentColor;
}

.activity-tab.is-active {
    color: var(--text);
}

/* The indicator, decoupled from the tab's width. Sized to the ICON and centred under it, so the
   tab can be a full fifth of the strip while the bar stays small. bottom:-1px sits it on the nav
   rule. The 2px weight is deliberate: the ask was shorter, never thinner. */
.activity-tab.is-active::after {
    content: "";
    position: absolute;
    left: 50%;
    bottom: -1px;
    transform: translateX(-50%);
    width: 26px;
    height: 2px;
    border-radius: 1px;
    background: var(--brand-yellow);
}

/* The head mark is an image, so it cannot take currentColor the way the Bootstrap glyphs do. It
   gets a couple of px more than they do because its smile is the first detail to disappear as it
   shrinks. */
.activity-tab-mark {
    display: block;
    width: 26px;
    height: 26px;
}

/* Unselected tabs are muted. The mark carries its own brand colours, so mute it by desaturating
   rather than by recolouring it, and let hover and selected bring it back. */
.activity-tab:not(.is-active) .activity-tab-mark {
    filter: grayscale(1);
    opacity: 0.55;
}

.activity-tab:hover .activity-tab-mark,
.activity-tab:focus-visible .activity-tab-mark {
    filter: none;
    opacity: 1;
}

/* ---- Grid tabs: asked / liked / saved ---- */
.activity-grid {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: var(--space-2);
    margin: 0;
    padding: var(--space-3);
    list-style: none;
}

.activity-tile {
    position: relative;
    display: block;
    /* Square cells keep the rows aligned whatever the source image aspect ratio is. */
    aspect-ratio: 1 / 1;
    overflow: hidden;
    /* Rounder than the old --radius-sm (ADO #639), from the scale rather than a magic number, and
       rounder again at round 6. The list cards below share this value so the two layouts read as
       one family -- change both or neither. */
    border-radius: var(--radius-lg);
    background: var(--surface-2);
    color: var(--text);
    text-decoration: none;
}

.activity-tile-image {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

/* The no-picture tile (ADO #639 round 3). The face takes the PHOTO's place and nothing else about
   the tile changes -- same caption, same gradient, same hover. A text tile and a picture tile are
   the same component with different content behind them, not two designs. */
.activity-tile-mark-fill {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--tile-mark-bg);
}

/* Sized as a share of the TILE rather than in fixed pixels. The tab strip pins the mark to 22px
   because its smile vanishes below roughly 24; a tile is several times larger, so a proportional
   size keeps the face a focal point at every column width and stays well clear of that floor. */
.activity-tile-mark-fill img {
    width: 42%;
    max-width: 96px;
    height: auto;
}

/* A tile that HAS a working picture keeps the face in the DOM but out of sight. The sibling
   combinator identifies such a tile -- the face always follows the image -- which avoids :has(),
   still too new for the engines this site supports. */
.activity-tile-image ~ .activity-tile-mark-fill {
    display: none;
}

/* Hover / focus caption (ADO #639): question text at the bottom over a scrim fading in from the
   top. Faded in, not snapped.
   THE DEFAULT IS THE THEMED ONE, for the face tile (round 4). A face tile is a themed grey card, so
   white-on-black would be unreadable on it in light mode -- which is the whole reason the tile was
   temporarily made dark in round 3. Picture tiles override this below. */
.activity-tile-caption {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: flex-end;
    padding: var(--space-2);
    background: linear-gradient(to bottom, transparent 30%, var(--tile-mark-scrim) 100%);
    color: var(--text);
    font-size: 0.8rem;
    font-weight: 600;
    line-height: 1.3;
    overflow: hidden;
    opacity: 0;
    transition: opacity 160ms ease;
    /* The scrim fills the tile and pins its text to the bottom. Without the clamp below, a long
       question grows UPWARDS past the top edge and is cut mid-line. */
    /* It covers the whole tile, so it must not intercept the click that opens the question. */
    pointer-events: none;
}

/* A tile with a working PICTURE keeps white-on-dark: a photo is not themed, so the caption over it
   cannot be either. The sibling combinator identifies such a tile without :has(). */
.activity-tile-image ~ .activity-tile-caption {
    background: linear-gradient(to bottom, transparent 35%, rgba(0, 0, 0, 0.78) 100%);
    color: #FFFFFF;
}

/* ...unless that picture failed, in which case the face is showing and the themed treatment is
   correct again. Higher specificity, so this wins over the rule above. */
.activity-tile.has-broken-image .activity-tile-caption {
    background: linear-gradient(to bottom, transparent 30%, var(--tile-mark-scrim) 100%);
    color: var(--text);
}

/* Clamped so a long question ends in an ellipsis at the bottom of the tile instead of overflowing
   the top of it. Four lines is what a tile holds at every column width the grid produces. */
.activity-tile-caption-text {
    display: -webkit-box;
    -webkit-line-clamp: 4;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.activity-tile:hover .activity-tile-caption,
.activity-tile:focus-visible .activity-tile-caption {
    opacity: 1;
}

@media (prefers-reduced-motion: reduce) {
    .activity-tile-caption {
        transition: none;
    }
}

/* A poster that resolves but FAILS to load (ADO #639). Hide the image and its play badge and
   uncover the face, leaving the tile otherwise untouched -- the caption still behaves exactly as it
   does on every other tile, which is what makes a dead poster indistinguishable from a question
   that never had a picture. */
.activity-tile.has-broken-image .activity-tile-image,
.activity-tile.has-broken-image .activity-tile-play {
    display: none;
}

.activity-tile.has-broken-image .activity-tile-mark-fill {
    display: flex;
}

.activity-tile-play,
.activity-row-play {
    position: absolute;
    right: var(--space-2);
    bottom: var(--space-2);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 22px;
    height: 22px;
    border-radius: 50%;
    /* Resolves against the video frame, not the page, so this follows the same theme-constant
       reasoning as the follow badge. */
    background: var(--overlay);
    color: #FFFFFF;
    font-size: 0.8rem;
}

/* ---- List tabs: answered / skipped ---- */
.activity-list {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    margin: 0;
    padding: var(--space-3);
    list-style: none;
}

/* Discrete cards (ADO #639) sharing the grid tiles' radius, so the two layouts read as one
   family -- change both or neither. No fill AT REST: a card inside a card should not stack two
   identical surfaces (ADO #640). It does fill on hover, which is the next rule. */
.activity-row {
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
}

/* The WHOLE ROW fills on hover (round 6) and the title does NOT underline -- the rule that used to
   is deleted. An underlined title reads as a link sitting inside the row; filling the row makes it
   read as the single target it actually is. Same grey as the outlined buttons, deliberately. */
.activity-row:hover {
    background: var(--surface-2);
    border-color: var(--text-muted);
}

.activity-row-link {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-3);
    color: inherit;
    text-decoration: none;
}

.activity-row-thumb {
    position: relative;
    flex: 0 0 auto;
    width: 56px;
    height: 56px;
    border-radius: var(--radius-sm);
    overflow: hidden;
    background: var(--surface-2);
    display: inline-flex;
    align-items: center;
    justify-content: center;
}

.activity-row-thumb img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

.activity-row-thumb-text {
    color: var(--text-muted);
    font-size: 1.1rem;
}

.activity-row-body {
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
}

.activity-row-title {
    font-weight: 600;
    /* Two lines then ellipsis: one line truncates most real questions mid-thought. */
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.activity-row-meta {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    color: var(--text-muted);
    font-size: 0.85rem;
}

/* Yellow category chip, right-aligned on the row (ADO #636). margin-left:auto rather than a spacer
   element, so a row with no category simply ends after the age -- see the partial for why an
   uncategorised question renders nothing at all rather than an empty pill. */
.activity-row-category {
    flex: 0 0 auto;
    margin-left: auto;
    align-self: center;
    padding: var(--space-1) var(--space-3);
    border-radius: var(--radius-pill);
    background: var(--brand-yellow);
    color: var(--on-brand-yellow);
    font-size: 0.75rem;
    font-weight: 700;
    white-space: nowrap;
}

/* ---- Empty states ---- */
.activity-empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-8) var(--space-4);
    text-align: center;
}

.activity-empty-icon {
    font-size: 2rem;
    color: var(--text-muted);
}

.activity-empty-headline {
    margin: 0;
    font-weight: 700;
}

.activity-empty-body {
    margin: 0 0 var(--space-2);
    color: var(--text-muted);
    max-width: 34ch;
}
