/* Public site: editorial, calm, chronological (PRD §23-32/§53/§82). */

.site-header {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
    padding: 24px 20px 12px;
    max-width: 1000px;
    margin-inline: auto;
    flex-wrap: wrap;
}
.site-title-block { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.site-title { display: inline-flex; align-items: center; gap: 8px; font-family: var(--font-serif); font-size: 24px; font-weight: 700; color: var(--color-text); text-decoration: none; }
.site-title:hover { text-decoration: none; }
.site-title-logo-img { width: 32px; height: 32px; object-fit: cover; border-radius: var(--radius-sm); vertical-align: middle; }
.site-title-logo-emoji { font-size: 26px; line-height: 1; }
.site-title-description { font-size: 13px; color: var(--color-text-muted); }
.site-nav { display: flex; align-items: center; gap: 8px; font-size: 14px; }
.site-nav a {
    color: var(--color-text-secondary);
    border: 1px solid var(--color-border);
    background: var(--color-surface);
    border-radius: 999px;
    padding: 6px 14px;
    line-height: 1;
    transition: border-color .15s ease, color .15s ease;
}
.site-nav a:hover { border-color: var(--color-accent); color: var(--color-text); text-decoration: none; }
.site-nav-user { display: inline-flex; align-items: center; gap: 6px; }
.site-nav-avatar { width: 18px; height: 18px; border-radius: 50%; object-fit: cover; background: var(--color-surface-muted); }
.site-nav-avatar-placeholder { display: inline-block; }
.site-nav-avatar-emoji {
    display: flex; align-items: center; justify-content: center;
    font-size: 11px; line-height: 1; border: 1px solid var(--color-border);
}

/* Item: on mobile, .site-nav shows only the hamburger -- Bookmarks/user/
   Admin/Login move up into the sidebar's own first row instead (rendered
   there always; hidden with plain CSS at desktop widths where the header
   still shows them, avoiding a duplicate). */
.sidebar-mobile-nav-row { display: none; }
@media (max-width: 640px) {
    .site-nav > a, .site-nav > .site-nav-user { display: none; }
    /* Item: no margin/padding/border of its own -- every other row in this
       sidebar spaces itself purely via .sidebar's own flex `gap`, so extra
       spacing here on top of that just made the gap to the widget below
       look inconsistently large (and this row look oddly tall). */
    .sidebar-mobile-nav-row {
        display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
    }
    .sidebar-mobile-nav-row a {
        color: var(--color-text-secondary); border: 1px solid var(--color-border);
        background: var(--color-surface); border-radius: 999px; padding: 6px 14px;
        line-height: 1; font-size: 14px;
    }
    .sidebar-mobile-nav-row a:hover { border-color: var(--color-accent); color: var(--color-text); text-decoration: none; }
    .sidebar-mobile-nav-row .site-nav-user { display: inline-flex; align-items: center; gap: 6px; }
}

.layout {
    max-width: 680px;
    margin-inline: auto;
    padding: 12px 20px 60px;
    transition: max-width .15s ease;
}
.layout.layout-grid { max-width: 1100px; }
/* Sidebar "inline" mode: a real column beside main instead of the default
   hamburger-toggled off-canvas panel (:has() lets .layout react to its
   sidebar child's mode without any extra wrapper markup). */
.layout:has(.sidebar-inline) { display: flex; align-items: flex-start; gap: 28px; max-width: 960px; }
.layout.layout-grid:has(.sidebar-inline) { max-width: 1360px; }
.layout:has(.sidebar-inline) main { flex: 1; min-width: 0; }

.sidebar-toggle {
    border: 1px solid var(--color-border);
    background: var(--color-surface);
    color: var(--color-text);
    border-radius: 50%;
    width: 36px;
    height: 36px;
    font-size: 18px;
    line-height: 1;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0;
}
/* Inline sidebar mode has no need for the toggle once there's room for the
   real side column -- but it reappears once that column would no longer
   fit (see the .sidebar-inline fallback breakpoint below). */
body[data-sidebar-mode="inline"] .sidebar-toggle { display: none; }
@media (max-width: 900px) {
    body[data-sidebar-mode="inline"] .sidebar-toggle { display: block; }
}

.sidebar-backdrop {
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, .35);
    z-index: 60;
    opacity: 0; visibility: hidden; pointer-events: none;
    transition: opacity .18s ease, visibility .18s ease;
}
.sidebar-backdrop.open { opacity: 1; visibility: visible; pointer-events: auto; }

.sidebar {
    position: fixed;
    top: 0;
    right: 0;
    bottom: 0;
    width: min(320px, 88vw);
    background: var(--color-background);
    border-left: 1px solid var(--color-border);
    padding: 20px;
    z-index: 61;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    gap: 24px;
    transform: translateX(100%);
    transition: transform .2s ease;
}
.sidebar.open { transform: translateX(0); }
.sidebar.sidebar-inline {
    position: static;
    width: 280px;
    flex: 0 0 280px;
    border-left: none;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    padding: 16px;
    transform: none;
    z-index: auto;
}
@media (max-width: 900px) {
    /* Not enough room for a real side column at this width -- fall back to
       the off-canvas panel even when "inline" is the configured mode. */
    .layout:has(.sidebar-inline) { display: block; max-width: 680px; }
    .layout.layout-grid:has(.sidebar-inline) { max-width: 1100px; }
    .sidebar.sidebar-inline {
        position: fixed; top: 0; right: 0; bottom: 0; width: min(320px, 88vw);
        border-left: 1px solid var(--color-border); border-radius: 0;
        transform: translateX(100%); transition: transform .2s ease; z-index: 61;
    }
    .sidebar.sidebar-inline.open { transform: translateX(0); }
}

/* Item 15: quick-composer lost its card look (no background/border) but
   keeps its position and full width; "Write something…" plus the new
   🤍/🖼/📸/🎤 icon row are centered together. Clicking the bar anywhere
   still opens the quick-compose modal, EXCEPT the icon buttons/tag
   popover, which do their own thing (composer.js intercepts those). */
.quick-composer {
    display: flex;
    align-items: center;
    justify-content: center;
    flex-wrap: wrap;
    gap: 10px;
    padding: 14px 16px;
    border: 1px solid transparent;
    border-radius: var(--radius-md);
    background: transparent;
    color: var(--color-text-muted);
    margin-bottom: 20px;
    font-size: 15px;
    cursor: pointer;
    position: relative;
    transition: background .15s ease, border-color .15s ease;
}
/* Hovering the bar as a whole reveals the card look it otherwise lost
   (item 15), matching .post-card:hover's own accent-colored border. */
.quick-composer:hover { border-color: var(--color-accent); background: var(--color-surface); }
.quick-composer-text {
    cursor: pointer;
    padding: 7px 16px;
    border: 1px solid var(--color-border);
    border-radius: 999px;
    background: var(--color-surface);
    line-height: 1.3;
    transition: background .15s ease, border-color .15s ease;
}
.quick-composer:hover .quick-composer-text { border-color: var(--color-accent); background: transparent; }
.quick-composer-icons { display: flex; align-items: center; gap: 8px; }
.quick-composer-icon {
    width: 32px; height: 32px;
    border-radius: 50%;
    border: 1px solid var(--color-border);
    background: var(--color-surface);
    color: var(--color-text-secondary);
    font-size: 15px;
    cursor: pointer;
    display: flex; align-items: center; justify-content: center;
    line-height: 1;
    flex-shrink: 0;
    transition: background .15s ease, border-color .15s ease;
}
.quick-composer:hover .quick-composer-icon { border-color: var(--color-accent); background: transparent; }
/* A specific icon under the cursor still gets its own accent highlight,
   on top of the bar-wide blend above (same selector specificity, so this
   just needs to come after it in source order to win). */
.quick-composer-icon:hover { border-color: var(--color-accent); color: var(--color-text); background: var(--color-surface); }
/* 📸 only makes sense with a device camera. */
.quick-composer-camera-only { display: none; }
@media (max-width: 640px) {
    .quick-composer-camera-only { display: flex; }
}
/* 🤍's inline tag-emoji picker popover. */
.quick-composer-tag-picker {
    position: absolute;
    top: calc(100% + 6px);
    left: 50%;
    transform: translateX(-50%);
    background: var(--color-surface-elevated);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    padding: 8px;
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    max-width: min(320px, 90vw);
    z-index: 20;
    box-shadow: 0 4px 16px rgba(0, 0, 0, .12);
}
.quick-composer-tag-picker[hidden] { display: none; }
.quick-composer-tag-picker button {
    width: 32px; height: 32px;
    border-radius: 50%;
    border: 1px solid var(--color-border);
    background: var(--color-surface);
    font-size: 16px;
    cursor: pointer;
    display: flex; align-items: center; justify-content: center;
}
.quick-composer-tag-picker button:hover { border-color: var(--color-accent); }

#grid-columns {
    border: 1px solid var(--color-border);
    background: var(--color-surface);
    color: var(--color-text-secondary);
    padding: 6px 12px;
    border-radius: 999px;
    font-size: 13px;
    line-height: 1;
    cursor: pointer;
}
#grid-columns:hover { border-color: var(--color-accent); color: var(--color-text); }

.timeline-date-heading {
    font-size: 13px;
    font-weight: 700;
    letter-spacing: .04em;
    text-transform: uppercase;
    color: var(--color-text-muted);
    margin: 28px 0 10px;
}
.timeline-date-heading:first-child { margin-top: 0; }

#timeline-list, .post-list { display: flex; flex-direction: column; gap: 14px; }

.post-card {
    position: relative;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    background: var(--color-surface);
    overflow: hidden;
    cursor: pointer;
    transition: border-color .15s ease;
}
/* Item: pin-to-top-of-Grid indicator (top-left corner, above any image/
   hover-action row so it's never covered). */
.post-card-pinned {
    position: absolute; top: 8px; left: 8px; z-index: 3;
    width: 26px; height: 26px; border-radius: 50%;
    background: var(--color-surface-elevated); border: 1px solid var(--color-border);
    display: flex; align-items: center; justify-content: center;
    font-size: 13px; box-shadow: 0 1px 4px rgba(0, 0, 0, .15);
}
.post-card:hover { border-color: var(--color-accent); }
/* Item: "Show drafts" Grid toggle -- owner-only preview of a draft post
   right in the Grid, plain-text-labelled so it's never mistaken for a real
   published post. */
.post-card-draft-prefix { color: var(--color-danger); font-weight: 700; }

/* Hover-only Edit/Save/Bin icons, top-right of a Grid card (post-card.php /
   timeline.js's renderCard()). Desktop only -- on mobile there's no hover to
   reveal them with, and permanently-visible icons crowded every card, so
   they're hidden there entirely instead. */
