""" APNG animated card generation for T3 and T4 refractor tiers. Captures animation frames by scrubbing CSS animations via Playwright — each frame is rendered with a negative animation-delay that freezes the render at a specific point in the animation cycle. The captured PNGs are then assembled into a looping APNG using the apng library. Cache / S3 path convention: Local: storage/cards/cardset-{id}/{card_type}/{player_id}-{date}-v{variant}.apng S3: cards/cardset-{id}/{card_type}/{player_id}-{date}-v{variant}.apng """ import os import tempfile from apng import APNG from playwright.async_api import Page # --------------------------------------------------------------------------- # Animation specs per tier # Each entry: list of (css_selector, animation_duration_seconds) pairs that # need to be scrubbed, plus the frame count and per-frame display time. # --------------------------------------------------------------------------- _T3_SPEC = { "selectors_and_durations": [("#header::after", 2.5)], "num_frames": 12, "frame_delay_ms": 200, } _T4_SPEC = { "selectors_and_durations": [ ("#header::after", 6.0), (".tier-diamond.diamond-glow", 2.0), ], "num_frames": 24, "frame_delay_ms": 250, } ANIM_SPECS = {3: _T3_SPEC, 4: _T4_SPEC} def apng_cache_path( cardset_id: int, card_type: str, player_id: int, d: str, variant: int ) -> str: """Return the local filesystem cache path for an animated card APNG.""" return f"storage/cards/cardset-{cardset_id}/{card_type}/{player_id}-{d}-v{variant}.apng" async def generate_animated_card( page: Page, html_content: str, output_path: str, tier: int, ) -> None: """Generate an animated APNG for a T3 or T4 refractor card. Scrubs each CSS animation by injecting an override