/* static/css/post-page.css — larger-screen overrides + post-detail chrome.
 *
 * Fifth of the seven stylesheets split out of the former single
 * ``gallae.css`` (tasks/FILE_SPLIT_TASKS.md Phase 1). It carries the
 * ``min-width: 720px`` larger-screen layer plus the post-detail page
 * furniture: tag-autocomplete, the v1 post-detail layout + metadata column,
 * the icon controls, the subscribe picker, and the toast notifications (which
 * ride along here to keep the cut contiguous). Because it overrides earlier
 * rules at equal specificity via source order, it MUST load after
 * ``pages.css`` — and before the reading files, which override again.
 */

/* ---------------------------------------------------------------------
 * Larger screens
 *
 * The single ``min-width: 720px`` breakpoint widens the container
 * padding and restores the per-level contribution indent that was
 * tightened above. We keep the breakpoint count intentionally low
 * (one) to honor the file-level "stay simple, swap later" goal —
 * additional breakpoints earn their place when a real design
 * lands, not as a polish layer.
 * ------------------------------------------------------------------ */

@media (min-width: 720px) {
    .container {
        padding: 2rem 1rem;
    }

    .contribution-children {
        /* Restore the desktop indent declared in the Task 5.4
         * block above. */
        padding-left: 1rem;
    }
}

/* ----- Tag autocomplete dropdown (Task H.10, _post_form.html).
 * The wrapping ``.tag-autocomplete`` is the combobox surface; the
 * ``<ul class="tag-autocomplete-list">`` inside is the listbox.
 * ``position: relative`` on the wrapper lets the absolutely-
 * positioned dropdown line up under the input regardless of where
 * the form sits on the page. ``[x-cloak]`` hides the dropdown
 * during Alpine's mount so we don't flash an empty list at load.
 */
[x-cloak] {
    display: none !important;
}

.tag-autocomplete {
    position: relative;
}

.tag-autocomplete-list {
    list-style: none;
    margin: 0;
    padding: 0;
    /* Float over the form rather than displacing the help text
     * and the submit button below. ``top: 100%`` aligns the top
     * of the dropdown with the bottom of the input. */
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    z-index: 10;
    background: var(--surface-raised);
    border: 1px solid var(--border);
    border-top: none;
    /* Cap the height so a long match list doesn't run off the
     * viewport on a narrow phone. Scrolls within the dropdown
     * past the cap. */
    max-height: 12rem;
    overflow-y: auto;
    /* Subtle shadow signals "floating UI" without leaning on a
     * border-radius that would clash with the input above. */
    box-shadow: 0 2px 4px var(--shadow-sm);
}

.tag-autocomplete-item {
    padding: 0.4rem 0.6rem;
    cursor: pointer;
    /* Slightly smaller than body text to keep the dropdown
     * visually quieter than the surrounding form copy. */
    font-size: 0.95em;
}

.tag-autocomplete-item--active,
.tag-autocomplete-item:hover {
    background: var(--accent-tint);
}

/* ---------------------------------------------------------------------
 * Pseud badge (Task U.1)
 *
 * Renders an author ``@handle`` link via
 * ``templates/works/_author_badge.html`` — the single rendering
 * point for every author handle in the codebase. (It once also
 * carried a ``...`` trigger opening a Follow / Mute / Block
 * popover; that was removed — block/follow live on the profile
 * page now, and mute moved to a plain form on the reading card —
 * so the badge is just the handle link.)
 *
 * The badge is an inline-flex span so the handle flows with the
 * surrounding text. The link inherits site-specific styling via
 * the optional ``link_class`` parameter (e.g. ``post-author``,
 * ``contribution-author``, ``meta-panel-comment-author``). */
.author-badge {
    display: inline-flex;
    align-items: baseline;
    gap: 0.15rem;
}

/* Author handle link: the single home of the username treatment — bold,
 * author-coloured, no underline. Every byline that renders through
 * _author_badge.html (post/riff metacol, comment row, search, post-list)
 * inherits this one rule, so the name reads identically everywhere.
 * Uses --card-author-color (set inline on each card element) when
 * inside a card, falling back to currentColor on other surfaces
 * (profile page, search results) where no card colour is set. */
.author-badge-handle {
    font-weight: bold;
    color: var(--card-author-color, currentColor);
    text-decoration: none;
}

/* On hover, drop the author's own colour for the theme's high-contrast text
 * token (near-black in light, cream-white in dark) so an oddly light or
 * low-contrast custom colour stays legible. Hover-only by design: the resting
 * colour is always the author's own. The comment-row carve-out below opts out
 * (comments are accent-coloured, not author-tinted). */
.author-badge-handle:hover {
    color: var(--text);
}