.post-card-actions {
    position: absolute;
    top: 8px; right: 8px;
    z-index: 2;
    display: flex;
    gap: 4px;
    opacity: 0;
    transition: opacity .15s ease;
}
.post-card:hover .post-card-actions { opacity: 1; }
@media (max-width: 640px) { .post-card-actions { display: none; } }
.post-card-action {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 28px; height: 28px;
    border-radius: 50%;
    border: 1px solid var(--color-border);
    background: var(--color-surface-elevated);
    color: var(--color-text-secondary);
    font-size: 13px;
    line-height: 1;
    cursor: pointer;
    text-decoration: none;
    box-shadow: 0 1px 4px rgba(0, 0, 0, .12);
}
.post-card-action:hover { border-color: var(--color-accent); color: var(--color-text); text-decoration: none; }
.post-card-action-bin:hover { border-color: var(--color-danger); color: var(--color-danger); }
/* Persisted "already saved" state (item 2) -- click again to unsave. */
.post-card-action-save.is-saved { background: var(--color-accent); border-color: var(--color-accent); color: var(--color-accent-foreground); }

/* Item 28: hide button + its 3s "about to disappear" window + fade-out. */
.post-card-action-hide[data-hiding="1"], .post-card.post-card-hiding .post-card-action-hide { background: var(--color-danger); border-color: var(--color-danger); color: #fff; }
.post-card-hiding { outline: 2px dashed var(--color-danger); outline-offset: 3px; }
.post-card-hidden-fade { opacity: 0; transform: scale(.97); transition: opacity .25s ease, transform .25s ease; pointer-events: none; }

/* Item 28: toast (past the 3s window) with its own Undo. */
.hide-toast {
    position: fixed; right: 20px; bottom: 20px; z-index: 200;
    display: flex; align-items: center; gap: 12px;
    background: var(--color-surface-elevated); color: var(--color-text);
    border: 1px solid var(--color-border); border-radius: var(--radius-md);
    padding: 10px 14px; font-size: 13px;
    box-shadow: 0 6px 24px rgba(0, 0, 0, .18);
    opacity: 0; transform: translateY(8px);
    transition: opacity .2s ease, transform .2s ease;
}
.hide-toast.open { opacity: 1; transform: translateY(0); }
.hide-toast-undo {
    border: 1px solid var(--color-accent); background: transparent; color: var(--color-accent);
    border-radius: var(--radius-sm); padding: 4px 10px; font-size: 12px; font-weight: 600; cursor: pointer;
}
.hide-toast-undo:hover { background: var(--color-accent); color: var(--color-accent-foreground); }
.post-card-body { padding: 14px 16px; }
.post-card-audio { width: 100%; display: block; margin-bottom: 10px; }
.post-card .post-title { font-family: var(--font-serif); font-size: 20px; margin: 0 0 6px; }
.post-card .post-title a { color: var(--color-text); }
/* Reset to 0: a bare <p> (a Note has no title above it) otherwise keeps the
   browser's default paragraph margin, which stacks on top of
   .post-card-body's own padding and creates lopsided whitespace compared to
   an Article/Media card (whose heading's margin is already reset above). */
.post-card .post-excerpt { color: var(--color-text-secondary); font-size: 15px; line-height: 1.6; margin: 0; }
.post-card .post-note-content { font-size: 17px; line-height: 1.7; color: var(--color-text); margin: 0; }

/* Card preview: the 1st paragraph shows in full (however many lines it
   takes); the 2nd paragraph (if any) is clamped to 10 lines with an
   ellipsis. If the 1st paragraph alone is already long (>10 lines,
   detected client-side in timeline.js), .is-long shrinks the font down to
   the Article excerpt's size so a long Note doesn't dominate the grid. */
.post-card-excerpt .pce-p1 { margin: 0; }
.post-card-excerpt .pce-p2 {
    margin: .5em 0 0;
    display: -webkit-box;
    -webkit-line-clamp: 10;
    -webkit-box-orient: vertical;
    overflow: hidden;
}
/* Author-origin `display: -webkit-box` above beats the UA stylesheet's
   `[hidden]` rule (author styles always win regardless of specificity) --
   restate it, since timeline.js hides .pce-p2 when paragraph 1 is long. */
.post-card-excerpt .pce-p2[hidden] { display: none; }
.post-card-excerpt.is-long { font-size: 15px; }
.post-card .post-meta-line { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-top: 8px; }

/* Badge look shared by category tags, tag tags, and the date/category
   chips in a card's meta line -- bordered pill, theme-colored, no bare-text
   links. Tag sits to the right of categories (post-single: right of
   .post-categories; post-card: right of the category chip in the meta line). */
.post-categories,
.post-tags,
.post-card .post-meta-line { display: flex; flex-wrap: wrap; gap: 6px; }
.post-categories a,
.post-tags a,
.post-card .post-meta-line .post-meta {
    display: inline-flex;
    align-items: center;
    padding: 3px 10px;
    border: 1px solid var(--color-border);
    border-radius: 999px;
    font-size: 12px;
    color: var(--color-text-secondary);
    background: var(--color-surface);
}
.post-categories a:hover,
.post-tags a:hover,
.post-card .post-meta-line .post-meta:hover { border-color: var(--color-accent); color: var(--color-text); text-decoration: none; }

/* /tags: a bubble cluster instead of a plain list -- bubble size scales
   with how many published posts carry that tag (tags.php computes each
   one's px size server-side), and hovering highlights + enlarges a bubble
   to make the popular (small, tightly-packed) ones easier to click. */
.tag-bubbles {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: 14px;
    padding: 20px 0;
}
.tag-bubble {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    border-radius: 50%;
    border: 1px solid var(--color-border);
    background: var(--color-surface);
    line-height: 1;
    transition: transform .15s ease, border-color .15s ease, background .15s ease, box-shadow .15s ease;
}
.tag-bubble:hover {
    transform: scale(1.12);
    border-color: var(--color-accent);
    background: var(--color-surface-elevated);
    box-shadow: 0 4px 16px rgba(0, 0, 0, .12);
    text-decoration: none;
    z-index: 1;
}
/* Item 5: post count + most-recent-use date under each bubble (also still
   in the bubble's title tooltip). */
.tag-bubble-wrap { display: flex; flex-direction: column; align-items: center; gap: 6px; width: 120px; }
.tag-bubble-caption { font-size: 11px; color: var(--color-text-muted); text-align: center; line-height: 1.4; }

/* Item 5: /tags' own tag-over-time line chart. */
.tag-chart-block { margin-top: 32px; padding-top: 20px; border-top: 1px solid var(--color-border); }
.tag-chart-controls { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; margin: 12px 0; font-size: 13px; }
.tag-chart-controls label { display: flex; align-items: center; gap: 5px; cursor: pointer; }
.tag-chart-controls select, .tag-chart-controls input[type="number"] {
    padding: 5px 8px; border-radius: var(--radius-sm); border: 1px solid var(--color-border);
    background: var(--color-surface); color: var(--color-text); font-size: 13px;
}
/* tags-chart.js's SVG reuses admin-list.css's .dashboard-chart-svg name,
   but this page doesn't load that stylesheet -- same rule here. */
.dashboard-chart-svg { width: 100%; height: auto; color: var(--color-text); }

/* Thumbnail mosaic: outside Grid mode only the first photo shows (16:9, full
   width, matches the reading-feed look) -- except a Media post in List mode,
   which gets the same Keep-style collage Grid mode uses. */
.post-card-thumb-mosaic { display: flex; flex-wrap: wrap; gap: 2px; }
.post-card-thumb-mosaic .post-card-thumb-link { display: block; line-height: 0; flex: 1 1 100%; overflow: hidden; }
.post-card-thumb-mosaic .post-card-thumb { width: 100%; height: 100%; aspect-ratio: 16 / 9; object-fit: cover; display: block; }
.post-card-thumb-mosaic .post-card-thumb-link:not(:first-child) { display: none; }
/* Explicit Thumbnail badge image and/or a voice recording (post-card-audio-thumb)
   can both precede the content-images mosaic -- a small gap between the two
   stacked mosaics keeps them from looking like one seamless block. */
.post-card-thumb-mosaic + .post-card-thumb-mosaic { margin-top: 2px; }

/* Audio-only "thumbnail" (item 34): a flat blue tile with a white circle
   and an orange play triangle -- a CSS recreation of the reference
   audio.png -- so a Grid card with only a voice recording still gets a
   recognizable tile instead of nothing. Clicking it plays the recording
   inline without leaving the grid. Deliberately fixed colors (not theme
   tokens) -- this is meant to read as a static piece of "album art", the
   same every theme, not something that reflows with light/dark mode. */
.post-card-audio-thumb {
    display: flex; align-items: center; justify-content: center;
    background: #4a90d9;
    aspect-ratio: 16 / 9;
}
.timeline-grid .post-card-audio-thumb,
.post-card[data-type="media"] .post-card-audio-thumb { aspect-ratio: 16 / 9; }
.post-card-audio-play {
    width: 56px; height: 56px;
    border-radius: 50%;
    background: #fff; color: #ff9466;
    border: none; font-size: 0; line-height: 1;
    position: relative; flex-shrink: 0;
    cursor: pointer;
    box-shadow: 0 2px 10px rgba(0, 0, 0, .15);
}
.post-card-audio-play::before {
    content: "";
    position: absolute; top: 50%; left: 54%; transform: translate(-50%, -50%);
    width: 0; height: 0;
    border-style: solid;
    border-width: 12px 0 12px 20px;
    border-color: transparent transparent transparent #ff9466;
}
.post-card-audio-play.playing { background: #fff; }
.post-card-audio-play.playing::before { border-color: transparent transparent transparent var(--color-text); }

.timeline-grid .post-card-thumb-mosaic .post-card-thumb,
.post-card[data-type="media"] .post-card-thumb-mosaic .post-card-thumb { aspect-ratio: 16 / 10; }
.timeline-grid .post-card-thumb-mosaic[data-count="1"] .post-card-thumb,
.post-card[data-type="media"] .post-card-thumb-mosaic[data-count="1"] .post-card-thumb { aspect-ratio: 16 / 9; }
/* A single-image mosaic's aspect-ratio follows the image's own orientation
   (data-orientation, set by post-card.php/timeline.js) instead of always
   assuming landscape -- horizontal keeps the 16/9 default above. */
.timeline-grid .post-card-thumb-mosaic[data-count="1"][data-orientation="vertical"] .post-card-thumb,
.post-card[data-type="media"] .post-card-thumb-mosaic[data-count="1"][data-orientation="vertical"] .post-card-thumb { aspect-ratio: 3 / 4; }
.timeline-grid .post-card-thumb-mosaic[data-count="1"][data-orientation="square"] .post-card-thumb,
.post-card[data-type="media"] .post-card-thumb-mosaic[data-count="1"][data-orientation="square"] .post-card-thumb { aspect-ratio: 1 / 1; }

.timeline-grid .post-card-thumb-mosaic[data-count="2"] .post-card-thumb-link,
.post-card[data-type="media"] .post-card-thumb-mosaic[data-count="2"] .post-card-thumb-link { display: block; flex-basis: calc(50% - 1px); }
.timeline-grid .post-card-thumb-mosaic[data-count="3"] .post-card-thumb-link,
.post-card[data-type="media"] .post-card-thumb-mosaic[data-count="3"] .post-card-thumb-link { display: block; flex-basis: calc(33.333% - 2px); }
.timeline-grid .post-card-thumb-mosaic[data-count="4"] .post-card-thumb-link:nth-child(1),
.post-card[data-type="media"] .post-card-thumb-mosaic[data-count="4"] .post-card-thumb-link:nth-child(1) { display: block; flex-basis: 100%; }
.timeline-grid .post-card-thumb-mosaic[data-count="4"] .post-card-thumb-link:nth-child(n+2),
.post-card[data-type="media"] .post-card-thumb-mosaic[data-count="4"] .post-card-thumb-link:nth-child(n+2) { display: block; flex-basis: calc(33.333% - 2px); }
.timeline-grid .post-card-thumb-mosaic[data-count="5"] .post-card-thumb-link:nth-child(-n+2),
.post-card[data-type="media"] .post-card-thumb-mosaic[data-count="5"] .post-card-thumb-link:nth-child(-n+2) { display: block; flex-basis: calc(50% - 1px); }
.timeline-grid .post-card-thumb-mosaic[data-count="5"] .post-card-thumb-link:nth-child(n+3),
.post-card[data-type="media"] .post-card-thumb-mosaic[data-count="5"] .post-card-thumb-link:nth-child(n+3) { display: block; flex-basis: calc(33.333% - 2px); }
.timeline-grid .post-card-thumb-mosaic[data-count="6"] .post-card-thumb-link,
.post-card[data-type="media"] .post-card-thumb-mosaic[data-count="6"] .post-card-thumb-link { display: block; flex-basis: calc(33.333% - 2px); }

/* #timeline-list has higher specificity than a bare .timeline-grid class
   (ID beats class), so display:flex from the rule above would otherwise
   always win -- qualify the selector with the ID to override it.
   Grid mode is a CSS-columns masonry (Keep-style: each card is its own
   natural height, packed tightly top-to-bottom within its column, no
   leftover blank space) -- a real CSS Grid with row-first auto-flow was
   tried instead (to get strict newest-first-left-to-right reading order)
   but rejected: every row's height became the tallest card in it, leaving
   visible gaps under shorter cards in the same row. Column-fill-first
   ordering (fill column 1 top-to-bottom, then column 2, ...) is the
   accepted trade-off for a tightly packed masonry look. */
#timeline-list.timeline-grid {
    display: block;
    column-count: var(--grid-columns, 3);
    column-gap: 14px;
}
#timeline-list.timeline-grid .post-card {
    display: inline-block;
    width: 100%;
    vertical-align: top;
    break-inside: avoid;
    margin-bottom: 14px;
}
@media (max-width: 640px) {
    #timeline-list.timeline-grid { column-count: var(--grid-columns-mobile, 1); }
}

