/* Shared comic tile — the grid card used by /omnis and /sales.
 *
 * Moved verbatim out of the page-scoped omnis.css so a second page could use
 * the tile without loading 1,995 lines of /omnis chrome. Class names are
 * unchanged on purpose: renaming to a cc-tile* primitive would touch
 * omnis.html, omnis_component.mjs (omniCardClass), the DOM-windowing and
 * stagger selectors, and the omnis tests -- on the site's highest-traffic
 * page, in service of naming alone.
 *
 * omnis.css still references the --omni-* custom properties declared here, so
 * any page linking omnis.css MUST link this sheet too. tests/test_sales_page.py
 * pins that both templates do.
 *
 * Staying in omnis.css (omnis-only): .omni-card.is-owned, the interest heart,
 * the skeletons, .omni-grid.cc-stagger, .omni-grid.is-refreshing, the
 * content-visibility windowing rules, and the source badges.
 */

:root {
    --omni-surface: var(--surface);
    --omni-border: var(--border);
    --omni-text: var(--text);
    --omni-text-muted: var(--text-muted);
    --omni-radius: 10px;
    --omni-radius-sm: 6px;
}

.omni-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
    gap: 1.25rem;
}

@media (max-width: 575.98px) {
    .omni-grid {
        grid-template-columns: repeat(2, 1fr);
        gap: 0.75rem;
    }
}

.omni-card-shell {
    position: relative;
    display: flex;
    min-width: 0;
}

.omni-card-shell > .omni-card {
    flex: 1 1 auto;
    width: 100%;
}

.omni-card {
    background: var(--omni-surface);
    border: 1px solid var(--omni-border);
    border-radius: var(--omni-radius);
    overflow: hidden;
    cursor: pointer;
    display: flex;
    flex-direction: column;
    color: inherit;
    text-decoration: none;
    /* Phase 2: the card is a query container so its body typography/padding adapt
       to the CARD's own width (dense grid → compact), not the viewport. See the
       @container block under "Card Body". */
    container-type: inline-size;
    container-name: omnicard;
    transition: transform 0.3s ease, box-shadow 0.3s ease, border-color 0.3s ease;
}

/* Card hover affordances are keyed to `.omni-card-shell:hover`, NEVER to
   `.omni-card:hover`. The card is the element being transformed, and
   hit-testing uses its TRANSFORMED box: keyed to itself, the lift pulls the
   card out from under a pointer resting near its bottom edge, hover drops, the
   transform reverts, the card lands back under the pointer, hover fires again —
   a frame-rate oscillation that reads as flicker (measured 40 hover
   transitions in 2s with the cursor held still 4px above the bottom edge).
   The shell is `position: relative`, never transformed, and the card is a
   `width: 100%` flex child, so the shell's border box IS the card's
   untransformed box — a hover proxy the lift cannot move. */
.omni-card-shell:hover > .omni-card {
    transform: translateY(-8px) scale(1.02);
}

/* The glow takes the publisher's own color (`--pub-glow`, gradients.css) when
   the tile carries `.pub-glow`, so hovering a Marvel book glows Marvel red and
   matches the tint already on the card. The mode accent stays as the fallback,
   which is what a tile with no publisher renders -- unchanged from before.
   Kept per-mode rather than collapsed into one base rule for exactly that
   fallback, and because a base rule would also glow `mode-mine`, which has
   none by design (see the note above `.mode-sale` below). */
.omni-card-shell:hover > .omni-card.mode-upcoming {
    box-shadow: 0 12px 40px color-mix(in srgb, var(--pub-glow, var(--accent-upcoming)) 20%, transparent);
}

.omni-card-shell:hover > .omni-card.mode-recent {
    box-shadow: 0 12px 40px color-mix(in srgb, var(--pub-glow, var(--accent-recent)) 20%, transparent);
}

