fix: ядро (15) правильные центры групп + оптимизация

This commit is contained in:
2026-09-06 02:10:42 +03:00
parent 5904fc675d
commit 0bf393616f
4 changed files with 77 additions and 18 deletions
+45 -13
View File
@@ -269,13 +269,23 @@ def generate_halftone_svg(
prime_base = PRIME_DELAYS[anim_index % len(PRIME_DELAYS)]
# Масштабируем задержку относительно длительности анимации с округлением до десятых
delay = round(prime_base * (duration / 2.0), 1)
# Вычисляем уникальный центр трансформации для каждой группы анимации.
# Центры группируются вокруг центра холста (50% 50%) с органическим разбросом,
# благодаря чему группы дышат и покачиваются вокруг слегка смещенных фокусов.
angle_jitter = (anim_index * (2 * math.pi / max(1, animation_variants))) + (seed % 7)
spread_factor = 0.02 + 0.13 * abs((scale - 1000) / 200.0) # от 2% до 15%
orig_x = round(50.0 + math.cos(angle_jitter) * spread_factor * 100)
orig_y = round(50.0 + math.sin(angle_jitter) * spread_factor * 100)
origin_rule = f";transform-origin:{orig_x}% {orig_y}%"
else:
delay = 0.0
origin_rule = ""
# Генерируем правило задержки в CSS
# Генерируем правило задержки и центра в CSS
if is_animated:
delay_str = f"{int(delay)}" if delay == int(delay) else f"{delay:.1f}".rstrip("0").rstrip(".")
animation_classes.append(f".a{encoded_class}{{--d:{delay_str}s}}")
animation_classes.append(f".a{encoded_class}{{--d:{delay_str}s{origin_rule}}}")
uses = "".join(
f'<use href="#{shape_id}" x="{cx}" y="{cy}"/>'
@@ -290,15 +300,29 @@ def generate_halftone_svg(
# 6. Стилизация и Keyframes
scale_val = max(0.5, min(1.5, scale / 1000.0))
scale_str = f"{scale_val:.3f}".rstrip("0").rstrip(".")
rot_str = f"{rotation}deg"
has_scale = scale_val != 1.0
has_rotation = rotation != 0
# Корректный цвет с прозрачностью
transform_0 = []
transform_100 = []
if has_scale:
transform_0.append("scale(1)")
transform_100.append(f"scale({scale_str})")
if has_rotation:
transform_0.append("rotate(0deg)")
transform_100.append(f"rotate({rotation}deg)")
tf_0_rule = f";transform:{' '.join(transform_0)}" if transform_0 else ""
tf_100_rule = f";transform:{' '.join(transform_100)}" if transform_100 else ""
# Корректный цвет и прозрачность
clean_color = color.strip() if color else "#a855ff"
if not clean_color.startswith("#"):
clean_color = f"#{clean_color}"
# Добавляем альфа-канал в hex при необходимости или используем CSS opacity
fill_style = f"fill:{clean_color};stroke:{clean_color};opacity:{opacity:.1f};"
has_custom_opacity = round(opacity, 2) < 1.0
opacity_rule = f"opacity:{opacity:.1f};" if has_custom_opacity else ""
fill_style = f"fill:{clean_color};stroke:{clean_color};{opacity_rule}"
if is_animated:
# Управление паузой/запуском через CSS Custom Property --hypn0-play.
@@ -309,23 +333,29 @@ def generate_halftone_svg(
f"animation-delay:var(--d,0s);"
f"animation-play-state:var(--hypn0-play,running);"
)
op_0 = f"{max(0.2, opacity * 0.7):.1f}".rstrip("0").rstrip(".")
op_50 = f"{opacity:.1f}".rstrip("0").rstrip(".")
op_100 = f"{max(0.1, opacity * 0.5):.1f}".rstrip("0").rstrip(".")
keyframes_rule = (
f"@keyframes noise{{"
f"0%{{opacity:{max(0.2, opacity * 0.7):.1f};transform:scale(1) rotate(0deg)}}"
f"50%{{opacity:{opacity:.1f}}}"
f"100%{{opacity:{max(0.1, opacity * 0.5):.1f};transform:scale({scale_str}) rotate({rot_str})}}"
f"0%{{opacity:{op_0}{tf_0_rule}}}"
f"50%{{opacity:{op_50}}}"
f"100%{{opacity:{op_100}{tf_100_rule}}}"
f"}}"
)
else:
anim_rule = "animation:none;"
keyframes_rule = ""
frozen_opacity = f";opacity:{opacity:.1f}!important" if has_custom_opacity else ""
svg_css = (
f"<style>"
f"svg{{background:transparent}}"
f"circle,rect,polygon,path,text{{{fill_style}transform-origin:center;{anim_rule}transition:all .5s ease-out}}"
f"circle,rect,polygon,path,text{{{fill_style}}}"
f"g[class^=\"a\"]{{{anim_rule}transition:all .5s ease-out}}"
f"{keyframes_rule}"
f".frozen circle,.frozen rect,.frozen polygon,.frozen path,.frozen text{{animation:none!important;opacity:{opacity:.1f}!important;transform:none!important}}"
f".frozen g[class^=\"a\"],.frozen circle,.frozen rect,.frozen polygon,.frozen path,.frozen text{{animation:none!important{frozen_opacity};transform:none!important}}"
f"{animation_css}"
f"</style>"
)
@@ -364,7 +394,7 @@ def prepare_gallery_svg(svg_content: str) -> str:
hover_css = (
"svg{--hypn0-play:paused}"
":host(:hover) svg,svg:hover{--hypn0-play:running!important}"
"circle,rect,polygon,path,text{animation-play-state:var(--hypn0-play,paused)!important}"
"g[class^=\"a\"],circle,rect,polygon,path,text{animation-play-state:var(--hypn0-play,paused)!important}"
)
if "</style>" in svg_content:
@@ -386,12 +416,14 @@ def prepare_active_svg(svg_content: str) -> str:
# Удаляем внедренные правила паузы через CSS-переменные и старые форматы
gallery_css_variants = [
"svg{--hypn0-play:paused}:host(:hover) svg,svg:hover{--hypn0-play:running!important}g[class^=\"a\"],circle,rect,polygon,path,text{animation-play-state:var(--hypn0-play,paused)!important}",
"svg{--hypn0-play:paused}:host(:hover) svg,svg:hover{--hypn0-play:running!important}.shape,circle,rect,polygon,path{animation-play-state:var(--hypn0-play,paused)!important}",
"svg{--hypn0-play:paused}:host(:hover) svg,svg:hover{--hypn0-play:running!important}circle,rect,polygon,path{animation-play-state:var(--hypn0-play,paused)!important}",
"svg{--hypn0-play:paused}:host(:hover) svg,svg:hover{--hypn0-play:running!important}circle,rect,polygon,path,text{animation-play-state:var(--hypn0-play,paused)!important}",
"svg{--hypn0-play:paused}svg:hover{--hypn0-play:running}.shape,circle,rect,polygon,path{animation-play-state:var(--hypn0-play,paused)!important}",
"svg{--hypn0-play:paused}svg:hover{--hypn0-play:running}circle,rect,polygon,path{animation-play-state:var(--hypn0-play,paused)!important}",
"svg{--hypn0-play:paused}svg:hover{--hypn0-play:running}circle,rect,polygon,path,text{animation-play-state:var(--hypn0-play,paused)!important}",
"svg{--hypn0-play:paused}svg:hover{--hypn0-play:running}g[class^=\"a\"],circle,rect,polygon,path,text{animation-play-state:var(--hypn0-play,paused)!important}",
]
cleaned = svg_content
for gcss in gallery_css_variants:
@@ -400,7 +432,7 @@ def prepare_active_svg(svg_content: str) -> str:
cleaned = re.sub(r"svg\s*\{[^}]*--hypn0-play:\s*paused[^}]*\}", "", cleaned)
cleaned = re.sub(r"(:host\(:hover\)\s*svg\s*,\s*)?svg:hover\s*\{[^}]*--hypn0-play:[^}]*\}", "", cleaned)
cleaned = re.sub(
r"(\.shape,)?circle,rect,polygon,path(,text)?\s*\{animation-play-state:\s*var\(--hypn0-play,\s*paused\)!important\}",
r"(g\[class\^=\"a\"\],)?(\.shape,)?circle,rect,polygon,path(,text)?\s*\{animation-play-state:\s*var\(--hypn0-play,\s*paused\)!important\}",
"",
cleaned,
)