/* MoonlightWeb — Apps view */

.apps-view {
    width: 100%;
}

.apps-header {
    display: flex;
    align-items: center;
    gap: var(--space-4);
    margin-bottom: var(--space-5);
}
.apps-host-info {
    flex: 1;
    min-width: 0;
}
.apps-host-name {
    font-weight: 600;
    font-size: var(--fs-lg);
    color: var(--text-1);
}
.apps-host-addr {
    font-size: var(--fs-xs);
    color: var(--text-2);
    font-family: var(--font-mono);
}
.apps-spacer {
    width: 100px;
} /* balance the back button */

.apps-loading,
.apps-error,
.apps-empty {
    text-align: center;
    padding: var(--space-8) var(--space-5);
    color: var(--text-2);
    background-color: var(--surface-2);
    border-radius: var(--radius-md);
    border: 1px solid var(--border-1);
}

/* ── App grid ──────────────────────────────────────────────────────────── */
.apps-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
    gap: var(--space-6);
}

.app-card {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-5) var(--space-4);
    text-align: center;
    cursor: pointer;
    transition:
        border-color 0.2s,
        transform 0.15s,
        box-shadow 0.2s;
}
/* Desktop: fixed-width cards in a wrapping row, so the host box width is
   driven by its content (side-by-side host boxes in hosts.css). */
@media (min-width: 601px) {
    .apps-grid {
        display: flex;
        flex-wrap: wrap;
        gap: var(--app-card-gap);
    }
    .app-card {
        width: var(--app-card-w);
        padding-left: var(--space-5);
        padding-right: var(--space-5);
    }
}
.app-card:hover {
    box-shadow: var(--edge-accent), var(--glass-highlight);
    filter: var(--glow-accent);
    transform: translateY(-3px);
}
/* Press feedback (tap on mobile / click) — quick scale-down for tactile cue. */
.app-card:active {
    transform: translateY(-1px) scale(0.96);
    transition: transform 0.06s ease;
}

.app-card-image {
    width: 100%;
    aspect-ratio: 3 / 4;
    overflow: hidden;
    border-radius: var(--radius-sm);
    background-color: var(--surface-3);
    margin-bottom: 2px;
}
.app-card-image img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}
/* A cover being re-fetched after a failed request: the frame shows the default
   screen meanwhile, and the image stays in the DOM to come back into. Needed
   because the rule above outranks the browser's own [hidden] handling. */
.app-card-image img[hidden] {
    display: none;
}

/* ── No cover: an arcade sprite over its line (AppPlaceholder.js) ───────── */
/* Sized off the frame's own width (cqi), so the line under the sprite still
   fits in the narrow two-column tiles of a phone. */
.app-card-image {
    container-type: inline-size;
}
.app-card-image .app-icon {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 14cqi;
    width: 100%;
    height: 100%;
    background: linear-gradient(var(--surface-2), var(--surface-1));
}
/* CRT scanlines over the whole frame. */
.app-card-image .app-icon::after {
    content: '';
    position: absolute;
    inset: 0;
    pointer-events: none;
    background: repeating-linear-gradient(0deg, rgba(0, 0, 0, 0.18) 0 2px, transparent 2px 4px);
}
/* Every sprite shares one 15×13 canvas, so this width sets one pixel size for
   the whole cast. */
.app-icon-sprite {
    width: 66%;
    height: auto;
    filter: drop-shadow(0 0 5px rgba(0, 229, 255, 0.55));
}
.app-icon-sprite--yellow {
    filter: drop-shadow(0 0 5px rgba(252, 238, 10, 0.5));
}
.app-icon-sprite--magenta {
    filter: drop-shadow(0 0 5px rgba(255, 42, 109, 0.6));
}
.app-icon-cyan {
    fill: var(--neon-cyan);
}
.app-icon-yellow {
    fill: var(--accent-1);
}
.app-icon-magenta {
    fill: var(--neon-magenta);
}
.app-icon-white {
    fill: var(--text-1);
}

/* ── Native host: the machine a display belongs to (HostArt.js) ────────── */
/* No frame, no scanlines: the machine stands on the card itself. */
.app-card-image--host,
.app-card-image--host .app-icon {
    background: transparent;
}
.app-card-image--host .app-icon::after {
    display: none;
}
/* The picture is wider than it is tall; one canvas for every device keeps a
   pixel the same size on every card. */
.app-icon--host .app-icon-sprite {
    width: 92%;
    filter: drop-shadow(0 0 4px rgba(90, 190, 170, 0.26));
}
.app-icon--windows .app-icon-sprite {
    filter: drop-shadow(0 0 4px rgba(80, 150, 230, 0.3));
}
.app-icon--macos .app-icon-sprite {
    filter: drop-shadow(0 0 4px rgba(170, 140, 230, 0.28));
}
.app-icon-label {
    font-family: var(--font-mono);
    font-size: 9cqi;
    letter-spacing: 0.15em;
    line-height: 1;
    white-space: nowrap;
    color: var(--accent-1);
}
/* Hovering a coverless card makes the line blink, as it does on a cabinet
   waiting for a player. */
.app-card:hover .app-icon-label,
.app-card:focus-visible .app-icon-label {
    animation: app-press-start 1s steps(1, end) infinite;
}
@keyframes app-press-start {
    50% {
        opacity: 0;
    }
}

.app-card-name {
    font-weight: 500;
    font-size: var(--fs-sm);
    line-height: 1.3;
    word-break: break-word;
}