.omni-card-cover {
    position: relative;
    width: 100%;
    aspect-ratio: 2 / 3;
    background: var(--surface-2);
    overflow: hidden;
    /* Mobile cover swiping (omnis_cover_swipe.mjs). Surrendering horizontal pans
       here is what lets that module keep every touch listener passive -- it
       never calls preventDefault(). `pinch-zoom` is named explicitly because
       `pan-y` alone would also disable zooming into the artwork. */
    touch-action: pan-y pinch-zoom;
}

.omni-card-cover img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    transition: transform 0.4s ease;
}

.omni-card-shell:hover .omni-card-cover img {
    transform: scale(1.06);
}

/* The base image carries `transition: transform 0.4s` for the hover zoom above.
   During a drag that leaves it lagging most of half a second behind the finger,
   so the gesture is dropped for the duration. omnis_cover_swipe.mjs adds and
   removes `is-swiping`. */
.omni-card-cover.is-swiping img {
    transition: none;
}

/* The cover being dragged INTO view. omnis_cover_swipe.mjs creates it on gesture
   lock and removes it after the commit -- it never exists at render time, so a
   card nobody swipes still requests exactly one image. Below the variants layer
   (z-index 2) and the covers badge (3): the artwork slides UNDER the controls.
   `.omni-card-cover`'s `overflow: hidden` does the clipping, and the sizing
   comes from the `.omni-card-cover img` rule above, which this also matches. */
.omni-card-cover-drag-img {
    position: absolute;
    inset: 0;
    z-index: 1;
}

.omni-card-date-badge {
    position: absolute;
    top: 8px;
    left: 8px;
    background: color-mix(in srgb, var(--shadow-ink) 70%, transparent);
    backdrop-filter: blur(8px);
    color: var(--on-image);
    padding: 0.25rem 0.6rem;
    border-radius: var(--omni-radius-sm);
    font-size: 0.7rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    border: 1px solid color-mix(in srgb, var(--on-image) 10%, transparent);
}

.omni-card-date-badge.soon {
    background: color-mix(in srgb, var(--accent-upcoming) 85%, transparent);
    border-color: color-mix(in srgb, var(--accent-upcoming) 50%, transparent);
    color: var(--shadow-ink);
}

/* Static glow — deliberately NOT animated. The badge's base rule applies
   `backdrop-filter`, so the element re-samples and re-blurs its backdrop on
   every invalidation; an infinite pulse here held Firefox's GPU process at
   ~100% CPU on release days (one pulsing sampler per .today card) and starved
   scroll rasterization into full-viewport black flashes. Chrome absorbs the
   same CSS, which is how it shipped. Pinned by
   test_omnis_no_animation_on_backdrop_filter.py; a future pulse belongs on an
   overlay element that has no backdrop-filter. */
/* Opaque and darkened: at 90% over a cover the badge resolved to #d7786f, which
   is 3.08:1 against the white label. Mixing toward black instead of transparent
   clears 4.5:1 and stops the ratio depending on the cover behind it.
   --accent-interest is shared with the interest heart, so the darkening is
   scoped to this badge rather than applied to the token. 70% measures ~4.8:1;
   80% only reached 3.97:1, so keep margin above the threshold. */
