/* ============================================================
   Glommer visual system – components
   Every site component (capitalised classes), animations,
   list resets, Quill editor, code copy, and other helpers.
   ============================================================ */

/* ---------- Cards & surfaces ---------- */

/* Everything that reads as a raised surface, listed by what it IS rather than
   composed into each one's class attribute. .Card is the plain standalone one
   (about/terms/privacy build it directly); .Form covers every form carrying the
   shared identity (see FormForm), so its subclasses are all included; likewise
   .User covers OtherUser/BannedUser/CurrentUser and .SearchBox its four boxes.
   The rule below it zeroes the trailing margin inside one, and has to name the
   same set - keep the two lists in step. */
:is(
    .Card,
    .Post, .Message, .User, .Report, .Conversation, .HelpArticleSummary,
    .Form, .SearchBox, .ThemeSelector, .RememberedDevice,
    .NavDropdownMenu, .NotificationDropdown, .ConfirmDialogCard,
    .PostMap, .MapScrubber, .NearbyLocationPrompt, .NotificationList, .WelcomeBanner,
    .TestResults, .TestSuitePanel, .NotificationTestPanel, .LogoutEverywherePanel,
    .UploadWorkerStatus, .TrendingTimerStatus, .WebSocketStatus, .BannedTrendingEntity,
    .SiteCounters, .ModerationActionCard,
    .VideoCallTestPanel
) {
    background-color: var(--surface);
    background-image: var(--surface-image, none);
    border: 1px solid var(--hairline);
    border-radius: var(--radius-card);
    padding: 1rem;
    box-shadow: var(--shadow-card);
    margin-bottom: 1rem;
    overflow: hidden;
}

.NavDropdownMenu {
    max-height: 90vh;
    overflow-y: auto;
}

/* The dropdown is already a card; the list inside it doesn't get to be a
   second one nested in the first. */
.NotificationDropdown .NotificationList {
    background: none;
    border: none;
    border-radius: 0;
    padding: 0;
    box-shadow: none;
    margin-bottom: 0;
}

:is(
    .Card,
    .Post, .Message, .User, .Report, .Conversation, .HelpArticleSummary,
    .Form, .SearchBox, .ThemeSelector, .RememberedDevice,
    .NavDropdownMenu, .NotificationDropdown, .ConfirmDialogCard,
    .PostMap, .MapScrubber, .NearbyLocationPrompt, .NotificationList, .WelcomeBanner,
    .TestResults, .TestSuitePanel, .NotificationTestPanel, .LogoutEverywherePanel,
    .UploadWorkerStatus, .TrendingTimerStatus, .WebSocketStatus, .BannedTrendingEntity,
    .SiteCounters, .ModerationActionCard,
    .VideoCallTestPanel
) > *:last-child {
    margin-bottom: 0;
}

/* ---------- Animations ---------- */

@keyframes spin {
    to { transform: rotate(360deg); }
}

@keyframes mount-in {
    from { opacity: 0; transform: translateY(0.5rem); }
    to   { opacity: 1; transform: translateY(0); }
}

.LoadingSpinner {
    display: flex;
    justify-content: center;
    padding: 1rem;
}

.LoadingSpinner::after {
    content: '';
    width: 1.5rem;
    height: 1.5rem;
    border: 2px solid var(--hairline);
    border-top-color: var(--accent);
    border-radius: 50%;
    animation: spin 0.6s linear infinite;
}

.MountIn {
    animation: mount-in 0.25s ease;
}

.SlidingOut {
    overflow: hidden;
    opacity: 0;
    transition: height 0.2s ease, opacity 0.2s ease;
}

@media (prefers-reduced-motion: reduce) {
    .SlidingOut { transition: none; }
}

/* ---------- Post hover effect ---------- */

.Post {
    transition: transform 0.15s ease, box-shadow 0.15s ease;
}

.Post:hover {
    transform: translateY(-1px);
    box-shadow: var(--shadow-pop);
}

/* ---------- A refused field ---------- */

/* Under the input it is about, in the colour everything that has gone wrong
   wears. Tied to the input by aria-describedby, so it is read out on reaching
   the box rather than found by hunting - which is the whole point of putting
   it here instead of in a toast floating at the edge of the screen. */
.FieldError {
    color: var(--danger);
    font-size: 0.8125rem;
    margin: 0.25rem 0 0;
}

/* The box itself, so the eye lands on which one before reading why. */
.InputField :is(input, textarea)[aria-invalid='true'],
.TextareaField :is(input, textarea)[aria-invalid='true'] {
    border-color: var(--danger);
    outline-color: var(--danger);
}

/* ---------- Skipping the navigation ---------- */

/* Out of the way until somebody tabs to it, then a real control in the corner.
   Moved off-screen rather than hidden: display:none and visibility:hidden both
   take it out of the tab order, which is the one thing it must stay in. */
.SkipLink {
    position: absolute;
    top: 0;
    inset-inline-start: 0;
    z-index: 1000;
    padding: 0.75rem 1rem;
    background-color: var(--surface);
    color: var(--ink);
    border: 1px solid var(--hairline);
    border-radius: 0 0 var(--radius-card) 0;
    transform: translateY(-120%);
}

.SkipLink:focus {
    transform: translateY(0);
}

/* The content it lands on is only focusable so the jump lands - it is not a
   control, and should not draw a ring as though it were. */
#PageContent:focus {
    outline: none;
}

/* ---------- Buttons ---------- */

.Button {
    background-color: var(--surface-2);
    background-image: var(--button-image, none);
    color: var(--ink) !important;
    border: none;
    border-radius: var(--radius-pill);
    padding: 0.5rem 1rem;
    font-weight: 550;
    font-size: 0.875rem;
    line-height: 1.4;
    cursor: pointer;
    transition: background-color 0.15s ease, color 0.15s ease, transform 0.1s ease, opacity 0.15s ease;
}

.Button:hover {
    background-color: color-mix(in srgb, var(--surface-2) 80%, var(--ink) 8%);
    /* Falls back to the resting image, so a theme with a gradient but no hover
       variant simply keeps the one it has rather than losing it on hover. */
    background-image: var(--button-image-hover, var(--button-image, none));
    color: var(--ink) !important;
}

.Button:active { transform: scale(0.97); }
.Button:disabled { opacity: 0.55; cursor: default; transform: none; }

/* Waiting on the server. Disabling alone only dims a control slightly and
   then nothing happens, which on anything slow reads as a press that did not
   land - so it pulses for as long as it is working. The animation beats the
   :disabled opacity above by being an animation, which is the whole reason
   this is not just another opacity rule. */
@keyframes throb {
    0%, 100% { opacity: 1; }
    50%      { opacity: 0.4; }
}

.Working {
    animation: throb 1.1s ease-in-out infinite;
    cursor: progress;
}

/* Somebody who has asked for less movement still needs to know it is working,
   so the pulse becomes a steady dimming rather than nothing at all. */
@media (prefers-reduced-motion: reduce) {
    .Working { animation: none; opacity: 0.5; }
}

/* Forms lay their fields out in a column, and the button that submits one sits
   at the trailing edge rather than stretching across it. */
.SubmitButton { align-self: flex-end; }

button[type='submit'].Button,
.ProfileSaveButton {
    background-color: var(--accent);
    background-image: var(--accent-image, none);
    color: var(--accent-ink) !important;
}

button[type='submit'].Button:hover,
.ProfileSaveButton:hover {
    background-color: var(--accent-strong);
    color: var(--accent-ink) !important;
}

.UserBanButton,
.TrendingEntityBanButton,
.UserBlockButton,
.ReportButton,
.PostDeleteButton,
.FriendRemoveButton,
.ReportedContentDeleteButton,
.AccountDeleteButton,
.UserModButton[data-is-mod='1'],
.Removing {
    background-color: transparent;
    /* The theme's button gradient stays off these in both states: painted over
       the transparent background it reads as the theme's accent, which is not
       the colour of taking something away. */
    background-image: none;
    color: var(--danger) !important;
    box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--danger) 35%, transparent);
}

.UserBanButton:hover,
.TrendingEntityBanButton:hover,
.UserBlockButton:hover,
.ReportButton:hover,
.PostDeleteButton:hover,
.FriendRemoveButton:hover,
.ReportedContentDeleteButton:hover,
.AccountDeleteButton:hover,
.UserModButton[data-is-mod='1']:hover,
.Removing:hover {
    background-color: var(--danger-soft);
    background-image: none;
    color: var(--danger) !important;
}


/* A checkbox or radio with a label wrapped round it is a control you press, so
   it takes the button's own colours and hover and keeps the box inside. The
   wrapping is what makes the whole of it the target rather than the box alone,
   so this only claims the ones built that way. */
