/* ============================================================
   UNIQorn — draw-deck.css
   The seventy-eight card backs: the fan, the shuffle, the pick, the melt.

   Scoped, deliberately
   --------------------
   Every rule here is scoped to `.draw-deck[data-draw-deck]`. The ask page's
   six-card shuffle uses the same class names in assets/draw.css and must not
   move by a pixel, so the 78-card deck is the one that carries the data
   attribute and the six are left alone. Same names, same language, different
   instance — that is the point (docs/DRAW-EXPERIENCE.md §2).

   One consequence worth knowing: draw.css draws the inner hairline with
   `.draw-deck-card::after`, and the 78-card markup carries a real
   `.draw-deck-back` span instead. Both would draw an inner line, so the
   pseudo-element is switched off below. It is switched off only under
   [data-draw-deck]; the six keep theirs.

   A card back is one shared image
   -------------------------------
   Every back in the 78-card deck paints the same file, img/tarot/
   tarot-card-back.webp (see `.draw-deck-back` below), so the browser fetches it
   once however many cards are shown. The six-card shuffle still draws its own
   hairline back with draw.css's `::after`; that line is switched off here, so
   the image is the only thing on a 78-card back.

   Geometry: one circle, seventy-eight points on it
   -----------------------------------------------
   The cards stand on a circle whose centre is below the stage, so the middle
   card is highest and the ends drop away — the sketch's wide fan. Every card's
   place is a function of --i and four numbers, not of 78 hand-placed
   coordinates:

     --deck-arc   36      half the arc's total angle, in degrees (the end tilt)
     --deck-r     6.5397  the radius, in *card widths*
     --deck-mid   38.5    (78 - 1) / 2
     --deck-card-w 8.2%   the card's width, as a share of the deck box

   Those four were chosen from two constraints: the fan's outer edges span 78%
   of the deck box at every width, and the end card tilts 36°. Solving
   V = R·sinθ + (w/2)cosθ + (h/2)sinθ for V = 0.39·box gives the radius above.
   Because the radius is in card widths and the card width is a percentage, the
   whole fan is *scale-invariant*: 390 gets the same fan at half size, inside
   the box, with no media query and no overflow. That is the narrow-screen
   answer — a tighter arc, not a scrollable one, because the ritual is one
   glance and one press.

   The box's own height is that fan's bounding box, also derived: 0.2564 of its
   width, hence aspect-ratio 3.9. Change --deck-arc and this must be recomputed.

   The deck box is absolutely positioned with `top: auto`, so it sits exactly
   where it would have sat in flow but contributes no height — the fan cannot
   push the sentence above it or the answer below it.

   One number per animation, and its default is the state the deck rests in
   -------------------------------------------------------------------------
     --deck-p     0 = the deck gathered, 1 = fanned into the arc
     --dissolve-p 0 = all 78 present, 1 = only the drawn card

   `--deck-p` defaults to 1, the fanned deck, because that is the finished state
   and no-JS, reduced motion and print should all get it for free.

   `--dissolve-p` defaults to **0**, and it was 1 until this was looked at. The
   rule is "the default is the finished state", and it was read as "the end of
   the animation" — but the melt is an *event*, not a resting state, and the
   state the deck rests in is "all 78 present". With the default at 1 the deck
   was fully dissolved the moment it was shown: every card at opacity 0, the fan
   invisible, and nothing in the CSS to explain why. JS writes both inline and
   inline beats this stylesheet, so the animation is unaffected either way; what
   the default decides is what a deck looks like when nobody has driven it yet.

   The fan and the melt use different mechanisms on purpose. The fan is a
   *target*: JS sets --deck-p and the cards' own `translate`/`rotate` transitions
   carry them, each with a delay proportional to its distance from the middle,
   so the deck opens from the centre outward. The melt is a *clock*: --dissolve-p
   is registered as a number and transitions, and each card reads its own slice
   of it through a stagger computed in CSS. Neither can drive the other's
   properties, which is what keeps them from fighting.

   Nothing here animates width, height, top, left or margin.
   ============================================================ */

