/* public/css/home.css — the home page's styles (views/morgensite.ejs),
   moved out of the page so browsers cache them between visits (10/19
   efficiency pass). Linked from /v<build>/css/home.css at the spot the
   first inline <style> used to be, so the cascade order is unchanged (after
   the nav's own styles). Image URLs are relative (../img/…), so they stay
   under the same /v<build>/ path. */

/* ── was views/morgensite.ejs line 5 ── */
:root {
    --tile-radius: 20px;
    --tile-border: rgba(15, 23, 42, 0.06);
    --tile-shadow: 0 1px 2px rgba(15, 23, 42, 0.04), 0 4px 12px rgba(15, 23, 42, 0.04);
    --tile-shadow-hover: 0 2px 6px rgba(15, 23, 42, 0.08), 0 18px 40px rgba(15, 23, 42, 0.12);
}

body {
    background: #f4f6fb;
}

.bento {
    display: grid;
    grid-template-columns: repeat(6, 1fr);
    gap: 18px;
}

@media (max-width: 991px) {
    .bento {
        grid-template-columns: repeat(4, 1fr);
    }
}

@media (max-width: 575px) {
    .bento {
        grid-template-columns: repeat(2, 1fr);
        gap: 12px;
    }
}

.tile {
    position: relative;
    grid-column: span 2;
    min-height: 180px;
    padding: 22px;
    border-radius: var(--tile-radius);
    background: #fff;
    border: 1px solid var(--tile-border);
    box-shadow: var(--tile-shadow);
    color: inherit;
    text-decoration: none;
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    overflow: hidden;
    transition: transform 0.18s ease, box-shadow 0.18s ease;
    isolation: isolate;
}

.tile:hover {
    transform: translateY(-2px);
    box-shadow: var(--tile-shadow-hover);
    color: inherit;
    text-decoration: none;
}

.tile__icon {
    width: 44px;
    height: 44px;
    border-radius: 12px;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 22px;
    background: rgba(15, 23, 42, 0.06);
    color: #0f172a;
    margin-bottom: 14px;
}

.tile__title {
    font-size: 1.1rem;
    font-weight: 650;
    letter-spacing: -0.01em;
    margin: 0 0 4px;
    color: #0f172a;
}

.tile__desc {
    font-size: 0.9rem;
    line-height: 1.4;
    color: #475569;
    margin: 0;
}

.tile__footer {
    margin-top: 16px;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
}

.tile__badge {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 4px 10px;
    border-radius: 999px;
    background: rgba(15, 23, 42, 0.06);
    color: #0f172a;
    font-size: 0.78rem;
    font-weight: 550;
    line-height: 1.2;
}

.gameshare-facepile {
    display: inline-flex;
    align-items: center;
}

.gameshare-face {
    width: 32px;
    height: 32px;
    border-radius: 50%;
    background: rgba(15, 23, 42, 0.08);
    border: 2px solid #ffffff;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 0.78rem;
    font-weight: 700;
    color: #fff;
    overflow: hidden;
    margin-left: -8px;
}

.gameshare-face:first-child {
    margin-left: 0;
}

.gameshare-face img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.gameshare-face-more {
    margin-left: 6px;
    color: #64748b;
    font-size: 0.78rem;
    font-weight: 600;
}

.tile__badge--live::before {
    content: "";
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: #22c55e;
    box-shadow: 0 0 0 3px rgba(34, 197, 94, 0.2);
    animation: pulse 1.8s infinite ease-in-out;
}

@keyframes pulse {

    0%,
    100% {
        opacity: 1;
    }

    50% {
        opacity: 0.5;
    }
}

.tile__arrow {
    width: 32px;
    height: 32px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(15, 23, 42, 0.06);
    color: #0f172a;
    font-size: 14px;
    transition: transform 0.18s ease, background 0.18s ease;
}

.tile:hover .tile__arrow {
    transform: translateX(3px);
    background: #0f172a;
    color: #fff;
}

/* Gradient / themed tiles */
.tile--theme {
    color: #fff;
    border: none;
}

.tile--theme .tile__title,
.tile--theme .tile__desc {
    color: #fff;
}

.tile--theme .tile__desc {
    color: rgba(255, 255, 255, 0.82);
}

.tile--theme .tile__icon {
    background: rgba(255, 255, 255, 0.18);
    color: #fff;
    backdrop-filter: blur(4px);
}

.tile--theme .tile__badge {
    background: rgba(255, 255, 255, 0.18);
    color: #fff;
}

/* Live crowd level, top-right corner of the Disneyland card: a big band-
   colored number over a dark translucent chip (reads well on the photo). */
.tile__crowd {
    position: absolute;
    top: 16px;
    right: 16px;
    z-index: 2;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 1px;
    padding: 7px 14px 6px;
    border-radius: 14px;
    line-height: 1;
    background: rgba(0, 0, 0, 0.42);
    backdrop-filter: blur(3px);
    -webkit-backdrop-filter: blur(3px);
}
/* Band word is the headline (largest, band-colored); the minutes sit
   under it, with a tiny caption naming the stat. */
.tile__crowd-eyebrow {
    font-size: 1.45rem;
    font-weight: 800;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    line-height: 1;
}
/* Caption + minutes live in the footer's lower-left (where the "Live
   wait times" badge sits when there's no live reading). */