label:has(> input[type='checkbox']),
label:has(> input[type='radio']) {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    background-color: var(--surface-2);
    background-image: var(--button-image, none);
    color: var(--ink);
    border-radius: var(--radius-pill);
    padding: 0.5rem 1rem;
    margin-bottom: 0;
    font-weight: 550;
    font-size: 0.875rem;
    line-height: 1.4;
    cursor: pointer;
    user-select: none;
    transition: background-color 0.15s ease, color 0.15s ease;
}

label:has(> input[type='checkbox']):hover,
label:has(> input[type='radio']):hover {
    background-color: color-mix(in srgb, var(--surface-2) 80%, var(--ink) 8%);
    background-image: var(--button-image-hover, var(--button-image, none));
    color: var(--ink);
}

a.Button {
    display: inline-block;
    text-align: center;
}

a.Button:hover { color: var(--ink) !important; }

/* ---------- Third‑party sign‑in ---------- */

.GoogleSignInButton {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    padding: 0.5rem 1rem;
    background-color: var(--surface);
    color: var(--ink) !important;
    border: 1px solid var(--hairline);
    border-radius: var(--radius-ctl);
    font-weight: 550;
    font-size: 0.9rem;
    box-shadow: var(--shadow-card);
    transition: background-color 0.15s ease, box-shadow 0.15s ease, transform 0.1s ease;
}

.GoogleSignInButton::before {
    content: '';
    flex: none;
    width: 1.15rem;
    height: 1.15rem;
    background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA0OCA0OCI+PHBhdGggZmlsbD0iI0VBNDMzNSIgZD0iTTI0IDkuNWMzLjU0IDAgNi43MSAxLjIyIDkuMjEgMy42bDYuODUtNi44NUMzNS45IDIuMzggMzAuNDcgMCAyNCAwIDE0LjYyIDAgNi41MSA1LjM4IDIuNTYgMTMuMjJsNy45OCA2LjE5QzEyLjQzIDEzLjcyIDE3Ljc0IDkuNSAyNCA5LjV6Ii8+PHBhdGggZmlsbD0iIzQyODVGNCIgZD0iTTQ2Ljk4IDI0LjU1YzAtMS41Ny0uMTUtMy4wOS0uMzgtNC41NUgyNHY5LjAyaDEyLjk0Yy0uNTggMi45Ni0yLjI2IDUuNDgtNC43OCA3LjE4bDcuNzMgNmM0LjUxLTQuMTggNy4wOS0xMC4zNiA3LjA5LTE3LjY1eiIvPjxwYXRoIGZpbGw9IiNGQkJDMDUiIGQ9Ik0xMC41MyAyOC41OWMtLjQ4LTEuNDUtLjc2LTIuOTktLjc2LTQuNTlzLjI3LTMuMTQuNzYtNC41OWwtNy45OC02LjE5Qy45MiAxNi40NiAwIDIwLjEyIDAgMjRjMCAzLjg4LjkyIDcuNTQgMi41NiAxMC43OGw3Ljk3LTYuMTl6Ii8+PHBhdGggZmlsbD0iIzM0QTg1MyIgZD0iTTI0IDQ4YzYuNDggMCAxMS45My0yLjEzIDE1Ljg5LTUuODFsLTcuNzMtNmMtMi4xNSAxLjQ1LTQuOTIgMi4zLTguMTYgMi4zLTYuMjYgMC0xMS41Ny00LjIyLTEzLjQ3LTkuOTFsLTcuOTggNi4xOUM2LjUxIDQyLjYyIDE0LjYyIDQ4IDI0IDQ4eiIvPjwvc3ZnPg==');
    background-size: contain;
    background-repeat: no-repeat;
    background-position: center;
}

.GoogleSignInButton:hover {
    background-color: var(--surface-2);
    color: var(--ink) !important;
    box-shadow: var(--shadow-pop);
}

.GoogleSignInButton:active { transform: scale(0.99); }

.AuthDivider {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    color: var(--muted);
    font-size: 0.78rem;
    text-transform: uppercase;
    letter-spacing: 0.06em;
}

.AuthDivider::before,
.AuthDivider::after {
    content: '';
    flex: 1;
    height: 1px;
    background-color: var(--hairline);
}

/* ---------- Emoji picker ---------- */

.EmojiPicker { position: relative; }

.EmojiPickerTriggerButton {
    font-size: 1.25rem;
    line-height: 1;
}

/* In Quill's toolbar the trigger is a bare emoji among the formatting
   controls, not a Button - and its panel opens downward, since the toolbar
   sits at the top of the editor with the page above it. Sized and floated
   exactly like the snow theme's own buttons (24px boxes, floated left), or
   the glyph rides its text baseline well below their row. */
.ql-toolbar .EmojiPickerTriggerButton {
    background: none;
    border: none;
    cursor: pointer;
    float: left;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    height: 24px;
    width: auto;
    padding: 0 5px;
    font-size: 1.125rem;
    line-height: 1;
}

/* The picker is the popup - there is nothing wrapped around it. A box drawn
   around it could only ever be told a size, and any size but the element's
   own leaves a rim down one side or a gap along another. It shrinks to fit a
   short viewport and scrolls its list inside itself.
 *
 * Selected on the bare element rather than through .EmojiPicker: opening it
 * moves it to the body, out of any wrapper, and a descendant selector would
 * drop everything here at exactly that moment. Where it sits is script's
 * business (it is placed against the trigger, in viewport coordinates); this
 * is only what it looks like and whether it is showing. */
emoji-picker {
    --background: var(--surface);
    --border-color: var(--hairline);
    --indicator-color: var(--accent);
    --input-border-color: var(--hairline);
    --input-font-color: var(--ink);
    --outline-color: var(--accent);
    --category-font-color: var(--ink);

    height: min(400px, 70vh);
    z-index: 20;
    border-radius: var(--radius-card);
    border-end-start-radius: var(--glom-corner);
    box-shadow: var(--shadow-pop);
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.15s ease;
    display: none;
}

emoji-picker.Active {
    opacity: 1;
    pointer-events: auto;
    display: flex;
}

.emoji-text {
    font-size: 1.25rem;
    line-height: 1;
    vertical-align: middle;
}

.emoji-only .MessageLine,
.emoji-only .PostBody { text-align: center; }

.emoji-only .emoji-text {
    font-size: 2.5rem;
    line-height: 1.3;
}

/* ---------- Forms (component classes) ---------- */

.CheckboxField {
    display: flex;
    align-items: center;
    gap: 0.5rem;
}

.CheckboxField input { width: auto; margin: 0; }
.CheckboxField label { display: inline; margin-bottom: 0; }

/* Reserves the widget's real rendered height up front, so Cloudflare's iframe
   loading in asynchronously doesn't jank whatever sits next to it down. */
.TurnstileWidget {
    width: 300px;
    height: 65px;
    overflow: hidden;
}

.LoginFormFooter {
    flex-wrap: wrap;
    gap: 0.5rem;
}

.TextareaField textarea {
    width: 100%;
    resize: vertical;
}

/* Holds the space the composer will take before it takes it. The editor is a
   CDN script and arrives noticeably late - without this the whole page below
   jumps several seconds after it is already being read.

   Measured, not guessed: 334.8px with everything loaded, in the 40rem column
   the content is capped to, which is the width every screen wider than that
   gets. Rounded up to the nearest quarter so it can only ever over-reserve -
   a shortfall is the jump this exists to prevent. Narrower screens are taller
   still (the buttons wrap), and take the smaller reservation rather than
   leaving a gap on every desktop to suit a phone. */
.Composer {
    min-height: 21rem;
    position: relative;
}

.Composer > h2 {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    text-align: center;
    margin: 0;
    width: 100%;
}

.ComposerFieldset {
    border-radius: var(--radius-card);
}

.PostComposerFields .InputField {
    flex: 1 1 0;
    min-width: 0;
}

.Composer fieldset { margin-bottom: 0.5rem; }
/* Two groups, not one row: what goes into the post on the left, what to do
   with it on the right. Anchoring everything to one edge meant any button
   changing width shifted every button beside it, so Post moved when a poll
   was added. Now a change is confined to its own group and the primary
   actions stay where the hand expects them. */
/* One wrapping row of buttons, not two groups side by side: a group is a
   flex item as wide as everything in it, so it claims a whole line and
   nothing can share it. Flowing them all as siblings lets the committing
   pair sit at the end of the last line the others leave room on. */
.ComposerActions {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.5rem;
}