/* "AI" byline badge (CW.1.3): a small "AI" glyph on bot authors' bylines
 * (see templates/works/_author_badge.html), driven by User.is_bot. Never part
 * of the handle, so it can't be spoofed by renaming.
 *
 * Rendered as static/icons/ai-badge.svg used as a mask over a solid fill:
 * background-color paints the badge in the AI author's OWN colour
 * (--card-author-color — the same variable that tints the handle), so
 * different AIs show their own colour rather than one shared hue. The "AI"
 * letters are knocked out of the SVG, so they stay transparent and show the
 * card background through them.
 *
 * Sizing: one knob — --ai-badge-h (height). Width follows the art's 112:71
 * aspect ratio, so tweak the height alone to scale the badge against the
 * handle. Height is tuned so the badge, sitting on the baseline, rises to just
 * above the handle's x-height (top of an "e") but below its ascenders (top of
 * a "d") — a compact chip that reads as part of the line. margin-left gives it
 * a touch of breathing room from the name. */
.byline-ai-marker {
    --ai-badge-h: 0.68em;
    flex: none;
    /* Bottom-align the badge to the handle's text baseline so it sits in the
     * glyph band rather than centring on the line box (which rides low because
     * of line-height / descender space below the baseline). align-self covers
     * the flex byline (.author-badge); vertical-align covers the non-flex
     * profile <h1>. Baseline-relative, so it holds across zoom and font-size.
     * Each property is a no-op where the other applies, so one rule serves
     * both contexts. */
    align-self: baseline;
    vertical-align: baseline;
    /* Baseline lands it in the glyph band; `top` is the fine optical-nudge knob
     * (positive = down, negative = up) for seating it exactly on the handle's
     * line. Purely visual — doesn't shift layout. Currently 1px (a hair below
     * the baseline) to seat it on the handle's line. */
    position: relative;
    top: 1px;
    display: inline-block;
    height: var(--ai-badge-h);
    width: calc(var(--ai-badge-h) * 112 / 71);
    margin-left: 0.1em;
    background-color: var(--card-author-color, currentColor);
    -webkit-mask: url("../icons/ai-badge.5838bcba001a.svg") center / contain no-repeat;
    mask: url("../icons/ai-badge.5838bcba001a.svg") center / contain no-repeat;
}

/* Comment-panel tweaks to the badge:
 *  - Lift it ~10% of its own height — it sits a touch low against the comment
 *    byline. translateY's percentage tracks the element's height, so it stays
 *    proportional to --ai-badge-h and composes with the base `top` nudge.
 *  - margin-right gives the ":" separator a bit of breathing room from the
 *    badge (the .author-badge gap is 0 here so the colon otherwise hugs it).
 *    It's on the badge, which only exists for bots, so a human's "handle:"
 *    stays tight.
 *  - margin-left adds a touch more space between the handle and the badge than
 *    the base 0.1em, since the comment row's gap:0 tightens it. Symmetric with
 *    the right margin so the badge sits evenly between handle and colon. */
.meta-panel-comment-text .byline-ai-marker {
    transform: translateY(-10%);
    margin-right: 0.15em;
    margin-left: 0.15em;
}

/* ---------------------------------------------------------------------
 * Post-detail layout (Task U.4 + U.6)
 *
 * The page-level grid container the post-detail page leans on.
 * U.4 laid the column tracks; U.5 promoted the riff tree to
 * subgrid against those tracks; U.6 placed the top-level post
 * body + post-level meta panel in the same columns the riff
 * tree's rows occupy.
 *
 * Mobile (<880 px): a single 1fr column. The three structural
 * children — post ``<article>``, post-meta ``<aside>``, riff
 * tree ``<ul>`` — stack vertically in source order, matching the
 * pre-U.4 mobile flow.
 *
 * Desktop (≥880 px): two columns — a flexible body track plus a
 * fixed 22rem meta-rail. Explicit placement:
 *
 * - ``.post-detail`` (the article) → column 1.
 * - ``.post-meta-mount`` (the aside) → column 2.
 * - ``.contribution-tree`` spans both columns. The tree's own
 *   subgrid (U.5) inherits
 *   the page-level tracks so every riff's body / meta pair
 *   shares the same column lines as the top-level post / meta
 *   pair above it.
 *
 * Pre-U.6 the universal rule ``.post-detail-layout > * {
 * grid-column: 1 / -1; }`` forced every child to span both
 * columns. That was the right move while U.6 was unfinished —
 * it preserved the pre-U.4 single-stack visual at desktop width
 * without committing to placement that hadn't shipped. With U.6
 * in place, the explicit per-child placements below replace
 * that wildcard.
 *
 * The 22rem meta-rail track width matches the inner-per-row
 * ``.contribution`` grid's 18rem column (pages.css) with
 * room for the meta panel's heading + comment list. U.5
 * inherits these tracks via subgrid so every depth of the riff
 * tree shares the same column lines.
 */
.post-detail-layout {
    display: grid;
    grid-template-columns: 1fr;
    row-gap: 1rem;
}

@media (min-width: 880px) {
    /* Two-column grid. Uses named lines (``[body]`` / ``[meta]``)
     * so the per-child placement rules below resolve against
     * stable aliases rather than raw column numbers; the
     * grid-template-columns declaration is what wins. */
    .post-detail-layout {
        grid-template-columns: [body] minmax(0, 1fr) [meta] 22rem [meta-end];
        column-gap: 1.25rem;
        row-gap: 0.75rem;
    }
    .post-detail-layout > .post-detail {
        grid-column: body;
    }
    .post-detail-layout > .post-meta-mount {
        grid-column: meta;
    }
    /* The tree sits in a row below the post + meta pair and spans
     * the full grid. The subgrid block above (.contribution-tree,
     * U.5) sets the same ``grid-column: 1 / -1`` when subgrid is
     * available; this rule acts as a belt-and-braces baseline for
     * the no-subgrid fallback. */
    .post-detail-layout > .contribution-tree {
        grid-column: 1 / -1;
    }
}

