/* Kitae Studio — app-specific styles.
   The loading-screen (.app-loading) and fullscreen-IDE (body.cat-ide-fullscreen) styles
   both ship with the Catanari framework CSS, so nothing is needed here to get the IDE
   shell looking right. Add Kitae Studio-specific overrides below as the app grows. */

/* ── Boot loading bar ────────────────────────────────────────────────────────
   The app-loading overlay (framework CSS) centres this. One determinate bar,
   width driven by kitaestudio:progress events — see _Layout.cshtml. */
.app-loading-track {
    width: 280px;
    height: 6px;
    border-radius: 3px;
    background: var(--tblr-border-color, #e6e7e9);
    overflow: hidden;
    margin: 0 auto 0.75rem;
}

.app-loading-bar {
    height: 100%;
    width: 5%;
    border-radius: 3px;
    background: var(--tblr-purple, #ae3ec9);
    transition: width 250ms ease;
}

/* ── WalletDisplay bob (Stage 10 polish) ─────────────────────────────────────
   The three coin icons drift up and down at the SAME speed but with a different
   tween + phase each, so they never move in lockstep. Applied per-icon by
   WalletDisplay.bob().

   will-change promotes each icon onto its own compositor layer: without it the
   transform is applied during main-thread paint, which snaps positions to whole
   device pixels — a 3px drift over 2.6s then judders in visible 1px steps. On a
   compositor layer fractional offsets are resampled smoothly. */
@keyframes ks-wallet-bob {
    0%, 100% { transform: translate3d(0, 0, 0); }
    50%      { transform: translate3d(0, -3px, 0); }
}

.ks-wallet-bob-0 { will-change: transform; animation: ks-wallet-bob 2.6s ease-in-out infinite; }
/* NeonDisplay's orb: same drift, different duration + negative delay so the neon orb and the
   gold coin never bob in lockstep (their phases slowly slide past each other). */
.ks-wallet-bob-1 { will-change: transform; animation: ks-wallet-bob 2.9s ease-in-out -1.2s infinite; }

@media (prefers-reduced-motion: reduce) {
    .ks-wallet-bob-0, .ks-wallet-bob-1 { animation: none; }
}

/* ── About dialog credits crawl ──────────────────────────────────────────────
   A slow, seamless upward crawl inside an overflow-hidden viewport. AboutDialog
   renders [spacer + credits] twice back-to-back — the spacer is exactly one
   viewport tall (100cqh; the viewport is a size container), so the reel opens
   empty and the credits rise in from below the bottom edge, and translating the
   track by -50% still loops perfectly. (Hover-pause can't trigger inside the
   About overlay — it is pointer-transparent by design so the cursor reaches the
   live scene.) */
@keyframes ks-credits-crawl {
    from { transform: translate3d(0, 0, 0); }
    to   { transform: translate3d(0, -50%, 0); }
}

.ks-credits-viewport {
    position: relative;
    height: 170px;
    overflow: hidden;
    container-type: size;
    /* Soft fade at both ends so lines materialise rather than pop */
    -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 12%, #000 88%, transparent 100%);
    mask-image: linear-gradient(to bottom, transparent 0, #000 12%, #000 88%, transparent 100%);
}

.ks-credits-spacer {
    height: 100cqh;
}

.ks-credits-track {
    /* 56s keeps the old 36s crawl speed now each loop half also spans the spacer */
    animation: ks-credits-crawl 56s linear infinite;
    will-change: transform;
}

.ks-credits-viewport:hover .ks-credits-track {
    animation-play-state: paused;
}

@media (prefers-reduced-motion: reduce) {
    .ks-credits-track { animation: none; }
    /* No motion means the reel never rises in — show it statically from the top. */
    .ks-credits-spacer { display: none; }
}

/* ── About dialog: landscape hero stage ──────────────────────────────────────
   The modal body IS the scene: a fixed-height landscape stage with the live
   Kitae canvas filling it edge-to-edge (the browser happily composites DOM over
   a WebGL/WebGPU canvas) and the identity + credits sitting on top. The whole
   overlay is pointer-transparent so hover and clicks fall straight through to
   the canvas — that's how the scene (embers, vector balls, the wordmark
   letters) gets to react to the cursor. */
.ks-about-body {
    position: relative;
    height: 440px;
    overflow: hidden;
}

.ks-about-hero {
    position: absolute;
    inset: 0;
}

.ks-about-overlay {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: stretch;
    justify-content: space-between;
    gap: 1rem;
    padding: 1.25rem 1.5rem;
    /* Everything above the scene lets the cursor through to the canvas below. */
    pointer-events: none;
    /* Edge scrims keep the text legible over the brightest embers. */
    background: linear-gradient(90deg, rgba(9, 11, 22, 0.55) 0%, rgba(9, 11, 22, 0.15) 40%, rgba(9, 11, 22, 0) 55%, rgba(9, 11, 22, 0.45) 100%);
}

.ks-about-overlay * {
    pointer-events: none;
}

/* The scene is always a dark night sky whatever the app theme, so the overlay
   text is always light, with a soft shadow for lift. */
.ks-about-identity {
    align-self: flex-end;
    max-width: 55%;
    color: #eef0fa;
    text-shadow: 0 1px 10px rgba(0, 0, 0, 0.65);
}

.ks-about-identity h3 {
    color: inherit;
}

.ks-about-identity .text-secondary,
.ks-about-identity .text-muted {
    color: rgba(228, 231, 246, 0.78) !important;
}

.ks-about-credits {
    width: 280px;
    flex: 0 0 auto;
    display: flex;
    flex-direction: column;
    color: #eef0fa;
    text-shadow: 0 1px 10px rgba(0, 0, 0, 0.65);
}

.ks-about-credits .ks-credits-viewport {
    height: 100%;
    flex: 1 1 auto;
}

.ks-about-credits .text-secondary {
    color: rgba(228, 231, 246, 0.7) !important;
}

/* ==================== Beta dialog (BetaDialog) ====================
   Reuses .ks-about-body / .ks-about-hero / .ks-about-overlay for the stage + scene.
   The beta manifesto sits in a panel on the RIGHT half of the stage (the wordmark
   column lives on the left of the canvas), always light on the dark night sky. */
.ks-beta-overlay {
    /* Text lives on the right, so scrim that side harder for legibility. */
    background: linear-gradient(90deg, rgba(6, 8, 20, 0) 0%, rgba(6, 8, 20, 0) 42%, rgba(6, 8, 20, 0.5) 62%, rgba(6, 8, 20, 0.82) 100%);
    align-items: center;
    justify-content: flex-end;
}

.ks-beta-panel {
    width: 52%;
    max-width: 500px;
    max-height: 100%;
    align-self: center;
    color: #eef0fa;
    text-shadow: 0 1px 12px rgba(0, 0, 0, 0.75);
}

/* The manifesto is the rendered `beta-welcome` markdown — a lot of text on a fixed-height
   stage — so the type and margins are tightened to always fit without spilling past the
   top/bottom of the scene. Styles target the Markdown control's generated elements. */
.ks-beta-panel h1,
.ks-beta-panel h2,
.ks-beta-panel h3 {
    /* "Welcome to the Beta" wears the current account theme's primary colour (the named themes
       swap --tblr-primary), lifted a touch for legibility over the dark night-sky scene. */
    color: var(--tblr-primary, #6c5ce7);
    font-size: 1.1rem;
    margin: 0 0 0.4rem 0;
}

.ks-beta-panel p {
    color: rgba(233, 236, 248, 0.92);
    font-size: 0.8rem;
    line-height: 1.3;
    margin: 0 0 0.45rem 0;
}

.ks-beta-panel p:last-child {
    margin-bottom: 0;
}

/* The closing italic line reads as the muted sign-off. */
.ks-beta-panel p em {
    color: rgba(228, 231, 246, 0.74);
    font-style: italic;
}

.ks-beta-panel strong {
    color: #fff;
    font-weight: 700;
}

.ks-beta-panel ul {
    margin: 0 0 0.4rem 0;
    padding-left: 1.05rem;
    color: rgba(233, 236, 248, 0.92);
    font-size: 0.77rem;
    line-height: 1.28;
}

.ks-beta-panel li {
    margin-bottom: 0.18rem;
}

/* ── Artwork mosaic (Project Properties → Artwork) ───────────────────────────
   MosaicGrid (src/881): a container-queried CSS grid of SQUARE cells — side =
   (container width − gaps) ÷ columns — onto which ArtworkTiles (src/882) are
   placed at solver-computed coordinates. The custom properties are set by the
   control; the container query keeps cells square at every width, so the pack
   scales proportionally and never develops holes. */

.ks-mosaic-host {
    container-type: inline-size;
}

.ks-mosaic {
    display: grid;
    grid-template-columns: repeat(var(--ks-mosaic-cols, 12), 1fr);
    grid-auto-rows: calc((100cqw - (var(--ks-mosaic-cols, 12) - 1) * var(--ks-mosaic-gap, 8px)) / var(--ks-mosaic-cols, 12));
    gap: var(--ks-mosaic-gap, 8px);
}

/* ── ArtworkTile — image frame, empty face, hover overlay ── */

.ks-art-tile {
    position: relative;
    overflow: hidden;
    border-radius: var(--tblr-border-radius, 4px);
    border: 1px solid var(--tblr-border-color);
    background: var(--tblr-bg-surface-tertiary, var(--tblr-bg-surface-secondary));
}

.ks-art-tile-img {
    position: absolute;
    inset: 0;
    background-size: cover;
    background-position: center;
    background-repeat: no-repeat;
}

.ks-art-tile--empty .ks-art-tile-img {
    display: none;
}

/* The missing-artwork face: a big muted glyph + the slot's name, centred. */
.ks-art-tile-empty-face {
    position: absolute;
    inset: 0;
    display: none;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.3rem;
    padding: 0.5rem;
    text-align: center;
    color: var(--tblr-secondary);
}

.ks-art-tile--empty .ks-art-tile-empty-face {
    display: flex;
}

/* While the overlay is up its title says the same thing — fade the face to a
   watermark so the slot name doesn't read twice. */
.ks-art-tile:hover .ks-art-tile-empty-face,
.ks-art-tile:focus-within .ks-art-tile-empty-face,
.ks-art-tile.cat-file-drop-over .ks-art-tile-empty-face {
    opacity: 0.3;
    transition: opacity 0.12s ease-in-out;
}

/* Tabler's `.icon` class boxes glyphs at 20×20 — at 2.4rem the glyph overflows the
   box and crowds the label below. Re-box at 1em with line-height 1 so the glyph is
   optically centred over the label (same doctrine as the TreeListView icon fix). */
.ks-art-tile-empty-face .ti {
    font-size: 2.4rem;
    width: 1em;
    height: 1em;
    line-height: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    opacity: 0.35;
}

.ks-art-tile-empty-label {
    font-size: 0.8125rem;
    opacity: 0.75;
}

/* The hover overlay: invisible until hover/focus/drag-over; a bottom-weighted
   gradient keeps the artwork legible behind the white info text. pointer-events
   stays off so clicks fall through to the drop zone (browse) — only the action
   buttons re-enable it. */
.ks-art-tile-overlay {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
    gap: 0.15rem;
    padding: 0.625rem;
    opacity: 0;
    transition: opacity 0.12s ease-in-out;
    background: linear-gradient(180deg, rgba(16, 19, 26, 0.35) 0%, rgba(16, 19, 26, 0.05) 40%, rgba(16, 19, 26, 0.82) 100%);
    pointer-events: none;
}

.ks-art-tile:hover .ks-art-tile-overlay,
.ks-art-tile:focus-within .ks-art-tile-overlay,
.ks-art-tile.cat-file-drop-over .ks-art-tile-overlay {
    opacity: 1;
}

.ks-art-tile-ratio {
    position: absolute;
    top: 0.5rem;
    right: 0.5rem;
    padding: 0.125rem 0.4rem;
    border-radius: 999px;
    font-size: 0.6875rem;
    font-weight: 600;
    letter-spacing: 0.03em;
    color: #fff;
    background: rgba(16, 19, 26, 0.65);
    border: 1px solid rgba(255, 255, 255, 0.25);
}

.ks-art-tile-title {
    color: #fff;
    font-weight: 600;
    font-size: 0.875rem;
    line-height: 1.2;
    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.55);
}

.ks-art-tile-ideal {
    color: rgba(255, 255, 255, 0.85);
    font-size: 0.75rem;
    line-height: 1.25;
}

.ks-art-tile-purpose {
    color: rgba(255, 255, 255, 0.62);
    font-size: 0.75rem;
    line-height: 1.25;
}

.ks-art-tile-actions {
    display: flex;
    gap: 0.375rem;
    margin-top: 0.3rem;
    pointer-events: auto;
}

/* Glassy Tabler buttons that read on any artwork. */
.ks-art-tile-actions .btn {
    --tblr-btn-color: #fff;
    --tblr-btn-bg: rgba(255, 255, 255, 0.14);
    --tblr-btn-border-color: rgba(255, 255, 255, 0.28);
    --tblr-btn-hover-color: #fff;
    --tblr-btn-hover-bg: rgba(255, 255, 255, 0.28);
    --tblr-btn-hover-border-color: rgba(255, 255, 255, 0.45);
    --tblr-btn-active-color: #fff;
    --tblr-btn-active-bg: rgba(255, 255, 255, 0.34);
    --tblr-btn-active-border-color: rgba(255, 255, 255, 0.5);
    backdrop-filter: blur(4px);
}

/* Drag-over: light the frame hard and force the overlay on, so the drop target
   is unmistakable mid-drag. */
.ks-art-tile.cat-file-drop-over {
    border-color: var(--tblr-primary);
    outline: 2px solid var(--tblr-primary);
    outline-offset: -2px;
}

/* ── SoundTile — the app's one sound surface (sound-bin-plan SB2) ──
   The three overlays (tick, name, actions) are hover-revealed with OPACITY, never
   `display` — an inline display:none loses to a utility class's !important (the
   documented Catanari trap). The "always on" rules come AFTER the :hover rules and
   carry equal specificity, so source order decides and the pinned state wins. */

.ks-sound-tile-tick,
.ks-sound-tile-name,
.ks-sound-tile-actions {
    opacity: 0;
    transition: opacity 0.12s ease-in-out;
}

.ks-sound-tile:hover .ks-sound-tile-tick,
.ks-sound-tile:hover .ks-sound-tile-name,
.ks-sound-tile:hover .ks-sound-tile-actions,
.ks-sound-tile:focus-within .ks-sound-tile-tick,
.ks-sound-tile:focus-within .ks-sound-tile-name,
.ks-sound-tile:focus-within .ks-sound-tile-actions {
    opacity: 1;
}

/* The ACTIVE tick is always visible — the point of a rack is seeing the chosen
   sound from across the board. A broken tile keeps its name readable for the same
   reason: you must be able to see WHAT you lost without hunting for it. */
.ks-sound-tile .ks-sound-tile-tick.is-active,
.ks-sound-tile.is-broken .ks-sound-tile-name {
    opacity: 1;
}

.ks-sound-tile-name {
    z-index: 5;
    max-width: calc(100% - 22px);
    padding: 0 4px 1px 4px;
    border-radius: 0 4px 0 4px;
    background: rgba(16, 19, 26, 0.55);
    color: #fff;
    pointer-events: auto;
}

[data-bs-theme='light'] .ks-sound-tile-name {
    background: rgba(255, 255, 255, 0.72);
    color: var(--tblr-body-color);
}

.ks-sound-tile-name-input {
    height: 18px;
    min-height: 0;
    padding: 0 3px;
    font-size: 11px;
}

/* The link glyph: this file lives elsewhere in the project (SB4). Always visible. */
.ks-sound-tile-link {
    position: absolute;
    right: 3px;
    bottom: 2px;
    z-index: 5;
    font-size: 12px;
    line-height: 1;
    opacity: 0.6;
    color: var(--tblr-secondary);
    pointer-events: none;
}

/* The broken face: named, red-framed, and it stays until the user removes it (SB7). */
.ks-sound-tile.is-broken {
    outline: 1px solid var(--tblr-red);
    outline-offset: -1px;
}

.ks-sound-tile-broken {
    padding: 4px;
    text-align: center;
}

.ks-sound-tile-broken .ti {
    font-size: 1.25rem;
    width: 1em;
    height: 1em;
    line-height: 1;
    display: flex;
    align-items: center;
    justify-content: center;
}

.ks-sound-tile-broken-label {
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    color: var(--tblr-red);
}

/* ── Sound Bin node — the whole face is the drop target (SB8) ──
   No permanent dropzone chrome: one overlay does both jobs. It is shown solid
   while the bin is EMPTY, and faded up on drag-over via `cat-file-drop-over`
   (the class FileDropZone toggles) the rest of the time. Opacity, never
   `display` — an inline display:none loses to a utility class's !important. */

.ks-soundbin-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
    grid-auto-rows: 56px;
    align-content: start;
    gap: 4px;
}

.ks-soundbin-single {
    display: flex;
}

.ks-soundbin-tile {
    border: 1px solid var(--tblr-border-color);
    background: var(--tblr-bg-surface-tertiary, var(--tblr-bg-surface-secondary));
}

.ks-soundbin-dropface {
    z-index: 6;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.12s ease-in-out;
    color: var(--tblr-secondary);
    border-radius: var(--tblr-border-radius, 4px);
    background: var(--tblr-bg-surface, rgba(16, 19, 26, 0.9));
}

.ks-soundbin-dropface .ti {
    font-size: 1.75rem;
    width: 1em;
    height: 1em;
    line-height: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    opacity: 0.5;
}

/* Empty bin: the face is the whole content, permanently (muted). */
.ks-soundbin-dropface.is-empty {
    opacity: 0.85;
}

/* Mid-drag: light the frame hard and force the face on, whichever of the three
   drag kinds is in flight (OS files, tree rows, asset links). */
.ks-soundbin-zone.cat-file-drop-over .ks-soundbin-dropface {
    opacity: 1;
}

.ks-soundbin-zone.cat-file-drop-over {
    outline: 2px solid var(--tblr-primary);
    outline-offset: -2px;
    border-radius: var(--tblr-border-radius, 4px);
}
