:root {
    color-scheme: light;
    --ao-accent: #4f46e5;
    --ao-accent-hover: #4338ca;
    --ao-accent-active: #3730a3;
    --ao-surface: #ffffff;
    --ao-border: #e6e8f0;
}

/* Sticky footer: pinned to the bottom of the viewport on a short page, pushed down to
   after the content (not floating over it) on a page tall enough to scroll. #ao-main
   grows to fill any leftover space; the footer stays its own natural size after it. */
html, body {
    height: 100%;
}

body {
    background-color: #f8f9fb;
    display: flex;
    flex-direction: column;
    min-height: 100vh;
}

#ao-main {
    flex: 1 0 auto;
}

.ao-footer {
    flex-shrink: 0;
    max-width: 960px;
    margin: 0 auto;
    width: 100%;
    padding: 1rem;
    display: flex;
    justify-content: flex-end;
}

.ao-footer-logo {
    height: 28px;
    width: auto;
    opacity: 0.8;
}

/* Recolor Bootstrap's primary buttons/links to the indigo accent without a custom build. */
.btn-primary {
    --bs-btn-bg: var(--ao-accent);
    --bs-btn-border-color: var(--ao-accent);
    --bs-btn-hover-bg: var(--ao-accent-hover);
    --bs-btn-hover-border-color: var(--ao-accent-hover);
    --bs-btn-active-bg: var(--ao-accent-active);
    --bs-btn-active-border-color: var(--ao-accent-active);
    --bs-btn-disabled-bg: var(--ao-accent);
    --bs-btn-disabled-border-color: var(--ao-accent);
}

a {
    color: var(--ao-accent);
}

a:hover {
    color: var(--ao-accent-hover);
}

/* Header */
.ao-header {
    background-color: var(--ao-surface);
    border-bottom: 1px solid var(--ao-border);
}

.ao-header-inner {
    max-width: 960px;
    margin: 0 auto;
    padding: 0.6rem 1rem;
    display: flex;
    align-items: center;
    gap: 1rem;
}

.ao-brand {
    display: inline-flex;
    align-items: center;
    gap: 0.6rem;
    text-decoration: none;
    color: inherit;
}

.ao-logo {
    height: 40px;
    width: 40px;
}

.ao-brand-name {
    font-weight: 700;
    font-size: 1.1rem;
    letter-spacing: 0.02em;
    color: #1f2330;
}

.ao-header-controls {
    margin-left: auto;
    display: flex;
    align-items: center;
    gap: 0.75rem;
}

.ao-back-link {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    font-size: 0.9rem;
    text-decoration: none;
    color: #5a6072;
    white-space: nowrap;
}

.ao-back-link:hover {
    color: var(--ao-accent);
}

#ao-auth-control {
    display: inline-flex;
    align-items: center;
}

@media (max-width: 575.98px) {
    .ao-brand-name { display: none; }
    .ao-back-link span { display: none; }
}

/* Top nav — same row on every page (Collection/Boosters/History/Import), so moving
   between them never requires going back to a landing page first. */
.ao-nav {
    background-color: var(--ao-surface);
    border-bottom: 1px solid var(--ao-border);
}

.ao-nav-inner {
    max-width: 960px;
    margin: 0 auto;
    padding: 0 1rem;
    display: flex;
    gap: 0.25rem;
    overflow-x: auto;
}

.ao-nav-link {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.7rem 0.9rem;
    text-decoration: none;
    color: #5a6072;
    font-weight: 600;
    font-size: 0.9rem;
    white-space: nowrap;
    border-bottom: 2px solid transparent;
}

.ao-nav-link:hover {
    color: var(--ao-accent);
}

.ao-nav-link.ao-nav-link--active {
    color: var(--ao-accent);
    border-bottom-color: var(--ao-accent);
}

/* Unopened-booster count next to the Boosters nav link — hidden at 0 (see app.js). */
.ao-nav-badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 1.1rem;
    height: 1.1rem;
    padding: 0 0.3rem;
    border-radius: 999px;
    background: var(--ao-accent);
    color: #fff;
    font-size: 0.7rem;
    font-weight: 700;
    line-height: 1;
}