/* Wide-desktop refinement (≥1200 px).
 *
 * Below 1200 px the post-detail page lives inside the default
 * ``.container`` 42rem cap, which leaves the body column visibly
 * narrower than the 22rem meta-rail once the two-column grid kicks
 * in at 880 px (40rem inner − 22rem meta − 1.25rem gap ≈ 16.75rem
 * body, i.e. body < meta). That's fine on a laptop but feels
 * cramped on a wide external monitor.
 *
 * At ≥1200 px we do two things together:
 *
 * - Widen ``.container--post-detail`` (opted in by
 *   ``templates/works/post_detail.html``'s ``container_modifier``
 *   block override) to 56rem, breaking out of the default
 *   single-column reading-width cap. The wider cap is scoped to
 *   the post-detail page so other pages keep the 42rem
 *   line-length-friendly cap. 56rem (≈ 896 px) sits below the
 *   typical 70–80-character readability ceiling for the body
 *   column even after the meta column is shrunk below.
 *
 * - Shrink the meta column from 22rem to 15rem (roughly ⅔ of
 *   its narrower-viewport size). 15rem still comfortably fits
 *   the meta panel's "META AREA" heading plus a few stacked
 *   comment cards; the freed horizontal space goes to the body
 *   column via the existing ``minmax(0, 1fr)`` track.
 *
 * Combined effect: at 1200+ px the body column grows from
 * ≈16.75rem to ≈37.75rem (≈ 2.25× wider) while the meta column
 * shrinks proportionally. The 880–1199 px range keeps the
 * narrower defaults so the existing tablet-landscape / small-
 * laptop layout doesn't shift.
 */
@media (min-width: 1200px) {
    .container.container--post-detail {
        max-width: 56rem;
    }
    .post-detail-layout {
        grid-template-columns: [body] minmax(0, 1fr) [meta] 15rem [meta-end];
    }
}

/* ---------------------------------------------------------------------
 * Post metadata column (post-metadata-ui).
 *
 * The text-only left rail beside the post card — see
 * templates/works/_post_metacol.html. A transitional duplicate of the
 * card's byline + action band, rendered as plain underlined text rather
 * than icons/pill buttons. Two stacked flex containers (top: identity /
 * permalink; bottom: actions). Action items that are <button>/<form>
 * under the hood are stripped of their control chrome by
 * ``.post-metacol-item`` so they read as text like their <a> / <time>
 * siblings.
 * --------------------------------------------------------------------- */
.post-metacol {
    display: flex;
    /* Mobile: the identity block (handle / date) and the action-icon band sit
     * side by side on one row, so the icons run horizontally *beside* the
     * byline rather than stacking into a tall column wedged between the byline
     * and the title (vertical-space efficiency on phones). ``flex-start``
     * anchors the icon row to the top, level with the handle, so it stays put
     * even when the identity column grows taller (the edit-history disclosure /
     * expanded version list). The desktop fixed rail flips this back to a
     * vertical column — see the ``@media (min-width: 880px)`` navigator block. */
    flex-direction: row;
    align-items: flex-start;
    gap: 1rem;
    /* Mobile only: a little breathing room between the metadata row and the
     * body text beneath it. Inert at >=880px, where the rail is position: fixed
     * in the left margin (see the navigator block) and out of the card's flow. */
    margin-bottom: 0.5rem;
}
/* The rail belongs to the ACTIVE card: it shows only while the post card is
 * the selected one (``data-active``, which pathnav.js moves between cards as
 * you navigate the spine). When the selection moves to a riff the post card
 * loses ``data-active`` and its rail vanishes; selecting the post again brings
 * it back. SSR stamps ``data-active`` on the post when it's the active card,
 * so the rail is correct on first paint and with JS off. */
.container--post-detail .post-detail:not([data-active]) > .post-metacol {
    display: none;
}
/* Riff rails follow selection too: hidden by default, shown only when the
 * rail's OWN card (the ``.contribution`` <li>) is active. Child combinators
 * keep a nested active riff's rail visible even while an ancestor riff is
 * inactive (so it's the rail's own card state that decides, not an ancestor's).
 *
 * NOTE the leading ``.contribution >``: the POST card's <article> carries
 * BOTH ``post-detail`` and ``contribution-body`` classes, so a bare
 * ``.contribution-body > .post-metacol`` would also match the post rail and
 * hide it. Scoping through ``.contribution >`` excludes the post (whose body's
 * parent is ``.post-detail-layout``, not a ``.contribution`` <li>). */
