/* ============================================================
   dashboard.css

   Owns the dashboard grid layout end-to-end — the Phase 11b
   work tracked as db1 (column shell), db3 (Media + Links column
   chrome), db4 (swipe/pagination + narrow-viewport fallback),
   db5 (+ button / phantom spacer), and db6 (column header).

   Also owns the per-column-type tile chrome for the Media and
   Links columns, and the .dashboard-grid-scoped visual refreshes
   from pr33 (feed-column post cards), pr35 (links tiles), and
   pr36 (media tiles). Those refreshes only apply when posts /
   links / media render INSIDE a dashboard column — the
   standalone post page, profile feeds, and drafts page use the
   un-scoped rules in feed.css.

   The Profile column header refresh (pr24, pr37) lives in
   profile.css, NOT here.

   Reference: markdown/design/DESIGN_DECISIONS_CSS_REFACTOR.md
   ============================================================ */

/* === Dashboard (Phase 11b db1, db4) ============================== */

/* Outer grid container. Always a flex row holding (in order):
     [optional phantom spacer]
     [.dashboard-grid-wrapper holding columns]
     [optional + button]
     [pagination dots, hidden in default mode]
   In default mode, the wrapper just passes columns through with its
   own flex layout. In swipe mode, JS adds .swiper / .swiper-wrapper /
   .swiper-slide classes and SwiperJS owns the wrapper. */
.dashboard-grid {
    display: flex;
    flex-direction: row;
    gap: 1rem;
    align-items: flex-start;
    justify-content: center;
}

/* Inner wrapper. In default mode it's a flex row distributing columns
   side by side. The flex: 1 1 auto + min-width: 0 lets it grow to fill
   available space between the phantom and the + button. */
.dashboard-grid-wrapper {
    display: flex;
    flex-direction: row;
    gap: 1rem;
    align-items: flex-start;
    flex: 1 1 auto;
    min-width: 0;
    /* Bound the wrapper so 1-column users don't get a comically wide
       single column — caps wrapper width to (3 columns + 2 gaps). */
    max-width: calc(540px * 3 + 1rem * 2);
    /* Center the columns inside the wrapper. With 2 or 3 columns,
       the columns naturally fill the wrapper's width via flex-grow,
       so this is a no-op. With 1 column, the column is capped at
       540px and the remaining wrapper width is split evenly on
       either side, centering it. */
    justify-content: center;
}

/* In swipe mode, JS adds .swiper-wrapper to this element. SwiperJS's
   own CSS sets width/transform on .swiper-wrapper. Our flex rules need
   to get out of the way so SwiperJS can measure correctly. */
.dashboard-grid.is-swipe .dashboard-grid-wrapper {
    /* Don't set `display` here — SwiperJS's own CSS sets
       .swiper-wrapper to display: flex, which is what we want for the
       horizontal slide layout. Overriding it breaks the carousel.

       Don't set `width` either — SwiperJS computes wrapper width
       based on slide count × slide width × etc, and sets it inline.

       We just neutralize our default-mode flex rules so they don't
       fight SwiperJS's layout. */
    flex: initial;
    gap: 0;
    max-width: none;
    min-width: 0;
    width: max-content;
}

/* In swipe mode, the outer grid becomes block so SwiperJS's transform
   on .swiper-wrapper has full container width to translate against. */
.dashboard-grid.is-swipe {
    display: block;
}

/* A single column. Default mode: flex item in .dashboard-grid-wrapper. */
.dashboard-column {
    flex: 1 1 0;
    min-width: 0;
    max-width: 540px;
    /* app1 (round 6) — was --color-bg-surface; split so navbar can
       stay green while column bodies drop to a near-black surface. */
    background: var(--color-bg-column);
    border: 1px solid var(--bs-border-color);
    border-radius: var(--bs-border-radius);
    overflow: hidden;
}

/* In swipe mode, kill flex behavior. SwiperJS sets explicit inline widths
   on each .swiper-slide that we mustn't fight. */
.dashboard-grid.is-swipe .dashboard-column {
    flex: none;
    max-width: none;
    min-height: 70vh;
}

