#!/usr/bin/env python3
"""Visuel kit social 3AS Racing — fond noir, titre, points clés, desktop liquid glass."""

from __future__ import annotations

import importlib.util
import shutil
from pathlib import Path

import numpy as np
from PIL import Image, ImageDraw, ImageFilter, ImageFont

ROOT = Path(__file__).resolve().parents[1]
KIT = ROOT / "www/propositions/kit-social-3as-racing/visuels"
ASSETS = ROOT / "assets"
BEZEL = ROOT / "assets/device-frames/png/iphone-14-starlight-portrait.png"
FONT_WOFF2 = ROOT / "www/wp-content/themes/numberone/fonts/Montserrat-ExtraBold.woff2"
FONT_EXTRABOLD = Path("/tmp/Montserrat-ExtraBold.ttf")
FONT_MULI = Path("/tmp/Muli-Regular.ttf")
FONT_MULI_SB = Path("/tmp/Muli-SemiBold.ttf")
FONT_UI = "/System/Library/Fonts/Supplemental/Arial.ttf"
FONT_UI_BOLD = "/System/Library/Fonts/Supplemental/Arial Bold.ttf"
FONT_UI_ITALIC = "/System/Library/Fonts/Supplemental/Arial Italic.ttf"

W, H = 1920, 1080
ORANGE = (233, 128, 48)
CYAN = (0, 175, 255)
DARK = (8, 8, 10)
CREAM = (232, 224, 210)
WHITE = (255, 255, 255)
INK = (18, 18, 18)

POINTS = [
    "300 000+ références pièces moto",
    "Recherche par modèle : 4 M de combinaisons",
    "Connecteurs stocks, achats, CRM",
    "Facettes, mobile, hébergement dimensionné",
]


def load_hex():
    path = ROOT / "scripts/generate_cabinet_hexagone_mockups.py"
    spec = importlib.util.spec_from_file_location("hex_mockups", path)
    mod = importlib.util.module_from_spec(spec)
    assert spec.loader is not None
    spec.loader.exec_module(mod)
    return mod


def woff2_to_ttf(src: Path, dst: Path) -> str:
    if dst.exists():
        return str(dst)
    from fontTools.ttLib import TTFont

    font = TTFont(str(src))
    font.flavor = None
    font.save(str(dst))
    return str(dst)


def ensure_fonts() -> tuple[str, str, str]:
    extra = woff2_to_ttf(FONT_WOFF2, FONT_EXTRABOLD)
    fonts = ROOT / "www/wp-content/themes/numberone/fonts"
    muli = woff2_to_ttf(fonts / "Muli-Regular.woff2", FONT_MULI)
    muli_sb = woff2_to_ttf(fonts / "Muli-SemiBold.woff2", FONT_MULI_SB)
    return extra, muli, muli_sb


def font(path: str, size: int) -> ImageFont.FreeTypeFont:
    return ImageFont.truetype(path, size)


def crop_logo(src: Image.Image) -> Image.Image:
    arr = np.array(src.convert("RGBA"))
    lum = arr[:, :, :3].astype(np.int16).sum(axis=2)
    ys, xs = np.where(lum > 60)
    box = int(xs.min()), int(ys.min()), int(xs.max()) + 1, int(ys.max()) + 1
    return src.convert("RGBA").crop(box)


def cover(img: Image.Image, size: tuple[int, int]) -> Image.Image:
    tw, th = size
    scale = max(tw / img.width, th / img.height)
    resized = img.resize(
        (max(1, int(img.width * scale)), max(1, int(img.height * scale))),
        Image.Resampling.LANCZOS,
    )
    left = (resized.width - tw) // 2
    top = (resized.height - th) // 2
    return resized.crop((left, top, left + tw, top + th))


def cover_top(img: Image.Image, size: tuple[int, int]) -> Image.Image:
    tw, th = size
    scale = max(tw / img.width, th / img.height)
    resized = img.resize(
        (max(1, int(img.width * scale)), max(1, int(img.height * scale))),
        Image.Resampling.LANCZOS,
    )
    left = (resized.width - tw) // 2
    return resized.crop((left, 0, left + tw, th))


def rounded_rect(draw: ImageDraw.ImageDraw, box, radius, fill, outline=None, width=1):
    draw.rounded_rectangle(box, radius=radius, fill=fill, outline=outline, width=width)