.container--post-detail .contribution > .contribution-body > .post-metacol {
    display: none;
}
.container--post-detail .contribution[data-active] > .contribution-body > .post-metacol {
    display: flex;
}
/* On mobile the rail isn't a floating margin element — it renders inline at the
 * card's top/bottom — so show it for EVERY riff card, not just the active one.
 * Otherwise an incoming swipe-peek sibling (not yet data-active) carries no
 * byline/actions and pops taller the moment it lands and gains data-active.
 * Narrow-only; desktop keeps the active-card-only floating rail. */
@media (max-width: 879px) {
    .container--post-detail .contribution > .contribution-body > .post-metacol {
        display: flex;
    }
}
/* The two halves the caller asked for. The TOP (identity / permalink) is a
 * vertical stack of items; the BOTTOM (action icons) is a horizontal row on
 * mobile (set below at ``.post-metacol-bottom``) so the icons sit beside the
 * byline. ``align-items: flex-start`` left-aligns the top's items.
 *
 * The desktop rail flips both halves to flex-end so the text/icons right-align
 * against the card, and restores the bottom to a vertical column — see the
 * ``.container--post-detail`` fixed-rail rule in the v2 navigator media block.
 *
 * Desktop placement note: at ≥880px the v2 navigator overrides
 * ``.post-detail-layout`` to ``display: block`` with a *centred* card, so
 * there is no grid cell to the card's left — the rail is positioned into
 * the viewport's left margin with ``position: fixed`` instead. That rule
 * lives beside the rest of the navigator layout, not here. */
.post-metacol-top,
.post-metacol-bottom {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 0;
}
.post-metacol-top {
    margin: -0.1rem 0 0 0;
}
/* The action items (delete / subscribe / like / cowrite) are <button>s
 * wrapped in a <form> for the no-JS POST. Left in normal flow the <form>
 * generates its own block box, and the <button> sits inside it as an
 * inline-block on a line — that line's leading/descender space makes the
 * action items taller than the bare <a>/<time> links beside them, so the
 * column reads with an uneven, loose line-height.
 *
 * ``display: contents`` drops the form's box entirely: its children (the
 * button + the hidden CSRF input, which stays display:none) become direct
 * flex children of the rail column, so each action button is laid out as
 * its OWN flex line — identical to the text links. Form submission is
 * unaffected (it follows DOM ancestry, not layout). Net: every item, link
 * or action, is one tight text line broken by the column's flex gap. */
.post-metacol form {
    display: contents;
}

/* The shared text treatment. Resets button/form-control chrome first so a
 * <button> reads identically to an <a> or <time>, then applies the
 * underline + hover-to-link-blue the caller specified. */
.post-metacol-item {
    appearance: none;
    -webkit-appearance: none;
    margin: 0;
    padding: 0;
    border: 0;
    /* The global ``button { min-height: 2.5rem }`` tap-target floor (pages.css
     * ~884) makes a <button> action item 40px tall against a ~19px text link.
     * Reset it so an action button is exactly one text line like its <a>/<time>
     * siblings — the rail items are a text list, not tap targets. */
    min-height: 0;
    background: none;
    font: inherit;
    /* Rail action items (edit / delete / cowrite / subscribe / like / the
     * permadate) read in the mono "label" voice, overriding the inherited
     * body serif from ``font: inherit`` above. */
    font-family: var(--font-mono);
    font-size: 0.85rem;
    line-height: 1.4;
    text-align: left;
    /* Override the default ``a { color: var(--link) }`` so the resting
     * state is body text, not the link colour — that's the hover signal. */
    color: var(--text, inherit);
    text-decoration: underline;
    cursor: pointer;
}
/* The cowrite action is the rail's primary call to action — bold it so it
 * stands out from the neutral edit/subscribe/like siblings. */
.post-metacol-cowrite {
    font-weight: 700;
}
.post-metacol-item:hover,
.post-metacol-item:focus-visible {
    /* ``--link`` is the project's link/accent colour, theme-aware (light:
     * #b3562d, dark: #99a3ff) — the "lights up" hover the caller asked
     * for, without hardcoding a value that ignores dark mode. */
    color: var(--link);
}

/* Chroma of the author-hued rail-icon tint, declared per card so a near-gray
 * author's card can zero it WITHOUT leaking that zero into a colourful nested
 * child card (riff cards nest recursively). Every card article matches this
 * base rule and re-declares the default, so an ancestor's value never inherits
 * through. The .rail-desaturated variant (added by the templates when the
 * author's own colour is under 10% saturation) drops chroma to 0 → a plain gray
 * rail at the same lightness, instead of one more colourful than the author. */
.post-detail,
.contribution {
    --rail-icon-chroma: 0.042;
}
.post-detail.rail-desaturated,
.contribution.rail-desaturated {
    --rail-icon-chroma: 0;
}

/* ---------------------------------------------------------------------
 * Icon controls in the action band (.post-metacol-bottom). Each action —
 * edit / delete / subscribe / like / cowrite — renders as a single SVG
 * glyph rather than a text label. The glyph is a decorative CSS mask
 * painted in ``currentColor`` (the same technique as the permadate chain
 * glyph below), so it inherits the item's colour, lights to --link on
 * hover, and stays theme-aware in dark mode for free — the SVGs' baked-in
 * #000 fill is irrelevant to a mask. The control's accessible name lives
 * in its ``aria-label`` (+ a ``title`` tooltip); the button/link carries
 * no visible text. The band is one tall column (one icon per line), so
 * edit and delete each take their own line rather than sharing a
 * slash-separated row.
 * --------------------------------------------------------------------- */