/* Saving and posting stay together at the end, on whatever line they finish
   on. The auto margin holds them at the trailing edge - space-between would
   only do it while everything shared one line - and the pair neither wraps
   nor shrinks, so the one thing it can do when it stops fitting is move to
   the next line whole, rather than leaving Save Draft up there with Post
   alone underneath. */
.ComposerCommitActions {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin-inline-start: auto;
    flex-wrap: nowrap;
    flex-shrink: 0;
}

/* Every wording the button can show, stacked in one cell so the widest of
   them sets the width once and toggling never resizes it. */
.ToggleButton {
    display: inline-grid;
    grid-template-areas: 'label';
    place-items: center;
}

.ToggleButtonLabel { grid-area: label; }

.ToggleButtonLabel.Inactive { visibility: hidden; }

/* ---------- The action bar under a post ---------- */

/* The core actions are a glyph and nothing else, so they no longer need the
   width a word asked for. Padded to sit square-ish rather than as a stretched
   pill, and the glyph itself set a little larger than the text it replaced so
   it reads at a glance. The three worded ones - Edit, Delete, Report - keep
   the ordinary button padding: they are deliberate acts and the difference in
   weight is the point. */
.PostActionBar :is(
    .PostShareButton, .PostTranslateButton, .PostLikeButton,
    .PostRepostButton, .PostQuoteButton, .PostBookmarkButton, .PostPinButton
) {
    padding: 0.375rem 0.625rem;
    font-size: 1rem;
    line-height: 1.2;
}

/* The reply link is an anchor rather than a button, so it is named here
   instead of by a class of its own. */
.PostActionBar a.Button:not(.PostQuoteButton) {
    padding: 0.375rem 0.625rem;
    font-size: 1rem;
    line-height: 1.2;
}

.ComposerAttachmentList,
.PostEditAltList {
    list-style: none;
    margin: 0 0 0.5rem;
    padding: 0;
}

.ComposerAttachmentThumb {
    width: 3rem;
    height: 3rem;
    object-fit: cover;
    border-radius: var(--radius-ctl);
    flex-shrink: 0;
}

.ComposerAttachmentName {
    max-width: 10rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    flex-shrink: 0;
}

.ComposerAttachmentAltInput {
    flex: 1 1 8rem;
    min-width: 0;
}

.MessageComposerFields {
    display: grid;
    grid-template-columns: 1fr auto auto;
    align-items: end;
    gap: 0.5rem;
}

.MessageComposerFields .TextareaField { grid-row: 1 / 3; }
.MessageComposerFields .MessagePrivacyButton {
    grid-row: 1;
    grid-column: 2 / 4;
    justify-self: end;
}
.MessageComposerFields .EmojiPicker { grid-row: 2; grid-column: 2; }
.MessageComposerFields .SubmitButton { grid-row: 2; grid-column: 3; }

.EditorRow { align-items: flex-start; }

.EditorColumn {
    flex: 1 1 0;
    min-width: 0;
    min-height: 7.25rem;
}

.PostEditForm .EditorColumn { flex: 0 0 auto; }
.PostEditForm .ql-container,
.PostEditForm .ql-editor { height: auto; }

.LinkImagePreview {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    align-items: flex-start;
    flex-shrink: 0;
}

.LinkImagePreviewThumb {
    display: block;
    max-width: 8rem;
    max-height: 8rem;
    border-radius: var(--radius-ctl);
    object-fit: cover;
}

.ProgressBar {
    display: none;
    width: 100%;
    height: 0.4rem;
    border-radius: var(--radius-pill);
    overflow: hidden;
    margin-top: 0.5rem;
    accent-color: var(--accent);
}

.ProgressBar.Active { display: block; }

label.visually-hidden { margin-bottom: 0; }

/* ---------- Quill editor ---------- */

.ql-toolbar.ql-snow {
    background-color: var(--surface);
    border-color: var(--hairline);
    border-start-start-radius: var(--radius-card);
    border-start-end-radius: var(--radius-card);
}

.ql-container.ql-snow {
    border-color: var(--hairline);
    border-end-start-radius: var(--radius-card);
    border-end-end-radius: var(--radius-card);
    font-size: 0.9375rem;
}

.ql-editor {
    background-color: var(--surface);
    color: var(--ink);
    min-height: 4.5rem;
    border-end-start-radius: var(--radius-card);
    border-end-end-radius: var(--radius-card);
}

/* The other way of writing the same post, standing where the editor stands so
   switching moves nothing around it. Monospaced, because what is typed here
   is markers as much as it is words. */
.WelcomeBannerActions {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.5rem;
}

/* The checkbox already reads as a control of its own, so it does not also
   need the button chrome the shared label rule gives one. */
.WelcomeBannerActions .CheckboxField { margin-inline-start: auto; }

.MarkdownInput {
    /* A textarea is inline by default, so it sits on the text baseline and
       the line box round it reserves room underneath for descenders - a
       couple of pixels the editor it stands in for does not have, arriving
       and leaving with every switch. */
    display: block;
    width: 100%;
    min-height: 7.25rem;
    padding: 0.75rem;
    background-color: var(--surface);
    color: var(--ink);
    border: 1px solid var(--hairline);
    border-radius: var(--radius-card);
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 0.9375rem;
    line-height: 1.5;
    resize: vertical;
}

.ql-editor.ql-blank::before {
    color: var(--muted);
    opacity: 1;
    font-style: normal;
    font-size: 0.9375rem;
}

.ql-snow .ql-stroke { stroke: var(--ink); }
.ql-snow .ql-fill,
.ql-snow .ql-stroke.ql-fill { fill: var(--ink); }
.ql-snow .ql-picker { color: var(--ink); }
.ql-snow .ql-picker-options {
    background-color: var(--surface);
    border-color: var(--hairline);
}

/* ---------- Feed ---------- */

.PostMeta { flex-shrink: 0; }

