body {-webkit-tap-highlight-color: transparent;}
/* Read-only deck viewing (official precons + other people's community decks).
   Scoped to #manifest-view only, so the global header nav is untouched —
   this is deliberately NOT toggleWorkspaceLock(), which disables every
   button/input in the document and is reserved for the cEDH lock. */
.nexus-readonly [data-requires-edit] {display: none !important;}
.nexus-readonly [data-readonly-disable] {
    pointer-events: none;
    opacity: 0.45;
    cursor: not-allowed;
}
.nexus-readonly #studio-deck-name {cursor: default;}
/* Keyboard focus indicator. Applies to every focusable element and is the
   single source of truth for focus. `focus:outline-none` used to defeat
   this on 49 elements (it compiles to outline:2px solid transparent at
   higher specificity); all of those were removed 2026-08-25. Do not
   reintroduce it — if an element needs a bespoke indicator, override
   this rule rather than blanking it. */
:where(a[href], button, input, select, textarea, [tabindex]):focus-visible {
    outline: 2px solid rgb(var(--nx-accent));
    outline-offset: 2px;
    border-radius: 2px;
}
.magic-glass {
    background: rgb(var(--nx-card)/0.65);
    backdrop-filter: blur(16px);
    -webkit-backdrop-filter: blur(16px);
    border: 1px solid rgb(var(--nx-hair)/0.08);
}

.card-bg {
    background-image: url("data:image/svg+xml,%3Csvg width='60' height='60' viewBox='0 0 60 60' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cg fill='%234c1d95' fill-opacity='0.05'%3E%3Cpath d='M36 34v-4h-2v4h-4v2h4v4h2v-4h4v-2h-4zm0-30V0h-2v4h-4v2h4v4h2V6h4V4h-4zM6 34v-4H4v4H0v2h4v4h2v-4h4v-2H6zM6 4V0H4v4H0v2h4v4h2V6h4V4H6z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E");
}

.fade-transition {
    transition: opacity 0.8s ease-in-out;
}

.modal-glass {
    background: rgb(var(--nx-base)/0.85);
    backdrop-filter: blur(24px);
    -webkit-backdrop-filter: blur(24px);
}

.custom-scrollbar::-webkit-scrollbar {
    height: 8px;
    width: 8px;
}

.custom-scrollbar::-webkit-scrollbar-track {
    background: transparent;
    border-radius: 4px;
}

.custom-scrollbar::-webkit-scrollbar-thumb {
    background: rgb(var(--nx-surface-hover));
    border-radius: 4px;
}

.custom-scrollbar::-webkit-scrollbar-thumb:hover {
    background: rgb(var(--nx-violet));
}

/* Hide default browser arrows on number inputs */
input[type="number"]::-webkit-inner-spin-button,
input[type="number"]::-webkit-outer-spin-button {
    -webkit-appearance: none;
    margin: 0;
}

input[type="number"] {
    -moz-appearance: textfield;
    appearance: textfield;
}

/* PHASE 3: Volatile Memory Animations & Hardware Acceleration */
@keyframes pulseLatestAdd {

    0%,
    100% {
        box-shadow: inset 0 0 0 2px #10B981, inset 0 0 20px rgba(16, 185, 129, 0.4), 0 0 12px rgba(16, 185, 129, 0.35);
        background-color: rgba(2, 44, 34, 0.4);
    }

    50% {
        box-shadow: inset 0 0 0 3px #10B981, inset 0 0 30px rgba(16, 185, 129, 0.8), 0 0 24px rgba(16, 185, 129, 0.65);
        background-color: rgba(2, 44, 34, 0.6);
    }
}

@keyframes pulseLatestCut {

    0%,
    100% {
        box-shadow: inset 0 0 0 2px rgb(var(--nx-error)), inset 0 0 20px rgb(var(--nx-error)/0.4), 0 0 12px rgb(var(--nx-error)/0.35);
        background-color: rgba(76, 5, 25, 0.4);
    }

    50% {
        box-shadow: inset 0 0 0 3px rgb(var(--nx-error)), inset 0 0 30px rgb(var(--nx-error)/0.8), 0 0 24px rgb(var(--nx-error)/0.65);
        background-color: rgba(76, 5, 25, 0.6);
    }
}