/* Content */
#ao-main {
    max-width: 960px;
}

/* Collection page: filter panel + its rows (faction/set/rarity/type) and card grid.
   Sizing/radii/scrim values below are ported from altered.re's own card-search filters
   (plugins/core-altered-cards assets.css .filter-toggle / .set-qf-btn), not guessed. */
.ao-filters {
    padding: 0.75rem;
    border: 1px solid var(--ao-border);
    border-radius: 0.5rem;
    background: var(--ao-surface);
}

.ao-icon-filter-row {
    display: flex;
    flex-wrap: wrap;
    gap: 0.35rem;
}

/* Faction + rarity share one row (altered.re groups them the same way), separated by a
   thin rule rather than the row's own gap so the two groups still read as distinct. */
.ao-filter-row-combo {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.5rem;
}

.ao-filter-sep {
    align-self: stretch;
    width: 1px;
    background: var(--ao-border);
}

.ao-icon-filter-btn {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 4px 10px;
    border: 1px solid var(--ao-border);
    border-radius: 6px;
    background: transparent;
    color: #5a6072;
    font-size: 0.82rem;
    cursor: pointer;
}

.ao-icon-filter-btn img {
    width: 18px;
    height: 18px;
    object-fit: contain;
}

.ao-icon-filter-btn.active {
    background: var(--ao-accent);
    border-color: var(--ao-accent);
    color: #fff;
}

/* Rarity: same pill, smaller icon/padding, bolder text — altered.re's compact variant. */
.ao-icon-filter-btn--compact {
    padding: 4px 8px;
    font-weight: 700;
}

.ao-icon-filter-btn--compact img {
    width: 15px;
    height: 15px;
}

/* Set/edition filters: a tile with the key-art as the button's own background and the name
   captioned over a full-tile dark scrim that thins out on hover/active — ported from
   altered.re's own set-quick-filter buttons, including the actual art (same 82×60 size,
   radius, scrim opacities, and the same small_bg/<SET>.webp crops, copied from
   alteredcore-website's plugins/core-altered-cards/assets/set/small_bg/ into
   img/sets/ — these are pre-cropped landscape key-art, unlike the placeholder icons that
   were there before, so cover (fill + crop to the box) is the right fit now, not contain. */
.ao-image-filter-row {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
}

.ao-image-filter-btn {
    position: relative;
    display: flex;
    overflow: hidden;
    width: 82px;
    height: 60px;
    flex: 0 0 82px;
    padding: 0;
    border: 2px solid transparent;
    border-radius: 8px;
    cursor: pointer;
    background-color: #cfd3e0;
    background-repeat: no-repeat;
    background-position: center;
    background-size: cover;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    transition: border-color 0.15s;
}

/* No scrim at all while the set filter isn't in use (nothing selected — every set already
   passes, see applyFilters) — a dark overlay on every tile in that state read as "all
   excluded", the opposite of what's true. Once at least one set is selected (the row gets
   .ao-image-filter-row--filtering, toggled in imageToggleRow), unselected tiles dim to show
   they're excluded and the selected one(s) stay bright. */
.ao-image-filter-btn::before {
    content: '';
    position: absolute;
    inset: 0;
    opacity: 0;
    background: rgba(0, 0, 0, 0.35);
    transition: opacity 0.2s, background 0.2s;
    pointer-events: none;
}

.ao-image-filter-btn:hover::before {
    opacity: 1;
}

.ao-image-filter-row--filtering .ao-image-filter-btn::before {
    opacity: 1;
    background: rgba(0, 0, 0, 0.62);
}

.ao-image-filter-btn.active {
    border-color: var(--ao-accent);
}

.ao-image-filter-row--filtering .ao-image-filter-btn.active::before {
    background: rgba(0, 0, 0, 0.08);
}

.ao-image-filter-btn span {
    position: relative;
    z-index: 1;
    padding: 0 4px;
    font-size: 0.62rem;
    font-weight: 700;
    line-height: 1.2;
    text-align: center;
    color: #fff;
    text-shadow: 0 1px 4px rgba(0, 0, 0, 0.9);
}

/* Per-set glyph (see css/alteredicons.css), stacked above the label — matches
   altered.re's own .set-qf-inner layout. */