.post-metacol-icon {
    /* No chrome and no padding: the control is just its glyph. The
     * background always matches whatever sits behind the rail so the global
     * button:hover tint never paints a box here — the only hover cue is the
     * glyph itself lighting up (shared .post-metacol-item:hover colour).
     * No underline — there's no visible text to underline. */
    padding: 0;
    background: transparent;
    text-decoration: none;
    /* Resting glyph colour: a muted tint of the card author's identity colour so
     * each card's action icons carry a hint of that author's hue while still
     * receding until hovered. Built in OKLCH from --card-author-color (inherited
     * from the card ancestor, always a hex) — we KEEP only its hue and PIN the
     * lightness (--rail-icon-tint-l, tuned per theme to match the old neutral's
     * brightness) and chroma (low, so it stays muted, not a saturated blob).
     * Pinning in a perceptual space is the point: fixed HSV value/saturation
     * would swing wildly in perceived brightness across hues (a blue icon would
     * read near-black next to a yellow one), so contrast against the rail would
     * be uneven per author; fixed OKLCH lightness holds it even. Falls back to
     * the plain neutral --rail-icon-color if no author colour is in scope. The
     * neutral declaration comes first as the progressive-enhancement fallback:
     * a browser without relative-colour syntax keeps it when the oklch resolves
     * invalid. The chroma channel reads --rail-icon-chroma (default 0.042; the
     * .rail-desaturated card variant zeroes it so a near-gray author gets a gray
     * rail, not one more colourful than they are). Built inline here, not via a
     * :root var — see the note by --rail-icon-tint-l. The .likers-popover border
     * uses the same formula. :hover still lifts to --link (shared
     * .post-metacol-item:hover rule). */
    color: var(--rail-icon-color);
    color: oklch(from var(--card-author-color, var(--rail-icon-color)) var(--rail-icon-tint-l) var(--rail-icon-chroma, 0.042) h);
}
/* Cancel the global ``button:hover:not(:disabled)`` / focus background tint
 * for the icon controls — they get no box, ever. Two classes plus the
 * pseudo-class out-specify that global rule. */
.post-metacol-item.post-metacol-icon:hover,
.post-metacol-item.post-metacol-icon:focus-visible {
    background: transparent;
}
.post-metacol-icon::before {
    content: "";
    display: block;
    width: 1.35rem;
    height: 1.35rem;
    background-color: currentColor;
    -webkit-mask: var(--metacol-icon) center / contain no-repeat;
    mask: var(--metacol-icon) center / contain no-repeat;
}
/* The comment control carries its count INSIDE the speech bubble. The bubble
 * glyph is the masked ::before above; the count digit is a separate child
 * layered over it — deliberately NOT inside the masked element, since a mask
 * clips its content and would clip the digit to the bubble outline. The icon
 * element becomes the positioning context; the digit inherits currentColor so
 * it tracks the rail's resting/hover colour like the glyph. */
.post-metacol-icon--comment {
    position: relative;
}
/* Stretch the speech bubble ~15% taller than the square base glyph so its top
 * edge pulls away from the centred count digit, opening a little breathing room
 * above the number. The base mask is ``contain`` (aspect-preserving); here we
 * switch to a full-box stretch so the extra height elongates the bubble rather
 * than letterboxing it. */
.post-metacol-icon--comment::before {
    height: 1.97245125rem; /* 1.35rem × 1.15 × 1.10 × 1.05 × 1.10 */
    -webkit-mask-size: 100% 100%;
    mask-size: 100% 100%;
}
.post-metacol-comment-count {
    position: absolute;
    inset: 0;
    display: grid;
    place-items: center;
    transform: translateY(calc(-7% + 1px)); /* centre on the bubble body, above the tail; nudged 1px down */
    /* Sized so a single digit reads clearly and the rare "9+" still fits the
     * bubble interior (the bubble is 1.35rem with a ~2px stroke each side). */
    font-size: 0.768rem;
    font-weight: 600;
    line-height: 1;
    color: var(--brand-red);
    pointer-events: none;
}
.post-metacol-icon--edit { --metacol-icon: url("../icons/edit.5c619f0087e2.svg"); }
.post-metacol-icon--delete { --metacol-icon: url("../icons/delete.95e7f1d4da44.svg"); }
.post-metacol-icon--like { --metacol-icon: url("../icons/heart.e4fc1484c8e0.svg"); }
.post-metacol-icon--liked { --metacol-icon: url("../icons/hearted.b15d8e75bac1.svg"); }
.post-metacol-icon--cowrite { --metacol-icon: url("../icons/cowrite.bacb4ba2b9d0.svg"); }
.post-metacol-icon--comment { --metacol-icon: url("../icons/comment.587ff5f95d46.svg"); }
/* Edit-mode / composer action glyphs — shown in the rail's edit band while a
 * riff card is edited in place (see _contribution_metacol.html) and in the riff
 * composer's action row (see _compose_form.html). --save is the checkmark used
 * for "Save edits to a live riff"; --draft is a distinct page glyph for "Save as
 * a draft", so the two never read the same. Placeholder icons. */