/* Phantom spacer and + button hide in swipe mode. */
.dashboard-grid.is-swipe .add-column-phantom,
.dashboard-grid.is-swipe .add-column-btn {
    display: none;
}

/* === Pagination dots (db4) ======================================= */

/* Hidden by default. JS adds .swiper-pagination class in swipe mode. */
.dashboard-swipe-pagination {
    display: none;
}

.dashboard-swipe-pagination.swiper-pagination-bullets {
    display: block;
    position: fixed;
    bottom: calc(1rem + var(--safe-area-bottom));
    left: 0;
    right: 0;
    text-align: center;
    z-index: 10;
}

.dashboard-swipe-pagination .swiper-pagination-bullet {
    background: var(--color-text-muted);
    opacity: 0.4;
    width: 8px;
    height: 8px;
    margin: 0 4px;
    transition: opacity var(--transition-medium), background var(--transition-medium);
}

.dashboard-swipe-pagination .swiper-pagination-bullet-active {
    background: var(--color-accent);
    opacity: 1;
}

/* === CSS fallback for narrow viewports (db4) ===================== */

/* Pre-JS / no-JS safety net: at very narrow widths, force columns
   to stack vertically full-width. JS will replace this with proper
   swipe behavior on hydration. */
@media (max-width: 600px) {
    .dashboard-grid:not(.is-swipe) {
        flex-direction: column;
    }

    .dashboard-grid:not(.is-swipe) .dashboard-grid-wrapper {
        flex-direction: column;
        max-width: none;
    }

    .dashboard-grid:not(.is-swipe) .dashboard-column {
        max-width: none;
        width: 100%;
    }

    .dashboard-grid:not(.is-swipe) .add-column-phantom {
        display: none;
    }
}

.dashboard-column-header {
    padding: 0.75rem 1rem;
    border-bottom: 1px solid var(--bs-border-color);
    /* app1 (round 6) — was --bs-tertiary-bg; split so column header
       can sit one notch lighter than the column body without affecting
       other --bs-tertiary-bg consumers (post-detail-panel hover etc). */
    background: var(--color-bg-column-header);
    display: flex;
    align-items: center;
    justify-content: space-between;
}

/* Drag affordance — cursor turns into the grab hand on hover, the
   closed grab hand while dragging. SortableJS doesn't manage these
   directly; the host CSS owns them. */
.dashboard-column-header {
    cursor: grab;
}

.dashboard-column-header:active,
.dashboard-column.dashboard-column-chosen .dashboard-column-header {
    cursor: grabbing;
}

.column-menu-btn {
    background: transparent;
    border: 0;
    color: var(--bs-secondary-color);
    opacity: 0.5;
    padding: 4px;
    border-radius: 4px;
    cursor: pointer;
    transition: opacity 0.15s ease, background 0.15s ease;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    text-decoration: none;
}

.column-menu-btn:hover {
    opacity: 1;
    background: var(--bs-secondary-bg);
}

.dashboard-column-title {
    font-weight: 600;
    text-transform: capitalize;
    /* "feed" → "Feed" without changing data */
    font-size: 0.95rem;
}

.dashboard-column-body {
    padding: 1rem;
}

/* The "+" button at the right end of the row when fewer than 3
   columns exist. */
.add-column-btn {
    flex: 0 0 auto;
    width: 60px;
    height: 60px;
    align-self: flex-start;
    position: sticky;
    top: 80px;
    display: flex;
    align-items: center;
    justify-content: center;
    background: transparent;
    border: 2px dashed var(--bs-border-color);
    border-radius: var(--bs-border-radius);
    color: var(--bs-secondary-color);
    text-decoration: none;
    font-size: 1.5rem;
    font-weight: 300;
    cursor: pointer;
    transition: all 0.15s ease;
}

.add-column-btn:hover {
    border-color: var(--bs-primary);
    color: var(--bs-primary);
    background: var(--bs-primary-bg-subtle);
}