@keyframes pulseSlate {

    0%,
    100% {
        box-shadow: inset 0 0 0 2px rgb(var(--nx-muted)), inset 0 0 20px rgb(var(--nx-muted)/0.4);
        background-color: rgb(var(--nx-muted)/0.2);
    }

    50% {
        box-shadow: inset 0 0 0 3px rgb(var(--nx-muted)), inset 0 0 30px rgb(var(--nx-muted)/0.8);
        background-color: rgb(var(--nx-muted)/0.4);
    }
}

@keyframes nexusFoilSweep {
    0% { transform: translate3d(-140%, -140%, 0) rotate(18deg); }
    100% { transform: translate3d(140%, 140%, 0) rotate(18deg); }
}

.nexus-foil-fx {
    position: relative;
    isolation: isolate;
}

.nexus-foil-fx::before {
    content: '';
    position: absolute;
    inset: -45%;
    pointer-events: none;
    z-index: 12;
    opacity: 0.42;
    mix-blend-mode: screen;
    background: linear-gradient(115deg, transparent 20%, rgba(255, 255, 255, 0.18) 34%, rgb(var(--nx-accent)/0.38) 43%, rgb(var(--nx-rare)/0.32) 52%, rgb(var(--nx-violet)/0.36) 61%, rgba(255, 255, 255, 0.16) 70%, transparent 82%);
    transform: translate3d(-140%, -140%, 0) rotate(18deg);
    animation: nexusFoilSweep 3.8s linear infinite;
    will-change: transform;
}

/* Reduced motion, added 2026-09-06. nexus.css honours this query in six places;
   this rule did not, and the sweep kept running -- confirmed rather than
   assumed, with Chrome's --force-prefers-reduced-motion:
   `reduced-motion-matches=true | animation-name=nexusFoilSweep`.
   Today that is one card someone chose to mark foil. It stops being minor the
   moment foil becomes a supporter cosmetic on avatars and deck tiles across
   every view at once (docs/proposals/PATREON_TIERS.md).

   The sweep is STOPPED, not removed. `animation:none` alone would leave the
   gradient parked at its -140% start position, i.e. off the element entirely,
   which silently deletes a cosmetic somebody is paying for. Instead it is
   parked mid-element as a static diagonal sheen: the card still reads as foil,
   it just holds still. */
@media (prefers-reduced-motion: reduce) {
    .nexus-foil-fx::before {
        animation: none;
        transform: translate3d(0, 0, 0) rotate(18deg);
        opacity: 0.3;
    }
}

.latest-highlight-add::after {
    content: '';
    position: absolute;
    inset: 0;
    pointer-events: none;
    border-radius: inherit;
    z-index: 20;
    animation: pulseLatestAdd 1.5s ease-in-out infinite;
}

.latest-highlight-cut::after {
    content: '';
    position: absolute;
    inset: 0;
    pointer-events: none;
    border-radius: inherit;
    z-index: 20;
    animation: pulseLatestCut 1.5s ease-in-out infinite;
}

.persist-highlight-add::after {
    content: '';
    position: absolute;
    inset: 0;
    pointer-events: none;
    border-radius: inherit;
    z-index: 20;
    box-shadow: inset 0 0 0 2px #10B981;
}

.persist-highlight-cut::after {
    content: '';
    position: absolute;
    inset: 0;
    pointer-events: none;
    border-radius: inherit;
    z-index: 20;
    box-shadow: inset 0 0 0 2px rgb(var(--nx-error));
}

/* Tag changes get a DASHED AMBER outline, deliberately unlike the solid
   green/red of add/remove. A tag edit is invisible until you open the
   card, so the card must say "something in here changed" without
   claiming the card itself was added or cut. The +T / -T corner chip
   carries the additive-vs-reductive colour. Amber is brightened from
   the nexus-warning token (217 119 6) the same way the add/cut colours
   are, so it reads on the dark surface. */
.tag-change-latest::after {
    content: '';
    position: absolute;
    inset: 0;
    pointer-events: none;
    border-radius: inherit;
    z-index: 20;
    border: 2px dashed #F59E0B;
    animation: pulseTagChange 1.5s ease-in-out infinite;
}