.tile__crowd-stat { display: flex; flex-direction: column; gap: 2px; }
.tile__crowd-num { font-size: 1.2rem; font-weight: 700; letter-spacing: -0.01em; line-height: 1; color: rgba(255, 255, 255, 0.92); }
.tile__crowd-cap { font-size: 0.58rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; color: rgba(255, 255, 255, 0.75); }
.tile__crowd--1 .tile__crowd-eyebrow { color: #4ade80; }
.tile__crowd--2 .tile__crowd-eyebrow { color: #86e08a; }
.tile__crowd--3 .tile__crowd-eyebrow { color: #ffd24a; }
.tile__crowd--4 .tile__crowd-eyebrow { color: #ffb74d; }
.tile__crowd--5 .tile__crowd-eyebrow { color: #ff8a80; }
/* Plain open/closed status (no crowd number yet): neutral white, so it
   never reads as one of the band colors above. */
.tile__crowd--status .tile__crowd-eyebrow { color: rgba(255, 255, 255, 0.9); }

.tile--theme .tile__arrow {
    background: rgba(255, 255, 255, 0.2);
    color: #fff;
}

.tile--theme:hover .tile__arrow {
    background: #fff;
    color: #0f172a;
}

.tile--music {
    background: linear-gradient(135deg, #7c3aed 0%, #db2777 100%);
}

.tile--tips {
    background: linear-gradient(135deg, #059669 0%, #10b981 100%);
}

.tile--myip {
    background: linear-gradient(135deg, #1e3a8a 0%, #4f46e5 100%);
}

.tile--filedrop {
    background: linear-gradient(135deg, #0369a1 0%, #0891b2 100%);
}

.tile--contacts {
    background: linear-gradient(135deg, #b45309 0%, #d97706 100%);
}

/* Contacts card search: a field + Search button above the card's
   whole-tile link; submitting goes to /contacts/search (the card never
   changes size). */
.ct-tile-search {
    position: relative;
    z-index: 2;
    display: flex;
    margin-top: .6rem;
}
.ct-tile-search input {
    flex: 1;
    min-width: 0;
    font-size: 16px;               /* iOS zooms into smaller inputs */
    padding: .45rem .75rem;
    border: 0;
    border-radius: .6rem 0 0 .6rem;
    background: rgba(255, 255, 255, .92);
    color: #1f2937;
    outline: none;
    box-shadow: 0 1px 2px rgba(0, 0, 0, .15);
}
.ct-tile-search input:focus { background: #fff; }
.ct-tile-search input::-webkit-search-cancel-button { cursor: pointer; }
.ct-tile-search button {
    flex: 0 0 auto;
    width: 2.75rem;
    border: 0;
    border-radius: 0 .6rem .6rem 0;
    background: #7c2d12;
    color: #fff;
    box-shadow: 0 1px 2px rgba(0, 0, 0, .15);
}
.ct-tile-search button:hover { background: #6b260f; }
.ct-tile-search:focus-within { box-shadow: 0 0 0 3px rgba(255, 255, 255, .45); border-radius: .6rem; }

/* Whole-card link for a tile that's a div (so it can hold another
   link, like a corner gear, above it). */
.tile-stretch {
    position: absolute;
    inset: 0;
    z-index: 1;
    border-radius: inherit;
}

.tile--photomap {
    background: linear-gradient(135deg, #0e7490 0%, #155e75 100%);
}

/* Escape Rooms: black card, ghost-white key + keyhole drawn in one
   inline SVG layer (single layer so the shapes can't double up their
   translucency where they'd overlap). The gradient underneath is the
   fallback if the data URI ever fails to paint. */
.tile--escaperooms {
    background:
        url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 220 120'><g fill='%23fff' fill-opacity='0.13'><circle cx='168' cy='42' r='24'/><path d='M168 54 L190 108 L146 108 Z'/></g><g stroke='%23fff' stroke-opacity='0.16' stroke-width='7' fill='none' stroke-linecap='round' transform='rotate(-28 60 85)'><circle cx='38' cy='85' r='14'/><path d='M52 85 H118 M100 85 v14 M114 85 v18'/></g></svg>") right center no-repeat,
        linear-gradient(150deg, #000 0%, #101010 60%, #1b1b1b 100%);
    background-size: auto 105%, auto;
}

/* Night castle photo, full-bleed. All the card text is left-aligned, so a
   left→right scrim keeps the title/description/badge legible while the lit
   castle on the right stays clear; a bottom scrim covers the badge + arrow.
   Deep-indigo fallback keeps the card readable if the image fails to load. */
.tile--themeparks {
    background:
        linear-gradient(to right, rgba(0,0,0,0.55) 0%, rgba(0,0,0,0.15) 45%, rgba(0,0,0,0) 66%),
        linear-gradient(to top, rgba(0,0,0,0.5) 0%, rgba(0,0,0,0) 32%),
        url('../img/DisneylandCastle.jpg') center/cover no-repeat,
        #1a1440;
}

/* Sharks card: the team logo full-bleed, anchored right, with a left→right
   scrim keeping the title/description legible and a bottom scrim behind
   the badge and arrow — the same layering the Disneyland card uses.

   It points at SharksLogoCard.jpg, not the SharksLogo.png source: the
   artwork centres the shark, and the card is wider than it is tall, so a
   straight `cover` put the shark directly behind the description text.
   The card asset is that same artwork padded on the left with its own
   background teal to a 2.25 ratio, so `right center/cover` lands the shark
   in the right half and leaves the text side clear. It's also 45KB against
   the source's 678KB, which matters for a homepage background. Regenerate
   it from the PNG with sharp: extend left to ratio 2.25 on rgb(0,60,76),
   then resize to 1400w (3x the widest the card renders) — extend and resize
   must be separate passes, since sharp resizes before extending.

   The deep-teal gradient is the LAST background layer, so it doubles as
   the fallback: if the image ever fails to load the card still renders as a
   readable teal tile instead of losing its background. Fixed brand colours
   rather than theme tokens — the tile is white-on-dark in both themes, like
   the other .tile--theme cards. */
.tile--sharks {
    background:
        linear-gradient(to right, rgba(0, 0, 0, 0.78) 0%, rgba(0, 0, 0, 0.52) 38%, rgba(0, 0, 0, 0.14) 64%, rgba(0, 0, 0, 0) 80%),
        linear-gradient(to top, rgba(0, 0, 0, 0.52) 0%, rgba(0, 0, 0, 0) 34%),
        url('../img/SharksLogoCard.jpg') right center/cover no-repeat,
        linear-gradient(150deg, #0a4d52 0%, #063a3f 55%, #04282c 100%);
}

/* NHL card: the league shield on scuffed ice. NHLLogoCard.jpg is built
   from the supplied 1394x842 artwork with sharp: mirrored ice added top and
   bottom so the shield is ~62% of the height, right-side ice trimmed so its
   centre sits 0.42 x height from the right edge, then the plain ice (left
   400 columns) mirror-tiled leftwards to a 2.4 ratio, 1400 wide. With
   `right center/cover` the shield stays in the right third of the card at
   any width and the text side is clear. The ice is light, so the left scrim
   is a touch heavier than the Sharks card's. Charcoal last layer = fallback
   if the image fails. */
.tile--nhl {
    background:
        linear-gradient(to right, rgba(0, 0, 0, 0.82) 0%, rgba(0, 0, 0, 0.6) 40%, rgba(0, 0, 0, 0.18) 66%, rgba(0, 0, 0, 0) 82%),
        linear-gradient(to top, rgba(0, 0, 0, 0.55) 0%, rgba(0, 0, 0, 0) 36%),
        url('../img/NHLLogoCard.jpg') right center/cover no-repeat,
        linear-gradient(150deg, #3a3f47 0%, #24282e 100%);
}
/* Tonight chip, top-right: "3 live" (goal-light red dot) or "11 games today". */
.tile__nhl-chip {
    position: absolute;
    top: 16px;
    right: 16px;
    z-index: 2;
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.2rem 0.55rem;
    border-radius: 0.35rem;
    background: rgba(0, 0, 0, 0.55);
    color: #fff;
    font-size: 0.8rem;
    font-weight: 600;
    line-height: 1.2;
    white-space: nowrap;
}
.tile__nhl-dot {
    width: 0.5rem;
    height: 0.5rem;
    border-radius: 50%;
    background: #ff2a45;
    animation: nhlTileDot 1.6s ease-in-out infinite;
}
@keyframes nhlTileDot { 50% { opacity: 0.3; } }
@media (prefers-reduced-motion: reduce) { .tile__nhl-dot { animation: none; } }

/* Next-game chip, top-right: opponent then the date and time. Teal for
   home / orange for away — the same pair the /sharks month grid uses, so
   the card doubles as the legend.

   The matchup and the date/time are each kept unbreakable and allowed to
   wrap as whole units, so a long opponent ("@ Golden Knights") drops the
   date to a second right-aligned line instead of splitting mid-phrase.
   The max-width stops the chip reaching across the top into the icon. */
.tile__sharks-next {
    position: absolute;
    top: 16px;
    right: 16px;
    z-index: 2;
    display: flex;
    flex-wrap: wrap;
    justify-content: flex-end;
    align-items: baseline;
    column-gap: 0.4rem;
    row-gap: 0.05rem;
    max-width: 64%;
    padding: 0.2rem 0.5rem;
    border-radius: 0.35rem;
    background: rgba(0, 0, 0, 0.34);
    border-left: 3px solid #17878f;
    color: #fff;
    font-size: 0.82rem;
    line-height: 1.2;
    text-align: right;
}
.tile__sharks-next.is-away { border-left-color: #d4700f; }
.tile__sharks-matchup {
    display: inline-flex;
    align-items: baseline;
    gap: 0.25rem;
    white-space: nowrap;
}
.tile__sharks-vs { opacity: 0.75; font-size: 0.72rem; }
.tile__sharks-team { font-weight: 600; }
.tile__sharks-when {
    white-space: nowrap;
    opacity: 0.9;
    font-size: 0.76rem;
}

/* Admin gear overlay — shown only for users with the right role */
.tile__admin {
    position: absolute;
    top: 10px;
    right: 10px;
    z-index: 3;
    width: 30px;
    height: 30px;
    border-radius: 50%;
    background: rgba(0, 0, 0, 0.28);
    color: rgba(255, 255, 255, 0.85);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 0.95rem;
    cursor: pointer;
    transition: background 0.15s, color 0.15s, transform 0.15s;
}

.tile__admin:hover {
    background: rgba(0, 0, 0, 0.55);
    color: #fff;
    transform: rotate(30deg);
}

/* Same pill reused for non-gear actions (MorgenGram slideshow play) —
   the gear-spin hover reads wrong on a play glyph, grow instead. */
.tile__admin--play:hover {
    transform: scale(1.15);
}

/* Clickable secondary badge on a tile (e.g. "N orders") */
.tile__aux {
    padding: 4px 12px 4px 10px;
    border-radius: 999px;
    background: rgba(0, 0, 0, 0.38);
    color: rgba(255, 255, 255, 0.92);
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: 0.78rem;
    font-weight: 600;
    cursor: pointer;
    text-decoration: none;
    transition: background 0.15s, color 0.15s, transform 0.15s;
}

.tile__aux:hover {
    background: rgba(0, 0, 0, 0.62);
    color: #fff;
    transform: translateY(-1px);
}

/* Stack multiple action pills in the top-right of a tile. */
.tile__actions {
    position: absolute;
    top: 10px;
    right: 10px;
    z-index: 3;
    display: flex;
    gap: 8px;
    align-items: center;
}

.tile__actions .tile__admin {
    position: static;
    top: auto;
    right: auto;
}

.tile--store {
    background: linear-gradient(135deg, #0ea5e9 0%, #6366f1 100%);
}

.tile--gram {
    background: linear-gradient(135deg, #f59e0b 0%, #ef4444 100%);
}

.tile--radio {
    background: linear-gradient(135deg, #0f172a 0%, #334155 100%);
}

/* LCD Radio tile — retro green-on-black LCD aesthetic with a
   recessed screen, scanlines, mini dial readout, and tappable
   preset chips that deep-link straight to /radio?station=<slug>. */
.tile--lcd-radio {
    background:
        radial-gradient(circle at 25% 18%, #1a1a1c 0%, #0a0b0d 60%, #050608 100%);
    color: #5cffbf;
    font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
    padding: 14px;
    overflow: hidden;
    /* ~30% taller than the default tile row so the station list has
       more room. As a grid item this drives its row's height; the
       flex:1 list fills the extra space. */
    min-height: 256px;
}
.tile--lcd-radio .lcd-card-bezel {
    position: relative;       /* anchor for the admin gear shortcut */
    width: 100%;
    height: 100%;
    border-radius: 10px;
    padding: 4px;
    background: linear-gradient(180deg, #2a2a30 0%, #0c0c0e 100%);
    box-shadow:
        inset 0 1px 0 rgba(255,255,255,.08),
        inset 0 -3px 6px rgba(0,0,0,.6);
}
/* Tiny gear in the top-right of the bezel that routes radio-admins
   and per-station managers to /admin/radio. Matches the LCD's
   green-phosphor palette so it reads as a deliberate hardware
   button on the chassis rather than UI chrome. */
.tile--lcd-radio .lcd-card-gear {
    position: absolute;
    bottom: 8px;
    left: 8px;
    width: 24px;
    height: 24px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    color: rgba(92, 255, 191, 0.80);
    background: rgba(92, 255, 191, 0.10);
    border: 1px solid rgba(92, 255, 191, 0.30);
    text-decoration: none;
    font-size: .8rem;
    z-index: 3;
    transition: background .12s, color .12s, transform .15s;
}
.tile--lcd-radio .lcd-card-gear:hover {
    color: #5cffbf;
    background: rgba(92, 255, 191, 0.22);
    box-shadow: 0 0 8px rgba(92, 255, 191, 0.5);
    transform: rotate(30deg);
}
/* Whole-card tap target → /radio. Covers the entire bezel beneath the
   gear (z-index 3) and the station presets (z-index 2), so tapping
   empty chrome tunes in, while the gear and each station keep their
   own destinations. */
.tile--lcd-radio .lcd-card-stretch {
    position: absolute;
    inset: 0;
    z-index: 1;
    border-radius: inherit;
    text-decoration: none;
}
.tile--lcd-radio .lcd-card-stretch:focus-visible {
    outline: 2px solid rgba(92, 255, 191, 0.8);
    outline-offset: -3px;
}
.tile--lcd-radio .lcd-card-screen {
    position: relative;
    width: 100%;
    height: 100%;
    border-radius: 8px;
    background: radial-gradient(ellipse at 50% 40%, #0c1f17 0%, #050a08 80%);
    box-shadow:
        inset 0 0 30px rgba(92, 255, 191, 0.10),
        inset 0 0 4px rgba(0,0,0,.8);
    padding: 12px 14px;
    display: flex;
    flex-direction: column;
    gap: 10px;
    overflow: hidden;
}
/* Faint horizontal scanlines for the CRT/LCD feel. */
.tile--lcd-radio .lcd-card-scanlines {
    position: absolute;
    inset: 0;
    background-image: repeating-linear-gradient(
        to bottom,
        rgba(92, 255, 191, 0.04) 0px,
        rgba(92, 255, 191, 0.04) 1px,
        transparent 1px,
        transparent 3px
    );
    pointer-events: none;
    border-radius: inherit;
}
.tile--lcd-radio .lcd-card-headline {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: .78rem;
    letter-spacing: .25em;
    color: rgba(92, 255, 191, 0.95);
    text-shadow: 0 0 6px rgba(92, 255, 191, 0.5);
    position: relative;
}
.tile--lcd-radio .lcd-card-brand { font-weight: 700; flex-shrink: 0; }
.tile--lcd-radio .lcd-card-dot {
    width: 7px; height: 7px; border-radius: 50%;
    background: #5cffbf;
    box-shadow: 0 0 8px rgba(92, 255, 191, 0.9);
    flex-shrink: 0;
    animation: lcd-card-blink 1.6s ease-in-out infinite;
}
@keyframes lcd-card-blink {
    0%, 100% { opacity: 1; }
    50%      { opacity: .35; }
}
/* Mini dial: a horizontal row of tickmarks with a single
   tickmark glowing brighter — purely decorative, evokes the
   full dial inside the radio. */
.tile--lcd-radio .lcd-card-dial {
    position: relative;
    flex: 1 1 auto;       /* fill the headline row to the right of RADIO */
    min-width: 0;
    height: 18px;
    border-top: 1px solid rgba(92, 255, 191, 0.18);
    border-bottom: 1px solid rgba(92, 255, 191, 0.18);
}
.tile--lcd-radio .lcd-card-ticks {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    padding: 0 2px;
}
.tile--lcd-radio .lcd-card-ticks span {
    display: block;
    width: 1px;
    background: rgba(92, 255, 191, 0.4);
}
.tile--lcd-radio .lcd-card-ticks span.min { height: 4px; }
.tile--lcd-radio .lcd-card-ticks span.mid { height: 8px; }
.tile--lcd-radio .lcd-card-ticks span.maj {
    height: 13px;
    background: rgba(92, 255, 191, 0.75);
}
.tile--lcd-radio .lcd-card-needle {
    position: absolute;
    top: -4px;
    bottom: -4px;
    left: 38%;
    width: 2px;
    background: #5cffbf;
    box-shadow: 0 0 6px rgba(92, 255, 191, 0.9);
    animation: lcd-card-drift 9s ease-in-out infinite;
}
@keyframes lcd-card-drift {
    0%, 100% { left: 22%; }
    50%      { left: 78%; }
}
@media (prefers-reduced-motion: reduce) {
    .tile--lcd-radio .lcd-card-needle,
    .tile--lcd-radio .lcd-card-dot { animation: none; }
}
/* Preset chips. Each is a deep-link to /radio?station=<slug>;
   the freq is the "channel number" and the name reads like
   a station ID. */
/* Presets list as a vertical marquee — items continuously scroll
   upward. When the station count is small enough to fit, the
   .is-scrolling flag isn't set and the track stays static. */
.tile--lcd-radio .lcd-card-presets {
    position: relative;
    z-index: 2;               /* station chips sit above the card stretch link */
    overflow: hidden;
    /* flex-basis 0 (not auto): the list FILLS the card's spare height
       without its content driving the card taller — otherwise the tile
       grows to fit every station and dwarfs its neighbours. */
    flex: 1 1 0;
    min-height: 0;
    /* Container is click-through; only the chips themselves are
       tappable, so empty rows/gaps still hit the card stretch link. */
    pointer-events: none;
}
.tile--lcd-radio .lcd-card-preset { pointer-events: auto; }
.tile--lcd-radio .lcd-card-presets.is-scrolling {
    /* Soft fade at the top/bottom so the loop seam is invisible. */
    -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 14%, #000 86%, transparent 100%);
            mask-image: linear-gradient(to bottom, transparent 0, #000 14%, #000 86%, transparent 100%);
}
.tile--lcd-radio .lcd-card-presets-track {
    display: flex;
    flex-direction: column;
    /* No flex `gap` here on purpose: the row spacing is baked into each
       chip as margin-bottom so the two stacked copies are perfectly
       periodic. With `gap`, one loop period would be "a copy PLUS a
       gap" while the animation only translates one copy's worth (-50%),
       leaving a visible seam/blank at the wrap. */
}
.tile--lcd-radio .lcd-card-presets.is-scrolling .lcd-card-presets-track {
    animation: lcd-card-marquee var(--lcd-scroll-dur, 18s) linear infinite;
}
.tile--lcd-radio:hover .lcd-card-presets.is-scrolling .lcd-card-presets-track {
    animation-play-state: paused;
}
@keyframes lcd-card-marquee {
    from { transform: translateY(0); }
    to   { transform: translateY(-50%); }
}
@media (prefers-reduced-motion: reduce) {
    .tile--lcd-radio .lcd-card-presets.is-scrolling .lcd-card-presets-track {
        animation: none;
    }
}
.tile--lcd-radio .lcd-card-presets-empty {
    color: rgba(92, 255, 191, 0.55);
    font-size: .8rem;
    letter-spacing: .15em;
}
.tile--lcd-radio .lcd-card-preset {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 5px 9px;
    border-radius: 4px;
    background: rgba(92, 255, 191, 0.06);
    border: 1px solid rgba(92, 255, 191, 0.28);
    color: rgba(92, 255, 191, 0.9);
    text-decoration: none;
    font-size: .8rem;
    letter-spacing: .05em;
    transition: background .12s, border-color .12s, color .12s;
    flex-shrink: 0;
    margin-bottom: 6px;       /* row spacing baked in — see track note */
}
.tile--lcd-radio .lcd-card-preset:hover {
    background: rgba(92, 255, 191, 0.16);
    border-color: rgba(92, 255, 191, 0.6);
    color: #5cffbf;
    text-shadow: 0 0 6px rgba(92, 255, 191, 0.6);
}
.tile--lcd-radio .lcd-card-preset-freq {
    font-weight: 700;
    font-size: .78rem;
    padding-right: 6px;
    border-right: 1px solid rgba(92, 255, 191, 0.25);
}
.tile--lcd-radio .lcd-card-preset-name {
    font-size: .78rem;
    opacity: .9;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    flex: 1 1 0;
    min-width: 0;
}
/* TUNE IN button — bottom-right of the screen, clear of the lower-left
   gear. A real link to /radio (same destination as the whole-card tap)
   that doubles as the visible affordance. Raised above the card stretch
   link so it gets its own hover state. */
.tile--lcd-radio .lcd-card-cta {
    position: relative;
    z-index: 2;
    align-self: flex-end;
    margin-top: 4px;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: .76rem;
    font-weight: 700;
    letter-spacing: .18em;
    color: rgba(92, 255, 191, 0.92);
    text-decoration: none;
    padding: 5px 11px;
    border-radius: 5px;
    background: rgba(92, 255, 191, 0.10);
    border: 1px solid rgba(92, 255, 191, 0.40);
    transition: background .12s, border-color .12s, color .12s, text-shadow .12s;
}
.tile--lcd-radio .lcd-card-cta:hover {
    color: #5cffbf;
    background: rgba(92, 255, 191, 0.20);
    border-color: rgba(92, 255, 191, 0.7);
    text-shadow: 0 0 8px rgba(92, 255, 191, 0.7);
}

.tile--admin {
    /* Burnt-sienna gradient — distinctive warm "admin zone" tone
       that sets the tile apart from neighbouring dark/cool
       themed tiles. White text from .tile--theme reads cleanly
       against this base (#87420d gives ~9.5:1 contrast against
       #fff, well above WCAG AAA for normal text). */
    background: linear-gradient(135deg, #87420d 0%, #5a2c08 100%);
}
/* Large faded gear glyph behind the Admin tile content. Positioned
   right-middle so the title + meta on the left read cleanly. Slow
   rotation (120s/rev) for a subtle hint of life; pointer-events
   off so the underlying <a>'s click area is unaffected. */
.tile--admin .admin-bg-gear {
    position: absolute;
    right: -40px;
    top: 50%;
    font-size: 220px;
    line-height: 1;
    color: rgba(255, 255, 255, 0.13);
    pointer-events: none;
    z-index: 0;
    transform-origin: center;
    transform: translateY(-50%);
    animation: admin-gear-spin 120s linear infinite;
    will-change: transform;
}
/* Keep text + footer above the gear glyph. The .tile is already
   isolation:isolate, so this stacking is local to the tile. */
.tile--admin > div,
.tile--admin > .tile__footer {
    position: relative;
    z-index: 1;
}
@keyframes admin-gear-spin {
    from { transform: translateY(-50%) rotate(0deg);   }
    to   { transform: translateY(-50%) rotate(360deg); }
}
@media (prefers-reduced-motion: reduce) {
    .tile--admin .admin-bg-gear { animation: none; }
}

.tile--albums {
    background: linear-gradient(135deg, #4f46e5 0%, #0ea5e9 100%);
}

/* Connections Helper: light purple base, with the brick-grid icon
   as a faded full-card backdrop (no outlines, no outer frame).
   Text sits on top — content gets z-index above the SVG. */
.tile--connections {
    background: #c4b5fd;
}
.tile--connections .connections-bg-bricks {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    opacity: 0.2;
    pointer-events: none;
    z-index: 0;
    preserveAspectRatio: none;
}
.tile--connections > div,
.tile--connections > .tile__footer {
    position: relative;
    z-index: 1;
}
.tile--connections .tile__icon {
    background: rgba(255, 255, 255, 0.55);
    color: #6d28d9;
}
.tile--connections .tile__badge {
    background: rgba(255, 255, 255, 0.55);
    color: #4c1d95;
}

/* GameShare: forest-green tile with a subtle crossword grid
   backdrop. The SVG sits absolutely behind the content; the
   content is bumped to z-index 1 (matches the other themed
   tiles). Black-square fill is just a darker green so it
   reads as crossword without crowding out the puzzle icon. */
/* Memories tile — sepia-toned, nostalgic. Warm brown gradient with
   five abstract polaroids drifting in the background. Each polaroid
   is a white card with a sepia-tinted "photo" inside, rotated to
   feel scattered, and gently floats vertically via a staggered
   animation. Respects prefers-reduced-motion. */
.tile--memories {
    background: linear-gradient(135deg, #6b4f3a 0%, #2d1a0e 100%);
    color: #f5ead7;
    overflow: hidden;
}
.tile--memories .tile__title,
.tile--memories .tile__desc { color: #fff4dc; }
.tile--memories .tile__desc { color: rgba(255, 244, 220, 0.82); }
.tile--memories .tile__icon {
    background: rgba(255, 255, 255, 0.20);
    color: #fff4dc;
}
.tile--memories .tile__badge {
    background: rgba(255, 255, 255, 0.18);
    color: #fff4dc;
}
.tile--memories .tile__arrow { color: #fff4dc; }
.memories-polaroids {
    position: absolute; inset: 0;
    pointer-events: none;
    overflow: hidden;
    z-index: 0;
}
.tile--memories > div:not(.memories-polaroids),
.tile--memories > .tile__footer { position: relative; z-index: 1; }
.polaroid {
    position: absolute;
    display: block;
    background: #faf3e5;
    padding: 4px 4px 14px;
    border-radius: 2px;
    box-shadow: 0 6px 14px rgba(0, 0, 0, 0.4);
    animation: polaroidFloat 7s ease-in-out infinite;
}
.polaroid .polaroid-photo {
    display: block;
    width: 100%; height: 100%;
    background: linear-gradient(160deg, #c9966a 0%, #6e4225 100%);
    filter: sepia(1) saturate(1.2);
}
.polaroid-1 {
    top: 6%; left: 3%;
    width: 58px; height: 68px;
    transform: rotate(-14deg);
    opacity: 0.95;
}
.polaroid-2 {
    top: 42%; left: 36%;
    width: 50px; height: 58px;
    transform: rotate(9deg);
    opacity: 0.85;
    animation-delay: -1.4s;
}
.polaroid-3 {
    top: 8%; right: 6%;
    width: 48px; height: 56px;
    transform: rotate(16deg);
    opacity: 0.9;
    animation-delay: -2.8s;
}
.polaroid-4 {
    bottom: 8%; left: 14%;
    width: 46px; height: 54px;
    transform: rotate(-7deg);
    opacity: 0.85;
    animation-delay: -4.2s;
}
.polaroid-5 {
    bottom: 16%; right: 22%;
    width: 42px; height: 50px;
    transform: rotate(22deg);
    opacity: 0.75;
    animation-delay: -5.6s;
}
/* translate property is independent of transform, so the rotation
   on each polaroid stays put while it drifts. */
@keyframes polaroidFloat {
    0%, 100% { translate: 0 0; }
    50%      { translate: 0 -5px; }
}
@media (prefers-reduced-motion: reduce) {
    .polaroid { animation: none; }
}

.tile--gameshare {
    background: linear-gradient(135deg, #166534 0%, #14532d 100%);
}
.gameshare-bg-grid {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    opacity: 0.55;
    pointer-events: none;
    z-index: 0;
}
.tile--gameshare > div,
.tile--gameshare > .tile__footer {
    position: relative;
    z-index: 1;
}
.tile--gameshare .tile__icon {
    background: rgba(255, 255, 255, 0.22);
    color: #ecfdf5;
}
.tile--gameshare .tile__badge {
    background: rgba(255, 255, 255, 0.18);
    color: #ecfdf5;
}
.tile--gameshare .gameshare-face {
    border-color: #14532d;
}

/* Cycling cover backdrop (for Store + Albums tiles) */
.tile__covers {
    position: absolute;
    inset: 0;
    z-index: -2;
    overflow: hidden;
}

.tile__covers::after {
    /* Gradient overlay to keep text legible over any cover image */
    content: "";
    position: absolute;
    inset: 0;
    background: linear-gradient(135deg, rgba(15, 23, 42, 0.55) 0%, rgba(15, 23, 42, 0.3) 50%, rgba(15, 23, 42, 0.65) 100%);
}

.tile__cover {
    position: absolute;
    inset: 0;
    background-size: cover;
    background-position: center;
    opacity: 0;
    transition: opacity 1.1s ease;
}

.tile__cover.is-active {
    opacity: 1;
}

/* Subtle glow on themed tiles */
.tile--theme::after {
    content: "";
    position: absolute;
    inset: 0;
    background: radial-gradient(circle at 100% 0%, rgba(255, 255, 255, 0.18), transparent 55%);
    pointer-events: none;
    z-index: -1;
}

/* Radio tile: player in-card */
.tile--radio .radio-player {
    display: flex;
    align-items: center;
    gap: 12px;
    margin-top: 10px;
}

.tile--radio .radio-btn {
    width: 40px;
    height: 40px;
    border-radius: 50%;
    border: none;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: rgba(255, 255, 255, 0.18);
    color: #fff;
    cursor: pointer;
    transition: background 0.15s ease, transform 0.15s ease;
}

.tile--radio .radio-btn:hover {
    background: rgba(255, 255, 255, 0.3);
}

.tile--radio .radio-btn--primary {
    background: #fff;
    color: #0f172a;
    width: 48px;
    height: 48px;
    font-size: 18px;
}

.tile--radio .radio-btn--primary:hover {
    background: #fff;
    transform: scale(1.05);
}

.tile--radio .radio-title {
    font-size: 0.85rem;
    color: rgba(255, 255, 255, 0.9);
    font-weight: 500;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.tile--radio .radio-title-wrap {
    flex: 1;
    min-width: 0;
}

.tile--radio .radio-label {
    font-size: 0.7rem;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: rgba(255, 255, 255, 0.6);
    margin-bottom: 2px;
}

/* Audio visualizer — one of three variants picked randomly per track */
.tile--radio .visualizer {
    position: absolute;
    inset: 0;
    pointer-events: none;
    overflow: hidden;
    z-index: 0;
}

.tile--radio .viz {
    position: absolute;
    inset: 0;
    opacity: 0;
    transition: opacity 0.5s ease;
}

/* Variant 1: liquid lava-lamp blobs (CSS gooey effect) */
.tile--radio .viz--blobs {
    filter: blur(22px) contrast(9) saturate(1.2);
}

.tile--radio.is-playing .viz--blobs.is-active {
    opacity: 0.55;
}

.tile--radio .viz--blobs>i {
    position: absolute;
    display: block;
    width: 180px;
    height: 180px;
    border-radius: 50%;
    animation: blob-move 12s ease-in-out infinite alternate;
    animation-play-state: paused;
}

.tile--radio.is-playing .viz--blobs.is-active>i {
    animation-play-state: running;
}

.tile--radio .viz--blobs>i:nth-child(1) {
    background: radial-gradient(circle, #7c3aed 0%, #7c3aed 52%, transparent 68%);
    top: 15%;
    left: 8%;
    animation-duration: 11s;
    animation-delay: 0s;
}

.tile--radio .viz--blobs>i:nth-child(2) {
    background: radial-gradient(circle, #ec4899 0%, #ec4899 52%, transparent 68%);
    top: 55%;
    left: 62%;
    width: 220px;
    height: 220px;
    animation-duration: 9s;
    animation-delay: -3s;
}

.tile--radio .viz--blobs>i:nth-child(3) {
    background: radial-gradient(circle, #0ea5e9 0%, #0ea5e9 52%, transparent 68%);
    top: -20%;
    left: 40%;
    width: 180px;
    height: 180px;
    animation-duration: 13s;
    animation-delay: -6s;
}

.tile--radio .viz--blobs>i:nth-child(4) {
    background: radial-gradient(circle, #f59e0b 0%, #f59e0b 52%, transparent 68%);
    top: 65%;
    left: 18%;
    animation-duration: 15s;
    animation-delay: -2s;
}

.tile--radio .viz--blobs>i:nth-child(5) {
    background: radial-gradient(circle, #10b981 0%, #10b981 52%, transparent 68%);
    top: 25%;
    left: 78%;
    width: 200px;
    height: 200px;
    animation-duration: 12s;
    animation-delay: -4s;
}

@keyframes blob-move {
    0% {
        transform: translate(-40%, -20%) scale(0.9);
    }

    50% {
        transform: translate(40%, 30%) scale(1.3);
    }

    100% {
        transform: translate(-10%, 40%) scale(1.0);
    }
}

/* Variant 2: circular spectrum — bars radiating from around the play button */
.tile--radio .viz--ring {
    /* positioned per play-button via JS */
}

.tile--radio.is-playing .viz--ring.is-active {
    opacity: 0.55;
}

.tile--radio .viz--ring__inner {
    position: absolute;
    width: 1px;
    height: 1px;
    left: var(--play-btn-x, 50%);
    top: var(--play-btn-y, 50%);
}

.tile--radio .viz--ring span {
    position: absolute;
    top: 0;
    left: 0;
    width: 3px;
    height: 10px;
    background: linear-gradient(to bottom, rgba(255, 255, 255, 0.2) 0%, #fff 100%);
    border-radius: 0 0 2px 2px;
    transform-origin: 50% 0;
    transform: translateX(-50%) rotate(var(--angle)) translateY(36px);
    animation: ring-bar 1.2s ease-in-out infinite;
    animation-play-state: paused;
}

.tile--radio.is-playing .viz--ring.is-active span {
    animation-play-state: running;
}

.tile--radio .viz--ring span:nth-child(7n) {
    animation-duration: 0.82s;
    animation-delay: -0.14s;
}

.tile--radio .viz--ring span:nth-child(7n+1) {
    animation-duration: 1.05s;
    animation-delay: -0.37s;
}

.tile--radio .viz--ring span:nth-child(7n+2) {
    animation-duration: 1.33s;
    animation-delay: -0.61s;
}

.tile--radio .viz--ring span:nth-child(7n+3) {
    animation-duration: 0.94s;
    animation-delay: -0.08s;
}

.tile--radio .viz--ring span:nth-child(7n+4) {
    animation-duration: 1.18s;
    animation-delay: -0.49s;
}

.tile--radio .viz--ring span:nth-child(7n+5) {
    animation-duration: 0.88s;
    animation-delay: -0.25s;
}

.tile--radio .viz--ring span:nth-child(7n+6) {
    animation-duration: 1.26s;
    animation-delay: -0.72s;
}

@keyframes ring-bar {

    0%,
    100% {
        height: 6px;
    }

    50% {
        height: 48px;
    }
}

/* Variant 3: aurora — vertical color bands drifting with heavy blur */
.tile--radio .viz--aurora {
    filter: blur(28px) saturate(1.4);
    overflow: hidden;
}

.tile--radio.is-playing .viz--aurora.is-active {
    opacity: 0.55;
}

.tile--radio .viz--aurora>i {
    position: absolute;
    top: -10%;
    height: 120%;
    width: 40%;
    opacity: 0.7;
    background: linear-gradient(to bottom,
            transparent 0%, currentColor 30%, currentColor 70%, transparent 100%);
    animation: aurora-sway 14s ease-in-out infinite;
    animation-play-state: paused;
    mix-blend-mode: screen;
}

.tile--radio.is-playing .viz--aurora.is-active>i {
    animation-play-state: running;
}

.tile--radio .viz--aurora>i:nth-child(1) {
    color: #10b981;
    left: -25%;
    animation-duration: 14s;
    animation-delay: 0s;
}

.tile--radio .viz--aurora>i:nth-child(2) {
    color: #06b6d4;
    left: 5%;
    animation-duration: 18s;
    animation-delay: -3s;
}

.tile--radio .viz--aurora>i:nth-child(3) {
    color: #8b5cf6;
    left: 35%;
    animation-duration: 12s;
    animation-delay: -6s;
}

.tile--radio .viz--aurora>i:nth-child(4) {
    color: #ec4899;
    left: 60%;
    animation-duration: 16s;
    animation-delay: -9s;
}

.tile--radio .viz--aurora>i:nth-child(5) {
    color: #3b82f6;
    left: 82%;
    animation-duration: 13s;
    animation-delay: -12s;
}

@keyframes aurora-sway {

    0%,
    100% {
        transform: translateX(0%) scaleY(1) skewX(-6deg);
    }

    25% {
        transform: translateX(10%) scaleY(1.15) skewX(2deg);
    }

    50% {
        transform: translateX(-6%) scaleY(0.9) skewX(6deg);
    }

    75% {
        transform: translateX(8%) scaleY(1.2) skewX(-2deg);
    }
}

/* Variant 4: spiral — rotating conic gradient, blurred */
.tile--radio .viz--spiral {
    display: flex;
    align-items: center;
    justify-content: center;
    overflow: hidden;
}

.tile--radio.is-playing .viz--spiral.is-active {
    opacity: 0.55;
}

.tile--radio .viz--spiral__inner {
    width: 240%;
    height: 240%;
    background: conic-gradient(from 0deg,
            #ff0080 0deg, #ff8000 60deg, #ffff00 120deg,
            #00ff80 180deg, #0080ff 240deg, #8000ff 300deg, #ff0080 360deg);
    filter: blur(38px);
    animation: spiral-spin 16s linear infinite;
    animation-play-state: paused;
    transform-origin: center center;
}

.tile--radio.is-playing .viz--spiral.is-active .viz--spiral__inner {
    animation-play-state: running;
}

@keyframes spiral-spin {
    0% {
        transform: rotate(0deg) scale(1);
    }

    50% {
        transform: rotate(180deg) scale(1.25);
    }

    100% {
        transform: rotate(360deg) scale(1);
    }
}

/* Variant 5: ripples — concentric rings pulsing outward from play button */
.tile--radio.is-playing .viz--ripples.is-active {
    opacity: 0.7;
}

.tile--radio .viz--ripples__inner {
    position: absolute;
    width: 1px;
    height: 1px;
    left: var(--play-btn-x, 50%);
    top: var(--play-btn-y, 50%);
}

.tile--radio .viz--ripples__inner>i {
    position: absolute;
    top: 0;
    left: 0;
    width: 40px;
    height: 40px;
    border: 2px solid #fff;
    border-radius: 50%;
    opacity: 0;
    animation: ripple-grow 3s ease-out infinite;
    animation-play-state: paused;
    will-change: transform, opacity;
}

.tile--radio.is-playing .viz--ripples.is-active .viz--ripples__inner>i {
    animation-play-state: running;
}

.tile--radio .viz--ripples__inner>i:nth-child(1) {
    animation-delay: 0s;
}

.tile--radio .viz--ripples__inner>i:nth-child(2) {
    animation-delay: -0.5s;
}

.tile--radio .viz--ripples__inner>i:nth-child(3) {
    animation-delay: -1.0s;
}

.tile--radio .viz--ripples__inner>i:nth-child(4) {
    animation-delay: -1.5s;
}

.tile--radio .viz--ripples__inner>i:nth-child(5) {
    animation-delay: -2.0s;
}

.tile--radio .viz--ripples__inner>i:nth-child(6) {
    animation-delay: -2.5s;
}

@keyframes ripple-grow {
    0% {
        transform: translate(-50%, -50%) scale(0.3);
        opacity: 0.9;
    }

    100% {
        transform: translate(-50%, -50%) scale(20);
        opacity: 0;
    }
}

/* Variant 6: starfield — tiny twinkling stars at random positions */
.tile--radio .viz--starfield {
    overflow: hidden;
}

.tile--radio.is-playing .viz--starfield.is-active {
    opacity: 0.9;
}

.tile--radio .viz--starfield span {
    position: absolute;
    background: #fff;
    border-radius: 50%;
    box-shadow: 0 0 6px #fff, 0 0 10px rgba(255, 255, 255, 0.5);
    animation: star-twinkle 2s ease-in-out infinite;
    animation-play-state: paused;
    opacity: 0.25;
}

.tile--radio.is-playing .viz--starfield.is-active span {
    animation-play-state: running;
}

@keyframes star-twinkle {

    0%,
    100% {
        opacity: 0.25;
        transform: scale(0.7);
    }

    50% {
        opacity: 1;
        transform: scale(1.3);
    }
}

/* Keep radio content above the visualizer */
.tile--radio> :not(.visualizer):not(audio) {
    position: relative;
    z-index: 1;
}

/* Facepile — used in the Admin tile footer */
.facepile {
    display: inline-flex;
    align-items: center;
    min-height: 30px;
}

.facepile__avatar {
    width: 30px;
    height: 30px;
    border-radius: 50%;
    border: 2px solid #0f172a;
    overflow: hidden;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    margin-left: -8px;
    flex-shrink: 0;
    font-size: 0.7rem;
    font-weight: 700;
    color: #fff;
    background: #6c757d;
    transition: transform 0.15s ease;
}

.facepile__avatar:first-child {
    margin-left: 0;
}

.facepile__avatar:hover {
    transform: translateY(-2px);
    z-index: 2;
}

.facepile__avatar img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.facepile__overflow {
    min-width: 30px;
    height: 30px;
    padding: 0 8px;
    border-radius: 999px;
    border: 2px solid #0f172a;
    background: rgba(255, 255, 255, 0.18);
    color: #fff;
    font-size: 0.72rem;
    font-weight: 600;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    margin-left: -8px;
}

.facepile__empty {
    color: rgba(255, 255, 255, 0.6);
    font-size: 0.8rem;
}

.home-heading {
    font-size: 0.78rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    color: #64748b;
    margin-bottom: 12px;
}

/* Chat tile — green speech-bubble icons drifting in the background.
   Each bubble runs its own float-and-pulse cycle on a different
   delay so the cluster feels alive without being distracting. */
.tile--chat {
    position: relative;
    overflow: hidden;
}
/* Lift the title block + footer above the bubble layer. The bubbles
   container is excluded because it stays absolutely-positioned. */
.tile--chat > div:not(.chat-tile-bubbles),
.tile--chat > .tile__footer {
    position: relative;
    z-index: 1;
}
.tile--chat .chat-tile-bubbles {
    position: absolute;
    inset: 0;
    z-index: 0;
    pointer-events: none;
    overflow: hidden;
}
.tile--chat .chat-tile-bubbles i {
    position: absolute;
    color: rgba(13, 110, 253, 0.22);
    line-height: 1;
    animation: chat-tile-float 6s ease-in-out infinite;
    will-change: transform, opacity;
}
@keyframes chat-tile-float {
    0%, 100% { transform: translateY(0); opacity: 0.55; }
    50%      { transform: translateY(-8px); opacity: 1; }
}
/* Hand-tuned positions, sizes, and timing for a balanced cluster. */
.tile--chat .chat-tile-bubbles i:nth-child(1) { left:  6%; top: 18%; font-size: 2.4rem; animation-duration: 6.5s; animation-delay: 0s;    }
.tile--chat .chat-tile-bubbles i:nth-child(2) { left: 24%; top: 62%; font-size: 1.8rem; animation-duration: 5.0s; animation-delay: -1.2s; }
.tile--chat .chat-tile-bubbles i:nth-child(3) { left: 44%; top: 28%; font-size: 1.5rem; animation-duration: 7.0s; animation-delay: -0.4s; }
.tile--chat .chat-tile-bubbles i:nth-child(4) { left: 60%; top: 70%; font-size: 2.1rem; animation-duration: 5.5s; animation-delay: -2.0s; }
.tile--chat .chat-tile-bubbles i:nth-child(5) { left: 78%; top: 22%; font-size: 1.65rem; animation-duration: 6.0s; animation-delay: -0.8s; }
.tile--chat .chat-tile-bubbles i:nth-child(6) { left: 88%; top: 56%; font-size: 2.0rem; animation-duration: 4.8s; animation-delay: -1.6s; }
.tile--chat .chat-tile-bubbles i:nth-child(7) { left: 14%; top: 42%; font-size: 1.4rem; animation-duration: 5.6s; animation-delay: -2.4s; }

/* Feeds tile — solid tile in the reader's #f26522 orange (the
   groups-tile treatment in a different palette), with one large
   RSS glyph bleeding off the bottom-right corner as a watermark.
   The headline line cycles the newest unread titles (script
   beside the tile) with a fade between them. */
.tile--feeds {
    position: relative;
    overflow: hidden;
    background: #f26522;
    color: #fff;
}
.tile--feeds .tile__title { color: #fff; }
.tile--feeds .tile__desc  { color: rgba(255,255,255,0.85); }
.tile--feeds .tile__icon {
    background: rgba(255,255,255,0.18) !important;
    color: #fff !important;
}
.tile--feeds .tile__badge {
    background: rgba(255,255,255,0.18);
    color: #fff;
}
.tile--feeds .tile__arrow { color: rgba(255,255,255,0.9); }
.tile--feeds > div:not(.feeds-tile-mark),
.tile--feeds > .tile__footer {
    position: relative;
    z-index: 1;
}
.tile--feeds .feeds-tile-mark {
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
    z-index: 0;
    pointer-events: none;
    font-size: 10rem;
    line-height: 1;
    color: rgba(255, 255, 255, 0.18);
}
/* Always two lines tall: the headline rotates every 6 s, and a one-line
   headline after a two-line one (or none at all) used to resize the card. */
.tile--feeds .feeds-tile-headline {
    font-size: 0.82rem;
    color: rgba(255,255,255,0.9);
    font-style: italic;
    line-height: 1.35;
    height: 2.7em;
    overflow: hidden;
    text-overflow: ellipsis;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    transition: opacity 0.3s ease;
    margin: 0.25rem 0 0;
}

/* Groups tile — solid purple background with white icons drifting
   in front of it. Same float-and-pulse animation as the chat
   tile; same overall pattern with a different palette + icon set. */
.tile--groups {
    position: relative;
    overflow: hidden;
    background: #7c3aed;
    color: #fff;
}
.tile--groups .tile__title { color: #fff; }
.tile--groups .tile__desc  { color: rgba(255,255,255,0.85); }
.tile--groups .tile__icon {
    background: rgba(255,255,255,0.18) !important;
    color: #fff !important;
}
.tile--groups .tile__badge {
    background: rgba(255,255,255,0.18);
    color: #fff;
}
.tile--groups .tile__arrow { color: rgba(255,255,255,0.9); }
.tile--groups > div:not(.groups-tile-people),
.tile--groups > .tile__footer {
    position: relative;
    z-index: 1;
}
.tile--groups .groups-tile-people {
    position: absolute;
    inset: 0;
    z-index: 0;
    pointer-events: none;
    overflow: hidden;
}
.tile--groups .groups-tile-people i {
    position: absolute;
    color: rgba(255, 255, 255, 0.30);
    line-height: 1;
    animation: chat-tile-float 6s ease-in-out infinite;
    will-change: transform, opacity;
}
.tile--groups .groups-tile-people i:nth-child(1) { left:  6%; top: 18%; font-size: 2.4rem; animation-duration: 6.5s; animation-delay: 0s;    }
.tile--groups .groups-tile-people i:nth-child(2) { left: 24%; top: 62%; font-size: 1.8rem; animation-duration: 5.0s; animation-delay: -1.2s; }
.tile--groups .groups-tile-people i:nth-child(3) { left: 44%; top: 28%; font-size: 1.5rem; animation-duration: 7.0s; animation-delay: -0.4s; }
.tile--groups .groups-tile-people i:nth-child(4) { left: 60%; top: 70%; font-size: 2.1rem; animation-duration: 5.5s; animation-delay: -2.0s; }
.tile--groups .groups-tile-people i:nth-child(5) { left: 78%; top: 22%; font-size: 1.65rem; animation-duration: 6.0s; animation-delay: -0.8s; }
.tile--groups .groups-tile-people i:nth-child(6) { left: 88%; top: 56%; font-size: 2.0rem; animation-duration: 4.8s; animation-delay: -1.6s; }
.tile--groups .groups-tile-people i:nth-child(7) { left: 14%; top: 42%; font-size: 1.4rem; animation-duration: 5.6s; animation-delay: -2.4s; }

/* Populated Groups tile — a scrollable list of the user's groups, each
   row a link to its group page with a per-group unread-chat badge.
   Replaces the floating-people empty state when the user has groups. */
.tile--groups.tile--groups-populated { justify-content: flex-start; padding: 16px; }
.groups-tile-head {
    display: flex; align-items: center; gap: .5rem;
    margin-bottom: .6rem; position: relative; z-index: 1;
}
.groups-tile-head .tile__icon {
    width: 2rem; height: 2rem; display: inline-flex;
    align-items: center; justify-content: center;
    border-radius: 10px; font-size: 1rem; flex: 0 0 auto;
}
.groups-tile-head h3 { margin: 0; font-size: 1.05rem; font-weight: 700; color: #fff; flex: 1 1 auto; }
.groups-tile-head .tile__arrow { color: rgba(255,255,255,0.9); text-decoration: none; }
.groups-tile-scroll {
    position: relative; z-index: 1;
    flex: 1 1 0; min-height: 0;
    overflow-y: auto; -webkit-overflow-scrolling: touch;
    display: flex; flex-direction: column; gap: 5px;
    margin: 0 -2px; padding: 0 2px;
}
.groups-tile-row {
    display: flex; align-items: center; gap: .55rem;
    padding: .45rem .6rem; border-radius: 9px;
    background: rgba(255,255,255,0.12); color: #fff; text-decoration: none;
    transition: background .15s ease;
}
.groups-tile-row:hover, .groups-tile-row:focus-visible { background: rgba(255,255,255,0.24); color: #fff; }
.groups-tile-row .gname {
    flex: 1 1 auto; min-width: 0;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    font-weight: 600; font-size: .92rem;
}
.groups-tile-row .gmembers {
    flex: 0 0 auto; font-size: .72rem; color: rgba(255,255,255,0.7);
    display: inline-flex; align-items: center; gap: .2rem;
}
.groups-tile-row .gunread {
    flex: 0 0 auto; background: #dc3545; color: #fff;
    border-radius: 999px; font-size: .72rem; font-weight: 700;
    padding: .05rem .42rem; min-width: 1.5em; text-align: center;
    box-shadow: 0 1px 2px rgba(0,0,0,0.25);
}
.groups-tile-scroll::-webkit-scrollbar { width: 6px; }
.groups-tile-scroll::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.32); border-radius: 3px; }
.groups-tile-scroll::-webkit-scrollbar-track { background: transparent; }

/* Shortcuts tile — user-pinned page links. Deep slate gradient with an
   amber star; rows reuse the groups-tile list pattern. */
.tile--shortcuts {
    background: linear-gradient(135deg, #1e293b 0%, #334155 55%, #1c2534 100%);
    justify-content: flex-start;
    padding: 16px;
}
.sc-tile-head {
    display: flex; align-items: center; gap: .5rem;
    margin-bottom: .6rem; position: relative; z-index: 1;
}
.sc-tile-head .tile__icon {
    width: 2rem; height: 2rem; display: inline-flex;
    align-items: center; justify-content: center;
    border-radius: 10px; font-size: 1rem; flex: 0 0 auto;
    background: rgba(251,191,36,0.18); color: #fbbf24;
    margin-bottom: 0;
}
.sc-tile-head h3 { margin: 0; font-size: 1.05rem; font-weight: 700; color: #fff; flex: 1 1 auto; }
.sc-tile-edit {
    flex: 0 0 auto; border: 0; background: rgba(255,255,255,0.12);
    color: rgba(255,255,255,0.85); border-radius: 8px;
    width: 1.8rem; height: 1.8rem; display: inline-flex;
    align-items: center; justify-content: center; font-size: .85rem;
}
.sc-tile-edit:hover, .sc-tile-edit.is-active { background: rgba(255,255,255,0.28); color: #fff; }
.sc-tile-scroll {
    position: relative; z-index: 1;
    flex: 1 1 0; min-height: 0;
    overflow-y: auto; -webkit-overflow-scrolling: touch;
    display: flex; flex-direction: column; gap: 5px;
    margin: 0 -2px; padding: 0 2px;
}
.sc-row {
    display: flex; align-items: center; gap: .55rem;
    padding: .45rem .6rem; border-radius: 9px;
    background: rgba(255,255,255,0.12); color: #fff; text-decoration: none;
    transition: background .15s ease;
}
.sc-row:hover, .sc-row:focus-visible { background: rgba(255,255,255,0.24); color: #fff; }
.sc-row .sc-label {
    flex: 1 1 auto; min-width: 0;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    font-weight: 600; font-size: .92rem;
}
.sc-row .sc-remove {
    flex: 0 0 auto; display: none;
    width: 1.5rem; height: 1.5rem; border-radius: 999px;
    align-items: center; justify-content: center;
    background: rgba(220,53,69,0.85); color: #fff; font-size: .8rem;
}
.tile--shortcuts.sc-editing .sc-remove { display: inline-flex; }
.sc-tile-scroll::-webkit-scrollbar { width: 6px; }
.sc-tile-scroll::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.32); border-radius: 3px; }
.sc-tile-scroll::-webkit-scrollbar-track { background: transparent; }

/* Collections tile — deep navy gradient with subtle floating
   tag-chip pills in the background, evoking the tagging theme. */
.tile--collections {
    background: linear-gradient(135deg, #0c2f5e 0%, #173b7b 50%, #0b2148 100%);
    position: relative;
    overflow: hidden;
}
.tile--collections .tile__title { color: #fff; }
.tile--collections .tile__desc  { color: rgba(255,255,255,0.86); }
.tile--collections .tile__icon {
    background: rgba(255,255,255,0.16);
    color: #fff;
}
.tile--collections .tile__badge {
    background: rgba(255,255,255,0.18);
    color: #fff;
    border: 0;
}
.tile--collections .tile__arrow { color: #fff; }
/* Checklist motif: faint rows of (icon)──── across the card. The icon
   cycles through checked-box (own), empty-box, and heart (want) so the
   tile reads as a list of own/want items at a glance. */
.collections-bg {
    position: absolute; inset: 0;
    pointer-events: none;
    z-index: 0;
    overflow: hidden;
}
.collections-bg .coll-bg-row {
    position: absolute;
    display: flex;
    align-items: center;
    gap: 10px;
    color: rgba(255, 255, 255, 0.38);
    font-size: 1.55rem;
    line-height: 1;
    white-space: nowrap;
}
.collections-bg .coll-bg-row .bar {
    height: 5px;
    border-radius: 999px;
    background: rgba(255, 255, 255, 0.22);
    display: inline-block;
}
.collections-bg .row-1 { top:  8%; left:  6%; }
.collections-bg .row-2 { top: 24%; left: 14%; }
.collections-bg .row-3 { top: 41%; left:  4%; }
.collections-bg .row-4 { top: 58%; left: 18%; }
.collections-bg .row-5 { top: 74%; left:  7%; }
.collections-bg .row-6 { top: 88%; left: 22%; }
.collections-bg .row-1 .bar { width: 130px; }
.collections-bg .row-2 .bar { width:  92px; }
.collections-bg .row-3 .bar { width: 108px; }
.collections-bg .row-4 .bar { width:  78px; }
.collections-bg .row-5 .bar { width: 118px; }
.collections-bg .row-6 .bar { width:  86px; }
/* Hearts tinted warm so they read as "want" vs the neutral checkboxes,
   and checks tinted slightly green to read as "owned". */
.collections-bg .bi-heart-fill        { color: rgba(252, 165, 165, 0.70); }
.collections-bg .bi-check-square-fill { color: rgba(167, 243, 208, 0.55); }
.collections-bg .bi-square            { color: rgba(255, 255, 255, 0.40); }
/* Tile content needs to sit above the background. The .collections-bg
   re-asserts position:absolute because the universal `> *` rule below
   sets all direct children to position:relative. */
.tile--collections > * { position: relative; z-index: 1; }

/* "Developing Polaroids": random item photos fade up like instant film
   and dissolve away in the card background. Keep the title/desc legible
   with a text-shadow since prints can drift behind them. */
.tile--collections .tile__title { text-shadow: 0 1px 6px rgba(0,0,0,0.55); }
.tile--collections .tile__desc  { text-shadow: 0 1px 5px rgba(0,0,0,0.50); }
.coll-polaroids { position: absolute; inset: 0; z-index: 0; overflow: hidden; pointer-events: none; }
.coll-polaroid {
    position: absolute;
    width: 84px;
    background: #fbfaf6;
    padding: 5px 5px 16px;
    border-radius: 2px;
    box-shadow: 0 8px 18px rgba(0,0,0,0.38);
    opacity: 0;
    transform: rotate(var(--rot, 0deg)) scale(0.96);
    transition: opacity 0.6s ease, transform 0.6s ease;
}
.coll-polaroid img {
    display: block; width: 100%; aspect-ratio: 1; object-fit: cover;
    background: #ded8c9; border-radius: 1px;
}
.coll-polaroid.is-in { opacity: var(--peak, 0.82); transform: rotate(var(--rot, 0deg)) scale(1); }
.coll-polaroid.is-out { opacity: 0; transition: opacity 0.9s ease; }
/* The photo "develops": overexposed + desaturated → full colour. */
.coll-polaroid.is-in img { animation: coll-develop var(--develop, 2600ms) ease-out both; }
@keyframes coll-develop {
    0%   { filter: brightness(2.4) contrast(0.35) saturate(0) sepia(0.55); }
    60%  { filter: brightness(1.25) contrast(0.8) saturate(0.7) sepia(0.2); }
    100% { filter: brightness(1) contrast(1) saturate(1) sepia(0); }
}
@media (prefers-reduced-motion: reduce) {
    .coll-polaroid { transition: none; }
    .coll-polaroid.is-in img { animation: none; }
}
.tile--collections > .collections-bg { position: absolute; inset: 0; z-index: 0; }

/* HeadsUp tile — vivid purple → fuchsia gradient with three faint
   "phrase cards" fanned in the background, evoking a deck of clues
   waiting to be acted out. */
.tile--headsup {
    background: linear-gradient(135deg, #6d28d9 0%, #9333ea 50%, #ec4899 100%);
    position: relative;
    overflow: hidden;
}
.tile--headsup .tile__title { color: #fff; }
.tile--headsup .tile__desc  { color: rgba(255,255,255,0.88); }
.tile--headsup .tile__icon  { background: rgba(255,255,255,0.18); color: #fff; }
.tile--headsup .tile__badge { background: rgba(255,255,255,0.20); color: #fff; border: 0; }
.tile--headsup .tile__arrow { color: #fff; }
.headsup-cards {
    position: absolute; inset: 0;
    pointer-events: none;
    z-index: 0;
    overflow: hidden;
}
.headsup-cards .headsup-card {
    position: absolute;
    font-family: ui-monospace, "SF Mono", "Menlo", monospace;
    font-weight: 700;
    font-size: .85rem;
    letter-spacing: .12em;
    color: rgba(255, 255, 255, 0.55);
    background: rgba(255, 255, 255, 0.10);
    border: 1px solid rgba(255, 255, 255, 0.22);
    border-radius: 6px;
    padding: .4rem .9rem;
    white-space: nowrap;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.22);
}
.headsup-cards .headsup-card-1 { top:  8%; right:  6%; transform: rotate( 11deg); }
.headsup-cards .headsup-card-2 { top: 38%; right: 18%; transform: rotate(  -7deg); }
.headsup-cards .headsup-card-3 { top: 68%; right:  4%; transform: rotate(  14deg); }
/* Tile content sits above the cards. */
.tile--headsup > * { position: relative; z-index: 1; }
.tile--headsup > .headsup-cards { position: absolute; inset: 0; z-index: 0; }

/* Calendar tile — warm coral gradient with faint grid lines. The whole
   card is the rotating event stage: title + description fill the space,
   no chrome around them. Admins get a corner gear into /calendar. */
.tile--calendar {
    background: linear-gradient(135deg, #d97706 0%, #b91c1c 60%, #7c2d12 100%);
    color: #fff;
    position: relative;
    overflow: hidden;
    padding: 1.5rem;
    /* The calendar tile is first in the DOM but renders hidden and is
       unhidden by JS only once today's events load. Placing it last in
       the grid (order:999) means that late reveal appends it after the
       other cards instead of shoving them all over by one cell. */
    order: 999;
}
.tile--calendar .cal-bg-grid {
    position: absolute; inset: 0;
    width: 100%; height: 100%;
    pointer-events: none; z-index: 0;
}
.tile--calendar > * { position: relative; z-index: 1; }
.tile--calendar > .cal-bg-grid { z-index: 0; }
.cal-tile-body {
    position: relative;
    width: 100%;
    height: 100%;
    min-height: 8rem;
}
.cal-tile-event {
    position: absolute; inset: 0;
    opacity: 0;
    transition: opacity .6s ease;
    pointer-events: none;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    text-align: center;
    padding: .25rem;
}
.cal-tile-event.is-active { opacity: 1; pointer-events: auto; }
.cal-tile-title {
    font-weight: 700;
    font-size: 1.6rem;
    line-height: 1.15;
    color: #fff;
    text-shadow: 0 1px 3px rgba(0,0,0,0.25);
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
}
.cal-tile-desc {
    margin-top: .6rem;
    font-size: 1rem;
    line-height: 1.35;
    color: rgba(255,255,255,0.92);
    display: -webkit-box;
    -webkit-line-clamp: 4;
    -webkit-box-orient: vertical;
    overflow: hidden;
}
.cal-tile-empty {
    font-weight: 500;
    font-size: 1.15rem;
    color: rgba(255,255,255,0.85);
}
/* Countdown pill for upcoming (flagged) events — sits above the title. */
.cal-tile-countdown {
    margin-bottom: .55rem;
    padding: .15rem .6rem;
    border-radius: 999px;
    font-size: .8rem;
    font-weight: 700;
    letter-spacing: .03em;
    text-transform: uppercase;
    color: #fff;
    background: rgba(0,0,0,0.22);
    border: 1px solid rgba(255,255,255,0.45);
    text-shadow: 0 1px 2px rgba(0,0,0,0.25);
}
.cal-tile-gear {
    position: absolute;
    top: .6rem;
    right: .6rem;
    width: 2rem;
    height: 2rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    color: #fff;
    background: rgba(255,255,255,0.18);
    text-decoration: none;
    z-index: 2;
    transition: background .15s ease, transform .15s ease;
}
.cal-tile-gear:hover {
    background: rgba(255,255,255,0.32);
    color: #fff;
    transform: rotate(30deg);
}

/* Travel tile — Ken-Burns world map background. The Leaflet
   container fills the tile and slowly scales while a JS interval
   jumps the viewport to a different city every 10s. The dark
   overlay keeps title + description legible against any map. */
.tile--travel {
    color: #fff;
    background: #0f172a;
}
.tile--travel .tile__title { color: #fff; }
.tile--travel .tile__desc  { color: rgba(255,255,255,0.85); }
.tile--travel .tile__icon {
    background: rgba(255,255,255,0.18) !important;
    color: #fff !important;
}
.tile--travel .tile__badge {
    background: rgba(255,255,255,0.18);
    color: #fff;
    border-color: rgba(255,255,255,0.25);
}
.tile--travel .tile__badge--link {
    text-decoration: none;
    transition: background 0.15s ease;
}
.tile--travel .tile__badge--link:hover {
    background: rgba(255,255,255,0.32);
    color: #fff;
}
.tile--travel .tile__arrow { color: #fff; }
.tile--travel .tile__map {
    position: absolute;
    inset: 0;
    z-index: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    transform-origin: center;
    filter: saturate(0.9);
    opacity: 0;
    will-change: transform, opacity;
}
.tile--travel .tile__map.is-active { opacity: 1; }
.tile--travel::after {
    content: '';
    position: absolute;
    inset: 0;
    z-index: 1;
    pointer-events: none;
    background: linear-gradient(180deg, rgba(15,23,42,0.45) 0%, rgba(15,23,42,0.78) 100%);
}
.tile--travel > div,
.tile--travel > .tile__footer { position: relative; z-index: 2; }
.tile--travel .tile__map-credit {
    position: absolute;
    right: 6px;
    bottom: 4px;
    z-index: 2;
    font-size: 0.55rem;
    color: rgba(255,255,255,0.55);
    text-decoration: none;
    letter-spacing: 0.02em;
}
.tile--travel .tile__map-credit:hover { color: rgba(255,255,255,0.85); }


/* ── was views/morgensite.ejs line 2414 (friend-network tile) ── */
/* Starfield covers the full card (edge-to-edge behind
   header + footer). Graph + empty-state message are
   ALSO absolutely positioned so they don't participate
   in the tile's flex sizing — matches how
   .tile__covers works on neighbor tiles (MorgenGram,
   Albums, Photo Store). Without this, the graph SVG's
   viewBox aspect ratio gives it intrinsic height that
   pushes the flex column well past the standard 180 px
   .tile min-height. */
#friendNetworkStarsBg,
#friendNetworkOverlay {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  pointer-events: none;
}
#friendNetworkStarsBg { z-index: 0; }
#friendNetworkOverlay { z-index: 1; display: flex; align-items: center; justify-content: center; }
#friendNetworkOverlay svg { width: 100%; height: 100%; display: block; }
/* Header + footer float above on z:2 so they read over
   the graph as well as the stars. */
#friendNetworkTile > .tile__header,
#friendNetworkTile > .tile__footer {
  position: relative; z-index: 2;
}