/* Item 19: the column-count control as the Grid's own leading tile,
   styled like a (much smaller) post-card so it fits the same column flow
   instead of needing its own layout. */
#timeline-list.timeline-grid .grid-columns-card {
    display: inline-flex;
    flex-direction: column;
    width: 100%;
    vertical-align: top;
    break-inside: avoid;
    margin-bottom: 14px;
    padding: 12px 14px;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    background: var(--color-surface);
    box-sizing: border-box;
}
#timeline-list.timeline-grid .grid-columns-card:hover { border-color: var(--color-accent); }
/* Item: columns + hidden/locked merged into one plain-text row -- no
   dropdown/checkbox chrome, just small text buttons underlined when active
   (matches compose's .status-picker-options/.badge-options buttons). */
.grid-columns-card-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; font-size: 13px; }
.grid-columns-options, .grid-columns-toggles { display: inline-flex; align-items: center; gap: 10px; }
.grid-columns-opt, .grid-toggle-opt {
    background: none; border: none; padding: 0; font: inherit; color: var(--color-text-secondary);
    cursor: pointer; text-decoration: none;
}
.grid-columns-opt:hover, .grid-toggle-opt:hover { color: var(--color-text); }
.grid-columns-opt.active, .grid-toggle-opt.active { color: var(--color-text); font-weight: 600; text-decoration: underline; text-underline-offset: 2px; }
/* The "nothing here" message must span the whole width of the multi-column
   flow (item 19) instead of sitting in its own narrow column. */
.grid-empty-message { column-span: all; }

.load-more { text-align: center; margin-top: 24px; }
/* Item 16/17: numbered pager (2..last) that replaces "Load more" once
   it's been used, or shows directly on a page>=2 request. */
.timeline-pager { display: flex; align-items: center; justify-content: center; gap: 8px; margin-top: 24px; flex-wrap: wrap; }
.timeline-pager a, .timeline-pager-current {
    display: inline-flex; align-items: center; justify-content: center;
    min-width: 32px; height: 32px; padding: 0 8px;
    border-radius: var(--radius-sm); border: 1px solid var(--color-border);
    color: var(--color-text-secondary); font-size: 13px; text-decoration: none;
}
.timeline-pager a:hover { border-color: var(--color-accent); color: var(--color-text); text-decoration: none; }
.timeline-pager-current { background: var(--color-accent); color: var(--color-accent-foreground); border-color: transparent; font-weight: 600; }

.sidebar-block { background: var(--color-surface); border: 1px solid var(--color-border); border-radius: var(--radius-md); padding: 14px; }
.sidebar-block h3 { font-size: 13px; text-transform: uppercase; letter-spacing: .04em; color: var(--color-text-muted); margin: 0 0 10px; }

.search-form { display: flex; gap: 6px; position: relative; }
.search-form input { flex: 1; padding: 8px 10px; border-radius: var(--radius-sm); border: 1px solid var(--color-input-border); background: var(--color-input-background); color: var(--color-input-text); font-size: 14px; }
.sidebar-search-results {
    position: absolute; top: calc(100% + 4px); left: 0; right: 0;
    background: var(--color-surface-elevated);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    box-shadow: 0 8px 20px rgba(0, 0, 0, .15);
    z-index: 20;
    max-height: 260px;
    overflow-y: auto;
}
.sidebar-search-result { display: block; padding: 8px 10px; font-size: 13px; color: var(--color-text); border-bottom: 1px solid var(--color-border); }
.sidebar-search-result:last-child { border-bottom: none; }
.sidebar-search-result:hover { background: var(--color-surface-muted); text-decoration: none; }
.sidebar-search-empty { padding: 8px 10px; font-size: 13px; color: var(--color-text-muted); }

/* Item: /search results highlight every match of the search words in the
   title/preview -- the accent color as a solid fill (not the browser's
   default yellow <mark>) keeps it legible and on-theme across all themes. */
mark.search-highlight { background: var(--color-accent); color: var(--color-background); padding: 0 2px; border-radius: 2px; }

/* Rounded emoji-first badges: Categories/Tags/Contacts widgets. */
.sidebar-badge-list { display: flex; flex-wrap: wrap; gap: 6px; }
.sidebar-badge {
    display: inline-flex; align-items: center; gap: 4px;
    padding: 3px 10px; border: 1px solid var(--color-border); border-radius: 999px;
    font-size: 12px; color: var(--color-text-secondary); background: var(--color-surface);
}
.sidebar-badge:hover { border-color: var(--color-accent); color: var(--color-text); text-decoration: none; }
.sidebar-badge.active { border-color: var(--color-accent); color: var(--color-accent); }
.badge-count { opacity: .6; }

/* "Chủ đề nổi bật" (Featured Topics) sidebar widget: one mini-block per
   admin-picked tag -- heading, the tag's newest post with a thumbnail,
   then its next 3 newest as a plain title list. */
#widget-featured-topics .featured-topic-block { margin-bottom: 18px; }
#widget-featured-topics .featured-topic-block:last-child { margin-bottom: 0; }
.featured-topic-heading { margin: 0 0 8px; font-size: 13.5px; }
.featured-topic-heading a { color: var(--color-text); text-decoration: none; }
.featured-topic-heading a:hover { color: var(--color-accent); }
.featured-topic-thumb-link { display: block; margin-bottom: 6px; }
.featured-topic-thumb { width: 100%; aspect-ratio: 16/9; object-fit: cover; border-radius: var(--radius-sm); display: block; }
.featured-topic-title { display: block; font-size: 14px; font-weight: 600; line-height: 1.4; color: var(--color-text); text-decoration: none; margin-bottom: 4px; }
.featured-topic-title:hover { color: var(--color-accent); }
.featured-topic-list { list-style: none; margin: 4px 0 0; padding: 0; display: flex; flex-direction: column; gap: 4px; }
.featured-topic-list a { font-size: 12.5px; color: var(--color-text-secondary); text-decoration: none; line-height: 1.4; }
.featured-topic-list a:hover { color: var(--color-accent); }

/* Bookmarks sidebar widget: no room for a Grid here (that's /bookmarks
   itself), so saved posts show as a compact title+excerpt list and saved
   categories fall back to the same badge pills used elsewhere in the sidebar. */