.PostByline .TimestampLink { font-weight: 500; }
.PostByline .TimestampLink:hover { color: var(--ink); }
.PostByline .UserHeader,
.PostByline .UserHeaderInfo { min-width: 0; }
.PostByline .UserHeaderName {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.PostContent > h3,
.PostContent > a > h3 {
    margin-top: 0.5rem;
    overflow-wrap: anywhere;
}

/* A figure, so the margin is stated in full - the browser default of
   1em 40px would inset every attachment. */
.FeedItem {
    position: relative;
    max-height: 50vh;
    margin: 0.5rem 0 0;
}

.LinkItem > a {
    display: flex;
    align-items: flex-start;
    gap: 0.5rem;
}

.LinkItemText {
    flex: 1 1 auto;
    min-width: 0;
    overflow-wrap: anywhere;
}

.LinkItemText > h3 { margin-top: 0.5rem; }

.LinkItemImage {
    display: block;
    flex-shrink: 0;
    max-width: 8rem;
    max-height: 8rem;
    border-radius: var(--radius-ctl);
    object-fit: cover;
}

/* Square at the most, never taller than it is wide. The width is the column's,
   so the ratio is what holds the block down on a narrow screen and .FeedItem's
   half-viewport holds it down on a tall one. A portrait block pushes the words
   and the buttons under it off the screen, and what is inside is letterboxed
   into the block either way.

   The width is stated rather than left to fill, because a box with a ratio and
   no width of its own answers a capped height by narrowing to match it - which
   is a block half the column wide on any screen shorter than it is. */
.ImageItem,
.VideoItem,
.AudioItem {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    width: 100%;
    aspect-ratio: 1 / 1;
    overflow: hidden;
    background-color: var(--surface-2);
    border-radius: var(--radius-ctl);
}

/* The player sits on the floor of the block and the spectrum fills whatever is
   left, hard against it - a gap between them would read as two things rather
   than a picture of what is playing. */
.AudioItem {
    padding: 1rem;
    gap: 0;
}

.SpectrumAnalyser {
    flex: 1 1 auto;
    width: 100%;
    min-height: 0;
    color: var(--accent);
}

.FeedList .PostBody {
    max-height: 50vh;
    overflow-y: auto;
}

.PostBody {
    margin-top: 0.5rem;
    overflow-wrap: anywhere;
}

.PostBody > p:last-child { margin-bottom: 0; }

.PostBody blockquote {
    border-inline-start: 3px solid var(--accent);
    margin: 0.5rem 0;
    padding-inline-start: 1rem;
    color: var(--muted);
}

.PostBody pre {
    background-color: var(--surface-2);
    border-radius: var(--radius-ctl);
    padding: 0.5rem 1rem;
    overflow-x: auto;
    font-size: 0.85rem;
}

.ImageItem img,
.VideoItem video {
    max-width: calc(100% - 1rem);
    height: calc(100% - 1rem);
    object-fit: contain;
    border-radius: var(--radius-ctl);
    display: block;
    margin: 0.5rem;
}

.VideoItem video {
    min-width: 0;
    min-height: 0;
}

.AudioItem audio {
    flex: 0 0 auto;
    display: block;
    width: 100%;
}

.PostActionBar {
    margin-top: 0.5rem;
    padding-top: 0.5rem;
    border-top: 1px solid var(--hairline);
}

.PostActionBarActions {
    flex-wrap: wrap;
    justify-content: flex-end;
}

.LoginPrompt { text-align: center; }

.SeeMore {
    display: block;
    width: fit-content;
    margin-inline-start: auto;
}

.PostBody a,
.LoginPrompt a,
.UserBio a,
.UserFieldValue a {
    color: var(--link);
    text-decoration: underline;
    text-decoration-color: color-mix(in srgb, var(--link) 35%, transparent);
    text-underline-offset: 2px;
}

.PostBody a:hover,
.LoginPrompt a:hover,
.UserBio a:hover,
.UserFieldValue a:hover {
    color: var(--accent-strong);
    text-decoration-color: currentColor;
}

/* ---------- Avatars ---------- */

.Avatar {
    border-radius: var(--radius-pill);
    object-fit: cover;
    width: 3rem;
    height: 3rem;
    flex-shrink: 0;
}

.AvatarInitial {
    display: flex;
    align-items: center;
    justify-content: center;
    background-color: hsl(var(--avatar-hue), 55%, 42%);
    color: #fff;
    font-weight: 600;
    text-transform: uppercase;
    user-select: none;
}

.Avatar.AvatarInitial { font-size: 1.25rem; }

.Notification .Avatar {
    width: 2rem;
    height: 2rem;
}

.Notification .Avatar.AvatarInitial { font-size: 0.75rem; }

/* ---------- People ---------- */

.User { display: flex; align-items: flex-start; }
.BannedUserRow { width: 100%; }

.UserMain {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}

.UserBio {
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}

/* Name and value on one line each, the names aligned down a column so the
   values start together and the list reads as a table of particulars. */
.UserFields {
    display: grid;
    grid-template-columns: auto 1fr;
    column-gap: 0.5rem;
    row-gap: 0.25rem;
    margin: 0;
    font-size: 0.9em;
}

.UserFields dt {
    color: var(--muted);
    font-weight: 600;
}

.UserFields dt::after {
    content: ':';
}

.UserFields dd {
    margin: 0;
    min-width: 0;
    overflow-wrap: anywhere;
}

.UserFields dt,
.UserFields dd {
    align-self: baseline;
}

.CurrentUser .DisplayName,
.CurrentUser .UserBio { cursor: pointer; }

.CurrentUser .UserBio:empty::before {
    content: 'Add a bio…';
    color: var(--muted);
}

.ProfileEditButton {
    padding: 0;
    border: none;
    background: none;
    line-height: 1;
    font-size: 0.8rem;
    cursor: pointer;
    opacity: 0.55;
}

.ProfileEditButton:hover { opacity: 1; }
.User.Editing .ProfileEditButton { display: none; }

.DisplayNameInput,
.UserBioInput {
    width: 100%;
    font: inherit;
    color: var(--ink);
    background-color: var(--surface);
    border: 1px solid var(--hairline);
    padding: 0.5rem;
}

.DisplayNameInput {
    border-radius: var(--radius-pill);
}

.UserBioInput {
    border-radius: var(--radius-ctl);
    min-height: 4.5rem;
    resize: vertical;
}

.ProfileSaveButton {
    align-self: flex-end;
    margin-top: 0.5rem;
}

.UserLink {
    display: flex;
    align-items: flex-start;
    gap: 0.5rem;
}

.User h2 { margin-bottom: 0.1rem; }

.UserIdentity { min-width: 0; }

.DisplayName {
    min-width: 0;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.OtherUser .Avatar,
.CurrentUser .Avatar {
    width: 4.5rem;
    height: 4.5rem;
}

.UserSection {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    margin-bottom: 1.5rem;
}

/* ---------- Search ---------- */

.SearchBox {
    position: relative;
    display: flex;
    align-items: center;
    padding: 0.5rem;
}

.SearchInput {
    width: 100%;
    border: none;
    background-color: transparent;
    font-size: 1rem;
}

.SearchInput:focus { box-shadow: none; }

.SearchClearButton {
    display: none;
    flex: none;
    padding: 0 0.25rem;
    border: none;
    background: none;
    color: var(--danger) !important;
    font-size: 1rem;
    line-height: 1;
    cursor: pointer;
}

.SearchBox.HasQuery .SearchClearButton { display: block; }

.FaviconPreview {
    width: 32px;
    height: 32px;
}

/* ---------- Help ---------- */

.HelpSearchResults {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}

.HelpCategory h2 { margin-bottom: 0.5rem; }

.HelpArticleList {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr));
    column-gap: 1rem;
}

.HelpArticleSummary { display: block; }

.HelpArticleSummary:hover {
    border-color: var(--accent);
    color: var(--ink);
}

.HelpArticleSummary h3 { margin-bottom: 0.35rem; color: var(--accent); }
.HelpArticleSummary p { margin: 0; }

.HelpArticleCategory {
    display: block;
    margin-bottom: 0.5rem;
}

.HelpArticleBody { line-height: 1.6; }

.HelpArticleBody h2 {
    margin-top: 1.5rem;
    margin-bottom: 0.5rem;
}

.HelpArticleBody p,
.HelpArticleBody ul,
.HelpArticleBody ol { margin-bottom: 1rem; }

.HelpArticleBody li { margin-bottom: 0.35rem; }

.HelpBackLink {
    display: inline-block;
    margin-top: 1.5rem;
    color: var(--accent);
}

/* ---------- Messages ---------- */

/* The composer is fixed to the bottom of the window, so nothing in the flow
   knows it is there and the page leaves the room by hand. Measured by
   MessageComposer.js rather than fixed, because the composer is not one size -
   the video call button appears when the other person does, and the last
   messages in the thread ended up underneath it. The fallback is the height it
   has with nothing extra in it, so the page is right before any script runs. */
body.MessagesPage { padding-bottom: calc(var(--composer-height, 7rem) + 2rem); }

.Message {
    position: relative;
    width: fit-content;
    max-width: 78%;
    border-radius: var(--radius-card);
    border-end-start-radius: var(--glom-corner);
    padding: 0.5rem 1rem;
}

/* The line breaks and the indentation somebody typed, kept - and still the
   site's own typeface, wrapping like every other paragraph on the page. The
   element is preformatted because the shape of the text is part of what was
   said, not because the words are code. */
.MessageBody {
    margin: 0;
    font: inherit;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}

.MessageByline {
    display: flex;
    align-items: flex-start;
    gap: 0.5rem;
    margin-bottom: 0.25rem;
}

