import { useEffect, useRef, useState } from "react"; import { createPortal } from "react-dom"; import { createSymbolMask, loadHellGirlImages } from "../utils/hellGirlImages"; import "./HellGirlFlames.scss"; /** Plamen vzplane a rozpadne se v jiskry za ~1,2 s, pak GIF drží černý snímek — mezitím se obsah vynoří z černé. */ const BURN_MS = 1500; /** `target` = obsah dialogu, ve kterém se hoří. */ type Burn = { src: string, target: HTMLElement }; /** * Doplňky stylu Hell Girl: při otevření dialogu jeho obsah zčerná, uprostřed jednou * vzplane plamen („Ippen, shinde miru?") a obsah se pak z černé vynoří. Dále symbol ohně ze znělky * u titulku dne (nastaví CSS proměnnou --hg-symbol, dokud se nenačte, zůstávají v titulku původní květy). */ const HellGirlFlames: React.FC = () => { const [burning, setBurning] = useState(); const flameBlobRef = useRef(undefined); const burn = (target: HTMLElement) => { const blob = flameBlobRef.current; if (!blob || window.matchMedia?.('(prefers-reduced-motion: reduce)').matches) return; // Pokaždé nová URL — jinak by prohlížeč GIF (bez opakování) znovu nepřehrál setBurning({ src: URL.createObjectURL(blob), target }); }; useEffect(() => { if (!burning) return; const timeout = setTimeout(() => setBurning(undefined), BURN_MS); return () => { clearTimeout(timeout); URL.revokeObjectURL(burning.src); }; }, [burning]); useEffect(() => { let cancelled = false; const root = document.documentElement; loadHellGirlImages().then(images => { if (!images || cancelled) return; fetch(images.flame) .then(r => r.ok ? r.blob() : undefined) .then(blob => { flameBlobRef.current = blob; }) .catch(() => { /* bez plamenů */ }); createSymbolMask(images.symbol).then(mask => { if (!mask || cancelled) return; root.style.setProperty('--hg-symbol', `url(${mask})`); root.setAttribute('data-hg-symbol', ''); }); }); return () => { cancelled = true; root.style.removeProperty('--hg-symbol'); root.removeAttribute('data-hg-symbol'); }; }, []); // Dialogy (react-bootstrap) se vkládají přes portál přímo do useEffect(() => { const observer = new MutationObserver(mutations => { const modalContent = mutations .flatMap(m => Array.from(m.addedNodes)) .map(node => node instanceof HTMLElement ? node.querySelector('.modal-content') : null) .find(element => !!element); if (modalContent) burn(modalContent); }); observer.observe(document.body, { childList: true }); return () => observer.disconnect(); }, []); if (!burning) return null; return createPortal( , burning.target, ); }; export default HellGirlFlames;