.omni-card-date-badge.today {
    background: color-mix(in srgb, var(--accent-interest) 70%, #000);
    border-color: color-mix(in srgb, var(--accent-interest) 60%, transparent);
    color: var(--on-image);
    box-shadow: 0 0 14px color-mix(in srgb, var(--accent-interest) 55%, transparent);
}

.omni-card-covers-badge {
    position: absolute;
    bottom: 8px;
    left: 8px;
    background: color-mix(in srgb, var(--shadow-ink) 70%, transparent);
    backdrop-filter: blur(8px);
    color: var(--on-image);
    padding: 0.2rem 0.5rem;
    border-radius: var(--omni-radius-sm);
    font-size: 0.65rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    border: 1px solid color-mix(in srgb, var(--on-image) 15%, transparent);
}

/* Cover flip. The whole layer lives inside `.omni-card-cover` (inside the card
   anchor) so it rides the hover lift; a shell-level control would stay put
   while the card moves and visibly detach from the artwork.
   `pointer-events: none` on the layer keeps the card's own click target intact;
   the controls opt back in. */
.omni-card-covers-badge.is-control {
    cursor: pointer;
    z-index: 3;
}

.omni-card-variants {
    position: absolute;
    inset: 0;
    pointer-events: none;
    z-index: 2;
}

.omni-card-variant-arrow {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    width: 28px;
    height: 28px;
    display: grid;
    place-items: center;
    pointer-events: auto;
    cursor: pointer;
    opacity: 0;
    transition: opacity 0.18s ease, background 0.18s ease;
    background: color-mix(in srgb, var(--omni-overlay-ink) 72%, transparent);
    border: 1px solid color-mix(in srgb, var(--on-image) 18%, transparent);
    border-radius: 50%;
    color: var(--on-image);
    font-size: 1rem;
    line-height: 1;
    backdrop-filter: blur(4px);
    -webkit-backdrop-filter: blur(4px);
}

.omni-card-variant-arrow.prev { left: 6px; }
.omni-card-variant-arrow.next { right: 6px; }

/* Keyed to the SHELL, never `.omni-card:hover` -- the card is the element being
   transformed, and hit-testing its transformed box oscillates the hover state
   (see the note above `.omni-card-shell:hover > .omni-card`). */
.omni-card-shell:hover .omni-card-variant-arrow,
.omni-card-variant-arrow:focus-visible {
    opacity: 1;
}

.omni-card-variant-arrow:hover {
    background: color-mix(in srgb, var(--omni-overlay-ink) 90%, transparent);
}

/* Its own band ABOVE the covers badge, never beside it. The badge sits at
   `bottom: 8px; left: 8px` and is ~24px tall, so a centered dots row sharing
   that band collides as soon as the dots are wide enough to reach it: measured
   at 390px the badge covered the first dot, and at 320px four of five. It also
   collides at desktop width from 6 covers up. Centering is what makes the
   overlap count- and width-dependent, so the band is separated instead of the
   centering being given up -- 8px badge + 24px badge height + 6px gap. */
.omni-card-variant-dots {
    position: absolute;
    bottom: 38px;
    left: 0;
    right: 0;
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    pointer-events: none;
}

.omni-card-variant-dot {
    position: relative;
    flex: 0 0 44px;
    width: 44px;
    height: 44px;
    pointer-events: auto;
    cursor: pointer;
}

/* The dot remains 7px; its surrounding transparent area is the tap target.
   Wrapping keeps up to eight non-overlapping targets inside narrow covers. */
.omni-card-variant-dot::before {
    content: "";
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    box-sizing: border-box;
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: color-mix(in srgb, var(--on-image) 40%, transparent);
    border: 1px solid color-mix(in srgb, var(--shadow-ink) 45%, transparent);
    transition: background 0.18s ease;
}

.omni-card-variant-dot.is-active::before {
    background: var(--on-image);
}

.omni-card-variant-arrow:focus-visible,
.omni-card-variant-dot:focus-visible,
.omni-card-covers-badge.is-control:focus-visible {
    outline: 2px solid var(--on-image);
    outline-offset: 2px;
}

/* No hover means the arrows can never be revealed -- don't leave them in the
   hit-test path. The badge and the dots are the whole interaction on touch. */
@media (hover: none) {
    .omni-card-variant-arrow {
        display: none;
    }
}

.omni-card-body {
    padding: 0.75rem;
    flex: 1;
    display: flex;
    flex-direction: column;
}

.omni-card-publisher {
    font-size: 0.65rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--omni-text-muted);
    margin-bottom: 0.2rem;
}