def rounded_mask(size: tuple[int, int], radius: int) -> Image.Image:
    mask = Image.new("L", size, 0)
    ImageDraw.Draw(mask).rounded_rectangle((0, 0, size[0] - 1, size[1] - 1), radius=radius, fill=255)
    return mask


def liquid_glass_browser(
    screenshot: Image.Image,
    backdrop: Image.Image,
    xy: tuple[int, int],
    *,
    content_width: int = 1000,
    viewport_ratio: float = 0.50,
) -> Image.Image:
    """Fenêtre navigateur liquid glass : chrome givré, reflet spéculaire, capture nette."""
    w = content_width
    bar_h = max(50, int(w * 0.048))
    radius = max(24, int(w * 0.026))
    content_h = int(content_width * viewport_ratio)
    content = cover_top(screenshot.convert("RGB"), (w, content_h)).convert("RGBA")
    total_h = bar_h + content.height
    x, y = xy

    bx1 = max(0, x)
    by1 = max(0, y)
    bx2 = min(backdrop.width, x + w)
    by2 = min(backdrop.height, y + total_h)
    region = Image.new("RGB", (w, total_h), DARK)
    cropped = backdrop.crop((bx1, by1, bx2, by2)).convert("RGB")
    region.paste(cropped, (bx1 - x, by1 - y))
    # Légère réfraction : décalage du fond derrière le verre
    shifted = region.copy()
    shifted.paste(region, (3, -2))
    frosted = Image.blend(region, shifted, 0.28).filter(ImageFilter.GaussianBlur(16))

    frost_tint = Image.new("RGBA", (w, total_h), (232, 238, 246, 36))
    glass = Image.blend(frosted.convert("RGBA"), frost_tint, 0.32)

    bar_tint = Image.new("RGBA", (w, bar_h + radius), (248, 250, 255, 64))
    bar_layer = Image.new("RGBA", (w, total_h), (0, 0, 0, 0))
    bar_layer.paste(bar_tint, (0, 0))
    glass = Image.alpha_composite(glass, bar_layer)

    spec = Image.new("RGBA", (w, total_h), (0, 0, 0, 0))
    sd = ImageDraw.Draw(spec)
    for i in range(bar_h + 110):
        alpha = int(70 * (1 - i / (bar_h + 110)) ** 1.45)
        sd.line((0, i, w, i), fill=(255, 255, 255, alpha))
    sd.ellipse((-int(w * 0.22), -int(total_h * 0.28), int(w * 0.62), int(total_h * 0.22)), fill=(255, 255, 255, 32))
    # Reflet latéral
    for i in range(28):
        a = int(42 * (1 - i / 28))
        sd.line((i, 0, i, total_h), fill=(255, 255, 255, a))
    glass = Image.alpha_composite(glass, spec)

    mask = rounded_mask((w, total_h), radius)
    glass.putalpha(mask)

    # Capture nette, coins inférieurs arrondis
    inner = Image.new("L", (w, content.height), 0)
    ImageDraw.Draw(inner).rounded_rectangle(
        (0, -radius, w - 1, content.height - 1),
        radius=max(14, radius - 4),
        fill=255,
    )
    content.putalpha(inner)

    out = Image.new("RGBA", (w, total_h), (0, 0, 0, 0))
    out.paste(glass, (0, 0), glass)
    out.paste(content, (0, bar_h), content)

    # Re-applique le masque global pour que le contenu ne dépasse pas
    out_a = np.minimum(np.array(out.split()[3]), np.array(mask))
    out.putalpha(Image.fromarray(out_a, "L"))

    overlay = Image.new("RGBA", (w, total_h), (0, 0, 0, 0))
    od = ImageDraw.Draw(overlay)
    # Liseré verre
    od.rounded_rectangle((0, 0, w - 1, total_h - 1), radius=radius, outline=(255, 255, 255, 110), width=2)
    od.rounded_rectangle((2, 2, w - 3, total_h - 3), radius=max(16, radius - 2), outline=(255, 255, 255, 40), width=1)
    od.line((16, bar_h, w - 16, bar_h), fill=(255, 255, 255, 50), width=1)

    cy = bar_h // 2
    for i, color in enumerate([(255, 95, 87, 220), (255, 189, 46, 220), (40, 202, 65, 220)]):
        cx = 26 + i * 22
        od.ellipse((cx - 7, cy - 7, cx + 7, cy + 7), fill=color)
        od.ellipse((cx - 7, cy - 7, cx + 7, cy + 7), outline=(255, 255, 255, 70), width=1)
    pill_w = int(w * 0.38)
    px1 = (w - pill_w) // 2
    od.rounded_rectangle((px1, cy - 13, px1 + pill_w, cy + 13), radius=13, fill=(255, 255, 255, 48))
    od.rounded_rectangle((px1, cy - 13, px1 + pill_w, cy + 13), radius=13, outline=(255, 255, 255, 55), width=1)

    out = Image.alpha_composite(out, overlay)
    return drop_shadow_device(out, blur=26, opacity=0.38, offset=(10, 22))