/* --dissolve-p has to be a registered number for the melt to be a transition
   rather than a rAF loop in JS: registration is what makes a custom property
   animatable. Without it the property still works as a token and the end state
   is identical — it just arrives in one step. */
@property --dissolve-p {
    syntax: "<number>";
    inherits: true;
    initial-value: 0;
}

@property --pk {
    syntax: "<number>";
    inherits: true;
    initial-value: 1;
}

.draw-deck[data-draw-deck] {
    /* ---------- the two numbers ---------- */
    --deck-p: 1;
    --dissolve-p: 0;
    /* The drawn card's index, written by JS at the pick. The melt radiates from
       it, so the cards nearest the drawn one are the last to go. */
    --pi: 0;

    /* ---------- the arc ---------- */
    --deck-card-w: 8.2%;
    --deck-hratio: 1.72727;   /* 4.75 / 2.75, the deck's own card ratio */
    --deck-mid: 38.5;
    --deck-arc: 36;
    --deck-r: 6.5397;         /* radius, in card widths */
    /* The gap between neighbouring cards, as a share of a card's width:
       the fan's centre-to-centre span (0.6304 of the box) over 77 gaps, divided
       by the card's own width. The cut below reuses it so the two halves of the
       riffle read as packets of countable edges, at the same pitch as the fan. */
    --deck-gap: 9.984%;
    /*
     * How thick the gathered deck is. Each card sits 0.12px below the one above
     * it at the design width, so the stack is about 9px of edge across a 100px
     * card — a deck's thickness, which is what a gathered 78-card deck actually
     * looks like.
     *
     * It cannot be a *countable* stack: 78 hairlines need 78px of pitch to be
     * countable, and that is a deck thicker than it is tall. At any pitch the
     * borders overlap and the edge reads as one soft line, so it is sized to
     * read as thickness and no more. This is the one place in the component
     * where hairlines pile up; it is kept thin so the pile never becomes a
     * colour block.
     */
    --deck-stack: .12;

    /* ---------- the shuffle's four beats ---------- */
    --deck-cut: 30%;          /* how far the two halves part, in card widths */
    --deck-cross: 9%;         /* how far they lift and dip, in card heights */

    /* ---------- the melt ---------- */
    /* How much of the melt the farthest card is held back by. */
    --deck-melt-stagger: .45;
    /* How much a card blooms before it goes. */
    --deck-melt-bloom: .22;

    /* ---------- the pick ---------- */
    --deck-lift-grow: 1.22;

    /* ---------- clocks, all read by draw-deck.js rather than restated there ---------- */
    --deck-beat: 1650ms;             /* one card's four beats */
    --deck-shuffle-stagger: 3ms;     /* between cards, during the shuffle */
    --deck-spread: 950ms;            /* one card's journey into the arc */
    --deck-stagger: 4ms;             /* between cards, fanned from the middle out */
    --deck-lift: 880ms;              /* the drawn card leaving the arc */
    --deck-melt: 1120ms;              /* the 77 going */

    /* The house easing. Growth keeps arriving for most of its length —
       cubic-bezier(.3, 0, .22, 1) is not easeOutExpo, which at a quarter of the
       clock has already grown 83% of the way and reads as "flung open" rather
       than as "arriving" (docs/DRAW-EXPERIENCE.md §3). The shuffle uses the
       faster curve the six-card version already runs on. */
    --deck-ease-grow: cubic-bezier(.3, 0, .22, 1);
    --deck-ease: cubic-bezier(.35, 0, .2, 1);

    /*
     * draw.css already gives `.draw-deck` a 32 x 55 box parked above the ask
     * page's button. That box is the six-card deck's, and it has to be undone
     * here or it wins: an absolutely positioned element with `bottom` set is
     * placed by that edge, and a definite width and height beat `left/right`
     * and `aspect-ratio`. This is the whole of the collision between the two
     * decks, and it is four properties wide.
     */
    position: absolute;
    left: 0;
    right: 0;
    top: auto;
    bottom: auto;
    width: auto;
    height: auto;
    margin-left: 0;
    aspect-ratio: 3.9;
    opacity: 0;
    visibility: hidden;
}