/* ── Running on the host (issue #24) ────────────────────────────────────
   The app a stopped stream left running: a cyan edge and a badge say "this
   one is live", clicking the card resumes it, and the small Quit under the
   name closes it on the host. Cyan rather than the accent yellow, which
   already means "launching". */
.app-card--running {
    position: relative;
    box-shadow:
        inset 0 0 0 1px var(--neon-cyan),
        var(--glass-highlight);
}
.app-card-image {
    position: relative;
}
.app-card-badge {
    position: absolute;
    top: var(--space-2);
    left: var(--space-2);
    padding: 1px var(--space-2);
    font-family: var(--font-mono);
    font-size: var(--fs-xs);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--on-accent);
    background: var(--neon-cyan);
    pointer-events: none;
}
.app-card-quit {
    margin-top: calc(-1 * var(--space-1));
    padding: 2px var(--space-3);
    font: inherit;
    font-size: var(--fs-xs);
    color: var(--c-danger-text);
    background: var(--c-danger-soft);
    border: 1px solid var(--c-danger);
    cursor: pointer;
}
.app-card-quit:hover,
.app-card-quit:focus-visible {
    color: var(--on-accent);
    background: var(--c-danger);
}
.app-card-quit:disabled {
    cursor: progress;
    opacity: 0.6;
}

/* ── Launching state ────────────────────────────────────────────────────── */
/* Keep a yellow edge for the whole launch, even after the pointer leaves
   (mouseleave/focusout) — otherwise the contour falls back to the cyan edge. */
.app-card--launching,
.app-card--launching:hover {
    cursor: progress;
    position: relative;
    box-shadow: var(--edge-accent), var(--glass-highlight);
    background:
        linear-gradient(rgba(252, 238, 10, 0.16), rgba(252, 238, 10, 0.16)), var(--surface-glass);
}
/* Status label: mono accent yellow, gently blinking to read as "loading". */
.app-card--launching .app-card-name {
    color: var(--accent-1);
    font-family: var(--font-mono);
    letter-spacing: 0.04em;
    animation: app-launch-blink 1.6s ease-in-out infinite;
}
@keyframes app-launch-blink {
    0%,
    100% {
        opacity: 1;
    }
    50% {
        opacity: 0.35;
    }
}

/* Rotating gradient ring around the card — clockwise, slow, "loader" cue.
   A conic gradient drawn into a border-thickness ring via mask compositing,
   notched to follow the CP2077 bevelled corners. */
@property --app-spin {
    syntax: '<angle>';
    initial-value: 0deg;
    inherits: false;
}
.app-card--launching::before {
    content: '';
    position: absolute;
    inset: 0;
    padding: 2px; /* ring thickness */
    border-radius: inherit;
    clip-path: var(--notch-lg);
    background: conic-gradient(
        from var(--app-spin),
        transparent 0deg,
        transparent 230deg,
        rgba(252, 238, 10, 0.55) 320deg,
        transparent 360deg
    );
    /* Keep only the padding ring (exclude the inner content box). */
    -webkit-mask:
        linear-gradient(#000 0 0) content-box,
        linear-gradient(#000 0 0);
    -webkit-mask-composite: xor;
    mask-composite: exclude;
    pointer-events: none;
    animation: app-spin-ring 3.2s linear infinite;
}
@keyframes app-spin-ring {
    to {
        --app-spin: 360deg;
    }
}

/* ── Launch refused ─────────────────────────────────────────────────────
   The launch never started and the user is still on the host list, so the
   card has to say so itself: an alert orange that fades back to neutral in
   one second. Long enough to be seen, short enough that no colour is left
   sitting on the card pretending to be a state — the card ends up plainly
   clickable again, which is the actual instruction.

   Its own orange rather than a token: --c-warning is an amber that reads as
   a paler version of the accent yellow the card has just left, and --c-danger
   is the magenta reserved for destructive things. A launch that didn't take
   is neither.

   The wash lives in ::after (::before is the launching ring) so nothing has
   to animate the card's own background or edge back to a computed value. */
.app-card--launch-failed {
    position: relative;
}
.app-card--launch-failed::after {
    content: '';
    position: absolute;
    inset: 0;
    clip-path: var(--notch-lg);
    background: rgba(255, 106, 31, 0.18);
    box-shadow: inset 0 0 0 1px rgba(255, 106, 31, 0.9);
    pointer-events: none;
    animation: app-launch-failed 1s ease-out forwards;
}
@keyframes app-launch-failed {
    from {
        opacity: 1;
    }
    to {
        opacity: 0;
    }
}

@media (prefers-reduced-motion: reduce) {
    .app-card--launching .app-card-name {
        animation: none;
    }
    .app-card--launching::before {
        animation: none;
    }
    .app-card:hover .app-icon-label,
    .app-card:focus-visible .app-icon-label {
        animation: none;
    }
    /* No fade: the wash simply holds for its second, then the timer in
       HostListView drops the class. Without this the global reduced-motion
       override (every animation collapsed to 0.01 ms) would run the fade to
       completion before the card was ever seen to change. */
    .app-card--launch-failed::after {
        animation: none;
    }
}

@media (max-width: 600px) {
    .apps-grid {
        grid-template-columns: repeat(auto-fill, minmax(90px, 1fr));
        gap: var(--space-3);
    }
    .app-card {
        gap: var(--space-2);
        padding: var(--space-3) var(--space-2);
    }
    .app-card-name {
        font-size: var(--fs-xs);
    }
    .apps-header {
        flex-wrap: wrap;
        gap: var(--space-3);
    }
    .apps-spacer {
        display: none;
    }
}