.ao-image-filter-btn i {
    position: relative;
    z-index: 1;
    font-size: 1.1rem;
    line-height: 1;
    color: #fff;
    text-shadow: 0 1px 4px rgba(0, 0, 0, 0.9);
    margin-bottom: 2px;
}

/* Search input, boxed off so it reads as its own control rather than blending into the
   filter chips below it. */
.ao-search-box {
    padding: 0.6rem 0.75rem;
    border: 1px solid var(--ao-border);
    border-radius: 0.6rem;
    background: var(--ao-surface);
}

.ao-search-box .form-control {
    border: none;
    padding: 0.25rem 0.1rem;
}

.ao-search-box .form-control:focus {
    box-shadow: none;
}

/* No border/background/padding of its own — the card's own rendered art (rounded
   corners baked in, see .ao-collection-cover-img below) already reads as a tile; wrapping
   it in another bordered white box just doubled the framing. */
.ao-collection-tile {
    position: relative;
    display: block;
    border: none;
    background: transparent;
    padding: 0;
    cursor: pointer;
}

.ao-collection-cover-img {
    max-width: 100%;
    height: 210px;
    object-fit: contain;
}

/* Quantity badge, overlaid on the card art itself instead of a separate "×N" line below
   it (which sat in the tile's own white background/border chrome, now removed). */
.ao-collection-qty-badge {
    position: absolute;
    z-index: 1;
    left: 0.4rem;
    bottom: 0.4rem;
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    padding: 0.15rem 0.5rem;
    border-radius: 999px;
    background: rgba(15, 17, 26, 0.75);
    color: #fff;
    font-size: 0.75rem;
    font-weight: 700;
}

.ao-collection-qty-badge img {
    width: 14px;
    height: 14px;
    object-fit: contain;
}

/* Booster tiles */
.ao-booster-tile {
    cursor: pointer;
    border: 1px solid var(--ao-border);
    border-radius: 0.5rem;
    background: var(--ao-surface);
    padding: 1rem;
    text-align: center;
}

.ao-booster-tile:disabled {
    cursor: default;
    opacity: 0.6;
}

.ao-booster-cover-img {
    max-width: 100%;
    height: 160px;
    object-fit: contain;
}

/* Booster-opening overlay: a static booster cover slides down on click to reveal the
   card sitting underneath it (ported from the click-triggered CSS transition in
   altered-draft's PackReveal component). Sized close to the viewport rather than a
   small fixed box, with the card's own aspect ratio (~63:88, the same proportions as
   the rendered card art) — matches how immersive the reveal feels in altered-draft. */
.ao-opener-backdrop {
    position: fixed;
    inset: 0;
    background: rgba(15, 17, 26, 0.75);
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 1rem;
    padding: 1rem;
    z-index: 1080;
}

.ao-opener-stage {
    position: relative;
    width: min(94vw, calc(82vh * 63 / 88));
    aspect-ratio: 63 / 88;
    perspective: 900px;
}

.ao-opener-card {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
}

.ao-opener-card altered-card,
.ao-tilt-card altered-card {
    display: block;
    width: 100%;
}

/* Booster reveal: held at ~the sealed pack's own apparent width (measured: contained to
   its aspect ratio, the pack renders at ~0.8 of the stage's width) while the pack is still
   sliding away, then grown to full size once it's clear — see boosters.js's revealBooster,
   which sequences .ao-card-pending → .ao-card-revealing off the pack's own transitionend
   rather than starting both at once. Applied to the <altered-card> element itself, not
   cardEl (which carries the tilt's own rotateY/rotateX) — a second transform on the same
   element would override that instead of composing with it. */
.ao-card-pending {
    transform: scale(0.8);
}

@keyframes ao-card-reveal {
    from { transform: scale(0.8); }
    to { transform: scale(1); }
}

.ao-card-revealing {
    animation: ao-card-reveal 500ms ease-out both;
}

/* The renderer draws a plain rectangular canvas/image with square corners — round it
   to match a real printed card. Booster pack art (.ao-booster-cover-img on the grid
   tiles, .ao-opener-cover-art in the opener) is deliberately excluded everywhere —
   its own art already has its shape baked in, at a size/silhouette that overflows or
   doesn't fill this rounding box the way a plain card render does. */