.sidebar-bookmark-list { display: flex; flex-direction: column; gap: 8px; }
.sidebar-bookmark-item { display: block; color: var(--color-text); text-decoration: none; }
.sidebar-bookmark-item:hover { text-decoration: none; }
.sidebar-bookmark-item:hover .sidebar-bookmark-title { color: var(--color-accent); }
.sidebar-bookmark-title { display: block; font-size: 13px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Item 10: a note (no title of its own) shows its excerpt as the title,
   clamped to 2 lines with an ellipsis instead of the single-line truncation
   an article's real title uses. */
.sidebar-bookmark-item-note .sidebar-bookmark-title {
    white-space: normal;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    font-weight: 400;
}

.sidebar-stat-row { display: flex; justify-content: space-between; font-size: 13px; padding: 3px 0; }

.sidebar-media-grid { display: grid; grid-template-columns: repeat(var(--sidebar-media-cols, 3), 1fr); gap: 4px; }
.sidebar-media-item { display: block; aspect-ratio: 1; overflow: hidden; border-radius: var(--radius-sm); }
.sidebar-media-item img { width: 100%; height: 100%; object-fit: cover; display: block; }

.sidebar-audio-item + .sidebar-audio-item { margin-top: 8px; }
.sidebar-audio-item audio { width: 100%; }

/* Item: avatar + name/username sit side-by-side (was stacked/centered,
   which looked odd) -- .sidebar-author-head is the row, everything else
   (bio, post count, top tag) is a plain left-aligned block below it. */
.sidebar-author-head { display: flex; align-items: center; gap: 12px; }
.sidebar-author-head + * { margin-top: 10px; }
.sidebar-author-headtext { min-width: 0; }
.sidebar-author-avatar { display: block; flex-shrink: 0; width: 56px; height: 56px; border-radius: 50%; object-fit: cover; }
.sidebar-author-avatar-emoji {
    display: flex; align-items: center; justify-content: center;
    font-size: 30px; line-height: 1; background: var(--color-surface-muted); border: 1px solid var(--color-border);
}
.sidebar-author-name { font-weight: 700; font-family: var(--font-serif); }

.category-nav { display: flex; flex-direction: column; gap: 6px; font-size: 14px; }
.category-nav a { color: var(--color-text-secondary); display: flex; justify-content: space-between; }
.category-nav a.active { color: var(--color-accent); font-weight: 600; }

.calendar-nav { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; font-size: 13px; }
.calendar-nav button { background: none; border: none; cursor: pointer; color: var(--color-text-secondary); font-size: 14px; padding: 2px 6px; }
.calendar-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; font-size: 11px; text-align: center; }
.calendar-grid .cal-dow { color: var(--color-text-muted); padding-bottom: 4px; }
.calendar-grid .cal-day {
    aspect-ratio: 1;
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    border-radius: var(--radius-sm);
    cursor: pointer;
    color: var(--color-text-secondary);
    background: var(--color-calendar-background);
}
.calendar-grid .cal-day:hover { background: var(--color-calendar-hover); }
.calendar-grid .cal-day.has-posts { font-weight: 700; color: var(--color-text); }
.calendar-grid .cal-day.today { outline: 1px solid var(--color-accent); }
.calendar-grid .cal-day .cal-emoji { font-size: 10px; line-height: 1; margin-top: 1px; }
.calendar-grid .cal-empty { visibility: hidden; }

/* /calendar: the same widget markup/JS, just given more room to breathe
   than it gets cramped into the sidebar. */
.calendar-page-widget { max-width: 420px; padding: 18px; }
.calendar-page-widget .calendar-nav { font-size: 16px; margin-bottom: 14px; }
.calendar-page-widget .calendar-nav button { font-size: 18px; padding: 4px 10px; }
.calendar-page-widget .calendar-grid { gap: 6px; font-size: 13px; }
.calendar-page-widget .calendar-grid .cal-day { padding: 10px 0; }
.calendar-page-widget .calendar-grid .cal-emoji { font-size: 13px; }

/* Item 29: draft preview banner -- only the author ever sees this (an
   unpublished post 404s for everyone else), so it's a clear "this isn't
   live" flag rather than a warning. */
.draft-preview-banner {
    background: color-mix(in srgb, var(--color-accent) 12%, var(--color-surface));
    color: var(--color-text);
    border: 1px solid color-mix(in srgb, var(--color-accent) 35%, var(--color-border));
    border-radius: var(--radius-sm);
    padding: 10px 14px;
    font-size: 13px;
    margin: 12px 0;
}

/* Paywall teaser. THE BUG THAT ATE THREE BACKEND FIXES (đợt Q khẩn cấp):
   this rule used to hard-clip the teaser at max-height:220px, so no matter
   how many paragraphs PaywallService correctly served (the "20% or 4 real
   paragraphs" spec), readers only ever SAW ~1.5 of them. The teaser now
   shows EVERYTHING the server decided to send -- the server-side slice in
   PaywallService::visibleBlocks() is the one and only gatekeeper -- and
   keeps just a short bottom fade so the cutoff still reads as intentional. */
.post-content-teaser { position: relative; }
.post-content-teaser::after {
    content: ""; position: absolute; inset: auto 0 0 0; height: 110px;
    background: linear-gradient(to bottom, transparent, var(--color-background));
    pointer-events: none;
}
/* Item: matches LK's real WordPress paywall CTA card as given by the user
   (screenshot, 2026-08-26) as closely as the block-array content model
   allows -- light blue card, bold navy heading, gold pill "subscribe"
   button, a plain login link underneath. Colors are deliberately literal
   (not theme tokens) since this is reproducing one specific real design,
   not participating in LK CMS's own token system -- revisit once the
   full frontend redesign pass (a separate, later phase) has its own
   palette to draw from instead. */