.MessageByline .UserHeader { min-width: 0; flex: 1 1 auto; }
.MessageByline .UserHeaderInfo { min-width: 0; }
.MessageByline .UserHeaderName {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.MessageByline .RelativeTime {
    flex-shrink: 0;
    margin-inline-start: auto;
}

.MessageLine {
    display: flex;
    align-items: flex-start;
    gap: 0.5rem;
}

.MessageLine > .MessageBody { flex: 1; }
.MessageLine > .ReportButton { flex-shrink: 0; }

.Message.Locked .MessageBody {
    font-style: italic;
    color: var(--muted);
}

.Message.Own {
    margin-inline-start: auto;
    background-color: var(--accent-soft);
    border-color: color-mix(in srgb, var(--accent) 22%, transparent);
    border-end-start-radius: var(--radius-card);
    border-end-end-radius: var(--glom-corner);
}

.MessageComposer {
    position: fixed;
    bottom: 1rem;
    left: 1rem;
    right: 1rem;
    max-width: 40rem;
    margin: 0 auto;
    background-color: var(--glass);
    -webkit-backdrop-filter: blur(14px);
    backdrop-filter: blur(14px);
    box-shadow: var(--shadow-pop);
}

/* A call takes the thread's place while it runs: the remote person fills the
   space the messages were in, with your own camera inset in a corner so you can
   see what you are sending. */
.VideoCallStage {
    position: relative;
    aspect-ratio: 16 / 9;
    background-color: #000;
    border-radius: var(--radius-card);
    overflow: hidden;
    margin-bottom: 1rem;
}

.VideoCallRemote {
    width: 100%;
    height: 100%;
    object-fit: contain;
}

.VideoCallLocal {
    position: absolute;
    right: 0.5rem;
    bottom: 0.5rem;
    width: 8rem;
    max-width: 30%;
    border-radius: var(--radius-ctl);
    box-shadow: var(--shadow-pop);
    /* Your own camera is a mirror, the way every other video app shows it. */
    transform: scaleX(-1);
}

/* Stands where the composer does, so the way out of a call is where the way to
   say something was. */
.VideoCallPanel {
    position: fixed;
    bottom: 1rem;
    left: 1rem;
    right: 1rem;
    max-width: 40rem;
    margin: 0 auto;
    padding: 0.5rem 1rem;
    border-radius: var(--radius-card);
    background-color: var(--glass);
    -webkit-backdrop-filter: blur(14px);
    backdrop-filter: blur(14px);
    box-shadow: var(--shadow-pop);
}

/* The admin check. A step carries its own outcome down its leading edge, so the
   one that stopped the run is findable without reading every line. */
.VideoCallTestResults {
    list-style: none;
    padding-inline-start: 0;
}

.VideoCallTestStep {
    border-inline-start: 3px solid var(--hairline);
    padding-inline-start: 0.5rem;
    margin-bottom: 0.5rem;
}

.VideoCallTestStep.Passed { border-inline-start-color: var(--accent); }
.VideoCallTestStep.Failed { border-inline-start-color: var(--danger); }

/* Empty until the run finishes, and taking no room while it is - the verdict
   appearing is itself the signal that the check is done. */
.VideoCallTestVerdict:empty { display: none; }

.VideoCallTestVerdict {
    margin-top: 0.5rem;
    padding: 0.5rem;
    border: 1px solid var(--hairline);
    border-radius: var(--radius-ctl);
    font-weight: 600;
}

.VideoCallTestVerdict.Passed {
    border-color: color-mix(in srgb, var(--accent) 40%, transparent);
    background-color: var(--accent-soft);
    color: var(--accent) !important;
}

.VideoCallTestVerdict.Partial {
    border-color: color-mix(in srgb, var(--muted) 40%, transparent);
    color: var(--muted);
}

.VideoCallTestVerdict.Failed {
    border-color: color-mix(in srgb, var(--danger) 40%, transparent);
    background-color: var(--danger-soft);
    color: var(--danger) !important;
}

.Conversation {
    transition: transform 0.15s ease, box-shadow 0.15s ease, color 0.15s ease;
}

.Conversation:hover {
    transform: translateY(-1px);
    box-shadow: var(--shadow-pop);
    color: var(--ink);
}

.Conversation h2 { margin-bottom: 0; }

/* ---------- Notifications ---------- */

.Notification > div,
.Notification a { padding: 0.5rem; }

.Notification a {
    border-radius: var(--radius-ctl);
    color: var(--ink);
    transition: background-color 0.15s ease;
}

.Notification a:hover { color: var(--ink); }

.Notification > a:hover { background-color: var(--surface-2); }

/* ---------- Carousel ---------- */

/* Media the author or a moderator classified as sensitive. Closed, it is a
   plain labelled bar taking up almost no room; open, the media sits below it
   exactly as an unmarked post's would, so nothing about the layout depends on
   which state it is in. */
.RepostAttribution {
    color: var(--muted);
    font-size: 0.8125rem;
    margin-bottom: 0.5rem;
}

.RepostAttribution a {
    font-weight: 550;
}

/* Matches the repost line above it, since the two do the same job: a quiet
   line of context over the card. The way back to the start of the thread sits
   at the far end of it, and drops to its own line when there is no room -
   which on a phone, against a long parent title, is most of the time. */
.ThreadContext {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.25rem 1rem;
    color: var(--muted);
    font-size: 0.8125rem;
    margin-bottom: 0.5rem;
}

.ThreadContext a {
    font-weight: 550;
}

.ThreadStartLink {
    white-space: nowrap;
}

/* ---------- Polls ---------- */

.Poll {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    margin-top: 0.5rem;
}

.PollOptionList {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}

/* Out-specifies the shared checkbox rule on purpose: an option is a row of the
   list, so it spans it rather than shrinking to its own words. */
.PollOption > .PollOptionControl {
    display: flex;
}

.PollVoteInput {
    width: auto;
    margin: 0;
}

.PollOptionResult {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
}

.PollOptionResult.Chosen .PollOptionTitle {
    font-weight: 600;
    color: var(--accent);
}

.PollOptionMeter {
    width: 100%;
    height: 0.5rem;
}

.PollOptionShare {
    color: var(--muted);
    font-size: 0.8125rem;
}

.PollVoteButton {
    align-self: flex-start;
}

.PollTally {
    display: flex;
    flex-direction: column;
    color: var(--muted);
    font-size: 0.8125rem;
}

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

.ComposerPoll {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    margin-top: 0.5rem;
    padding: 0.5rem;
    border: 1px solid var(--hairline);
    border-radius: var(--radius-card);
}

/* The schedule row sits above the buttons the way the poll does, framed the
   same. Hidden by an inline display:none until asked for - which is why the
   flex lives on the component selector rather than in a shared utility. */
.ComposerSchedule {
    display: flex;
    gap: 0.5rem;
    align-items: center;
    margin-bottom: 0.5rem;
    padding: 0.5rem;
    border: 1px solid var(--hairline);
    border-radius: var(--radius-card);
}

/* ---------- Signed-in profile actions ---------- */

/* A grid, so every control takes the widest one's width. The file input
   contributes no width of its own (width 0, min-width 100%) - its natural
   ~260px would otherwise become the column and drag every button wide. */
.CurrentUserActions {
    display: grid;
    gap: 0.5rem;
    justify-items: stretch;
    align-content: start;
}

/* Inside the column it is a cluster of controls, not a card within the
   profile card - the .Form surface chrome would inset its children 1rem and
   break the shared width. */
.CurrentUserActions .AvatarUploadForm {
    display: grid;
    gap: 0.5rem;
    justify-items: stretch;
    background: none;
    border: none;
    border-radius: 0;
    box-shadow: none;
    padding: 0;
    margin-bottom: 0;
    overflow: visible;
}

.CurrentUserActions input[type='file'] {
    width: 0;
    min-width: 100%;
}

.CurrentUserActions .SubmitButton {
    align-self: auto;
    justify-self: stretch;
}

/* ---------- Quoted post embed ---------- */

.QuotedPost {
    margin-top: 0.75rem;
    padding: 0.75rem;
    border: 1px solid var(--hairline);
    border-radius: var(--radius-ctl);
    background-color: var(--surface-2);
}

.QuotedPostTitle {
    font-weight: 640;
}

/* ---------- Nearby place search ---------- */

/* The card group clips its children (overflow: hidden, for the rounded
   corners); this one card holds a dropdown that must hang past its bottom
   edge, and nothing in it bleeds into the corners anyway. */
.NearbyLocationPrompt {
    overflow: visible;
}

.NearbyPlaceSearch {
    position: relative;
    max-width: 24rem;
}

.NearbyPlaceSuggestions {
    list-style: none;
    margin: 0.25rem 0 0;
    padding: 0.25rem;
    position: absolute;
    left: 0;
    right: 0;
    z-index: 20;
    background-color: var(--surface);
    background-image: var(--surface-image, none);
    border: 1px solid var(--hairline);
    border-radius: var(--radius-ctl);
    box-shadow: var(--shadow-pop);
}

.NearbyPlaceSuggestion {
    display: block;
    width: 100%;
    text-align: start;
    background: none;
    border: none;
    border-radius: var(--radius-ctl);
    padding: 0.5rem;
    color: var(--ink);
    cursor: pointer;
}

.NearbyPlaceSuggestion:hover {
    background-color: var(--accent-soft);
}

.PollMultipleToggle {
    align-self: flex-start;
}

.SensitiveMedia {
    margin-top: 0.5rem;
    border: 1px solid var(--hairline);
    border-radius: var(--radius-ctl);
    overflow: hidden;
}

.SensitiveMediaSummary {
    padding: 0.5rem 1rem;
    color: var(--muted);
    background: var(--button-image);
    cursor: pointer;
    user-select: none;
    font-size: 0.875rem;
}

.SensitiveMediaSummary:hover {
    background: var(--button-image-hover);
    color: var(--ink);
}

/* The disclosure triangle is the one part of a <summary> that can't be styled
   consistently across browsers, and the bar already reads as a control. */
.SensitiveMediaSummary::-webkit-details-marker {
    display: none;
}

.SensitiveMediaSummary { 
    list-style: none;
}

.SensitiveMedia[open] .SensitiveMediaSummary {
    border-bottom: 1px solid var(--hairline);
}

.SensitiveMedia .FeedItem {
    margin: 0.5rem;
}

.SensitiveMediaToggle {
    white-space: nowrap;
}

/* Sized by its words rather than stretched across the composer, which is a
   flex column and would otherwise give a rarely-used control the full width
   and its own row. */
.VideoCallStartButton {
    align-self: flex-start;
    padding: 0.25rem 0.75rem;
    font-size: 0.8125rem;
}

/* Content warning */

.ContentWarning {
    margin-top: 0.5rem;
    padding-inline: 0.5rem;
    border: 1px solid var(--hairline);
    border-radius: var(--radius-ctl);
    overflow: hidden;
}

.ContentWarning[open] {
    padding-bottom: 0.5rem;
}

.ContentWarningSummary {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    margin-inline: -0.5rem;
    padding: 0.5rem 1rem;
    color: var(--ink);
    background: var(--button-image);
    cursor: pointer;
    user-select: none;
    list-style: none;
}

.ContentWarningSummary:hover {
    background: var(--button-image-hover);
}

.ContentWarningSummary::-webkit-details-marker {
    display: none;
}

/* The same chevron the settings disclosures carry, and it turns the same way:
   two of these on one page pointing differently would read as two different
   kinds of control. */
.ContentWarningSummary::after {
    content: '';
    flex: none;
    width: 0.5rem;
    height: 0.5rem;
    border-right: 2px solid var(--muted);
    border-bottom: 2px solid var(--muted);
    transform: rotate(45deg);
    transition: transform 0.15s ease;
}

.ContentWarning[open] > .ContentWarningSummary::after {
    transform: rotate(-135deg);
}

.ContentWarning[open] .ContentWarningSummary {
    border-bottom: 1px solid var(--hairline);
}

.ContentWarningInput {
    width: 100%;
    margin-top: 0;
    margin-bottom: 0.5rem;
}

/* A label standing in for a file input, because the text on the input's own
   button belongs to the browser. The focus ring has to be drawn here: the
   thing being focused is the hidden input inside it. */
.ComposerFilePicker {
    cursor: pointer;
    margin-bottom: 0;
}

.ComposerFilePicker:focus-within {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

.Carousel {
    position: relative;
    overflow: hidden;
    border-radius: var(--radius-ctl);
}

.CarouselTrack {
    display: flex;
}

.CarouselSlide {
    display: none;
    width: 100%;
    flex-shrink: 0;
}

.CarouselSlide.Active {
    display: block;
}

.CarouselPrevButton,
.CarouselNextButton {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    background-color: rgb(15 18 16 / 0.55);
    -webkit-backdrop-filter: blur(6px);
    backdrop-filter: blur(6px);
    color: #ffffff !important;
    border: none;
    border-radius: var(--radius-pill);
    width: 2.25rem;
    height: 2.25rem;
    padding: 0;
    font-size: 1.25rem;
    line-height: 1;
    cursor: pointer;
    transition: background-color 0.15s ease;
}

.CarouselPrevButton:hover,
.CarouselNextButton:hover {
    background-color: rgb(15 18 16 / 0.75);
}

/* .Button:active's bare scale() would otherwise win on specificity and
   replace (not compose with) the translateY(-50%) centering above, jumping
   the button down by half its own height until the mouse releases. */
.CarouselPrevButton:active,
.CarouselNextButton:active {
    transform: translateY(-50%) scale(0.97);
}

.CarouselPrevButton {
    left: 0.5rem;
}

.CarouselNextButton {
    right: 0.5rem;
}

.CarouselCounter {
    position: absolute;
    bottom: 0.5rem;
    left: 0.5rem;
    background-color: rgb(15 18 16 / 0.55);
    -webkit-backdrop-filter: blur(6px);
    backdrop-filter: blur(6px);
    color: #ffffff;
    border-radius: var(--radius-pill);
    padding: 0.1rem 0.5rem;
    font-size: 0.75rem;
}

.CarouselAutoplayButton {
    position: absolute;
    bottom: 0.5rem;
    right: 0.5rem;
    background-color: rgb(15 18 16 / 0.55);
    -webkit-backdrop-filter: blur(6px);
    backdrop-filter: blur(6px);
    color: #ffffff !important;
    border: none;
    border-radius: var(--radius-pill);
    padding: 0.1rem 0.5rem;
    font-size: 0.75rem;
    cursor: pointer;
    transition: background-color 0.15s ease;
}

.CarouselAutoplayButton:hover {
    background-color: rgb(15 18 16 / 0.75);
}

.MediaFullscreenButton {
    position: absolute;
    top: 0.5rem;
    right: 0.5rem;
    background-color: rgb(15 18 16 / 0.55);
    -webkit-backdrop-filter: blur(6px);
    backdrop-filter: blur(6px);
    color: #ffffff !important;
    border: none;
    border-radius: var(--radius-pill);
    width: 2.25rem;
    height: 2.25rem;
    padding: 0;
    font-size: 1.1rem;
    line-height: 1;
    cursor: pointer;
    transition: background-color 0.15s ease;
}

.MediaFullscreenButton:hover {
    background-color: rgb(15 18 16 / 0.75);
}

.MediaFullscreenOverlay {
    position: fixed;
    inset: 0;
    margin: 0;
    z-index: 1000;
    background-color: #000000;
    display: flex;
    align-items: center;
    justify-content: center;
}

.InFullscreen {
    margin: 0;
}

.InFullscreen .FeedItem {
    margin-top: 0;
}

.InFullscreen.ImageItem,
.InFullscreen.VideoItem,
.InFullscreen.AudioItem,
.InFullscreen .ImageItem,
.InFullscreen .VideoItem,
.InFullscreen .AudioItem {
    width: 100vw;
    height: 100vh;
    max-width: 100vw;
    max-height: 100vh;
}

.Carousel.InFullscreen {
    width: 100vw;
    height: 100vh;
}

.MediaFullscreenOverlay .FeedItem {
    width: 100%;
}

/* ---------- Hashtags & Trending ---------- */

/* Chips wrap onto as many lines as they need, and a wrapped line sits hard
   against the one above it without this. Bottom only: the space belongs
   between the rows, not above the first one. */
.HashtagChip,
.HashtagNode,
.Entity {
    margin-block: 0 0.5rem;
}

/* The chip's own margin is the whole of the space between rows. Their lists
   carry a flex gap for the space along a row, and left to apply to both axes
   it would stack with that margin and separate the rows by twice as much. */
.TrendingEntityList,
.TrendingHashtagList,
.HashtagGraphList,
.EntityTypeLinks {
    row-gap: 0;
}

.HashtagChip,
.HashtagNode {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.5rem;
    border-radius: var(--radius-pill);
    background-color: var(--surface-2);
    color: var(--accent);
    text-decoration: none;
    font-size: 0.9375rem;
}

.HashtagChip:hover,
.HashtagNode:hover {
    background-color: var(--accent-soft);
}

.HashtagChipCount {
    color: var(--muted);
    font-size: 0.8125rem;
}

/* A topic's name, with what kind of thing it is under it - "Amazon" is a
   company or a river depending on which page this is, so the kind is not
   decoration. It is a link to the rest of its kind. */
.TopicHeading {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0.25rem 0.75rem;
    margin-bottom: 1rem;
}

.TopicHeading h2 {
    flex-basis: 100%;
    margin-bottom: 0.25rem;
}

.TopicSearchLink {
    font-size: 0.875rem;
}

/* Nothing at all until there are words for it. The paragraph is written to
   order for a topic nobody has opened before, so it arrives seconds after the
   page - an empty card holding its place would be a bordered box of nothing
   for as long as that took. */
.TopicSummaryCard.Awaited {
    display: none;
}

.TopicKind {
    color: var(--muted);
    font-size: 0.875rem;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

.TrendingEntityLink {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.5rem;
    border-radius: var(--radius-pill);
    background-color: var(--surface-2);
    color: var(--accent);
    text-decoration: none;
    font-size: 0.9375rem;
}

.TrendingEntityLink:hover {
    background-color: var(--accent-soft);
}

.TrendingEntityCount {
    color: var(--muted);
    font-size: 0.8125rem;
}

/* ---------- 3D force‑directed tag graph ---------- */

.HashtagGraphList {
    --HashtagEdge: rgb(31 36 33 / 0.28);
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
}

.HashtagGraphList.Active {
    display: block;
    position: relative;
    width: 100%;
    height: 26rem;
    margin-block: 0.5rem;
    overflow: hidden;
    cursor: grab;
    touch-action: none;
    user-select: none;
    -webkit-user-select: none;
    isolation: isolate;
}

.HashtagGraphList.Active:active {
    cursor: grabbing;
}

.HashtagGraphEdges {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    pointer-events: none;
}

.HashtagGraphList.Active .HashtagNode {
    top: 0;
    left: 0;
    font-weight: 550;
    line-height: 1;
    white-space: nowrap;
    will-change: transform, opacity;
    -webkit-user-drag: none;
}

@media (prefers-reduced-motion: reduce) {
    .HashtagNode { transition: none; }
}

/* The words for the language offer, rendered by the server because they are in
   a language this page is not in. LanguagePrompt.js takes them into a dialog
   and takes this away; it is never something to read in place. */
.LanguagePrompt { display: none; }

/* ---------- Confirm dialog ---------- */

.ConfirmDialogOverlay {
    position: fixed;
    inset: 0;
    z-index: 40;
    display: flex;
    align-items: center;
    justify-content: center;
    background-color: color-mix(in srgb, black 45%, transparent);
    padding: 1rem;
}

.ConfirmDialogCard {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    width: min(24rem, 100%);
    box-shadow: var(--shadow-pop);
}

.ConfirmDialogMessage {
    color: var(--ink);
}

.ConfirmDialogInput {
    resize: vertical;
}

.ConfirmDialogActions {
    justify-content: flex-end;
}

/* ---------- Test results ---------- */

.TestResultsBadge {
    display: inline-block;
    align-self: flex-start;
    padding: 0.25rem 0.5rem;
    border-radius: var(--radius-ctl);
    font-weight: 600;
    margin-bottom: 0.5rem;
}

.TestResultsPass {
    background-color: var(--accent-soft);
    color: var(--accent-strong);
}

.TestResultsFail {
    background-color: var(--danger-soft);
    color: var(--danger);
}

.TestResultsOutput {
    margin: 0;
    padding: 0.5rem;
    border-radius: var(--radius-ctl);
    background-color: var(--surface-2);
    color: var(--ink);
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 0.8125rem;
    line-height: 1.5;
    white-space: pre-wrap;
    overflow-x: auto;
}

/* ---------- Code copy button ---------- */

.CodeBlockWrapper {
    position: relative;
}

.CodeCopyButton {
    position: absolute;
    top: 0.5rem;
    right: 0.5rem;
    background: var(--color-bg-secondary, #f0f0f0);
    border: 1px solid var(--color-border, #ccc);
    border-radius: 4px;
    padding: 0.25rem 0.5rem;
    font-size: 0.75rem;
    cursor: pointer;
    opacity: 0;
    transition: opacity 0.2s;
}

.CodeBlockWrapper:hover .CodeCopyButton {
    opacity: 1;
}

.CodeCopyButton.copied {
    background: var(--color-success, #d4edda);
    border-color: var(--color-success-border, #c3e6cb);
}

/* ---------- Error & error list ---------- */

.Error { color: var(--danger); }

.ErrorList {
    background-color: var(--danger-soft);
    border: 1px solid color-mix(in srgb, var(--danger) 30%, transparent);
    border-radius: var(--radius-ctl);
    padding: 0.5rem 1rem;
    list-style: none;
    margin: 0;
    color: var(--danger);
}

/* ---------- List resets ---------- */

.FeedList,
.ReplyList,
.BookmarkList,
.NotificationList,
.UserList,
.TrendingHashtagList,
.TrendingEntityList,
.HashtagGraphList,
.BannedTrendingEntityList,
.BlockedServerList,
.RelayList,
.RememberedDeviceList,
.StagedPostList,
.ReportList,
.MessageList,
.PollOptionList,
.ConversationList {
    list-style: none;
    margin: 0;
    padding: 0;
}

/* Each notification carries its own padding, so the card around them needs
   only enough to keep the outermost ones off its edge. */
.NotificationList {
    padding: 0.5rem;
}


.TopicSummaryLabel {
    margin: 0.5rem 0 0;
}

.MachineTranslationLabel {
    margin: 0.5rem 0 0;
    font-style: italic;
}

/* ---------- Settings sections (fold‑outs) ---------- */

.SettingsSection {
    border-top: 1px solid var(--hairline);
}

.SettingsSection:last-of-type {
    border-bottom: 1px solid var(--hairline);
}

.SettingsSection > summary {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    cursor: pointer;
    padding-block: 1rem;
    list-style: none;
    font-family: var(--display-font);
    font-weight: 640;
    font-size: 1.2rem;
    line-height: 1.2;
    letter-spacing: -0.01em;
}

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

.SettingsSection > summary:hover {
    color: var(--accent);
}

.SettingsSection > summary::after {
    content: '';
    flex: none;
    width: 0.5rem;
    height: 0.5rem;
    border-right: 2px solid var(--muted);
    border-bottom: 2px solid var(--muted);
    transform: rotate(45deg);
    transition: transform 0.15s ease;
}

.SettingsSection[open] > summary::after {
    transform: rotate(-135deg);
}

.SettingsSection[open] > :last-child {
    margin-bottom: 1.25rem;
}

/* ---------- Reports ---------- */
.ReportDetails {
    flex: 1;
    min-width: 0;
}

.ReportActions {
    flex-shrink: 0;
}

.Report .PostContent {
    border-inline-start: 3px solid var(--accent);

    background-color: var(--surface-2);
    border-radius: var(--radius-ctl);
    margin: 0;
    padding: 0.5rem;
    overflow-wrap: anywhere;
}

.ReportedMedia {
    max-width: 100%;
    max-height: 24rem;
    border-radius: var(--radius-ctl);
}

/* ---------- Toasts ---------- */
.ToastContainer {
    position: fixed;
    bottom: 1rem;
    inset-inline-end: 1rem;
    z-index: 30;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    width: min(22rem, calc(100vw - 2rem));
}

.Toast {
    display: flex;
    align-items: flex-start;
    gap: 0.5rem;
    background-color: var(--surface);
    border: 1px solid color-mix(in srgb, var(--danger) 30%, transparent);
    border-radius: var(--radius-card);
    box-shadow: var(--shadow-pop);
    padding: 0.5rem 1rem;
    color: var(--ink);
    font-size: 0.875rem;
    opacity: 0;
    transform: translateY(0.5rem);
    transition: opacity 0.2s ease, transform 0.2s ease;
}

.Toast.Active {
    opacity: 1;
    transform: translateY(0);
}

.ToastMessage {
    flex: 1 1 auto;
}

.ToastCloseButton {
    background: none;
    border: none;
    padding: 0;
    line-height: 1;
    color: var(--muted);
    cursor: pointer;
    transition: color 0.15s ease;
}

.ToastCloseButton:hover {
    color: var(--ink);
}

@media (prefers-reduced-motion: reduce) {
    .Toast {
        transition: none;
    }
}

/* ---------- Map ---------- */

/* Card supplies the frame (shadow, rounded corners, margin) and clips the tiles
   to its corners; the map fills it edge to edge rather than sitting inside the
   Card's usual padding. isolation keeps Leaflet's stacking contexts from
   escaping the card. */
/* Square at the very most, and wider than tall wherever there is room for it.
   Sized from its own width rather than the viewport's height: a fixed vh on a
   phone made a tall portrait slab of it, several screens of map to scroll past
   to reach whatever came next. */
.PostMap {
    aspect-ratio: 1 / 1;
    max-height: 70vh;
    width: 100%;
    padding: 0;
    isolation: isolate;
}

.MapPopup a { font-weight: 600; }

/* The menu that opens out of a dropped pin. Leaflet's popup is narrow, so the
   actions stack and stretch rather than sitting in a row. */
.MapPinMenu { min-width: 9rem; }
.MapPinMenu .Button { text-align: center; }

/* Revealed by MapScrubber.js once it knows there's a span worth scrubbing. */
.MapScrubber { display: none !important; }
.MapScrubber.Active { display: flex !important; }

.MapScrubberLabel { font-weight: 550; }

.MapScrubberRange {
    width: 100%;
    accent-color: var(--accent);
    padding: 0;
}

.MapScrubberBounds { font-size: 0.8125rem; }

.MapScrubberModeButton.Active {
    background-color: var(--accent-soft);
    color: var(--accent) !important;
    box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent) 30%, transparent);
}

/* The place line under a post's timestamp, alongside the (edited) marker. */
.PostLocationLink {
    color: var(--muted);
    font-size: 0.8125rem;
    white-space: nowrap;
}

.PostLocationLink:hover { color: var(--accent); }

/* Revealed by PostMap.js once a point is picked. */
.MapComposer { display: none !important; }
.MapComposer.Active { display: flex !important; }


/* One relay on the admin page, and the field that picks how to subscribe. */
.RelayCard p { overflow-wrap: anywhere; }

.RelayFollowObjectField {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
}

.RelayFollowObjectField > p {
    margin-bottom: 0;
}

/* The conversation's safety code, and what it says once it has been checked
   or has changed underneath someone. */
.MessageKeyFingerprint {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    align-items: flex-start;
    margin-bottom: 1rem;
}

.MessageKeyFingerprintCode {
    font-family: monospace;
    font-size: 1.1rem;
    letter-spacing: 0.05em;
    user-select: all;
}

.MessageKeyFingerprint.Verified .MessageKeyFingerprintCode {
    color: var(--accent);
}

.MessageKeyFingerprintVerified {
    color: var(--accent);
    margin-bottom: 0;
}

.MessageKeyFingerprintWarning {
    border: 1px solid color-mix(in srgb, var(--danger) 35%, transparent);
    background-color: var(--danger-soft);
    border-radius: var(--radius-ctl);
    padding: 0.5rem;
    color: var(--ink);
    margin-bottom: 0;
}

.MessageUnlockForm {
    display: flex;
    align-items: flex-end;
    gap: 0.5rem;
}

.MessageKeySetupForm,
.MessageKeyPassphraseForm {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    max-width: 24rem;
}


/* A custom emoji from another server - an image standing in for a word, so it
   sits on the text baseline at roughly the height of one. */
.CustomEmoji {
    display: inline-block;
    width: 1.35em;
    height: 1.35em;
    object-fit: contain;
    vertical-align: -0.25em;
    margin: 0 0.05em;
}

/* Component selectors for formerly shared layout declarations. */

:is(.AccountDeleteForm, .AccountMigrationForm, .BotProtectionSettingsForm, .EmailChangeForm, .EmailDigestResubscribeForm, .EmailDigestSettingsForm, .EmailRevertForm, .EmailVerifyForm, .FaviconSettingsForm, .FrontPageImageSettingsForm, .GoogleAuthSettingsForm, .LanguageSelector, .LoginForm, .MailSettingsForm, .MapScrubber, .MapSettingsForm, .MessageComposer, .ModerationActionList, .NearbyLocationPrompt, .OpenRouterSettingsForm, .PasswordChangeForm, .PasswordResetForm, .PasswordResetRequestForm, .PushNotificationSetting, .RelaySubscribeForm, .RemoteFollowsForm, .ServerBlockForm, .ServicesStatus, .SetupForm, .SetupNextSteps, .SignupForm, .SiteInfoSettingsForm, .StagedPostCard, .StagedPostList, .ThemeSelector, .TrendingEntitySection, .TrendingHashtagSection, .TrendingTimerStatus, .TwoFactorForm, .TwoFactorSettingsForm, .UploadWorkerStatus, .VideoCallTestPanel, .WebSocketStatus) {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}

:is(.BannedTrendingEntity, .BlockedServerCard, .Conversation, .PostActionBar, .RelayCard, .RememberedDevice) {
    display: flex;
    align-items: center;
    gap: 1rem;
}

:is(.BannedTrendingEntityList, .BlockedServerList, .BookmarkList, .Composer, .ConversationList, .FeedList, .MessageList, .RelayList, .RememberedDeviceList, .ReplyList, .ReportList) {
    display: flex;
    flex-direction: column;
}

:is(.CurrentUserActions) {
    margin-inline-start: auto;
}

:is(.EntityTypeLinks, .TrendingEntityList, .TrendingHashtagList) {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
}

:is(.ErrorList, .ModerationActionCard, .NotificationList, .SiteCounters) {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
}

:is(.HiddenLabel, .SearchStatus) {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

:is(.LoginPrompt, .MoreLocationsLink, .PageGenerationTime, .PostEditedMarker, .StagedPostWhen, .TimestampLink) {
    color: var(--muted);
    font-size: 0.875rem;
}

:is(.LogoutEverywherePanel, .ModQueueLinks, .NotificationTestPanel, .TestSuitePanel) {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 0.5rem;
}

:is(.MainNavigation) {
    display: flex;
    gap: 1.5rem;
}

:is(.Notice) {
    color: var(--muted);
}

:is(.PostMeta) {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    margin-inline-start: auto;
}

:is(.RepliesHeading) {
    font-size: 1.125rem;
    font-weight: 700;
}

:is(.Report) {
    display: flex;
    align-items: flex-start;
    gap: 1rem;
}

:is(.SitePolicyLinks) {
    display: flex;
    gap: 1rem;
}

:is(.Entity) {
    display: flex;
    align-items: center;
    gap: 0.25rem;
}

:is(.VerificationNotice) {
    display: flex;
    flex-direction: column;
    gap: 1rem;
}

:is(
    .BannedUserRow,
    .BlockedServerCard,
    .BannedTrendingEntity,
    .NotificationContainer,
    .NotificationLink,
    .RelayCard,
    .RememberedDevice,
    .UserHeaderLink
) {
    display: flex;
    align-items: center;
    gap: 1rem;
}

:is(
    .BlockedServerCardInfo,
    .BannedTrendingEntityInfo,
    .RememberedDeviceInfo,
    .RelayCardInfo
) {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
}

:is(
    .BannedUserRow > :last-child,
    .BlockedServerCard > :last-child,
    .BannedTrendingEntity > :last-child,
    .RelayCard > :last-child,
    .RememberedDevice > :last-child,
    .OtherUserActions,
    .ReportActions,
    .PostMeta
) {
    margin-inline-start: auto;
}

:is(
    .ConversationUsername,
    .ConversationMeta,
    .HelpArticleCategory,
    .HelpArticleSummaryText,
    .LoginPrompt,
    .MoreLocationsLink,
    .NotificationMeta,
    .NotificationMeta .RelativeTime,
    .PageGenerationTime,
    .PostEditedMarker,
    .QuotedPostByline,
    .RelayCardDetail,
    .RemoteFollowsStatus,
    .RememberedDeviceDetail,
    .StagedPostLink,
    .StagedPostWhen,
    .TimestampLink,
    .TopicSummaryLabel,
    .UserJoined,
    .UserHeaderUsername,
    .UserUsername
) {
    color: var(--muted);
    font-size: 0.875rem;
}

:is(.DisplayNameRow) {
    display: flex;
    align-items: center;
    gap: 0.5rem;
}

:is(.PostByline, .MessageByline) {
    display: flex;
    align-items: flex-start;
    gap: 0.5rem;
}

:is(.LoginFormFooter) {
    display: flex;
    justify-content: space-between;
}

:is(.NearbyLocationPromptActions, .PostActionBarActions, .StagedPostActions, .VerificationNoticeActions) {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    flex-wrap: wrap;
}

:is(.ReportDetails, .ReportedAttachments) {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}

:is(.OtherUserActions, .ReportActions) {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}

:is(.RemoteFollowsItem) {
    display: flex;
    align-items: center;
    gap: 0.5rem;
}

:is(.MainNavigationSiteLinks, .MainNavigationAccountLinks) {
    display: flex;
    gap: 1.5rem;
}

.MainNavigationAccountLinks {
    margin-inline-start: auto;
}

:is(.MapScrubberHeader) {
    display: flex;
    align-items: center;
    gap: 0.5rem;
}

:is(.MapScrubberMode) {
    display: flex;
    gap: 0.25rem;
}

:is(.MapScrubberBounds) {
    display: flex;
    justify-content: space-between;
}

:is(.Composer, .PostComposer, .ReplyComposer, .PostEditForm) {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}

:is(.PostComposerFields, .EditorRow) {
    display: flex;
    gap: 0.5rem;
}

:is(.EditorRow, .PostEditAltRow) {
    align-items: flex-start;
}

:is(.ComposerAttachmentList, .PostEditAltList, .RemoteFollowsList) {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}

:is(.ComposerAttachment, .PostEditAltRow) {
    display: flex;
    align-items: center;
    gap: 0.5rem;
}

:is(.ComposerAttachmentCount, .ComposerAttachmentName, .HelpArticleCategory, .LoginRecaptchaNotice, .MapPinPosition, .MapPinAuthor, .ReportMeta, .VideoCallTestDetail, .VideoCallTestNote) {
    color: var(--muted);
    font-size: 0.875rem;
}

:is(.ComposerAttachmentRemoveButton, .PostEditActions, .VideoCallEndButton, .VideoCallAcceptButton) {
    margin-inline-start: auto;
}

:is(.PostEditActions) {
    display: flex;
    align-items: center;
    gap: 0.5rem;
}

:is(.MapPinMenu) {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
}

:is(.VideoCallPanel) {
    display: flex;
    align-items: center;
    gap: 0.5rem;
}

:is(.UserSearchList) {
    display: flex;
    flex-direction: column;
}