def scale_uniform(img: Image.Image, height: int | None = None, width: int | None = None) -> Image.Image:
    if height is not None:
        ratio = height / img.height
        return img.resize((max(1, int(img.width * ratio)), height), Image.Resampling.LANCZOS)
    assert width is not None
    ratio = width / img.width
    return img.resize((width, max(1, int(img.height * ratio))), Image.Resampling.LANCZOS)


def radial_orb(size: tuple[int, int], center: tuple[int, int], radius: int, color: tuple[int, int, int], strength: int) -> Image.Image:
    glow = Image.new("RGBA", size, (0, 0, 0, 0))
    d = ImageDraw.Draw(glow)
    for r in range(radius, 0, -10):
        alpha = int(strength * (1 - r / radius) ** 2.2)
        d.ellipse((center[0] - r, center[1] - r, center[0] + r, center[1] + r), fill=(*color, alpha))
    return glow.filter(ImageFilter.GaussianBlur(36))


def drop_shadow_device(device: Image.Image, blur: int = 24, opacity: float = 0.48, offset=(10, 22)) -> Image.Image:
    alpha = device.split()[3]
    shadow = Image.new("RGBA", device.size, (0, 0, 0, 255))
    mask = alpha.point(lambda p: int(p * opacity))
    shadow.putalpha(mask)
    shadow = shadow.filter(ImageFilter.GaussianBlur(blur))
    pad = blur * 2
    layer = Image.new("RGBA", (device.width + pad * 2, device.height + pad * 2), (0, 0, 0, 0))
    layer.paste(shadow, (pad + offset[0], pad + offset[1]), shadow)
    layer.paste(device, (pad, pad), device)
    return layer


def _last_transparent(alpha: np.ndarray, y: int, x: int, dy: int, dx: int) -> tuple[int, int]:
    h, w = alpha.shape
    last = (y, x)
    while 0 <= y < h and 0 <= x < w and alpha[y, x] < 20:
        last = (y, x)
        y += dy
        x += dx
    return last


def composite_iphone(bezel_path: Path, screenshot: Image.Image, *, inset: int = 3) -> Image.Image:
    bezel = Image.open(bezel_path).convert("RGBA")
    alpha = np.array(bezel)[:, :, 3]
    h, w = alpha.shape
    cy, cx = h // 2, w // 2
    probe_x = cx - w // 5
    y1, _ = _last_transparent(alpha, cy, probe_x, -1, 0)
    y2, _ = _last_transparent(alpha, cy, probe_x, 1, 0)
    _, x1 = _last_transparent(alpha, cy, cx, 0, -1)
    _, x2 = _last_transparent(alpha, cy, cx, 0, 1)
    x1 += inset
    y1 += inset
    x2 -= inset - 1
    y2 -= inset - 1
    sw, sh = x2 - x1 + 1, y2 - y1 + 1
    radius = max(36, int(sw * 0.12))
    mask = Image.new("L", (sw, sh), 0)
    ImageDraw.Draw(mask).rounded_rectangle((0, 0, sw - 1, sh - 1), radius=radius, fill=255)
    content = cover_top(screenshot.convert("RGB"), (sw, sh)).convert("RGBA")
    content.putalpha(mask)
    out = Image.new("RGBA", bezel.size, (0, 0, 0, 0))
    out.paste(content, (x1, y1), content)
    out.alpha_composite(bezel)
    return out


def _paste_logo(img: Image.Image, xy: tuple[int, int], height: int) -> int:
    logo = crop_logo(Image.open(KIT / "logo-3as.png"))
    logo = scale_uniform(logo, height=height)
    img.paste(logo, xy, logo)
    return logo.width