.post-metacol-icon--save { --metacol-icon: url("../icons/save.1324a040362f.svg"); }
.post-metacol-icon--publish { --metacol-icon: url("../icons/publish.cdb60d8522fa.svg"); }
.post-metacol-icon--cancel { --metacol-icon: url("../icons/cancel.273b1884e843.svg"); }
.post-metacol-icon--draft { --metacol-icon: url("../icons/draft.78a67277be85.svg"); }
.post-metacol-icon--preview { --metacol-icon: url("../icons/preview.5d5515b44324.svg"); }
/* Cancel (X) is destructive, so it breaks from the author-hued resting tint and
 * the shared --link hover: it rests at brand-red at ALL times, and on hover/focus
 * jumps to the max-contrast body-text colour — near-black in light, near-white in
 * dark, since ``--text`` is theme-aware (so we don't hardcode a value that ignores
 * dark mode). Two classes out-specify both the ``.post-metacol-icon`` resting
 * colour and the shared ``.post-metacol-item:hover`` rule. The glyph is
 * ``currentColor`` (the ::before mask), so setting ``color`` recolours the X. */
.post-metacol-icon.post-metacol-icon--cancel {
    color: var(--brand-red);
}
.post-metacol-item.post-metacol-icon--cancel:hover,
.post-metacol-item.post-metacol-icon--cancel:focus-visible {
    color: var(--text);
}
.subscribe-state--unsubscribed.post-metacol-icon { --metacol-icon: url("../icons/unsubscribed.492e1214f39d.svg"); }
.subscribe-state--subscribed.post-metacol-icon { --metacol-icon: url("../icons/subscribed.0f093b152634.svg"); }
.subscribe-state--supersubscribed.post-metacol-icon { --metacol-icon: url("../icons/supersubscribed.2acfbc5b8356.svg"); }

/* State (liked / subscribed / super-subscribed) is carried by the glyph shape
 * alone — the icons are uncoloured at rest and light up to --link on hover like
 * every other rail control (shared .post-metacol-item:hover rule above). No
 * resting tint, so super-subscribe doesn't stand out until hovered. */

/* Subscribe is a cycle (unsubscribed -> subscribed -> supersubscribed -> …),
 * so the button previews the glyph it will become if clicked, rather than just
 * lighting up the current one — a "this is what you'll set" cue. Pure mask swap
 * to the NEXT state's icon; these selectors out-specify the resting-state rule
 * above.
 *
 * Keyboard focus previews unconditionally: an HTMX outerHTML swap drops focus,
 * so there's no "stuck on the new button" equivalent of the pointer problem
 * handled below. */
.subscribe-state--unsubscribed.post-metacol-icon:focus-visible {
    --metacol-icon: url("../icons/subscribed.0f093b152634.svg");
}
.subscribe-state--subscribed.post-metacol-icon:focus-visible {
    --metacol-icon: url("../icons/supersubscribed.2acfbc5b8356.svg");
}
.subscribe-state--supersubscribed.post-metacol-icon:focus-visible {
    --metacol-icon: url("../icons/unsubscribed.492e1214f39d.svg");
}

/* Pointer hover previews the next state only once the pointer has actually
 * MOVED over this freshly-rendered button (the ``subscribe-preview-armed``
 * class Alpine adds on the first pointermove). Clicking cycles the state via an
 * HTMX swap that parks the cursor over the NEW button; without this gate :hover
 * is already active on it and it would immediately preview the
 * state-after-next, reading as if the click over-shot. The swapped-in button
 * starts un-armed, so right after a click the hovered button just shows its new
 * current state (lit) until the reader moves the mouse — then the next-state
 * preview returns. pointermove (not mouseover) is the trigger precisely because
 * it fires on real movement, not when the DOM shifts under a still cursor. */
.subscribe-preview-armed.subscribe-state--unsubscribed.post-metacol-icon:hover {
    --metacol-icon: url("../icons/subscribed.0f093b152634.svg");
}
.subscribe-preview-armed.subscribe-state--subscribed.post-metacol-icon:hover {
    --metacol-icon: url("../icons/supersubscribed.2acfbc5b8356.svg");
}
.subscribe-preview-armed.subscribe-state--supersubscribed.post-metacol-icon:hover {
    --metacol-icon: url("../icons/unsubscribed.492e1214f39d.svg");
}

/* 2-state cycle (super disabled — the ``.subscribe-cycle-2`` marker): the
 * "subscribed" face previews UNSUBSCRIBED, not super, because the next click
 * wraps straight back to off for these users. The extra class out-specifies the
 * 3-state rules above; only the subscribed face needs an override (unsubscribed
 * already previews subscribed, and a 2-state user never reaches the super
 * face). */
.subscribe-cycle-2.subscribe-state--subscribed.post-metacol-icon:focus-visible,
.subscribe-preview-armed.subscribe-cycle-2.subscribe-state--subscribed.post-metacol-icon:hover {
    --metacol-icon: url("../icons/unsubscribed.492e1214f39d.svg");
}