.omni-card-title {
    font-weight: 600;
    font-size: 0.85rem;
    line-height: 1.3;
    color: var(--omni-text);
    margin-bottom: 0.35rem;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

/* Sits under the clamped title and carries the artist-cover / printing suffix
   the clamp used to eat. It wraps instead of clamping: two cards for the same
   omnibus differ ONLY here, so truncating it recreates the collision (GH-114). */
.omni-card-variant {
    font-size: 0.72rem;
    font-weight: 500;
    line-height: 1.25;
    color: var(--omni-text-muted);
    margin-top: -0.2rem;
    margin-bottom: 0.35rem;
    overflow-wrap: anywhere;
}

/* Date beside price (plus the shipping-threshold note). On a two-up phone grid the
   tile is ~158px, narrower than "Sep 2025" + "$16.99" + "($4.99 shipping below
   $50.00)", so the row WRAPS rather than squeezing the date to its min-content width,
   where it breaks into "Sep" / "2025" and is clipped at the tile edge. */
.omni-card-footer {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    align-items: center;
    column-gap: 0.4rem;
    row-gap: 0.1rem;
    margin-top: auto;
    font-size: 0.75rem;
    color: var(--omni-text-muted);
}

.omni-card-footer .date {
    white-space: nowrap;
}

.omni-card-footer .price {
    font-weight: 600;
    color: var(--omni-text);
}

/* Compact card-internal typography when the CARD itself is narrow (dense grid or
   a narrow container), independent of viewport. Replaces the old viewport
   @media (max-width: 640px) rules so a 200px card reads compactly anywhere. */
@container omnicard (max-width: 220px) {
    .omni-card-body { padding: 0.5rem; }
    .omni-card-publisher { font-size: 0.6rem; }
    .omni-card-title { font-size: 0.78rem; }
    .omni-card-footer { font-size: 0.65rem; }
}

/* Touch: suppress the hover lift, add press feedback.
 *
 * ORDER IS LOAD-BEARING. This block MUST stay last -- after the base
 * `.omni-card-shell:hover > .omni-card` rule above. Both selectors are 0,3,0,
 * so SOURCE ORDER alone decides. Hoisted above the base rule, the lift
 * survives on touch, where :hover sticks after a tap. */
@media (max-width: 640px) {
    /* Card-internal typography/padding moved to an @container query (see
       "Card Body") so it tracks the card's own width, not the viewport. */
    /* Must mirror the shell-keyed base rule above, or it loses on specificity
       and the lift survives on touch (where :hover sticks after a tap). */
    .omni-card-shell:hover > .omni-card {
        transform: none;
        box-shadow: none;
    }

    /* Shell-qualified purely to match the specificity of the hover rule above
       (both 0,3,0) so source order decides, as it did when both were 0,2,0.
       Unqualified, the sticky post-tap :hover would beat this and kill the
       press feedback. */
    .omni-card-shell > .omni-card:active {
        transform: scale(0.97);
    }
}


/* ============================================================
   /sales variants
   ============================================================ */

/* A new BASE hover rule here would restyle /omnis, whose cards always carry a
   mode class; a variant cannot. */
.omni-card-shell:hover > .omni-card.mode-sale {
    box-shadow: 0 12px 40px color-mix(in srgb, var(--pub-glow, var(--accent-canonical)) 20%, transparent);
}

/* Used copies must read differently at a glance, not only on close reading:
   the border and the condition badge both shift tone. */
.omni-card.is-used {
    border-color: color-mix(in srgb, var(--text-muted) 40%, var(--omni-border));
}
.omni-card.is-used .omni-card-covers-badge {
    border-color: color-mix(in srgb, var(--on-image) 30%, transparent);
}
.omni-card:not(.is-used) .omni-card-covers-badge {
    color: var(--accent-canonical);
    border-color: color-mix(in srgb, var(--accent-canonical) 45%, transparent);
}

/* /sales renders the grid as a <ul> to keep list semantics; /omnis uses divs
   and is unaffected. Kept as a separate rule so the moved .omni-grid block
   above stays a verbatim copy that can be diffed against git history. */
.omni-grid {
    list-style: none;
    padding-left: 0;
    margin-bottom: 0;
}