.paywall-gate {
    margin: 4px 0 28px; padding: 36px 32px;
    border-radius: 22px; background: #e9f1fb; text-align: center;
}
.paywall-gate h3 {
    margin: 0 0 18px; font-family: var(--font-serif); font-weight: 800;
    font-size: 26px; line-height: 1.3; color: var(--cm-ink, #000); max-width: 30ch; margin-inline: auto;
}
.paywall-gate p { margin: 0 auto 14px; max-width: 46ch; color: #33384a; font-size: 16px; line-height: 1.65; }
.paywall-gate p:last-of-type { margin-bottom: 0; }
.paywall-gate a { color: var(--color-link); font-weight: 600; }
.paywall-gate-cta { margin-top: 22px !important; }
.paywall-gate-cta a {
    display: inline-block; padding: 14px 32px; border-radius: 999px;
    background: #f4c542; color: var(--cm-ink, #000); font-weight: 800; font-size: 16px;
    text-decoration: none;
}
.paywall-gate-cta a:hover { background: #eeb92b; }
.paywall-gate-login { margin-top: 14px !important; font-size: 14px; color: #5a5f70; }
.paywall-gate-login a { color: var(--cm-ink, #000); text-decoration: underline; font-weight: 700; }

/* Banners/campaigns: admin-authored HTML blocks at fixed positions --
   never rendered at all (empty list, not just hidden) on a page where the
   viewer is currently paywall-blocked, see App\Services\BannerService. */
.banner-slot {
    margin: 20px 0; padding: 16px 18px;
    border: 1px solid var(--color-border); border-radius: var(--radius-md);
    background: var(--color-surface-muted);
}
.banner-slot:empty { display: none; }
.banner-slot p:last-child { margin-bottom: 0; }
.banner-slot img { max-width: 100%; height: auto; border-radius: var(--radius-sm); }

.post-single { padding-top: 8px; }
.post-single .post-title { font-family: var(--font-serif); font-size: var(--heading-font-size, 34px); line-height: var(--heading-line-height, 1.15); letter-spacing: var(--heading-letter-spacing, normal); margin: 12px 0 8px; }
/* Note posts have no title, so .post-categories-row sits directly above
   .post-meta -- give it its own top/bottom spacing here (scoped to
   post-single only) so it doesn't crowd the date/time row the way it
   does when a title's own margins provide that gap instead. */
.post-single .post-categories-row { margin: 12px 0 8px; }
/* Item: font-size controls' own row -- always rendered (even with no
   categories/tags, so it always has somewhere to live), categories left,
   controls right, never wrapping to a 2nd row even on a phone (.post-
   categories itself scrolls horizontally instead if it's too wide to fit). */
.post-categories-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: nowrap; }
.post-categories-row .post-categories { margin: 0; flex: 1; min-width: 0; overflow-x: auto; scrollbar-width: none; flex-wrap: nowrap; }
.post-categories-row .post-categories::-webkit-scrollbar { display: none; }
.post-single .post-meta { color: var(--color-text-muted); font-size: 14px; margin-bottom: 20px; display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.post-single .post-meta-left { display: flex; flex-wrap: wrap; align-items: center; gap: 4px; }
/* Item: action links (Full screen/View thumbnail/Bin/Save/Edit) align
   right of the date -- margin-left:auto claims .post-meta's own flex gap
   for itself rather than needing justify-content:space-between (simpler
   with a variable number of visible links). a+a::before middot-separates
   consecutive links regardless of which combination is actually shown
   (server-side conditionals just omit an <a> entirely). */
.post-single .post-meta-actions { display: flex; align-items: center; flex-wrap: wrap; margin-left: auto; }
.post-single .post-meta-actions > a + a::before { content: "\00b7"; margin: 0 8px; color: var(--color-text-muted); text-decoration: none; }
/* Wave 5: A-/default/A+ body text size control. */
/* Item: this is meant to read as one joined segmented control (shared
   borders, only the outer corners rounded) -- a flex `gap` was adding a
   visible sliver between each button regardless, so every button ended up
   looking like its own separate rounded pill instead. */
.post-fontsize-controls { display: flex; align-items: center; flex-shrink: 0; }
.post-fontsize-btn {
    border: 1px solid var(--color-border); background: var(--color-surface);
    color: var(--color-text-secondary); border-radius: 0;
    font-size: 12px; line-height: 1; padding: 6px 10px; cursor: pointer;
}
.post-fontsize-btn:hover { border-color: var(--color-accent); color: var(--color-text); position: relative; z-index: 1; }
.post-fontsize-btn:first-child { border-radius: var(--radius-sm) 0 0 var(--radius-sm); }
.post-fontsize-btn:last-child { border-radius: 0 var(--radius-sm) var(--radius-sm) 0; }
.post-fontsize-btn + .post-fontsize-btn { border-left: none; }
/* Body text size levels -- only .post-content's own font-size changes,
   line-height/spacing stay as authored (per the user's spec). */
/* Wave 3: Bin's "delete attached media too?" confirm dialog -- self-
   contained (post-actions.js runs on public pages, which only load this
   stylesheet, not composer.css). */
.bin-confirm-backdrop {
    position: fixed; inset: 0; background: rgba(0, 0, 0, .4);
    display: flex; align-items: center; justify-content: center; z-index: 90;
}
.bin-confirm-panel {
    background: var(--color-surface-elevated); border-radius: var(--radius-lg);
    padding: 20px; width: min(380px, 92vw);
}
.bin-confirm-panel h3 { margin: 0 0 10px; font-size: 16px; }
.bin-confirm-panel p { margin: 0 0 16px; font-size: 13px; color: var(--color-text-secondary); }
.bin-confirm-actions { display: flex; flex-wrap: wrap; gap: 8px; justify-content: flex-end; }

/* Relative to --body-content-font-size (Settings > Fonts' Body text size,
   defaulting to 18px) rather than fixed px, so the reader's A-/A+ steps
   compose correctly with whatever base size the admin has configured. */
.post-single .post-content.post-fs--2 { font-size: calc(var(--body-content-font-size, 18px) - 4px); }
.post-single .post-content.post-fs--1 { font-size: calc(var(--body-content-font-size, 18px) - 2px); }
.post-single .post-content.post-fs-1 { font-size: calc(var(--body-content-font-size, 18px) + 2px); }
.post-single .post-content.post-fs-2 { font-size: calc(var(--body-content-font-size, 18px) + 4px); }
.post-single .post-content { font-size: var(--body-content-font-size, 18px); line-height: var(--body-content-line-height, 1.8); letter-spacing: var(--body-content-letter-spacing, normal); }
.post-single .post-content p { margin: 0 0 1.2em; }
.post-single .post-content blockquote { margin: 1.2em 0; padding: 4px 0 4px 18px; border-left: 3px solid var(--color-quote-border); background: var(--color-quote-background); color: var(--color-text-secondary); font-style: italic; }
.post-single .post-content pre { background: var(--color-code-background); color: var(--color-code-text); padding: 14px; border-radius: var(--radius-sm); overflow-x: auto; font-family: var(--font-mono); font-size: 14px; }
.post-single .post-content code { font-family: var(--font-mono); background: var(--color-code-background); color: var(--color-code-text); padding: 0 4px; border-radius: 4px; }
.post-single .post-content pre code { background: none; padding: 0; }
.post-single .post-content hr { border: none; border-top: 1px solid var(--color-border); margin: 2em 0; }
.post-content .task-list { list-style: none; margin: 0 0 1.2em; padding: 0; }
.post-content .task-list .task-list { margin: 4px 0 0 26px; }
.post-content .task-list-item { margin: 4px 0; }
.post-content .task-list-item label { display: flex; align-items: flex-start; gap: 8px; cursor: default; }
.post-content .task-list-item input[type="checkbox"] { margin-top: 5px; accent-color: var(--color-accent); }
.post-content .task-list-item-checked > label span { text-decoration: line-through; color: var(--color-text-muted); }
.post-single figure.post-media { margin: 1.5em 0; width: 100%; }
.post-single figure.post-media img, .post-single figure.post-media video {
    max-width: 100%;
    width: auto;
    height: auto;
    max-height: 80vh;
    border-radius: var(--radius-md);
    display: block;
    margin-inline: auto;
    cursor: zoom-in;
}
.post-single figure.post-media figcaption { font-size: 13px; color: var(--color-text-muted); margin-top: 6px; }
.post-single figure.post-audio audio { width: 100%; max-width: 480px; display: block; margin-inline: auto; }
@media (max-width: 640px) {
    /* 80vh is fine on a tall desktop window but leaves a tall portrait photo
       cramped-narrow on a short phone screen -- give it more headroom. */
    .post-single figure.post-media img, .post-single figure.post-media video { max-height: 70vh; }
}
.post-single .post-gallery { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px,1fr)); gap: 8px; margin: 1.5em 0; }
.post-single .post-gallery img { width: 100%; border-radius: var(--radius-sm); }

/* Multi-column image layout (item 5): consecutive `image` blocks in a post
   are grouped server-side (BlockRenderer/GalleryLayout) into rows of 1-3.
   Each row-mate gets an inline flex-grow + aspect-ratio equal to its
   orientation's nominal ratio (16:9 / 3:4 / 1:1) -- since both are driven
   by the same number, every image in the row renders at the same height,
   and same-orientation row-mates always end up exactly equal-width. */
/* Item 31: row-to-row spacing matches the gap between images inside a row
   (8px) instead of the larger 1.5em paragraph spacing -- adjacent rows'
   top/bottom margins collapse to that same 8px. */
.post-single .post-gallery-row { display: flex; gap: 8px; margin: 8px 0; align-items: flex-start; }
.post-single .post-gallery-row figure.post-media { margin: 0; flex-basis: 0; min-width: 0; width: auto; }
.post-single .post-gallery-row figure.post-media img {
    width: 100%; height: auto; max-width: none; max-height: none;
    object-fit: cover; border-radius: var(--radius-md); display: block; cursor: zoom-in;
}
.post-single .post-gallery-row figure.post-media figcaption { font-size: 12px; }
/* Item 12: a gallery image's caption (2+ images in the run -- including a
   3rd that wrapped alone into its own row) shows on hover, overlaid on the
   image's own bottom edge with a gradient so the text stays legible; a
   genuinely solo (1-image) post keeps its caption as normal text below the
   image (the base figure.post-media figcaption rule, untouched). */
.post-single figure.post-media[data-gallery="1"] { position: relative; overflow: hidden; }
.post-single figure.post-media[data-gallery="1"] figcaption {
    position: absolute; left: 0; right: 0; bottom: 0; margin-top: 0;
    padding: 20px 12px 10px;
    background: linear-gradient(to top, rgba(0, 0, 0, .75), transparent);
    color: #fff;
    opacity: 0;
    transition: opacity .15s ease;
    pointer-events: none;
}
.post-single figure.post-media[data-gallery="1"]:hover figcaption,
.post-single figure.post-media[data-gallery="1"]:focus-within figcaption { opacity: 1; }
/* Solo (row-of-1) image, extreme portrait (>=~16:9 tall): crop the inline
   display to 4:5 so it doesn't dominate the page -- the lightbox still
   opens the untouched original via data-lightbox-src. */
.post-single figure.post-media[data-crop="1"] img {
    aspect-ratio: 4 / 5; width: 100%; max-width: 480px; height: auto; max-height: none; object-fit: cover;
}
@media (max-width: 640px) {
    .post-single .post-gallery-row { gap: 6px; }
}

/* Rich link-preview card (composer's Link popup, "Rich card" mode --
   BlockRenderer::linkEmbed()). Mirrors composer.css's editor-side preview. */
.link-embed {
    display: flex; align-items: stretch;
    border: 1px solid var(--color-border); border-radius: var(--radius-md);
    overflow: hidden; text-decoration: none; color: inherit;
    background: var(--color-surface);
    margin: 1.5em 0;
    transition: border-color .15s ease;
}
.link-embed:hover { border-color: var(--color-accent); text-decoration: none; }
.link-embed-thumb { flex: 0 0 120px; }
.link-embed-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.link-embed-body { padding: 12px 14px; min-width: 0; display: flex; flex-direction: column; justify-content: center; gap: 4px; }
.link-embed-title { font-weight: 700; font-size: 14px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.link-embed-desc { font-size: 13px; color: var(--color-text-secondary); overflow: hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }
.link-embed-url { font-size: 12px; color: var(--color-text-muted); text-transform: uppercase; letter-spacing: .03em; }
@media (max-width: 480px) { .link-embed-thumb { flex-basis: 80px; } }

.lightbox-overlay {
    position: fixed; inset: 0; z-index: 200;
    background: rgba(0, 0, 0, .92);
    display: none; align-items: center; justify-content: center;
}
.lightbox-overlay.open { display: flex; }
.lightbox-figure { margin: 0; max-width: 92vw; max-height: 92vh; display: flex; flex-direction: column; align-items: center; gap: 10px; }
.lightbox-img { max-width: 92vw; max-height: 84vh; width: auto; height: auto; object-fit: contain; touch-action: pan-y; cursor: grab; border-radius: var(--radius-sm); }
.lightbox-caption { color: #eee; font-size: 14px; text-align: center; }
.lightbox-post-link { display: block; text-align: center; color: #fff; font-size: 13px; margin-top: 6px; opacity: .85; }
.lightbox-post-link:hover { opacity: 1; }
.lightbox-close, .lightbox-prev, .lightbox-next {
    position: fixed; background: rgba(255,255,255,.08); color: #fff; border: none; border-radius: 50%;
    width: 44px; height: 44px; font-size: 22px; line-height: 1; cursor: pointer; display: flex; align-items: center; justify-content: center;
}
.lightbox-close:hover, .lightbox-prev:hover, .lightbox-next:hover { background: rgba(255,255,255,.18); }
.lightbox-close { top: 16px; right: 16px; }
.lightbox-prev { left: 16px; top: 50%; transform: translateY(-50%); }
.lightbox-next { right: 16px; top: 50%; transform: translateY(-50%); }
/* Author-origin `display: flex` above beats the UA stylesheet's `[hidden]`
   rule (author styles always win regardless of specificity), so the prev/
   next buttons would stay visible even while `hidden` -- restate it here. */
.lightbox-prev[hidden], .lightbox-next[hidden] { display: none; }

.post-modal-backdrop {
    position: fixed; inset: 0; z-index: 90;
    background: rgba(0, 0, 0, .5);
    display: flex; align-items: flex-start; justify-content: center;
    overflow-y: auto; padding: 40px 16px;
    opacity: 0;
    visibility: hidden;
    transition: opacity .2s ease, visibility .2s ease;
}
.post-modal-backdrop.open { opacity: 1; visibility: visible; }
.post-modal-panel {
    position: relative;
    background: var(--color-background);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    max-width: 760px; width: 100%;
    max-height: calc(100vh - 80px);
    overflow-y: auto;
    transform: translateY(-14px);
    transition: transform .2s ease;
}
.post-modal-backdrop.open .post-modal-panel { transform: translateY(0); }
.post-modal-panel .post-single { padding: 44px 20px 24px; }
.post-modal-close {
    position: sticky; top: 10px; left: calc(100% - 44px);
    width: 34px; height: 34px; margin-bottom: -34px;
    border: 1px solid var(--color-border); border-radius: 50%;
    background: var(--color-surface); color: var(--color-text);
    font-size: 18px; line-height: 1; cursor: pointer; z-index: 1;
}

/* Item: /tag/xxx and /category/xxx's headings render at different natural
   sizes (a big emoji glyph vs. themed text) -- fix the wrapper's own height
   so the Grid/timeline that follows always starts at the same offset on
   both, instead of drifting a few px per page depending on font metrics. */
.archive-heading-wrap { display: flex; align-items: center; min-height: 52px; margin-bottom: 20px; }
.archive-heading-wrap h1 { line-height: 1; }

.author-header { display: flex; gap: 16px; align-items: center; margin-bottom: 20px; }
.author-avatar { width: 72px; height: 72px; border-radius: 50%; background: var(--color-surface-muted); object-fit: cover; border: 1px solid var(--color-border); }

/* Item: LK CMS's role badge on a staff/contributor's public profile
   (never shown for a plain reader -- that's the implicit default). */
.author-role-badge {
    display: inline-block; font-size: 11px; font-weight: 600; letter-spacing: .02em;
    text-transform: uppercase; padding: 2px 8px; border-radius: 999px;
    background: var(--color-surface-muted); color: var(--color-text-secondary);
    vertical-align: middle; margin-left: 4px;
}

/* Item: quick-edit name/bio/website right on the author page. */
.author-info { position: relative; }
.author-info-editable .author-info-view { padding-right: 60px; }
.author-info-edit-btn {
    position: absolute; top: 0; right: 0; background: none; border: 1px solid var(--color-border);
    border-radius: var(--radius-sm); color: var(--color-text-secondary); font-size: 12px;
    padding: 4px 8px; cursor: pointer;
}
.author-info-edit-btn:hover { color: var(--color-text); border-color: var(--color-text-secondary); }
/* Item: laid out as one row (fields side by side, not stacked) so opening
   the form doesn't grow the block's height much beyond the read view --
   stacking 3 full label+input rows used to push everything below it down. */
.author-info-edit-form { max-width: 620px; display: flex; flex-wrap: wrap; align-items: flex-end; gap: 10px 14px; }
.author-info-edit-form[hidden] { display: none; }
.author-info-edit-form .field { display: flex; flex-direction: column; gap: 4px; flex: 1 1 130px; min-width: 110px; }
.author-info-edit-form .field label { font-size: 12px; color: var(--color-text-muted); }
.author-info-edit-form .field input {
    width: 100%; padding: 7px 10px; border-radius: var(--radius-sm); border: 1px solid var(--color-input-border);
    background: var(--color-input-background); color: var(--color-input-text); font-size: 14px;
}
#author-info-error { flex-basis: 100%; margin: 0; }
.author-info-edit-actions { display: flex; gap: 8px; flex-shrink: 0; }
@media (max-width: 560px) {
    .author-info-edit-form { flex-direction: column; align-items: stretch; }
    .author-info-edit-actions { justify-content: flex-end; }
}

/* Author page's photo row: native aspect ratios, horizontal scroll, a
   right-edge fade into the page background with a "view all" 9-dot button
   that links to /author/{username}/media (the Instagram-style square grid). */
.author-photo-row-wrap { display: flex; align-items: flex-start; gap: 6px; margin-bottom: 24px; }
.author-photo-row { display: flex; gap: 6px; overflow-x: auto; scrollbar-width: none; padding-bottom: 2px; min-width: 0; }
.author-photo-row::-webkit-scrollbar { display: none; }
.author-photo-row img { height: 140px; width: auto; border-radius: var(--radius-sm); flex-shrink: 0; cursor: zoom-in; display: block; }
/* Full-height "view all" button flush against the last photo, its left
   edge fading semi-transparent into the page background so the row reads
   as trailing off rather than getting cut sharply. */
.author-photo-row-fade {
    position: relative; flex-shrink: 0; height: 140px; width: 64px;
    display: flex; align-items: stretch;
}
.author-photo-row-more {
    position: relative;
    width: 100%; height: 100%; border-radius: var(--radius-sm);
    background: linear-gradient(to right, color-mix(in srgb, var(--color-surface) 40%, transparent), var(--color-surface) 60%);
    border: 1px solid var(--color-border);
    display: flex; align-items: center; justify-content: center;
    color: var(--color-text-secondary);
    transition: border-color .15s ease, color .15s ease;
}
.author-photo-row-more:hover { border-color: var(--color-accent); color: var(--color-text); text-decoration: none; }
.dots-9 { display: grid !important; grid-template-columns: repeat(3, 4px); grid-template-rows: repeat(3, 4px); gap: 3px; align-content: center; justify-content: center; }
.dots-9 i { width: 4px; height: 4px; border-radius: 50%; background: currentColor; }

.media-grid-square { display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; }
.media-grid-square img { width: 100%; aspect-ratio: 1; object-fit: cover; display: block; cursor: zoom-in; border-radius: var(--radius-sm); }

.pagination { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 20px; }
.pagination-page {
    display: inline-flex; align-items: center; justify-content: center;
    min-width: 32px; height: 32px; padding: 0 8px; border-radius: 999px;
    border: 1px solid var(--color-border); color: var(--color-text-secondary);
    font-size: 13px; text-decoration: none; transition: border-color .15s ease, color .15s ease, background .15s ease;
}
.pagination-page:hover { border-color: var(--color-accent); color: var(--color-text); text-decoration: none; }
.pagination-page.active { background: var(--color-accent); border-color: var(--color-accent); color: var(--color-accent-foreground); font-weight: 600; }

/* /author/xxx/media topbar. */
.media-page-topbar { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; margin-bottom: 12px; }

.locked-notice { text-align: center; padding: 80px 20px; }
.locked-notice-icon { font-size: 40px; margin-bottom: 12px; opacity: .6; }
.locked-notice h1 { font-family: var(--font-serif); font-size: 22px; margin: 0 0 6px; }

/* /archive: each month is a toggle row that expands an inline post list
   instead of navigating to a separate page. Overrides .checklist li's
   default `display:flex` (row) since this row needs to stack its own
   toggle button above the (conditionally shown) posts panel. */
.archive-month-row { display: block; padding: 0; border-bottom: 1px solid var(--color-border-subtle); }
.archive-month-toggle {
    width: 100%; display: flex; align-items: center; gap: 8px;
    background: none; border: none; padding: 10px 0; cursor: pointer;
    font-size: 14px; color: var(--color-text); text-align: left;
}
.archive-month-toggle:hover { color: var(--color-accent); }
.archive-month-chevron { display: inline-block; transition: transform .15s ease; color: var(--color-text-muted); }
.archive-month-row.expanded .archive-month-chevron { transform: rotate(90deg); }
.archive-month-count { margin-left: auto; font-size: 13px; }
.archive-month-posts { padding: 0 0 10px 22px; display: flex; flex-direction: column; gap: 2px; }
.archive-month-posts[hidden] { display: none; }
.archive-month-empty { font-size: 13px; margin: 4px 0; }
.archive-post-row {
    display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
    padding: 5px 0; font-size: 14px; color: var(--color-text);
}
.archive-post-row:hover { color: var(--color-accent); text-decoration: none; }
.archive-post-title { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.archive-post-date { flex-shrink: 0; font-size: 12px; font-family: var(--font-mono); }

/* Pull-to-refresh indicator (Grid, mobile only -- timeline.js). Fixed at
   the top center, faded in/rotated as the page is pulled down from the
   very top, released past the threshold reloads the page. */
.pull-to-refresh-indicator {
    position: fixed;
    top: 10px;
    left: 50%;
    transform: translate(-50%, -40px);
    width: 32px;
    height: 32px;
    border-radius: 50%;
    background: var(--color-surface-elevated);
    border: 1px solid var(--color-border);
    color: var(--color-text-secondary);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 16px;
    opacity: 0;
    z-index: 90;
    box-shadow: 0 2px 8px rgba(0, 0, 0, .15);
    pointer-events: none;
    transition: opacity .15s ease;
}
.pull-to-refresh-indicator.ready { color: var(--color-accent); border-color: var(--color-accent); }
.pull-to-refresh-indicator.spinning { animation: pull-to-refresh-spin .6s linear infinite; }
@keyframes pull-to-refresh-spin { to { transform: translate(-50%, 0) rotate(360deg); } }

/* "Chú thích" -- the collapsed footnotes toggle at the end of an article
   (BlockRenderer::footnotes). Native <details> so it works without JS and
   is keyboard-accessible for free. */
.post-footnotes { margin: 32px 0 0; border-top: 1px solid var(--color-border); padding-top: 14px; }
.post-footnotes > summary {
    cursor: pointer; font-weight: 700; font-size: 15px; list-style: none;
    display: flex; align-items: center; gap: 8px; color: var(--color-text);
}
.post-footnotes > summary::-webkit-details-marker { display: none; }
.post-footnotes > summary::before {
    content: '▸'; display: inline-block; transition: transform .15s ease;
    color: var(--color-text-muted);
}
.post-footnotes[open] > summary::before { transform: rotate(90deg); }
.post-footnotes-list {
    margin: 12px 0 0; padding-left: 22px;
    font-size: 14px; line-height: 1.65; color: var(--color-text-secondary);
}
.post-footnotes-list li { margin-bottom: 8px; word-break: break-word; }

/* Footnote references in the body: [1] / * that link to their note. Styled
   as superscript-ish chips so they read as markers, not as ordinary links. */
.post-content a[href^="#fn-"] {
    /* Đợt Y: user yêu cầu "giống text bình thường" -- bỏ superscript/đậm/
       thu nhỏ; chỉ giữ màu accent để biết bấm được. */
    font-size: inherit; font-weight: inherit; vertical-align: baseline; line-height: inherit;
    text-decoration: none; color: var(--color-accent); padding: 0 1px;
    cursor: pointer; border-bottom: none;
}
.post-content a[href^="#fn-"]:hover { text-decoration: underline; }

/* An asterisk aside keeps its own marker instead of an ordinal. */
.post-footnotes-list li.fn-unnumbered { list-style: none; }
.post-footnotes-list li.fn-unnumbered::before {
    content: attr(data-marker); margin-left: -14px; margin-right: 6px;
    color: var(--color-text-muted);
}

/* Back-arrow inside a note -- revealed only after arriving via a marker.
   Sits inline after the note text: floating it into the left gutter covered
   the <ol>'s own number, leaving the very note you jumped to as the one
   item in the list with no visible number. */
.fn-back {
    display: inline-block; margin-left: 6px; text-decoration: none;
    color: var(--color-accent); font-size: 13px; line-height: 1;
    padding: 1px 5px; border-radius: 4px;
    background: color-mix(in srgb, var(--color-accent) 12%, transparent);
}
.fn-back:hover { background: color-mix(in srgb, var(--color-accent) 24%, transparent); }
.fn-back[hidden] { display: none; }

/* Brief highlight so the eye lands on the right line after a jump. */
.fn-target { animation: fn-flash 1.6s ease-out; border-radius: 3px; }
@keyframes fn-flash {
    0%, 30% { background: color-mix(in srgb, var(--color-accent) 22%, transparent); }
    100% { background: transparent; }
}

/* Tooltip: reads the note without leaving your place (the only behaviour on
   touch, where jumping to the end of an article is genuinely disorienting). */
.fn-tooltip {
    position: absolute; z-index: 60; max-width: 360px;
    padding: 10px 12px; border-radius: var(--radius-md);
    /* Sheet #19: nền vàng giống infobox (cùng bộ màu cố định). */
    background: #fff8dc; color: #3a3223;
    border: 1px solid #f1e1a6;
    box-shadow: 0 6px 24px rgba(0,0,0,.18);
    font-size: 13.5px; line-height: 1.55;
}
.fn-tooltip a { color: #7a5c12; }
.fn-tooltip[hidden] { display: none; }
.fn-tooltip a { word-break: break-word; }

/* A further-reading list: same toggle affordance, but unnumbered -- nothing
   in the article cites it by number. */
.post-references-list {
    margin: 12px 0 0; padding-left: 22px; list-style: disc;
    font-size: 14px; line-height: 1.65; color: var(--color-text-secondary);
}
.post-references-list li { margin-bottom: 8px; word-break: break-word; }

/* ---- No-sidebar article layout (imported from JNews, 44 posts) ----------
   These pieces were laid out without a sidebar on the old site, so the
   article keeps the full measure here too. Landscape images are allowed to
   break out slightly wider than the text column to give them the emphasis
   that layout was chosen for -- square and portrait images stay inline,
   since widening those makes them loom over the text rather than lead it.
   The breakout is clamped to the viewport so it can never cause sideways
   scrolling on a phone. */
.post-single.layout-no-sidebar .post-media-landscape {
    width: min(calc(100% + 160px), calc(100vw - 32px));
    margin-left: 50%;
    transform: translateX(-50%);
}
.post-single.layout-no-sidebar .post-media-landscape img {
    width: 100%;
    height: auto;
}
@media (max-width: 900px) {
    /* Below the breakpoint there is no spare gutter to break out into. */
    .post-single.layout-no-sidebar .post-media-landscape {
        width: 100%;
        margin-left: 0;
        transform: none;
    }
}

/* Featured image at the top of an article. Held outside the body blocks (the
   editor stores it as a separate Thumbnail field), so it is rendered here and
   spaced to read as the article's opening image rather than a stray figure. */
.post-lead-image { margin: 0 0 26px; }
.post-lead-image img {
    width: 100%; height: auto; display: block;
    border-radius: var(--radius-md);
}
.post-lead-image figcaption {
    margin-top: 8px; font-size: 13.5px; line-height: 1.5;
    color: var(--color-text-muted);
}

/* Infobox: a tinted callout for editor's notes and "read this series" boxes.
   Imported from both Gutenberg groups and Ghost callout cards, unified into
   one block so the site has a single concept to style. */
.post-infobox {
    display: flex; gap: 14px; align-items: flex-start;
    background-color: #fff8dc;
    border: 1px solid #f1e1a6;
    border-radius: 6px;
    /* Đợt AD: padding thật thay vì dựa vào margin con (cmb-theme đặt
       p{margin-top:0} thắng specificity nên chữ dính mép trên). */
    padding: 18px 20px;
    margin: 24px 0;
    color: #3a3223;
}
.post-infobox-emoji { font-size: 20px; line-height: 1; flex-shrink: 0; }
.post-infobox-body { min-width: 0; flex: 1; }
body .post-infobox-body > *:first-child { margin-top: 0; }
body .post-infobox-body > *:last-child { margin-bottom: 0; }
.post-infobox-body a { color: #7a5c12; }
/* Deliberately fixed colours, not theme tokens: this box is a highlight whose
   whole job is to stand out from the page, and it must keep the same warm
   parchment look in every one of the site's themes. */

/* Đợt BF: BẢNG trong bài.

   Bảng số liệu của toà soạn thường rộng hơn cột chữ, nên nó có khung cuộn
   ngang RIÊNG (.post-table-scroll) -- thân trang không bao giờ được cuộn
   ngang. Hàng tiêu đề dính trên khi cuộn dọc trong khung đó.

   Màu lấy từ token của giao diện, khác infobox: bảng là nội dung bài, không
   phải hộp nhấn mạnh, nên nó phải đổi màu theo giao diện người đọc chọn. */
.post-table-wrap { margin: 26px 0; }
.post-table-scroll {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    border: 1px solid var(--color-border);
    border-radius: 6px;
}
.post-table {
    border-collapse: collapse;
    width: 100%;
    font-size: 0.94em;
    line-height: 1.5;
    background: var(--color-surface);
}
.post-table th,
.post-table td {
    border: 1px solid var(--color-border);
    padding: 9px 12px;
    text-align: left;
    vertical-align: top;
}
/* Viền ngoài đã do khung cuộn vẽ, bỏ để không thành hai nét. */
.post-table tr > *:first-child { border-left: 0; }
.post-table tr > *:last-child { border-right: 0; }
.post-table thead tr:first-child > * { border-top: 0; }
.post-table tbody tr:last-child > * { border-bottom: 0; }

.post-table thead th {
    background: var(--color-surface-elevated, var(--color-surface));
    font-weight: 700;
    white-space: nowrap;
    padding: 0;
    position: sticky;
    top: 0;
    z-index: 1;
}
.post-table tbody th[scope="row"] {
    font-weight: 600;
    background: var(--color-surface-elevated, var(--color-surface));
}
.post-table tbody tr:nth-child(even) td { background: color-mix(in srgb, var(--color-text) 3%, transparent); }

/* Nút sắp xếp: lấp đầy ô tiêu đề để bấm chỗ nào trong ô cũng trúng. */
.post-table-sort {
    display: flex; align-items: center; gap: 6px;
    width: 100%; padding: 9px 12px;
    background: none; border: 0; cursor: pointer;
    font: inherit; font-weight: 700; color: inherit; text-align: left;
}
.post-table-sort:hover { color: var(--color-accent); }
.post-table-sort:focus-visible { outline: 2px solid var(--color-accent); outline-offset: -2px; }
.post-table-arrow {
    flex-shrink: 0; width: 8px; height: 10px; opacity: 0.35;
    /* Hai mũi tên chồng nhau, mờ khi chưa sắp xếp theo cột này. */
    background:
        linear-gradient(to bottom right, transparent 48%, currentColor 48%, currentColor 52%, transparent 52%) no-repeat 0 0 / 100% 45%,
        linear-gradient(to top right, transparent 48%, currentColor 48%, currentColor 52%, transparent 52%) no-repeat 0 100% / 100% 45%;
}
.post-table-sort[data-dir="asc"] .post-table-arrow,
.post-table-sort[data-dir="desc"] .post-table-arrow { opacity: 1; }
.post-table-sort[data-dir="asc"] .post-table-arrow { background-size: 100% 45%, 0 0; }
.post-table-sort[data-dir="desc"] .post-table-arrow { background-size: 0 0, 100% 45%; }
.post-table-sort[data-dir] { color: var(--color-accent); }

.post-table-wrap > figcaption {
    margin-top: 8px;
    font-size: 0.85em;
    color: var(--color-text-muted);
}

@media (max-width: 640px) {
    .post-table { font-size: 0.86em; }
    .post-table th, .post-table td { padding: 7px 9px; }
    .post-table-sort { padding: 7px 9px; }
}

/* "Đọc thêm" / "Chú thích" headings: identical treatment so a reader reads
   them as the article's footer rather than as more article content. */
.post-footnotes > summary h4,
/* Đợt BG: cùng cỡ và cùng khoảng cách với "Chú thích" / "Chủ đề" -- ba cái
   này là một hàng đồ đạc cuối bài, phải đọc như nhau. */
.post-readmore-heading {
    display: block; margin: 34px 0 14px; font-size: 18px; font-weight: 700; line-height: 1.3;
}

/* Link preview thumbnail: fixed 16:9, so a column of cards lines up instead
   of jumping around with each source image's own proportions. */
.link-embed-thumb {
    flex: 0 0 168px; aspect-ratio: 16 / 9; overflow: hidden;
    border-radius: var(--radius-sm);
}
.link-embed-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
@media (max-width: 560px) { .link-embed-thumb { flex-basis: 120px; } }


/* Đợt Y: căn ảnh từ popup tuỳ chọn ảnh trong composer. */
.post-media-align-left { float: left; max-width: 46%; margin: 6px 26px 12px 0; }
.post-media-align-right { float: right; max-width: 46%; margin: 6px 0 12px 26px; }
.post-media-align-center { margin-left: auto; margin-right: auto; max-width: 74%; }
.post-media-align-left img, .post-media-align-right img, .post-media-align-center img { width: 100%; height: auto; }
@media (max-width: 640px) {
    .post-media-align-left, .post-media-align-right { float: none; max-width: 100%; margin: 1.6em 0; }
}


/* Sheet #9: tránh chữ mồ côi -- trình duyệt hỗ trợ text-wrap:pretty sẽ tự
   cân dòng cuối; heading cân bằng dòng. */
.post-content p { text-wrap: pretty; }
.post-content h2, .post-content h3, .post-content h4, .post-title { text-wrap: balance; }

/* Đợt BG: bảng bật "cột trái là cột chính" -- cột đó đậm hơn và DÍNH LẠI khi
   cuộn ngang, để bảng rộng vẫn đọc được là dòng nào nói về cái gì. */
.post-table.has-row-header tbody th[scope="row"] {
    position: sticky;
    left: 0;
    z-index: 2;
    font-weight: 700;
    background: var(--color-surface-elevated, var(--color-surface));
    box-shadow: 1px 0 0 var(--color-border);
}
.post-table.has-row-header thead th:first-child {
    position: sticky;
    left: 0;
    z-index: 3;
}

/* Đợt BI: khối mã nhúng của bên thứ ba (trình phát, biểu đồ, bản đồ).
   Khung nhúng thường khai báo width/height cứng, nên ép nó co theo cột chữ
   và không bao giờ đẩy thân trang cuộn ngang. */
.post-embed { margin: 26px 0; max-width: 100%; }
.post-embed iframe {
    max-width: 100%;
    width: 100%;
    min-height: 240px;
    border: 0;
    border-radius: 6px;
    display: block;
}
.post-embed img, .post-embed video { max-width: 100%; height: auto; }
.post-embed table { width: 100%; border-collapse: collapse; }

/* Đợt BI: trình phát âm thanh của Luật Khoa (cm-audio-player.js).
   Nằm giữa bài phỏng vấn nên phải đọc như một phần của bài: cùng chữ, cùng
   viền, cùng bo góc với hộp thông tin và bảng, không phải một tiện ích lạ. */
.post-audio-card {
    margin: 26px 0;
    padding: 14px 16px;
    border: 1px solid var(--color-border);
    border-radius: 8px;
    background: var(--color-surface);
}
.post-audio-card > audio { width: 100%; }
.post-audio-card[data-cmb-audio-done] > audio { display: none; }
.post-audio-title {
    font-weight: 700; font-size: 0.95em; margin-bottom: 10px; line-height: 1.35;
}
.cmb-audio {
    display: flex; align-items: center; gap: 12px;
    --cmb-audio-pos: 0%;
    /* Màu của trình phát là XANH NAVY của Luật Khoa, không phải --color-accent.
       Trên trang công khai, accent là màu chữ gần đen (#191718), nên nút phát
       lấy theo nó sẽ ra một khối đen -- không phải nhận diện của toà soạn. */
    --cmb-audio-accent: var(--cmb-navy, var(--cm-ink, #000));
}
.cmb-audio button {
    flex: none; display: inline-flex; align-items: center; justify-content: center;
    border: 1px solid var(--color-border); background: var(--color-background);
    color: var(--color-text); cursor: pointer; border-radius: 999px;
    font: inherit; padding: 0;
}
.cmb-audio button:hover { border-color: var(--cmb-audio-accent); color: var(--cmb-audio-accent); }
.cmb-audio button:focus-visible { outline: 2px solid var(--cmb-audio-accent); outline-offset: 2px; }
.cmb-audio-play { width: 44px; height: 44px; background: var(--cmb-audio-accent) !important; border-color: var(--cmb-audio-accent) !important; }
.cmb-audio-play svg { width: 20px; height: 20px; fill: #fff; }
.cmb-audio-play:hover { filter: brightness(1.08); }
.cmb-audio-back { width: 36px; height: 36px; position: relative; }
.cmb-audio-back svg { width: 18px; height: 18px; fill: currentColor; }
.cmb-audio-back b {
    position: absolute; font-size: 8px; font-weight: 700; line-height: 1;
    bottom: 9px; left: 0; right: 0; text-align: center;
}
.cmb-audio-rate { min-width: 42px; height: 32px; font-size: 12.5px; font-weight: 700; }
.cmb-audio-mid { flex: 1; min-width: 0; }
/* Vùng bấm cao 24px để tua bằng ngón tay không bị trượt, còn vạch nhìn thấy
   chỉ dày 6px và nằm giữa. Vạch là một lớp ::before riêng chứ không phải nền
   của chính vùng bấm -- dùng background-clip cho việc này thì vạch không hiện
   ra ở một số trình duyệt. */
.cmb-audio-bar {
    position: relative; height: 24px; cursor: pointer;
}
.cmb-audio-bar::before {
    content: ""; position: absolute; left: 0; right: 0; top: 9px; height: 6px;
    border-radius: 999px; background: rgba(3, 29, 109, .16);
}
.cmb-audio-buffered, .cmb-audio-played {
    position: absolute; left: 0; top: 9px; height: 6px; border-radius: 999px;
}
.cmb-audio-buffered { width: 0; }
.cmb-audio-buffered { background: rgba(3, 29, 109, .28); }
.cmb-audio-played { background: var(--cmb-audio-accent); width: 0; }
.cmb-audio-knob {
    position: absolute; top: 50%; left: var(--cmb-audio-pos);
    width: 13px; height: 13px; border-radius: 50%;
    background: var(--cmb-audio-accent); border: 2px solid var(--color-background);
    transform: translate(-50%, -50%); pointer-events: none;
    opacity: 0; transition: opacity .15s ease;
}
.cmb-audio-bar:hover .cmb-audio-knob,
.cmb-audio.playing .cmb-audio-knob { opacity: 1; }
.cmb-audio-times {
    display: flex; justify-content: space-between;
    font-size: 12px; color: var(--color-text-muted);
    font-variant-numeric: tabular-nums; margin-top: 2px;
}
@media (max-width: 560px) {
    .cmb-audio { gap: 8px; }
    .cmb-audio-play { width: 38px; height: 38px; }
    .cmb-audio-back { display: none; }
}

/* Video: giữ điều khiển gốc của trình duyệt (đủ tốt và quen tay) nhưng đóng
   khung cho khớp với audio ở trên. */
.post-video-card {
    margin: 26px 0; border: 1px solid var(--color-border);
    border-radius: 8px; overflow: hidden; background: #000;
}
.post-video-card video { width: 100%; display: block; max-height: 70vh; }

/* Đợt BI: BIỂU ĐỒ (cm-chart.js). Vẽ bằng SVG tự dựng, không thư viện ngoài --
   trang này bị chặn ở Việt Nam nên mọi tệp phải nằm trên máy chủ toà soạn. */
.post-chart { margin: 28px 0; }
.cmb-chart-title { font-weight: 700; font-size: 1.02em; margin-bottom: 10px; line-height: 1.35; }
.cmb-chart-canvas { position: relative; }
.cmb-chart-svg { width: 100%; height: auto; display: block; overflow: visible; }
.cmb-chart-grid { stroke: var(--color-border); stroke-width: 1; }
.cmb-chart-axis { font-size: 11px; fill: var(--color-text-muted); font-family: inherit; }
.cmb-chart-bar, .cmb-chart-dot, .cmb-chart-slice {
    cursor: pointer;
    transition: opacity .15s ease, transform .15s ease;
    transform-origin: center;
}
.cmb-chart-canvas.has-tip .cmb-chart-bar,
.cmb-chart-canvas.has-tip .cmb-chart-dot,
.cmb-chart-canvas.has-tip .cmb-chart-slice { opacity: .45; }
.cmb-chart-bar:hover, .cmb-chart-dot:hover, .cmb-chart-slice:hover { opacity: 1 !important; }
.cmb-chart-dot:hover { r: 6.5; }
.cmb-chart-slice:hover { transform: scale(1.03); }
/* Vẽ xong thì các cột mọc lên một lần, đủ để mắt bám được chứ không thành trò. */
@media (prefers-reduced-motion: no-preference) {
    .cmb-chart-bar { animation: cmb-chart-grow .45s cubic-bezier(.2, .8, .3, 1) both; transform-origin: bottom; }
    .cmb-chart-line { stroke-dasharray: 2000; animation: cmb-chart-draw 1s ease-out both; }
}
@keyframes cmb-chart-grow { from { transform: scaleY(0); } to { transform: scaleY(1); } }
@keyframes cmb-chart-draw { from { stroke-dashoffset: 2000; } to { stroke-dashoffset: 0; } }
.cmb-chart-tip {
    position: absolute; z-index: 3; pointer-events: none;
    transform: translate(-50%, -100%);
    background: var(--color-text); color: var(--color-background);
    border-radius: 6px; padding: 6px 10px; font-size: 12.5px; line-height: 1.4;
    box-shadow: 0 4px 14px rgba(0, 0, 0, .22); white-space: nowrap;
}
.cmb-chart-tip b { display: block; font-size: 12px; }
.cmb-chart-tip span { display: block; opacity: .75; font-size: 11.5px; }
.cmb-chart-tip em { font-style: normal; font-weight: 700; font-variant-numeric: tabular-nums; }
.cmb-chart-legend { display: flex; flex-wrap: wrap; gap: 6px 16px; margin-top: 12px; font-size: 13px; }
.cmb-chart-key { display: inline-flex; align-items: center; gap: 6px; color: var(--color-text-muted); }
.cmb-chart-key i { width: 12px; height: 12px; border-radius: 3px; display: inline-block; }
.cmb-chart-empty { color: var(--color-text-muted); font-size: 13.5px; margin: 0; }

/* Đợt BI: MÀU cho hộp thông tin. Danh sách đóng, mỗi màu là một cặp nền + viền
   + màu liên kết đã được chọn để đọc được trên cả nền sáng lẫn nền tối. */
.post-infobox-vang { background-color: #fff8dc; border-color: #f1e1a6; color: #3a3223; }
.post-infobox-vang a { color: #7a5c12; }
.post-infobox-xanh { background-color: #eaf1fc; border-color: #bcd2f0; color: #1b2a44; }
.post-infobox-xanh a { color: #144a9c; }
.post-infobox-do { background-color: #fdeeec; border-color: #f3c9c2; color: #46201c; }
.post-infobox-do a { color: #9c2b1f; }
.post-infobox-luc { background-color: #eaf6ee; border-color: #bfe0cb; color: #1c3626; }
.post-infobox-luc a { color: #1a6b3d; }
.post-infobox-tim { background-color: #f3eefb; border-color: #d8c9ef; color: #2e1f45; }
.post-infobox-tim a { color: #5b2e9c; }
.post-infobox-xam { background-color: #f2f3f5; border-color: #d9dce1; color: #23262b; }
.post-infobox-xam a { color: #3c4450; }

/* Đợt BI: TRÍCH DẪN NỔI BẬT (pull quote). */
.post-pullquote { margin: 30px 0; }
.post-pullquote .pq-text {
    margin: 0; border: 0; padding: 0;
    font-family: var(--cmb-serif, Georgia, serif);
    font-size: 1.32em; line-height: 1.45; font-weight: 600;
    text-wrap: balance;
}
.post-pullquote .pq-cite {
    margin-top: 12px; font-size: .82em; line-height: 1.45;
    display: flex; flex-direction: column; gap: 1px;
}
.post-pullquote .pq-author { font-weight: 700; }
.post-pullquote .pq-role { color: var(--color-text-muted); }

/* Bốn kiểu trình bày. */
.pq-classic .pq-text { font-style: italic; }
.pq-classic .pq-cite::before { content: "— "; }
.pq-bar { border-left: 4px solid var(--cmb-navy, var(--cm-ink, #000)); padding-left: 20px; }
.pq-card {
    background: var(--color-surface-elevated, rgba(0, 0, 0, .03));
    border-radius: 10px; padding: 22px 24px;
}
.pq-bigmark { position: relative; padding-top: 26px; }
.pq-bigmark::before {
    content: "\201C"; position: absolute; top: -14px; left: -4px;
    font-family: var(--cmb-serif, Georgia, serif); font-size: 88px; line-height: 1;
    color: var(--cmb-navy, var(--cm-ink, #000)); opacity: .18;
}

/* Cỡ và vị trí. "narrow" là một phần tư cột chữ, để chữ chảy quanh. */
.pq-size-narrow { width: 30%; min-width: 210px; }
.pq-size-narrow.pq-align-left { float: left; margin: 8px 26px 16px 0; }
.pq-size-narrow.pq-align-right { float: right; margin: 8px 0 16px 26px; }
.pq-size-narrow.pq-align-center { margin-inline: auto; float: none; }
.pq-size-full.pq-align-left { text-align: left; }
.pq-size-full.pq-align-right { text-align: right; }
.pq-size-full.pq-align-center { text-align: center; }
.pq-size-full.pq-align-center .pq-cite { align-items: center; }
.pq-size-full.pq-align-right .pq-cite { align-items: flex-end; }
@media (max-width: 620px) {
    /* Trên điện thoại cột chữ đã hẹp, trích dẫn thu nhỏ nữa thì không đọc được. */
    .pq-size-narrow { width: auto; float: none !important; margin: 24px 0 !important; }
}