/* ---------------------------------------------------------------------
 * Subscribe level picker (sometimes-mode users only — see
 * works/_subscribe_picker.html). Clicking the rail subscribe icon opens a
 * small horizontal row of the three level cells, centred on the button so the
 * MIDDLE cell (subscribe) overlaps the resting button; unsubscribed extends
 * left, supersubscribe right. Never-mode users get the plain cycle toggle and
 * none of this.
 * --------------------------------------------------------------------- */
.subscribe-picker {
    /* Positioning anchor for the menu; sized to the button (inline-flex) so the
     * menu can centre on it. */
    position: relative;
    display: inline-flex;
}
.subscribe-picker .subscribe-picker-menu {
    /* Centre the 3-cell row on the button: the middle cell lands exactly over
     * the resting button, unsubscribed extends left and supersubscribe right.
     * The opaque surface backing covers the button beneath it (no doubled
     * glyph).
     *
     * The two-class selector (``.subscribe-picker .subscribe-picker-menu``) is
     * deliberate: this menu is a <form>, and the rail's
     * ``.post-metacol form { display: contents }`` rule would otherwise win and
     * drop the menu's box entirely — killing the border, backing, and absolute
     * positioning. Out-specifying it restores a real, floating box. */
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
    display: flex;
    flex-direction: row;
    align-items: center;
    gap: 0.5rem;
    padding: 0.35rem 0.5rem;
    background: var(--surface);
    /* Thin high-contrast hairline: black on light, white on dark. */
    border: 1px solid var(--picker-outline);
    border-radius: 0.5rem;
    box-shadow: 0 2px 8px var(--shadow-md);
    /* Above the rail (z-index 5) and cards; below toast (1000) and modals. */
    z-index: 50;
}
/* Each picker cell shows its OWN fixed glyph. The resting button's next-state
 * focus preview (the .subscribe-state…:focus-visible rules above) would swap a
 * focused cell to the next state's icon — neutralize it here (these three-class
 * selectors out-specify those two-class ones). Hover preview already needs
 * .subscribe-preview-armed, which cells never carry, so only focus needs it. */
.subscribe-picker-cell.subscribe-state--unsubscribed.post-metacol-icon:focus-visible {
    --metacol-icon: url("../icons/unsubscribed.492e1214f39d.svg");
}
.subscribe-picker-cell.subscribe-state--subscribed.post-metacol-icon:focus-visible {
    --metacol-icon: url("../icons/subscribed.0f093b152634.svg");
}
.subscribe-picker-cell.subscribe-state--supersubscribed.post-metacol-icon:focus-visible {
    --metacol-icon: url("../icons/supersubscribed.2acfbc5b8356.svg");
}
/* Light the cell for the current level so the picker shows where you are. */
.subscribe-picker-cell--current {
    color: var(--link);
}
/* The date doubles as the permalink: clicking it copies the canonical URL
 * (GallaeCopyLink), so it's a normal interactive .post-metacol-item — it
 * underlines and lights to --link on hover like its siblings. We underline
 * only the inner <time> (not the whole <a>) so the chain glyph's reserved
 * slot to its left isn't underlined too. */
.post-metacol-permadate {
    text-decoration: none;
}
.post-metacol-permadate time {
    text-decoration: underline;
}
/* The chain glyph (Twemoji 1f517) sits just left of the date. It's drawn as
 * a MASK over ``currentColor`` rather than an <img>, so it inherits the
 * link's colour — shifting to --link together with the date on hover, and
 * staying theme-aware in dark mode for free; the SVG's own fill is irrelevant
 * to an alpha mask. Its box is always reserved (width + margin) so revealing
 * it never shifts the date. Hidden at rest, shown on hover/focus. */
.post-metacol-permadate::before {
    content: "";
    display: inline-block;
    width: 0.8em;
    height: 0.8em;
    margin-right: 0.3em;
    vertical-align: -0.08em;
    background-color: currentColor;
    -webkit-mask: url("../img/link.93ffbd29b137.svg") center / contain no-repeat;
    mask: url("../img/link.93ffbd29b137.svg") center / contain no-repeat;
    opacity: 0;
    transition: opacity 0.12s ease-in-out;
}
.post-metacol-permadate:hover::before,
.post-metacol-permadate:focus-visible::before {
    opacity: 1;
}
/* On touch there is no hover to reveal the cue, so show the chain glyph
 * persistently — the date is tappable to copy regardless. */
@media (hover: none) {
    .post-metacol-permadate::before {
        opacity: 1;
    }
}

/* The username reuses the canonical handle treatment (bold, author-coloured,
 * no underline — see .author-badge-handle). Sized ~30% above the rail's other
 * items so the handle reads as the rail's heading rather than just another
 * line. --card-author-color is inherited from the card ancestor. */
.post-metacol-author {
    font-size: 1.1rem;
}