/*
 * The melt's clock, when this file is the one running it.
 *
 * --dissolve-p is registered above as a number, which is what makes it
 * animatable, and the cards read their own slice of it through the stagger in
 * their own rule. It is deliberately NOT on the deck's base rule: assets/
 * ink-dissolve.js drives the same number from a rAF loop when it is on the
 * page, and a transition underneath a value written every frame damps the
 * whole curve. So the transition is armed by the class this file adds when it
 * is the driver, and by nothing else.
 */
.draw-deck[data-draw-deck].is-melting {
    transition: --dissolve-p var(--deck-melt) var(--deck-ease-grow);
}

/* The six-card deck in draw.css already draws this line. Under the 78-card
   attribute the span does it instead, so the pseudo-element goes. */
.draw-deck[data-draw-deck] .draw-deck-card::after {
    content: none;
}

/* ---------- the cards ---------- */

.draw-deck[data-draw-deck] .draw-deck-card {
    position: absolute;
    left: 50%;
    top: 0;
    /* draw.css's `inset: 0` belongs to the six-card deck; the other two edges
       have to go or they over-constrain the box. */
    right: auto;
    bottom: auto;
    width: var(--deck-card-w);
    aspect-ratio: var(--card-ratio);
    margin: 0;
    padding: 0;
    border: 1px solid var(--indigo);
    border-radius: var(--radius-sm);
    background: var(--white);
    overflow: hidden;
    appearance: none;
    -webkit-appearance: none;
    /* The deck is the pick surface, not the cards: with 78 cards overlapping
       nine deep, the only per-card target is a 6px sliver, which is not a
       target. The pointer is mapped to a card by position instead — see
       indexAt() in draw-deck.js — and the buttons stay in the accessibility
       tree as the thing that mapping lands on. */
    pointer-events: none;

    /*
     * The arc, as a function of --i.
     *
     * --t runs -1..1 across the deck, --th is the card's angle from vertical,
     * and the two sine terms are its place on the circle: x along the chord,
     * y dropping away from the apex by R(1 - cos θ). --ayn is in *card heights*
     * because translateY's percentage resolves against the element's own
     * height, not its width; the division by --deck-hratio is that conversion
     * and nothing else.
     *
     * --pk is the per-card copy of --deck-p so the drawn card can be taken out
     * of the arc by zeroing its own multiplier without disturbing the other 77.
     */
    --pk: var(--deck-p);
    --t: calc((var(--i) - var(--deck-mid)) / var(--deck-mid));
    --th: calc(var(--t) * var(--deck-arc));
    --axn: calc(sin(calc(var(--th) * 1deg)) * var(--deck-r) * 100 * var(--pk));
    --ayn: calc((1 - cos(calc(var(--th) * 1deg))) * var(--deck-r) / var(--deck-hratio) * 100 * var(--pk));
    --syn: calc((var(--i) - var(--deck-mid)) * var(--deck-stack) * (1 - var(--deck-p)));
    --ar: calc(var(--th) * var(--pk));

    /*
     * The melt, staggered so it leaves from the far end and comes to the drawn
     * card last.
     *
     * --di is how many places away the drawn card is; --d is the share of the
     * clock that card waits, and it is inverted, so the cards *furthest* from
     * the drawn one go first and the ones beside it are the last to leave. --dp
     * is then a plain ramp at one rate for every card, divided by the span so
     * the farthest starts at --d and finishes exactly at the end of the clock —
     * which is what makes the cards finish in a wave rather than all at once.
     *
     * The clamp is the whole stagger. No delays, no second clock, one number.
     */
    --di: max(var(--i) - var(--pi), var(--pi) - var(--i));
    /* The furthest card from the drawn one — which is not --deck-mid, because
       the drawn card is rarely the middle one. Normalising by it keeps the
       stagger the same width wherever the pick lands: the card beside the drawn
       one always waits the full --deck-melt-stagger, and the far end always
       starts at zero. Without it a pick near an end compresses the whole wave
       into half the clock. */
    --dn: max(var(--pi), calc(var(--deck-mid) * 2 - var(--pi)));
    --d: calc((var(--dn) - var(--di)) / var(--dn) * var(--deck-melt-stagger));
    --dp: clamp(0, calc((var(--dissolve-p) - var(--d)) / (1 - var(--deck-melt-stagger))), 1);

    /*
     * The card is placed by its CENTRE, and the -50% is what does that: with
     * `left: 50%` the card's *left edge* is on the box's centre line, so
     * without it every card — and the whole fan, and the card that is finally
     * drawn — sits half a card to the right of where it belongs.
     *
     * The vertical term needs no equivalent: `top: 0` puts the card's top edge
     * on the box's top, and --ayn already carries the half-card-height that
     * moves the centre down onto the arc.
     */
    translate: calc(var(--axn) * 1% - 50%) calc(var(--ayn) * 1% + var(--syn) * 1%);
    rotate: calc(var(--ar) * 1deg);
    transform: scale(1);

    transition:
        --pk var(--deck-spread) var(--deck-ease-grow),
        translate var(--deck-spread) var(--deck-ease-grow),
        rotate var(--deck-spread) var(--deck-ease-grow),
        transform var(--deck-lift) var(--deck-ease-grow);
    /* Fanned from the middle outward: the middle card leaves first and the two
       ends last, which is what makes it read as a deck opening rather than as a
       row being dealt. */
    transition-delay: calc(max(var(--i) - var(--deck-mid), var(--deck-mid) - var(--i)) * var(--deck-stagger));
}