.tag-change-persist::after {
    content: '';
    position: absolute;
    inset: 0;
    pointer-events: none;
    border-radius: inherit;
    z-index: 20;
    border: 2px dashed #F59E0B;
    opacity: 0.7;
}

/* ---- Sticky rails -------------------------------------------------
   #manifest-view carried overflow-y:auto, which made it a scroll
   container that NEVER SCROLLED -- the document is the real scroller
   (measured: manifest scrollHeight === clientHeight === 14886, while
   documentElement scrolls 15077 in a 654 viewport). position:sticky
   resolves against the nearest scrollport, so every sticky inside it
   was pinned to a scrollport that never moves: silently inert. The card
   preview already carried `sticky top-[260px]` and had simply never
   worked. Measured before: viewport top -1320px at scrollY 2000.
   After: pins at exactly 260px.

   Tailwind here is PRECOMPILED inline with no build step, so a new
   arbitrary utility (e.g. top-[calc(...)]) would produce no CSS at all.
   These offsets are therefore plain CSS overriding the existing
   compiled utilities, scoped to #manifest-view so nothing else shifts. */
#manifest-view {
    overflow-y: visible;
}

/* Read-only / quarantine banner rides directly under the 70px sticky
   header, below it in z so the header always wins. */
#nexus-readonly-banner {
    position: sticky;
    top: 70px;
    z-index: 45;
    /* The /10 tint was legible as an inline element, but a sticky banner
       has the whole card grid scrolling underneath it -- card art bled
       straight through and the text became unreadable. It needs its own
       opaque ground. nexus-base is rgb(8 11 18); the blur keeps the
       glassy treatment used elsewhere in the shell. */
    background-color: rgb(var(--nx-base) / 0.94);
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
}

/* --nexus-banner-h is set by applyReadOnlyPresentation and is 0px when
   no banner exists, so an editable deck keeps the original offsets
   exactly. Without this the 68px toolbar would slide underneath the
   banner instead of stacking below it. */
#manifest-view .sticky.top-\[68px\] {
    top: calc(68px + var(--nexus-banner-h, 0px));
}

/* 260px is the toolbar's 68px offset plus its 164px height plus
   clearance; the side rails hang below it. */
#manifest-view .sticky.top-\[260px\] {
    top: calc(260px + var(--nexus-banner-h, 0px));
}

/* One clean stop line for all three rails.
   The side rails are bounded by their flex column, which ends at the
   row's CONTENT box (3231). The search toolbar is bounded by its
   parent's PADDING box (3263). That 32px discrepancy is exactly the
   row's padding-bottom, so the search bar out-travelled the rails by a
   hair and the three stopped on two different lines.
   A sticky element's constraint rect is inset by its own margins, so a
   32px bottom margin pulls the toolbar's stop up to 3231 to match. The
   row's padding-top is zeroed to hand that same 32px back to normal
   flow, so the gap under the toolbar is unchanged and nothing moves:
   verified column top 680 and row bottom 3263 identical before/after.

   Both rules are scoped to :not(.nexus-readonly) because the toolbar is
   display:none on a read-only deck. A hidden element contributes no
   margin, so the padding-top swap would have had nothing to compensate
   it and the grid would have jumped up an extra 32px there -- measured
   as a 196px shift where only the toolbar's own 164px is expected. */
#manifest-view:not(.nexus-readonly) .sticky.top-\[68px\].z-40 {
    margin-bottom: 32px;
}

#manifest-view:not(.nexus-readonly) .sticky.top-\[68px\].z-40 + div {
    padding-top: 0;
}

@keyframes pulseTagChange {
    0%, 100% {
        opacity: 0.45;
        box-shadow: 0 0 10px rgba(245, 158, 11, 0.25);
    }
    50% {
        opacity: 1;
        box-shadow: 0 0 16px rgba(245, 158, 11, 0.5);
    }
}

.undo-predictive-pulse::after {
    content: '';
    position: absolute;
    inset: 0;
    pointer-events: none;
    border-radius: inherit;
    z-index: 20;
    animation: pulseSlate 1.5s ease-in-out infinite;
}