/* Invisible spacer on the left of the column row, the same width
   as the "+" button on the right. Centers the columns visually. */
.add-column-phantom {
    flex: 0 0 auto;
    width: 60px;
    visibility: hidden;
    pointer-events: none;
}

/* === Reading-width content wrapper =============================== */

/* Default wrapper class for non-dashboard pages (timeline, drafts,
   new post, post detail, settings, etc). Capped at a comfortable
   reading width so post cards don't sprawl edge-to-edge on wide
   monitors. The dashboard overrides {% block content_wrapper_class %}
   to be empty so it can use the full container width. */
.content-narrow {
    max-width: 600px;
    margin: 0 auto;
}

/* === Media column (Phase 11b db3) ================================ */

/* Square-thumbnail grid. Always 3 tiles per row, regardless of the
   Media column's rendered width — `aspect-ratio: 1 / 1` + `object-fit: cover`
   on the tiles means images scale beautifully from narrow (~340px) to
   wide (~1080px) without needing different counts.

   Spec amendment vs. DESIGN_DECISIONS_VISUAL.md §3.4: the original 5/3/3
   responsive count was over-engineered. Three tiles at any width works.
   Logged for the next docs PR. */
.media-column-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 4px;
}

/* A single tile. Square aspect ratio enforced by CSS, so the grid
   lays out cleanly regardless of source image dimensions. The image
   inside is object-fit: cover, so it center-crops without distortion. */
.media-column-tile {
    position: relative;
    display: block;
    aspect-ratio: 1 / 1;
    overflow: hidden;
    border-radius: var(--radius-sm);
    background: var(--color-bg-subtle);
    text-decoration: none;
    color: inherit;
}

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

/* Stacked-cards indicator — top-right badge showing image count
   when a post has more than one image. */
.media-column-stack-indicator {
    position: absolute;
    top: 6px;
    right: 6px;
    background: rgba(0, 0, 0, 0.6);
    color: var(--color-text-on-accent);
    font-size: var(--font-size-xs);
    font-weight: var(--font-weight-semibold);
    padding: 2px 6px;
    border-radius: var(--radius-pill);
    line-height: 1.2;
}

/* media1 followup (video-ux-parity) -- the .media-column-tile-video-
   badge rule that used to live here was lifted into
   .media-thumb-video-badge in feed.css so the same play-badge overlay
   works across every surface that renders a video's poster as an <img>
   (messaging threads, share-preview, linked-events, edit carousel,
   plus the dashboard media-column tile that originally owned it).
   The dashboard tile template now references the shared class
   directly. */

/* Meta overlay (avatar + author name). Hidden by default;
   the desktop hover-reveal and mobile persistent-avatar treatments
   land in a later polish PR. For now: hidden, so tiles read as a
   clean photo grid. */
.media-column-tile-meta {
    display: none;
}

/* === Links column (Phase 11b db3) ================================ */

/* Masonry grid. Always 2 internal columns, regardless of width — same
   reasoning as the Media column (3 fixed): the 3/2/1 responsive count
   in the spec was over-engineered; a fixed 2-up renders cleanly across
   the full width range a Links column actually occupies in v1.

   Spec amendment vs. DESIGN_DECISIONS_VISUAL.md §3.5: dropped responsive
   3/2/1 count in favor of fixed 2 columns. Logged for the same docs PR
   that captures the Media count amendment. */
.links-column-grid {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    grid-auto-rows: 8px;
    gap: 12px;
}