/* The confirmed original water-ripples card back.
   Softens and melts with ink-dissolve as unpicked cards dissolve. */
.draw-deck[data-draw-deck] .draw-deck-back {
    position: absolute;
    inset: 0;
    border-radius: var(--radius-xs);
    background: url('img/tarot/tarot-card-back.webp') center / cover no-repeat;
    opacity: calc(1 - var(--dp));
}

/* ============================================================
   Two files, one melt

   assets/ink-dissolve.css draws the melt — ink in water, an edge that loses
   its definition before the shape goes. This file draws the arc. They are
   built to the same contract, so they are meant to run together rather than
   one instead of the other: the deck owns --dissolve-p and the ink module
   reads it, and whichever of them is on the page drives that one number.

   The two rules below are the whole of the seam.

   When the ink module is on the page it adds `.ink-dissolve` to the deck and
   `.ink-item` to each card, and takes over `opacity`, `filter`, `scale` and
   its own drift. This file then stops writing the melt appearance — hence the
   `:not()` — so the ink is what is seen, not a fade underneath it.

   But the ink also wants `translate` and `rotate`, and those are where the arc
   lives. Rather than let one of them win and lose the other, the arc's two
   terms and the ink's two terms are added together here: the card stays on the
   circle AND drifts, which is what "the other 77 melt where they stand" has to
   mean when they are standing on an arc.
   ============================================================ */

.draw-deck[data-draw-deck]:not(.ink-dissolve) .draw-deck-card {
    /* Squared, not linear: a hairline at half opacity is still a line, so a
       linear fade reads as a dimmer switch rather than as ink thinning. The
       outer edge is the last thing with any substance; the inner line above
       falls linearly and is gone well before it. */
    opacity: calc(1 - var(--dp) * var(--dp));
    scale: calc(1 + var(--dp) * var(--deck-melt-bloom));
}