.ao-tilt-card altered-card,
.ao-tilt-card img,
.ao-collection-cover-img {
    border-radius: 1rem;
    overflow: hidden;
}

.ao-opener-cover {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    transform: translateY(0);
    transition: transform 500ms ease-in;
    /* Without this, rotatorEl's own rotateY/rotateX (a grandchild of .ao-opener-stage,
       which sets perspective) gets flattened into this element's 2D plane before it ever
       reaches that perspective — the tilt still "moved" the numbers but didn't visually
       read as a 3D tilt at all. Matches .ao-tilt-card, which doesn't need this only
       because it IS a direct child of .ao-opener-stage. */
    transform-style: preserve-3d;
}

/* Viewport-relative (not a % of the cover's own box): the pack art bleeds past the cover's
   box on every side (see .ao-opener-cover img above, 115% width/free height), so a transform
   sized off the cover's own height was no longer enough to carry the taller/wider art fully
   off screen — part of it stayed visible at the bottom after "opening". 160vh clears the
   viewport regardless of how much the art overflows its box. */
.ao-opener-cover.ao-opening {
    transform: translateY(160vh);
}

/* Pack art contained within the card's own box (never bigger than it), preserving its
   native aspect ratio. altered-draft renders its pack art at 115% of the card's width
   with free height, bleeding past the card's edges — that only works because their asset
   is pre-cropped close to the card's own proportions. Our actual pack art is much taller
   per unit of width (measured: 480×836px, ratio 1.74, vs. the card's 63:88 ≈ 1.40): at
   115% width its free height ballooned past the viewport on both ends, pushing the
   Ouvrir button off screen entirely. Containing it is the robust fix regardless of any
   given booster's actual art proportions. */
.ao-opener-cover img {
    max-width: 100%;
    max-height: 100%;
}

.ao-opener-cover .ao-opener-cover-fallback {
    max-width: 100%;
    max-height: 100%;
}

/* Sealed cover's inner wrapper — centers the pack art without the rounded-corner clipping
   .ao-tilt-card applies for actual card renders (see the note above .ao-tilt-card img):
   the pack's own art already has its shape baked in, not a plain rectangle to round. */
.ao-opener-cover-art {
    width: 100%;
    height: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
    transform: rotateY(var(--ao-tilt-y, 0deg)) rotateX(var(--ao-tilt-x, 0deg));
    transition: transform 400ms ease-out;
    transform-style: preserve-3d;
}

/* Shown over the stage from the moment "Ouvrir"/the cover is tapped until the card has
   actually rendered (see boosters.js's waitForCardReady) — the draw + render can take a
   couple of seconds, and with nothing on screen during that wait it reads as broken rather
   than loading. */
.ao-opener-loading {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 2;
    pointer-events: none;
}

.ao-opener-spinner {
    width: 2.5rem;
    height: 2.5rem;
    border-radius: 50%;
    border: 3px solid rgba(255, 255, 255, 0.3);
    border-top-color: #fff;
    animation: ao-spin 800ms linear infinite;
}

@keyframes ao-spin {
    to { transform: rotate(360deg); }
}

/* Prev/next arrows, flanking the stage — browse other booster types (sealed,
   no server call) without leaving the overlay. */
.ao-opener-nav {
    position: absolute;
    top: 50%;
    translate: 0 -50%;
    width: 2.75rem;
    height: 2.75rem;
    border-radius: 0.5rem;
    border: none;
    /* Dark + shadowed (not a faint white tint) so the arrow stays readable over any card
       art behind it once the reveal has happened, not just over the dim backdrop. */
    background: rgba(10, 11, 18, 0.55);
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.35);
    color: #fff;
    font-size: 1.5rem;
    line-height: 1;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 1;
}
.ao-opener-nav:hover {
    background: rgba(10, 11, 18, 0.8);
}
.ao-opener-nav:disabled {
    opacity: 0;
    pointer-events: none;
}
.ao-opener-nav--prev {
    left: -3.75rem;
}
.ao-opener-nav--next {
    right: -3.75rem;
}
/* Not enough room off to the sides on narrow viewports (the stage itself can be
   up to 94vw wide) — overlap the arrows onto the card edge instead of letting
   them run off-screen. */