/* Mobile: the action band is a single horizontal row of icon controls
 * (edit / delete / subscribe / like / cowrite) sitting beside the byline, not
 * a tall column stacked above the title — this overrides the column direction
 * set on both halves by the shared rule above. ``flex-wrap`` lets the icons
 * spill to a second line on the narrowest phones rather than overflow, and a
 * slightly wider gap than the old vertical 0.3rem gives the glyphs horizontal
 * breathing room. The desktop fixed rail restores the vertical column. */
.post-metacol-bottom {
    flex-direction: row;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.55rem;
}

/* Edit-history disclosure: the toggle plus its revealed version list,
 * stacked and slightly indented so the versions read as nested under the
 * "show edit history" control. */
.post-metacol-history,
.post-metacol-history-versions {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 0.4rem;
}
.post-metacol-history-versions {
    margin-top: 0.4rem;
    padding-left: 0.75rem;
}
.post-metacol-history-version--active {
    font-weight: 600;
}

/* ---------------------------------------------------------------------
 * Toast notifications.
 * --------------------------------------------------------------------- */
/* Fixed-position stack of transient confirmation messages. Fired
 * from views via an ``HX-Trigger`` response header; rendered by
 * the Alpine ``$store.toasts`` queue declared in ``base.html``.
 *
 * Placement: bottom-center on phones (thumb-reachable area, matches
 * Material's snackbar guidance) and bottom-right on larger screens
 * (less intrusive when the user's attention is on the main column
 * rather than a single thumb's worth of viewport). The 720px break
 * matches the project's existing mobile/desktop pivot.
 *
 * The container is ``pointer-events: none`` so the empty gutter
 * around toasts never intercepts clicks; each individual toast
 * re-enables ``pointer-events`` so click-to-dismiss still works.
 *
 * Visible duration is 4s (set in the JS store, not here). Material
 * recommends 4-10s for snackbars; we sit at the short end because
 * the message is short ("Subscribed to updates on this post.") and
 * the user can replay the action trivially if they missed it. */
.toast-stack {
    position: fixed;
    bottom: 1rem;
    left: 50%;
    transform: translateX(-50%);
    z-index: 1000;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    pointer-events: none;
    /* No own background — the stack itself is invisible chrome; the
     * children carry all the surface. */
}

@media (min-width: 720px) {
    .toast-stack {
        /* Desktop: anchor bottom-right so the toast lives away
         * from the main reading column. Reset the mobile
         * centering transform so the right-edge alignment is
         * literal. */
        left: auto;
        right: 1rem;
        transform: none;
    }
}

.toast {
    /* Re-enable input on the toast itself so the click-to-dismiss
     * affordance works even though the parent stack is
     * pointer-events: none. */
    pointer-events: auto;
    cursor: pointer;

    /* Width sized to read comfortably on one or two lines. The
     * ``min(..., calc(...))`` form keeps the toast inside the
     * viewport gutter on very narrow phones (320px) without
     * overflowing the 360px ceiling on tablets. */
    max-width: min(360px, calc(100vw - 2rem));
    padding: 0.75rem 1rem;
    border-radius: 0.5rem;

    /* Dark surface against the page so the toast reads as a
     * separate layer, not as page chrome. No CSS-variable tokens
     * exist in this stylesheet yet — using a neutral dark
     * (``#222``) and matching the body's line-height for vertical
     * rhythm. */
    background: var(--toast-bg);
    color: var(--toast-text);
    font-size: 0.95rem;
    line-height: 1.35;

    /* The drop shadow is the second visual cue (after the dark
     * surface) that the toast is floating above the page rather
     * than embedded in it. */
    box-shadow: 0 4px 12px var(--shadow-lg);
}

/* Slide-up enter motion. Alpine's ``x-transition.opacity`` already
 * fades the toast in and out; this adds the bottom-up slide so the
 * toast feels like it's coming *from* the page edge rather than
 * materializing in place. We honour ``prefers-reduced-motion`` by
 * scoping the rule — users with reduced-motion preferences get the
 * Alpine fade only, no translation. */
@media (prefers-reduced-motion: no-preference) {
    .toast {
        animation: toast-slide-in 200ms ease-out;
    }
}

@keyframes toast-slide-in {
    from {
        transform: translateY(8px);
    }
    to {
        transform: translateY(0);
    }
}

/* Kind hooks. The modifier class comes straight from the toast
 * payload's ``kind`` (see base.html's ``:class`` binding), so each
 * kind only needs to override what differs from the base ``.toast``
 * rule. */
.toast--success {
    background: var(--toast-bg-success); /* slate-800-ish; reads as success-neutral against the page */
}

/* Error toasts (Task F.54): service-layer refusals (e.g. a refused
 * Follow) surface through the same toast channel as successes; the
 * muted-red left border marks them apart without shouting — same
 * restraint rationale as the danger-button treatment below. */
.toast--error {
    background: var(--toast-bg);
    border-left: 4px solid var(--danger);
}

/* Destructive-action buttons now use the shared ``.btn btn-danger``
 * classes (the ``.btn`` family near the top of this file); the old
 * one-off ``.danger-button-form`` wrapper rule was retired when the
 * account-deactivation form adopted them. The post-detail Delete
 * button still carries its own ``.post-action-form-delete`` red
 * accent — left as-is since it lives among the other bespoke
 * post-author-action controls. */