.draw-deck[data-draw-deck].ink-dissolve .draw-deck-card:not(.is-picked) {
    translate:
        calc(var(--axn) * 1% - 50% + var(--ink-x, 0px) * var(--ink-p, 0))
        calc(var(--ayn) * 1% + var(--syn) * 1% + var(--ink-y, 0px) * var(--ink-p, 0));
    rotate: calc(var(--ar) * 1deg + var(--ink-rot, 0deg) * var(--ink-p, 0));
}

/* ---------- the shuffle: four beats, at 78 cards ---------- */

/*
 * The deck gathers, is cut into two halves, the halves cross, it closes — the
 * rhythm assets/draw.css establishes for six cards, kept whole at seventy-eight.
 *
 * What changes with the count is the *scale of the cut*, not its shape. The two
 * halves part as packets, and each half's internal pitch is --deck-gap, the same
 * pitch the fan uses: thirty-nine cards a side, every edge still countable. If
 * the halves only separated by a fixed distance the cut would read as one deck
 * sliding rather than as two packets crossing, which is the failure the six-card
 * version's comment already describes.
 *
 * The last beat does not fade out the way the six-card version does. There the
 * deck is the press's gesture and the page navigates away underneath it; here
 * the deck has to still be a deck when the fan starts, so it closes and stays.
 */
@keyframes draw-deck-shuffle {
    /* entrance & gathered: cards materialize and settle at center */
    0%   { opacity: 0; transform: translate(0, 0) scale(.92); }
    15%  { opacity: 1; transform: translate(0, 0) scale(1); }
    22%  { opacity: 1; transform: translate(0, 0); }
    /* cut: the halves part, each as a packet of thirty-nine */
    42%  {
        transform: translate(
            calc(var(--dir) * (var(--deck-cut) + var(--k) * var(--deck-gap))),
            calc(var(--dir) * var(--deck-cross) * -1)
        );
    }
    /* riffle: the packets cross through each other */
    68%  {
        transform: translate(
            calc(var(--dir) * (var(--deck-cut) + var(--k) * var(--deck-gap)) * -1),
            calc(var(--dir) * var(--deck-cross) * -1.5)
        );
    }
    /* closed again into a unified deck */
    88%  { transform: translate(0, 0); }
    100% { transform: translate(0, 0); opacity: 1; }
}

.draw-deck[data-draw-deck].is-shuffling .draw-deck-card {
    animation: draw-deck-shuffle var(--deck-beat) var(--deck-ease) both;
    animation-delay: calc(var(--i) * var(--deck-shuffle-stagger));
}

/* ---------- the pick ---------- */

/*
 * The drawn card comes out of the arc: it straightens, travels to the center and
 * grows. It lifts toward the viewer with prominence and soft depth.
 */
.draw-deck[data-draw-deck] .draw-deck-card.is-picked {
    --pk: 0;
    --dp: 0;
    z-index: 10;
    translate: -50% 0%;
    rotate: 0deg;
    transform: translateY(-8px) scale(var(--deck-lift-grow));
    box-shadow: 0 16px 40px rgba(30, 58, 95, .28), 0 4px 12px rgba(30, 58, 95, .15);
    border-color: var(--indigo);
    transition:
        --pk var(--deck-lift) var(--deck-ease-grow),
        translate var(--deck-lift) var(--deck-ease-grow),
        rotate var(--deck-lift) var(--deck-ease-grow),
        transform var(--deck-lift) var(--deck-ease-grow),
        box-shadow var(--deck-lift) var(--deck-ease-grow);
    transition-delay: 0ms;
}

/* The keyboard's ring. Indigo, because the anchor red is one per screen and it
   already has a job on this page. */
.draw-deck[data-draw-deck] .draw-deck-card.is-focus {
    z-index: 3;
    outline: 2px solid var(--indigo);
    outline-offset: 2px;
}