@media (max-width: 640px) {
    .ao-opener-nav--prev {
        left: 0.5rem;
    }
    .ao-opener-nav--next {
        right: 0.5rem;
    }
}

/* Name/quantity + Open button, shown under the sealed cover (hidden once the
   opening slide starts) — ported from altered-draft's CardZoom pack-cover label
   and Open button. */
.ao-opener-info {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.75rem;
    text-align: center;
    color: #fff;
}

.ao-opener-open-btn {
    padding: 0.6rem 1.5rem;
    border-radius: 0.5rem;
    border: none;
    background: var(--ao-accent);
    color: #fff;
    font-weight: 700;
    cursor: pointer;
}
.ao-opener-open-btn:hover {
    opacity: 0.9;
}

/* History: generic card-back / booster icon next to each event row's ±X count
   (see renderDelta in history.js). */
.ao-delta-icon {
    width: 20px;
    height: auto;
    border-radius: 2px;
}

/* History: clickable card thumbnail (detail modal) that opens the zoom overlay. */
.ao-card-thumb-btn {
    border: none;
    background: none;
    padding: 0;
    cursor: pointer;
    line-height: 0;
}

/* Shared pointer-tilt rotator: the booster cover's inner content (kept separate
   from the cover's own translateY-on-open transform above) and the history
   "zoom" card both rotate through this same class/timing, so both feel
   identical — matches altered-draft's single .holo-card__rotator transition,
   including its rotateY-then-rotateX order and 400ms ease-out. */
.ao-tilt-card {
    width: 100%;
    height: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
    transform: rotateY(var(--ao-tilt-y, 0deg)) rotateX(var(--ao-tilt-x, 0deg));
    transition: transform 400ms ease-out;
    transform-style: preserve-3d;
}

/* Holo shine/glare (revealed unique cards + their history zoom) — gold sheen sliding
   under the pointer/tilt, ported from altered-draft's .holo-card--u. card-tilt.js writes
   --ao-tilt-px/-py (pointer position) and --ao-tilt-opacity (0 at rest, 1 while
   interacting); everything else — gradients, blend modes — lives here. */
.ao-tilt-holo {
    position: relative;
    border-radius: 1rem;
    overflow: hidden;
    box-shadow: 0 0 3px -1px #fff,
        0 0 3px 1px hsl(47 100% 78% / var(--ao-tilt-opacity, 0)),
        0 0 12px 2px hsl(175 100% 90% / var(--ao-tilt-opacity, 0));
}

.ao-tilt-shine,
.ao-tilt-glare {
    position: absolute;
    inset: 0;
    opacity: 0;
    border-radius: inherit;
    pointer-events: none;
    transition: opacity 400ms ease-out;
}

.ao-tilt-holo .ao-tilt-glare {
    opacity: var(--ao-tilt-opacity, 0);
    background-image: radial-gradient(
        farthest-corner circle at var(--ao-tilt-px, 50%) var(--ao-tilt-py, 50%),
        hsla(0, 0%, 100%, 0.8) 10%,
        hsla(0, 0%, 100%, 0.4) 30%,
        hsla(0, 0%, 0%, 0.3) 100%
    );
    mix-blend-mode: overlay;
}

.ao-tilt-holo .ao-tilt-shine {
    background-image: repeating-linear-gradient(
        65deg,
        hsl(42 90% 58%) 0%,
        hsl(42 90% 58%) 16%,
        hsl(280 50% 55%) 20%,
        hsl(42 90% 58%) 24%
    );
    background-size: 300% 300%;
    background-position: var(--ao-tilt-px, 50%) var(--ao-tilt-py, 50%);
    opacity: calc(var(--ao-tilt-opacity, 0) * 0.45);
    filter: brightness(1.05) contrast(1.05) saturate(0.8);
    mix-blend-mode: soft-light;
}

.ao-opener-cover-fallback {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 220px;
    height: 320px;
    border-radius: 0.75rem;
    background: linear-gradient(160deg, var(--ao-accent), var(--ao-accent-active));
    color: #fff;
}