def build_mobile_screenshot() -> Image.Image:
    sw, sh = 780, 1688
    img = Image.new("RGB", (sw, sh), WHITE)
    d = ImageDraw.Draw(img)
    ui = font(FONT_UI, 22)
    ui_sm = font(FONT_UI, 18)
    ui_b = font(FONT_UI_BOLD, 28)
    ui_i = font(FONT_UI_ITALIC, 24)

    y = 72
    d.rectangle((0, 0, sw, y), fill=WHITE)
    bar_h = 52
    d.rectangle((0, y, sw, y + bar_h), fill=ORANGE)
    d.text((sw // 2, y + bar_h // 2), "Caisse à outils Moto en stock", font=ui_i, fill=WHITE, anchor="mm")
    y += bar_h

    header_h = 108
    hx, hy = 36, y + 38
    for i in range(3):
        d.rounded_rectangle((hx, hy + i * 12, hx + 34, hy + 5 + i * 12), radius=2, fill=(20, 20, 20))
    _paste_logo(img, (86, y + 18), 72)

    def moto(cx: int, cy: int):
        d.ellipse((cx - 22, cy + 4, cx - 6, cy + 20), outline=INK, width=3)
        d.ellipse((cx + 8, cy + 4, cx + 24, cy + 20), outline=INK, width=3)
        d.line((cx - 6, cy + 12, cx + 8, cy + 12), fill=INK, width=3)
        d.line((cx - 2, cy + 12, cx - 8, cy - 6), fill=INK, width=3)
        d.line((cx - 8, cy - 6, cx + 4, cy - 6), fill=INK, width=3)

    def user(cx: int, cy: int):
        d.ellipse((cx - 10, cy - 14, cx + 10, cy + 6), outline=INK, width=3)
        d.arc((cx - 18, cy + 8, cx + 18, cy + 36), 200, 340, fill=INK, width=3)

    def cart(cx: int, cy: int):
        d.line((cx - 16, cy - 8, cx - 10, cy - 8), fill=INK, width=3)
        d.line((cx - 10, cy - 8, cx - 4, cy + 12), fill=INK, width=3)
        d.line((cx - 4, cy + 12, cx + 16, cy + 12), fill=INK, width=3)
        d.line((cx + 16, cy + 12, cx + 20, cy - 4), fill=INK, width=3)
        d.ellipse((cx - 2, cy + 16, cx + 8, cy + 26), outline=INK, width=2)
        d.ellipse((cx + 10, cy + 16, cx + 20, cy + 26), outline=INK, width=2)

    moto(520, y + 42)
    d.text((520, y + 88), "Mon garage", font=ui_sm, fill=INK, anchor="mm")
    user(620, y + 42)
    d.text((620, y + 88), "Mon compte", font=ui_sm, fill=INK, anchor="mm")
    cart(720, y + 38)
    d.ellipse((734, y + 14, 756, y + 36), fill=(220, 40, 40))
    d.text((745, y + 25), "1", font=font(FONT_UI_BOLD, 14), fill=WHITE, anchor="mm")
    d.text((720, y + 88), "Mon panier", font=ui_sm, fill=INK, anchor="mm")
    y += header_h

    pad = 28
    search_h = 92
    rounded_rect(d, (pad, y + 12, sw - pad, y + search_h - 12), 36, WHITE, outline=(210, 210, 210), width=3)
    d.text((pad + 28, y + search_h // 2), "Rechercher", font=ui, fill=(150, 150, 150), anchor="lm")
    lx, ly = sw - pad - 48, y + search_h // 2
    d.ellipse((lx - 12, ly - 12, lx + 8, ly + 8), outline=(40, 40, 40), width=3)
    d.line((lx + 6, ly + 6, lx + 16, ly + 16), fill=(40, 40, 40), width=3)
    y += search_h

    moto_h = 96
    d.rectangle((0, y, sw, y + moto_h), fill=CYAN)
    d.text((36, y + moto_h // 2), ">>", font=font(FONT_UI_BOLD, 34), fill=ORANGE, anchor="lm")
    d.text((110, y + 34), "AJOUTER MA MOTO", font=ui_b, fill=INK, anchor="lm")
    d.text((110, y + 68), "Pour voir les pièces compatibles", font=ui, fill=(30, 30, 30), anchor="lm")
    y += moto_h

    slide = cover(Image.open(KIT / "slide-alpi-mobile.webp").convert("RGB"), (sw, int(sw * 369 / 549)))
    img.paste(slide, (0, y))
    cy = y + slide.height // 2
    d.polygon([(18, cy), (48, cy - 22), (48, cy + 22)], fill=CYAN)
    d.polygon([(sw - 18, cy), (sw - 48, cy - 22), (sw - 48, cy + 22)], fill=CYAN)
    y += slide.height

    denali = cover(Image.open(KIT / "vignette-denali.png").convert("RGB"), (sw, int(sw * 320 / 532)))
    img.paste(denali, (0, y))
    return img


def build_desktop_screenshot() -> Image.Image:
    """Home desktop 3AS Racing — header réel + slide Alpinestars + vignettes CDN."""
    sw, sh = 1600, 980
    img = Image.new("RGB", (sw, sh), WHITE)
    d = ImageDraw.Draw(img)
    ui = font(FONT_UI, 18)
    ui_sm = font(FONT_UI, 15)
    ui_b = font(FONT_UI_BOLD, 22)
    ui_nav = font(FONT_UI_BOLD, 15)
    ui_i = font(FONT_UI_ITALIC, 20)

    y = 0
    bar_h = 36
    d.rectangle((0, y, sw, y + bar_h), fill=(12, 12, 12))
    d.text((sw // 2, y + bar_h // 2), "Caisse à outils Moto en stock", font=ui_i, fill=ORANGE, anchor="mm")
    y += bar_h

    header_h = 86
    logo_w = _paste_logo(img, (28, y + 12), 62)
    # Recherche centrée
    search_w, search_h = 520, 44
    sx = (sw - search_w) // 2
    sy = y + (header_h - search_h) // 2
    rounded_rect(d, (sx, sy, sx + search_w, sy + search_h), 22, WHITE, outline=(210, 210, 210), width=2)
    d.text((sx + 22, sy + search_h // 2), "Rechercher...", font=ui, fill=(150, 150, 150), anchor="lm")
    d.ellipse((sx + search_w - 38, sy + 10, sx + search_w - 18, sy + 30), outline=INK, width=2)
    d.line((sx + search_w - 22, sy + 26, sx + search_w - 12, sy + 36), fill=INK, width=2)

    def icon_lbl(cx: int, label: str, draw_icon):
        draw_icon(cx, y + 32)
        d.text((cx, y + 70), label, font=ui_sm, fill=INK, anchor="mm")

    def moto(cx, cy):
        d.ellipse((cx - 16, cy, cx - 4, cy + 12), outline=INK, width=2)
        d.ellipse((cx + 6, cy, cx + 18, cy + 12), outline=INK, width=2)
        d.line((cx - 4, cy + 6, cx + 6, cy + 6), fill=INK, width=2)

    def user(cx, cy):
        d.ellipse((cx - 7, cy - 10, cx + 7, cy + 4), outline=INK, width=2)
        d.arc((cx - 14, cy + 4, cx + 14, cy + 24), 200, 340, fill=INK, width=2)

    def cart(cx, cy):
        d.line((cx - 14, cy - 6, cx + 14, cy - 6), fill=INK, width=2)
        d.line((cx - 10, cy - 6, cx - 6, cy + 8), fill=INK, width=2)
        d.line((cx - 6, cy + 8, cx + 12, cy + 8), fill=INK, width=2)
        d.ellipse((cx - 4, cy + 10, cx + 4, cy + 18), outline=INK, width=2)
        d.ellipse((cx + 8, cy + 10, cx + 16, cy + 18), outline=INK, width=2)

    icon_lbl(sw - 220, "Mon garage", moto)
    icon_lbl(sw - 130, "Mon compte", user)
    icon_lbl(sw - 42, "Mon panier", cart)
    d.ellipse((sw - 28, y + 10, sw - 10, y + 28), fill=(220, 40, 40))
    d.text((sw - 19, y + 19), "1", font=font(FONT_UI_BOLD, 12), fill=WHITE, anchor="mm")
    y += header_h
    _ = logo_w

    nav_h = 46
    d.line((0, y, sw, y), fill=(230, 230, 230), width=1)
    items = [
        "EQUIPEMENT PILOTE",
        "MOTEUR MOTO QUAD",
        "PIÈCE MOTO QUAD",
        "EQUIPEMENT MOTO",
        "ATELIER MOTO",
        "SPORTSWEAR",
        "PROMOS FINS DE SERIES",
    ]
    x = 28
    for label in items:
        d.text((x, y + nav_h // 2), label, font=ui_nav, fill=INK, anchor="lm")
        tw = d.textlength(label, font=ui_nav)
        d.polygon(
            [(x + tw + 8, y + nav_h // 2 - 2), (x + tw + 16, y + nav_h // 2 - 2), (x + tw + 12, y + nav_h // 2 + 5)],
            fill=INK,
        )
        x += tw + 36
    y += nav_h

    moto_h = 70
    d.rectangle((0, y, sw, y + moto_h), fill=CYAN)
    d.text((28, y + 24), "AJOUTER MA MOTO", font=ui_b, fill=INK, anchor="lm")
    d.text((28, y + 50), "Pour voir les pièces compatibles", font=ui_sm, fill=(30, 30, 30), anchor="lm")
    # Faux selects
    sel_y = y + 16
    sel_h = 38
    sx = 430
    for placeholder in ("Marque...", "Année...", "Modèle..."):
        rounded_rect(d, (sx, sel_y, sx + 210, sel_y + sel_h), 6, WHITE)
        d.text((sx + 14, sel_y + sel_h // 2), placeholder, font=ui, fill=(90, 90, 90), anchor="lm")
        d.polygon([(sx + 190, sel_y + 14), (sx + 202, sel_y + 14), (sx + 196, sel_y + 24)], fill=(80, 80, 80))
        sx += 226
    rounded_rect(d, (sx, sel_y, sx + 150, sel_y + sel_h), 6, (180, 180, 180))
    d.text((sx + 75, sel_y + sel_h // 2), "Rechercher", font=ui_b, fill=WHITE, anchor="mm")
    y += moto_h

    slide = cover(Image.open(KIT / "slide-alpi-desktop.webp").convert("RGB"), (sw, 430))
    img.paste(slide, (0, y))
    cy = y + 215
    d.polygon([(24, cy), (58, cy - 28), (58, cy + 28)], fill=CYAN)
    d.polygon([(sw - 24, cy), (sw - 58, cy - 28), (sw - 58, cy + 28)], fill=CYAN)
    y += 430 + 16

    gap = 14
    card_w = (sw - 28 * 2 - gap * 2) // 3
    card_h = sh - y - 16
    paths = [
        KIT / "vignette-denali.png",
        KIT / "vignette-akrapovic.webp",
        KIT / "vignette-vertex.jpg",
    ]
    for i, path in enumerate(paths):
        card = cover(Image.open(path).convert("RGB"), (card_w, card_h))
        img.paste(card, (28 + i * (card_w + gap), y))
    return img


def compose() -> Image.Image:
    extra, muli, muli_sb = ensure_fonts()

    canvas = Image.new("RGBA", (W, H), (*DARK, 255))
    canvas.alpha_composite(radial_orb((W, H), (160, 50), 620, ORANGE, 46))
    canvas.alpha_composite(radial_orb((W, H), (1760, 30), 660, ORANGE, 48))
    canvas.alpha_composite(radial_orb((W, H), (960, -60), 400, (255, 150, 70), 20))

    title = Image.new("RGBA", (W, H), (0, 0, 0, 0))
    td = ImageDraw.Draw(title)
    f_title = font(extra, 96)
    td.text((64, 44), "REFONTE SITE", font=f_title, fill=(*CREAM, 220))
    td.text((64, 150), "E-COMMERCE", font=f_title, fill=(*CREAM, 220))
    canvas.alpha_composite(title)

    draw = ImageDraw.Draw(canvas)
    f_point = font(muli_sb, 28)
    y = 300
    for line in POINTS:
        draw.ellipse((72, y + 8, 86, y + 22), fill=ORANGE)
        draw.text((106, y), line, font=f_point, fill=(*CREAM, 235))
        y += 48

    desktop = build_desktop_screenshot()
    KIT.mkdir(parents=True, exist_ok=True)
    desktop.save(KIT / "3as-racing-desktop.jpg", "JPEG", quality=92, optimize=True)

    bx, by = 770, 398
    browser = liquid_glass_browser(
        desktop,
        canvas,
        (bx, by),
        content_width=1000,
        viewport_ratio=0.50,
    )
    canvas.alpha_composite(browser, (bx - 52, by - 52))

    url_font = font(extra, 24)
    draw.text((W - 48, H - 36), "arnaud-merigeau.fr", font=url_font, fill=ORANGE, anchor="rd")
    return canvas.convert("RGB")


def main() -> None:
    visual = compose()
    jpg = KIT / "visuel-3as-racing-refonte.jpg"
    png = KIT / "visuel-3as-racing-refonte.png"
    visual.save(jpg, "JPEG", quality=92, optimize=True, subsampling=0)
    visual.save(png, "PNG")
    shutil.copy2(jpg, ASSETS / "visuel-3as-racing-refonte.jpg")
    print(f"saved {jpg} {visual.size}")
    print(f"saved {png}")


if __name__ == "__main__":
    main()