/* A single link tile. Image at top, title + domain + attribution below. */
.links-column-tile {
    display: flex;
    flex-direction: column;
    width: 100%;
    min-width: 0;
    background: var(--color-bg-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    overflow: hidden;
    text-decoration: none;
    color: inherit;
    transition: box-shadow var(--transition-medium);
}

.links-column-tile:hover {
    box-shadow: var(--shadow-md);
}

/* Image wrapper — sized to the natural aspect ratio of the OG image
   so masonry's row-span is correct before the image loads. */
.links-column-tile-img-wrap {
    width: 100%;
    background: var(--color-bg-subtle);
}

.links-column-tile-img {
    width: 100%;
    height: auto;
    display: block;
}

/* Body section below the image: title, domain, attribution. */
.links-column-tile-body {
    padding: 8px 10px 10px;
}

.links-column-tile-title {
    font-size: var(--font-size-base);
    font-weight: var(--font-weight-semibold);
    color: var(--color-text);
    line-height: 1.3;
    margin-bottom: 2px;
    /* Truncate to two lines */
    display: -webkit-box;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.links-column-tile-domain {
    font-size: var(--font-size-xs);
}

.links-column-tile-attribution {
    font-size: var(--font-size-xs);
}

/* === Feed column post cards (pr33) ==============================
   Visual refresh to align dashboard Feed-column post cards with the
   pr31 notifications inbox aesthetic. CSS-only; no template changes.

   All rules are scoped under `.dashboard-grid .dashboard-column-body`
   so the standalone post-detail page, drafts, profile feed, and the
   dashboard side panel render with the original card chrome.

   Companion rules elsewhere in this file that interact with these:
     - `.card` (line ~330) sets the base card surface/border tokens
     - `.shared-post-card` (line ~682) keeps its --color-bg-subtle
       background so nested quote/repost cards stay distinct INSIDE
       our new transparent outer card
     - `.dashboard-grid .panel-clickable:hover` (line ~2112) is
       overridden below: the violet halo replaces the bg-color swap
   ================================================================ */

/* Card surface — transparent so the column body shows through.
   Borders soften to --color-border-subtle. Padding tightens slightly
   (12px / 16px) vs Bootstrap's default 16px all around. The Bootstrap
   .mb-3 utility in the template still spaces cards vertically — we
   don't fight it. */
.dashboard-grid .dashboard-column-body .card,
.profile-feed .card,
.drafts-list .card {
    --bs-card-bg: transparent;
    --bs-card-border-color: var(--color-border-subtle);
    background-color: transparent;
    border-color: var(--color-border-subtle);
}

.dashboard-grid .dashboard-column-body .card .card-body,
.profile-feed .card .card-body,
.drafts-list .card .card-body {
    padding: var(--space-3) var(--space-4);
}

/* Embedded share (quote / repost / orphaned) — the nested .card
   inside .shared-post-card needs to stay visually distinct against
   the now-transparent outer card. Re-assert the subtle-bg treatment
   that .shared-post-card already declares; the override above would
   otherwise have flattened it to transparent. */
.dashboard-grid .dashboard-column-body .card.shared-post-card,
.dashboard-grid .dashboard-column-body .shared-post-card,
.profile-feed .card.shared-post-card,
.profile-feed .shared-post-card,
.drafts-list .card.shared-post-card,
.drafts-list .shared-post-card {
    --bs-card-bg: var(--color-bg-subtle);
    background-color: var(--color-bg-subtle);
    border-color: var(--color-border-subtle);
}

/* Hover halo — lighter than the notifications three-layer halo so
   it doesn't shout at card scale. Single 1px violet ring + a soft
   16px outer glow. Replaces the existing .panel-clickable:hover
   background-color swap (which is overridden below). */
.dashboard-grid .dashboard-column-body .card.panel-clickable,
.profile-feed .card.panel-clickable,
.drafts-list .card {
    transition: box-shadow var(--transition-medium),
                border-color var(--transition-fast);
}

.dashboard-grid .dashboard-column-body .card.panel-clickable:hover,
.profile-feed .card.panel-clickable:hover,
.drafts-list .card:hover {
    background-color: transparent;
    border-color: rgba(var(--color-halo-rgb), 0.25);
    box-shadow:
        0 0 0 1px rgba(var(--color-halo-rgb), 0.30),
        0 0 16px 2px rgba(var(--color-halo-rgb), 0.12);
}

/* Action row (react / comment / quote / retweet / send).
   Border softens; font drops to --font-size-md; muted-by-default
   with a hover transition to full text color. The reaction cluster
   keeps its native styling (emoji color, picker behavior) — only
   the inline link siblings are affected by the muted-text rules. */
.dashboard-grid .dashboard-column-body .card .card-body > .border-top,
.profile-feed .card .card-body > .border-top,
.drafts-list .card .card-body > .border-top {
    border-top-color: var(--color-border-subtle) !important;
    gap: var(--space-3) !important;
}

.dashboard-grid .dashboard-column-body .card .card-body > .border-top a,
.profile-feed .card .card-body > .border-top a,
.drafts-list .card .card-body > .border-top a {
    font-size: var(--font-size-md);
    color: var(--color-text-muted);
    transition: color var(--transition-fast);
}

.dashboard-grid .dashboard-column-body .card .card-body > .border-top a:hover,
.profile-feed .card .card-body > .border-top a:hover,
.drafts-list .card .card-body > .border-top a:hover {
    color: var(--color-text);
}

/* === Links column tiles (pr35) ==================================
   Visual refresh of the dashboard Links column tiles. Third in the
   family established by pr31 (notifications), pr33 (feed cards),
   pr34 (messages rows): transparent surface, --color-border-subtle
   hairline, three-layer violet hover halo, quiet rest + loud hover.

   CSS-only. The JS masonry (dashboard_links_masonry.js) measures
   scrollHeight to compute each tile's grid-row span — nothing here
   equalizes heights or fights that calculation, so Pinterest-style
   staggered offsets between columns are preserved.

   Scope: every rule prefixed with
     `.dashboard-grid .dashboard-column-body`
   so the rules apply only inside dashboard Links columns. The base
   .links-column-tile rules from the db3 section above stay as the
   default for any future non-dashboard surface that consumes the
   partial.

   Companion rules elsewhere in this file that interact with these:
     - `.links-column-grid` (db3 section above) sets grid-template-
       columns, grid-auto-rows, and gap. Untouched — the JS depends
       on grid-auto-rows: 8px and gap: 12px matching its constants.
     - `.links-column-tile` (db3 section above) keeps its layout
       fundamentals (flex column, overflow: hidden, etc); we only
       override the visual chrome below.
   ================================================================ */

/* Tile surface — transparent so the column body shows through.
   Border softens to --color-border-subtle. Replaces the db3 default
   of --color-bg-surface + --color-border that read as a raised card
   against the column body. The transition picks up border-color
   in addition to box-shadow so the hover-state edge swap is smooth. */
.dashboard-grid .dashboard-column-body .links-column-tile {
    background: transparent;
    border-color: var(--color-border-subtle);
    transition: box-shadow var(--transition-medium),
                border-color var(--transition-fast);
}

/* Hover halo — three-layer violet glow, identical to .messages-row
   and .notification-row. Tiles are closer to row scale than card
   scale, so the full 3-layer intensity reads well. z-index lifts
   the hovered tile above its neighbors so the halo paints over
   their borders, not under them.

   Overrides the db3 default of `box-shadow: var(--shadow-md)`. */
.dashboard-grid .dashboard-column-body .links-column-tile:hover,
.dashboard-grid .dashboard-column-body .links-column-tile:focus-visible {
    z-index: 1;
    border-color: rgba(var(--color-halo-rgb), 0.30);
    box-shadow:
        0 0 0 1px rgba(var(--color-halo-rgb), 0.45),
        0 0 12px 2px rgba(var(--color-halo-rgb), 0.22),
        0 0 28px 4px rgba(var(--color-halo-rgb), 0.10);
    outline: none;
}

/* Force inherited text color on the tile's <a> so it doesn't paint
   accent-violet on rest state and doesn't re-color during halo
   transitions. */
.dashboard-grid .dashboard-column-body a.links-column-tile,
.dashboard-grid .dashboard-column-body a.links-column-tile:visited,
.dashboard-grid .dashboard-column-body a.links-column-tile:hover,
.dashboard-grid .dashboard-column-body a.links-column-tile:focus-visible {
    color: var(--color-text);
    text-decoration: none;
}

/* Body padding — slight retune from db3 (8/10/10) to align with the
   family's --space-* rhythm. Top padding tightens against the image
   for a quieter image→title hand-off. */
.dashboard-grid .dashboard-column-body .links-column-tile-body {
    padding: var(--space-2) var(--space-3) var(--space-3);
}

/* Title — keeps the two-line clamp from db3. Color asserted to
   --color-text so it stays loud against the muted domain + attribution. */
.dashboard-grid .dashboard-column-body .links-column-tile-title {
    color: var(--color-text);
}

/* Domain — uppercase tracked-out meta treatment. Reads as "secondary
   info" without shouting; differentiates from the title's full-weight
   sentence case. Single-line ellipsis on long domains.

   Note: the template's `text-muted small` Bootstrap utility classes
   are stripped in the pr35 markup pass, so this rule fully owns the
   font-size and color now. */
.dashboard-grid .dashboard-column-body .links-column-tile-domain {
    margin-top: 4px;
    font-size: var(--font-size-xs);
    color: var(--color-text-muted);
    text-transform: uppercase;
    letter-spacing: 0.05em;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Attribution row — single line, avatar + name + · + date. min-width: 0
   on the row and the name span lets the name truncate cleanly when long.

   Template classes `text-muted small d-flex align-items-center gap-1 mt-1`
   are dropped during the pr35 markup pass; this rule owns the entire
   visual shape. */
.dashboard-grid .dashboard-column-body .links-column-tile-attribution {
    display: flex;
    align-items: center;
    gap: 6px;
    margin-top: 6px;
    min-width: 0;
    font-size: var(--font-size-xs);
    color: var(--color-text-muted);
}

/* The username span — flex-grow + min-width: 0 enables ellipsis on
   long display names. The date span (last child) is fixed-width and
   pushed right by the name's flex. The · separator sits between. */
.dashboard-grid .dashboard-column-body .links-column-tile-attribution .who {
    flex: 0 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.dashboard-grid .dashboard-column-body .links-column-tile-attribution .sep {
    opacity: 0.5;
    flex-shrink: 0;
}

.dashboard-grid .dashboard-column-body .links-column-tile-attribution .when {
    flex-shrink: 0;
}

/* Empty state — promoted from the inline Bootstrap utility chain on
   the no-links div into a real class, matching how pr34 did
   .messages-empty. Quiet centered prompt. */
.dashboard-grid .dashboard-column-body .links-column-empty {
    text-align: center;
    padding: var(--space-5) var(--space-4);
    color: var(--color-text-muted);
}

.dashboard-grid .dashboard-column-body .links-column-empty-title {
    margin: 0 0 var(--space-1) 0;
    font-weight: var(--font-weight-semibold);
    font-size: 0.95rem;
    color: var(--color-text);
}

.dashboard-grid .dashboard-column-body .links-column-empty-cta {
    margin: 0;
    font-size: var(--font-size-base);
}

/* Reduced-motion: kill the transitions on the tile. The hover halo
   appearing/disappearing instantly is fine — it's a box-shadow
   change, not a motion change. */
@media (prefers-reduced-motion: reduce) {
    .dashboard-grid .dashboard-column-body .links-column-tile {
        transition: none;
    }
}

/* === Media column tiles (pr36) ==================================
   Visual refresh of the dashboard Media column tiles. Fourth in the
   family established by pr31 (notifications), pr33 (feed cards),
   pr34 (messages rows), pr35 (links tiles): three-layer violet hover
   halo, structured snarky empty state, prefers-reduced-motion block.

   The Media column is the family member with the least rest-state
   chrome — tiles read as pure image at rest, no border, no surface.
   The hover halo IS the chrome. Per DESIGN_DECISIONS_VISUAL.md §3.4,
   the meta overlay (avatar + name + time) fades in on hover/focus
   on desktop; on touch devices it becomes persistent but shrinks
   to avatar-only.

   CSS-only refresh; template edits are confined to wiring the time
   span and structuring the empty state. The JS-driven masonry that
   the Links column needs has no equivalent here — Media's grid is
   pure CSS Grid at fixed 3-up.

   Scope: every rule prefixed with
     `.dashboard-grid .dashboard-column-body`
   so the rules apply only inside dashboard Media columns. The base
   .media-column-* rules from the db3 section above stay as the
   default for any future non-dashboard surface that consumes the
   partial.

   Companion rules elsewhere in this file that interact with these:
     - `.media-column-tile` (db3 section, line ~1711) keeps its
       layout fundamentals (aspect-ratio, overflow, border-radius,
       subtle background fallback for slow-loading images). pr36
       only overrides the hover treatment and reveals the meta
       overlay that db3 explicitly hid.
     - `.media-column-tile-meta` (db3 section, line ~1748) was set
       to `display: none` as a deferred polish item. pr36 reactivates
       it with the overlay treatment.
   ================================================================ */

.dashboard-grid .dashboard-column-body .media-column-tile {
    transition: box-shadow var(--transition-medium);
}

.dashboard-grid .dashboard-column-body .media-column-tile:hover,
.dashboard-grid .dashboard-column-body .media-column-tile:focus-visible {
    z-index: 1;
    box-shadow:
        0 0 0 1px rgba(var(--color-halo-rgb), 0.45),
        0 0 12px 2px rgba(var(--color-halo-rgb), 0.22),
        0 0 28px 4px rgba(var(--color-halo-rgb), 0.10);
    outline: none;
}

.dashboard-grid .dashboard-column-body .media-column-stack-indicator {
    background: rgba(0, 0, 0, 0.5);
    -webkit-backdrop-filter: blur(8px);
    backdrop-filter: blur(8px);
    box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.2);
}

.dashboard-grid .dashboard-column-body .media-column-tile-meta {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    position: absolute;
    bottom: 0;
    left: 0;
    right: 0;
    padding: var(--space-4) var(--space-2) var(--space-2);
    background: linear-gradient(
        to top,
        rgba(0, 0, 0, 0.7) 0%,
        rgba(0, 0, 0, 0.3) 40%,
        transparent 70%
    );
    color: #fff;
    opacity: 0;
    transition: opacity var(--transition-medium);
    pointer-events: none;
}

.dashboard-grid .dashboard-column-body .media-column-tile:hover .media-column-tile-meta,
.dashboard-grid .dashboard-column-body .media-column-tile:focus-visible .media-column-tile-meta {
    opacity: 1;
}

.dashboard-grid .dashboard-column-body .media-column-tile-name {
    flex: 1 1 auto;
    min-width: 0;
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-semibold);
    color: #fff;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.dashboard-grid .dashboard-column-body .media-column-tile-time {
    flex-shrink: 0;
    font-size: var(--font-size-xs);
    color: rgba(255, 255, 255, 0.8);
    font-variant-numeric: tabular-nums;
}

.dashboard-grid .dashboard-column-body .media-column-empty {
    text-align: center;
    padding: var(--space-5) var(--space-4);
    color: var(--color-text-muted);
}

.dashboard-grid .dashboard-column-body .media-column-empty-title {
    margin: 0 0 var(--space-1) 0;
    font-weight: var(--font-weight-semibold);
    font-size: 0.95rem;
    color: var(--color-text);
}

.dashboard-grid .dashboard-column-body .media-column-empty-cta {
    margin: 0;
    font-size: var(--font-size-base);
}

@media (hover: none) and (pointer: coarse) {
    .dashboard-grid .dashboard-column-body .media-column-tile-meta {
        opacity: 1;
        padding: var(--space-2);
        background: linear-gradient(
            to top,
            rgba(0, 0, 0, 0.55) 0%,
            transparent 100%
        );
    }

    .dashboard-grid .dashboard-column-body .media-column-tile-meta .media-column-tile-name,
    .dashboard-grid .dashboard-column-body .media-column-tile-meta .media-column-tile-time {
        display: none;
    }
}

@media (prefers-reduced-motion: reduce) {
    .dashboard-grid .dashboard-column-body .media-column-tile,
    .dashboard-grid .dashboard-column-body .media-column-tile-meta {
        transition: none;
    }
}