/* Card Hover interaction on the fan: lifts outward radially with indigo glow & soft shadow */
.draw-deck[data-draw-deck] .draw-deck-card.is-hover:not(.is-picked) {
    z-index: 5;
    transform: translateY(-20px) scale(1.05);
    box-shadow: 0 10px 28px rgba(30, 58, 95, .22), 0 2px 6px rgba(30, 58, 95, .12);
    border-color: var(--indigo);
    transition:
        --pk var(--deck-spread) var(--deck-ease-grow),
        translate var(--deck-spread) var(--deck-ease-grow),
        rotate var(--deck-spread) var(--deck-ease-grow),
        transform 160ms cubic-bezier(.2, .8, .2, 1),
        box-shadow 160ms ease,
        border-color 160ms ease;
}

.draw-deck[data-draw-deck] .draw-deck-card.is-hover:not(.is-picked) .draw-deck-back {
    opacity: 1;
}

/* ---------- the deck as one control ---------- */

.draw-deck[data-draw-deck] {
    cursor: default;
    /*
     * A finger on the fan has to be able to sweep it.
     *
     * Without this the browser claims a horizontal drag for scrolling and the
     * card under the finger never lifts; with `pan-y` the page still scrolls
     * vertically and the horizontal gesture is delivered to the pointermove
     * that previews a card (draw-deck.js). The pick itself is still the click
     * that follows a tap, so nothing here chooses anything.
     */
    touch-action: pan-y;
}

.draw-stage[data-draw-state="spread"] .draw-deck[data-draw-deck] {
    cursor: pointer;
}

/*
 * Focus lives on the deck, not on the 78 cards. Seventy-eight tab stops is a
 * wall, so the group is the tab stop and the arrow keys move a ring across the
 * fan; aria-activedescendant points at the card the ring is on. The group's own
 * outline is suppressed because the ring *is* the focus indicator — see the
 * keyboard note in draw-deck.js.
 */
.draw-deck[data-draw-deck]:focus,
.draw-deck[data-draw-deck]:focus-visible {
    outline: none;
}

/* ---------- which states the deck is on screen in ---------- */

.draw-stage[data-draw-state="shuffle"] .draw-deck[data-draw-deck],
.draw-stage[data-draw-state="spread"] .draw-deck[data-draw-deck],
.draw-stage[data-draw-state="picked"] .draw-deck[data-draw-deck] {
    opacity: 1;
    visibility: visible;
}

/* ---------- arming, from the head, before the first paint ---------- */

/*
 * The deck's default is the finished state — fanned, and only the drawn card
 * left — which is right for no-JS and wrong for a script that is about to
 * shuffle. `is-deck-armed` goes on <html> from the document head, so the deck
 * starts gathered and nothing flashes and resets. Exactly the arrangement
 * assets/draw.js uses for the ink, and for the same reason.
 *
 * It is not added when reduced motion is asked for: there the finished state is
 * the correct starting state too.
 */
.is-deck-armed .draw-deck[data-draw-deck] {
    --deck-p: 0;
    --dissolve-p: 0;
}

/* ---------- reset, and reduced motion ---------- */

/* reset() has to put the deck back without a journey. */
.draw-deck[data-draw-deck].is-instant,
.draw-deck[data-draw-deck].is-instant .draw-deck-card {
    transition: none;
}

@media (prefers-reduced-motion: reduce) {
    .draw-deck[data-draw-deck],
    .draw-deck[data-draw-deck] .draw-deck-card,
    .draw-deck[data-draw-deck].is-shuffling .draw-deck-card {
        transition: none;
        animation: none;
    }
}

/* ---------- phones ---------- */

/*
 * Nothing. The arc is a proportion of its box and every distance is expressed
 * in card widths or card heights, so the whole fan scales with the stage and
 * lands inside it at every width from 320 up. At 390 the box is 350px, the fan
 * 273px across and 89px tall, and no horizontal overflow is possible by
 * construction — the widest thing in the component is the box itself.
 */
