Skip to content

Instantly share code, notes, and snippets.

@yelizariev
Last active January 3, 2026 15:46
Show Gist options
  • Select an option

  • Save yelizariev/6a738322b614741c4e7f5d4c5fa88422 to your computer and use it in GitHub Desktop.

Select an option

Save yelizariev/6a738322b614741c4e7f5d4c5fa88422 to your computer and use it in GitHub Desktop.
wartime.mydream42.com
Title Author PORTAL Sources PowerPointMan Slides Doc Next Icon
Ласкаво просимо до Києва! Іпотека для молодих сімей від 7,9% 💙💛
Ivan Yelizariev
0 L 15 HolyWar 1.mp4 cut0 v1
0 R 15 HolyWar 1.mp4 cut9 v1
10 L 30 HolyWar 1.mp4 cut17 v1
10 R 30 HolyWar 1.mp4 cut9 v1
30 L 30 HolyWar 13.mp4 cut0 v1
30 R 30 HolyWar 13.mp4 cut9 v1
50 C 90 HolyWar 2.mp4 cut20 v1.4
86 L 15 HolyWar 3.mp4 cut0 v1
86 R 15 HolyWar 3.mp4 cut0 v1
90 L 8 HolyWar 7.mp4 cut4 v1
90 R 8 HolyWar 7.mp4 cut14 v1
120 C 45 HolyWar 11.mp4 cut0 v1
145 L 15 HolyWar 2.mp4 cut55 v1
145 R 15 HolyWar 2.mp4 cut76 v1
145 C 120 HolyWar 5.mp4 cut0 v1
155 L 30 HolyWar 1.mp4 cut9 v1
155 R 30 HolyWar 1.mp4 cut0 v1
188 L 90 HolyWar 17.mp4 cut4 v1
188 R 90 HolyWar 17.mp4 cut20 v1
225 C 300 HolyWar 0.mp4 cut0 v1
<showup> <target> <duration> <source> <filename> cut<cut> v<velocity>
<!DOCTYPE html><!--head
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀--> <html lang="en"> <!--
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢲⣾⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣴⠆ --> <head> <!--
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣿⡿⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣾⠏ --> <meta charset="UTF-8" /> <!--
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢘⣿⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣴⡿⣿⠃ --> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <!--
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⠼⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣾⢳⢰⡇ --> <title>{{ markdown.yaml.Title }}</title> <!--
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⣷⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⣿⣢⢹⣆⠀⠀⠀⠀⠀⠀⠀⠀⠀⣏⠲⣹⠂⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣠⣤⣄⣤⡴⠖⠃
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢻⣧⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠜⢧⢣⠎⠷⣄⠀⠀⠀⠀⠀⠀⠀⢧⢣⡙⢧⡀⠀⠀⠀⠀⠀⠀⠀⣠⣾⠛⣭⠶⠛⠉
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⡽⢧⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠳⣎⢣⠺⡽⣆⠀⠀⠀⠀⠀⢸⠇⡜⡩⣷⠀⠀⠀⠀⠀⠀⣴⢇⣺⡏⠁
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⢿⡘⢳⣄⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣸⡷⡑⢦⣿⠀⠀⠀⠀⡴⡫⠜⡰⣧⠏⠀⠀⠀⠀⠀⢸⢋⠦⣽⠁
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠈⣿⣶⢨⡍⠛⠛⢻⣦⡄⠀⠀⠀⠀⣿⢣⠑⣦⠘⣦⠀⠀⣼⢱⠉⡎⣵⡏⠀⠀⠀⠀⠀⢠⣾⠉⡖⣿
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠙⠶⢭⣜⣢⢃⠏⠻⣄⠀⠀⠀⣿⠎⡑⢆⠣⢍⠟⡭⠓⡌⠳⡘⢤⠳⣄⣀⢠⡴⢾⠛⠭⣘⣴⡏
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠱⣧⢊⡵⢊⠷⡴⣺⠹⢌⣙⣬⠵⠮⠖⠛⠛⠚⠓⠧⠮⣖⣩⢩⣉⢆⠧⣙⣲⡾⠋⠀⠀⠀⠀⠀⠀⠀⢀⣤⣤⣤⣤⣄⣀ --> <link rel="icon" type="image/png" href="{{ markdown.yaml.Icon | default: 'https://jesus.lamourism.com/favicon.ico' }}"/> <!--
⠀⠀⠀⢢⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢳⣌⢲⠩⡜⢡⢆⡷⠋⠁⠀⠀⢀⣀⡤⡤⠤⣄⣀⠀⠀⠉⠙⠲⣎⡲⢱⣏⠀⠀⠀⠀⠀⠀⠀⣰⣞⢫⣳⡬⠖⠒⠚⠛⠛⠒ --> <meta property="og:image" content="https://gist.github.com/user-attachments/assets/c64786b8-a32f-4dde-ab08-26cd9fad963a"> <!--
⠀⠀⠀⠀⠙⣿⣦⡀⠀⠀⠀⠀⣀⣠⣀⣄⣀⠀⠀⠀⠀⣿⠆⣇⠣⣥⠞⠀⢀⣠⢴⠺⢹⣈⠒⡍⡚⣌⡺⢩⠳⠦⣄⠀⠈⠱⢧⢊⠷⣠⣤⢴⠤⡤⢾⡑⢬⣶⠏ --> <meta property="og:title" content="Whatever you think it is, it's not" /> <!--
⠀⠀⠀⠀⠀⠘⢶⣭⣓⠶⠶⠿⠿⣋⠼⡑⣊⠗⣦⣤⠞⢣⠚⣴⠋⠁⣠⡔⡏⢎⢆⡣⠗⠒⠛⠚⠋⠉⠙⠧⣏⡜⣈⠳⡄⠀⠈⢳⡘⠴⡠⢎⡒⢥⢒⣬⠟⠃ --> <meta property="og:image:type" content="image/png"> <!-- ☀️ --> </head> <!--
⠀⠀⠀⠀⠀⠀⠀⠉⠓⠿⠬⠧⠶⠭⠶⣗⢍⡚⠴⣨⠙⡆⣿⠃⠀⣰⢋⠴⣩⠞⠁⠀⣠⣤⡤⠴⡴⣤⣄⡀⠈⠙⢆⡝⢜⢦⠀⠀⢻⡢⡑⣮⠼⠖⠛⠁
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠻⢽⣣⠜⡱⠰⡿⠀⠀⡏⡎⢼⡏⠀⢀⡾⢃⣶⠾⠷⢦⣑⠦⡙⣷⡀⠈⣏⡜⡸⣧⠀⠀⢷⣹⡇
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣇⠱⢳⡇⠀⢸⠧⣙⢼⡇⠀⢸⢳⣹⠃⠀⢀⠀⠈⢳⡜⣸⡇⠀⢸⡜⡡⢻⡄⠀⢸⢫⢓⡦⣤⡀⠀⠀⠀⢀⣀⣀⣤⣀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣠⣤⢤⢤⣤⡞⢣⢍⢻⡇⠀⢸⡆⢡⢺⡇⠀⢸⡇⡜⢦⣤⣾⡇⠀⠀⣟⢼⡇⠀⢸⡧⡑⣻⡇⠀⣸⢃⠎⡴⢡⢛⠳⡶⡿⢟⡩⢩⣱⣎⡻⣦⣄
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⡞⢥⠱⣈⠖⣢⠜⡥⢊⢼⡇⠀⠈⣇⢣⠎⣧⠀⠀⠻⣞⣰⣍⡿⠏⠀⢠⣟⣾⡇⠀⢸⠇⡥⣿⠀⠀⡏⣎⣼⣐⣣⠎⡱⣑⣴⡺⠞⠏⠉⠈⠉⠳⢭⣦⡀
⠐⠒⠲⢴⣦⣤⣄⣀⣠⣴⠛⡥⢋⣶⠕⠷⢮⣴⣭⣴⡋⠼⣷⡀⠀⠙⣧⡙⢸⢧⣀⣀⠀⠀⠀⠀⣠⡴⠯⣱⡞⠀⢀⡾⣱⣸⠃⠀⣸⣙⣼⠁⠀⠈⠙⠛⠉⠁⠀⠀⠀⠀⠀⠀⠀⠀⠈⠉⠒
⠀⠀⠀⠀⠈⠙⠿⠯⣭⣦⠽⠞⠋⠀⠀⠀⠀⠀⠀⠈⢿⡖⢤⠻⣦⡀⠈⠻⣆⠦⣩⢉⡍⢫⡝⣩⠱⣘⡵⠋⠀⢠⡞⡱⣱⠏⠀⢀⡷⠌⣷⡀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣠⡾⡘⢤⠓⡬⡕⢤⡀⠈⠙⠒⠧⠮⠥⠼⠴⠛⠉⠀⣠⠴⣫⠰⣣⠏⠀⢀⣾⡱⣉⠼⡹⣆
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⡞⢫⠱⢨⡑⢎⡑⢦⠙⣌⠻⠶⣤⣄⣀⣠⣄⣀⣠⡤⠶⡛⢭⠚⣀⡷⠋⠀⣠⡿⠿⠶⣥⢢⠑⣌⠻⡖⠶⢶⢤⣀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢰⡟⡌⢣⣓⡦⠾⠞⠋⠙⢷⣌⢣⠓⡔⢢⠆⡱⢘⣦⣽⣴⣥⢷⠼⠛⠉⠀⣠⢾⣯⠀⠀⠀⠈⠳⣽⣢⢵⣼⣑⣮⣘⣭⣷⡄ --> <body> <!--
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡞⢢⡙⡼⠉⠀⠀⠀⠀⠀⢀⡿⣄⢋⡔⢃⣎⣥⠿⣥⣀⠀⠀⠀⠀⠀⠀⡞⢭⠡⢾⠀⠀⠀⠀⠀⠀⠉⠉⠀⠀⠈⠉⠙⣾⣻⡆ --> <audio id="music" autoplay loop crossorigin="anonymous" style="display:none"> <!--
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣠⡞⣩⢣⡾⠃⠀⠀⠀⠀⠀⢠⡟⢣⠜⣢⣼⠟⠉⠘⡧⢡⢋⢻⣆⠀⠀⠀⠀⣿⣄⠳⡸⣆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠈⢿⣯⡄ --> <source src="https://moses.lamourism.com/radio/mishary-rashid-alafasy-130-muslimcentral.com.mp3" type="audio/mpeg"> <!--
⠀⠀⠀⠀⠀⠀⠀⠀⣀⣤⣿⡿⣟⢧⣛⡤⠟⠀⠀⠀⠀⠀⠀⠀⣿⢸⢃⢼⡼⠃⠀⠀⢠⡇⠇⡼⣸⡟⠀⠀⠀⠀⢻⣿⡄⢣⢛⢧⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢻⣇ --> </audio> <!--
⠀⠀⠀⠀⠀⠀⠀⠈⠉⠉⠉⠉⠉⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢿⡡⢎⣾⠀⠀⠀⠀⢸⣏⠲⡹⣿⠁⠀⠀⠀⠀⠀⠑⠾⣧⡈⢦⠹⣳⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠛⠂
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⣇⠎⣾⠄⠀⠀⠀⠠⠻⣖⠡⠻⣆⠀⠀⠀⠀⠀⠀⠀⠀⠉⠓⣧⣘⣯
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣏⣾⡟⠀⠀⠀⠀⠀⠀⠹⣎⡕⢺⡧⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣟⣿⠄
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⠶⣿⡮⠊⠀⠀⠀⠀⠀⠀⠀⠀⠘⣧⣻⠇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⣿⡿
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡾⠿⠋⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡼⣿⡟⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣾⡇
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠠⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣰⣿⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠸⠧
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣴⡿⠁
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣾⠋
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣞⢻⢦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠔⠑⠒⠂⢏⠵⢄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡔⠁⠀⠤⠀⠀⠈⡏⠡⠵⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡴⠋⠀⠒⠒⠋⠉⠉⠉⠸⡐⠛⠋⠳⡀⠀⠀⠀⠀⢀⣔⣻⣦⡀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⠊⠈⠀⠀⠀⠁⠀⢀⣀⣀⣀⣣⠡⠶⠤⠬⠦⡀⢀⡴⠣⣤⣉⣫⢻⣆⠀⠀⠀⠀⠀
⠀⠀⠀⣠⣾⣦⡀⠀⠀⠀⣠⠊⠀⠀⠀⠀⠀⠀⠀⠐⢀⣀⠀⠀⠈⡆⠒⠒⠒⠒⠘⢿⡒⠒⣤⣄⣈⡖⣤⣑⣄⠀⠀⠀
⠀⢀⣾⣿⡽⠿⣿⣦⣀⠞⠁⡀⠀⠀⢀⠈⠉⠉⠉⠉⠉⠉⠉⠁⠀⠸⡉⠩⠭⢥⠀⠀⠙⢶⠮⢤⣤⠸⡬⢭⡞⠳⡄⠀
⠐⠛⠚⠓⠒⠓⠓⠓⠓⠒⠒⠒⠒⠒⠒⠒⠛⠛⠛⠛⠛⠛⠛⠛⠒⠒⠓⠛⠛⠛⠛⠛⠛⠒⠓⠒⠒⠒⠓⠒⠛⠛⠛⠂
--><script type="x-shader/x-fragment">#version 300 es
/*********
* Author: Matthias Hurrle (@atzedent)
* Source: https://codepen.io/atzedent/pen/NWVYOMG
*/
precision highp float;
out vec4 O;
uniform float time;
uniform vec2 resolution;
#define FC gl_FragCoord.xy
#define R resolution
#define T time
#define hue(a) (.6+.6*cos(6.3*(a)+vec3(0,83,21)))
float rnd(float a) {
vec2 p=fract(a*vec2(12.9898,78.233)); p+=dot(p,p*345.);
return fract(p.x*p.y);
}
vec3 pattern(vec2 uv) {
vec3 col=vec3(0);
for (float i=.0; i++<20.;) {
float a=rnd(i);
vec2 n=vec2(a,fract(a*34.56)), p=sin(n*(T+7.)+T*.5);
float d=dot(uv-p,uv-p);
col+=.00125/d*hue(dot(uv,uv)+i*.125+T);
}
return col;
}
void main(void) {
vec2 uv=(FC-.5*R)/min(R.x,R.y);
vec3 col=vec3(0);
float s=2.4,
a=atan(uv.x,uv.y),
b=length(uv);
uv=vec2(a*5./6.28318,.05/tan(b)+T);
uv=fract(uv)-.5;
col+=pattern(uv*s);
O=vec4(col,1);
}
</script>
<script>
window.onload = init
function init() {
let renderer, canvas
const dpr = Math.max(1, .5*devicePixelRatio)
const resize = () => {
const { innerWidth: width, innerHeight: height } = window
canvas.width = width * dpr
canvas.height = height * dpr
if (renderer) {
renderer.updateScale(dpr)
}
}
const source = document.querySelector("script[type='x-shader/x-fragment']").textContent
canvas = document.createElement("canvas")
//document.body.innerHTML = ""
document.body.appendChild(canvas)
document.body.style = "margin:0;touch-action:none;overflow:hidden"
canvas.style.width = "100%"
canvas.style.height = "auto"
canvas.style.userSelect = "none"
renderer = new Renderer(canvas, dpr)
renderer.setup()
renderer.init()
resize()
if (renderer.test(source) === null) {
renderer.updateShader(source)
}
window.onresize = resize
const loop = (now) => {
renderer.render(now)
requestAnimationFrame(loop)
}
loop(0);
}
class Renderer {
#vertexSrc = "#version 300 es\nprecision highp float;\nin vec4 position;\nvoid main(){gl_Position=position;}"
#fragmtSrc = "#version 300 es\nprecision highp float;\nout vec4 O;\nuniform float time;\nuniform vec2 resolution;\nvoid main() {\n\tvec2 uv=gl_FragCoord.xy/resolution;\n\tO=vec4(uv,sin(time)*.5+.5,1);\n}"
#vertices = [-1, 1, -1, -1, 1, 1, 1, -1]
constructor(canvas, scale) {
this.canvas = canvas
this.scale = scale
this.gl = canvas.getContext("webgl2")
this.gl.viewport(0, 0, canvas.width * scale, canvas.height * scale)
this.shaderSource = this.#fragmtSrc
this.mouseCoords = [0, 0]
this.pointerCoords = [0, 0]
this.nbrOfPointers = 0
}
get defaultSource() { return this.#fragmtSrc }
updateShader(source) {
this.reset()
this.shaderSource = source
this.setup()
this.init()
}
updateMouse(coords) {
this.mouseCoords = coords
}
updatePointerCoords(coords) {
this.pointerCoords = coords
}
updatePointerCount(nbr) {
this.nbrOfPointers = nbr
}
updateScale(scale) {
this.scale = scale
this.gl.viewport(0, 0, this.canvas.width * scale, this.canvas.height * scale)
}
compile(shader, source) {
const gl = this.gl
gl.shaderSource(shader, source)
gl.compileShader(shader)
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
console.error(gl.getShaderInfoLog(shader))
this.canvas.dispatchEvent(new CustomEvent('shader-error', { detail: gl.getShaderInfoLog(shader) }))
}
}
test(source) {
let result = null
const gl = this.gl
const shader = gl.createShader(gl.FRAGMENT_SHADER)
gl.shaderSource(shader, source)
gl.compileShader(shader)
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
result = gl.getShaderInfoLog(shader)
}
if (gl.getShaderParameter(shader, gl.DELETE_STATUS)) {
gl.deleteShader(shader)
}
return result
}
reset() {
const { gl, program, vs, fs } = this
if (!program || gl.getProgramParameter(program, gl.DELETE_STATUS)) return
if (gl.getShaderParameter(vs, gl.DELETE_STATUS)) {
gl.detachShader(program, vs)
gl.deleteShader(vs)
}
if (gl.getShaderParameter(fs, gl.DELETE_STATUS)) {
gl.detachShader(program, fs)
gl.deleteShader(fs)
}
gl.deleteProgram(program)
}
setup() {
const gl = this.gl
this.vs = gl.createShader(gl.VERTEX_SHADER)
this.fs = gl.createShader(gl.FRAGMENT_SHADER)
this.compile(this.vs, this.#vertexSrc)
this.compile(this.fs, this.shaderSource)
this.program = gl.createProgram()
gl.attachShader(this.program, this.vs)
gl.attachShader(this.program, this.fs)
gl.linkProgram(this.program)
if (!gl.getProgramParameter(this.program, gl.LINK_STATUS)) {
console.error(gl.getProgramInfoLog(this.program))
}
}
init() {
const { gl, program } = this
this.buffer = gl.createBuffer()
gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer)
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(this.#vertices), gl.STATIC_DRAW)
const position = gl.getAttribLocation(program, "position")
gl.enableVertexAttribArray(position)
gl.vertexAttribPointer(position, 2, gl.FLOAT, false, 0, 0)
program.resolution = gl.getUniformLocation(program, "resolution")
program.time = gl.getUniformLocation(program, "time")
program.touch = gl.getUniformLocation(program, "touch")
program.pointerCount = gl.getUniformLocation(program, "pointerCount")
program.pointers = gl.getUniformLocation(program, "pointers")
}
render(now = 0) {
const { gl, program, buffer, canvas, mouseCoords, pointerCoords, nbrOfPointers } = this
if (!program || gl.getProgramParameter(program, gl.DELETE_STATUS)) return
gl.clearColor(0, 0, 0, 1)
gl.clear(gl.COLOR_BUFFER_BIT)
gl.useProgram(program)
gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
gl.uniform2f(program.resolution, canvas.width, canvas.height)
gl.uniform1f(program.time, now * 1e-3)
gl.uniform2f(program.touch, ...mouseCoords)
gl.uniform1i(program.pointerCount, nbrOfPointers)
gl.uniform2fv(program.pointers, pointerCoords)
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4)
}
}
</script>
<!-- Slides part. -->
<style>
/* ------------------------------------------------------------
Layout base
------------------------------------------------------------ */
html, body { height: 100%; }
body{
margin:0;
overflow:hidden;
background:#000;
color:#fff;
cursor:pointer;
font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
}
#slides{
position:fixed;
inset:0;
pointer-events:none;
z-index:10;
}
#preload{
position:fixed;
left:-10000px;
top:-10000px;
width:1px;
height:1px;
overflow:hidden;
opacity:0;
pointer-events:none;
z-index:-1;
}
:root{
--c-margin: 3vmin; /* center margin */
--lr-margin: 6vmin; /* left/right margin */
--c-max-w: 50vw; /* C: max-width 50% */
--lr-max-w: 40vw; /* L/R: 40% each */
--lr-max-h: 90vh; /* safety cap to avoid giant elements */
}
/* ------------------------------------------------------------
Slide wrapper (effects only)
------------------------------------------------------------ */
#slides .slide{
position:absolute;
opacity:0;
transform-origin:50% 50%;
will-change: transform, opacity;
filter: drop-shadow(0 20px 60px rgba(0,0,0,0.45));
/* absolute appear/disappear time for *-CC/*-LL/*-RR */
--edge: 2s;
--hold: max(0s, calc(var(--dur, 2s) - (var(--edge) * 2)));
}
/* oval defines real visible shape */
#slides .slide .oval{
display:inline-block;
width:fit-content;
height:fit-content;
border-radius:50%;
overflow:hidden;
-webkit-mask-image: radial-gradient(circle,
rgba(0,0,0,1) 60%,
rgba(0,0,0,0.85) 68%,
rgba(0,0,0,0.4) 75%,
rgba(0,0,0,0.0) 82%
);
mask-image: radial-gradient(circle,
rgba(0,0,0,1) 60%,
rgba(0,0,0,0.85) 68%,
rgba(0,0,0,0.4) 75%,
rgba(0,0,0,0.0) 82%
);
}
/* video keeps aspect ratio, does NOT crop */
#slides .slide .oval > video{
display:block;
width:auto;
height:auto;
object-fit:contain;
object-position:center;
}
/* ------------------------------------------------------------
Targets: L / C / R (regular)
------------------------------------------------------------ */
/* offsets from screen center to target anchor */
#slides .slide.t-L{
--from-x: 30vw;
--from-y: 0;
--to-x: 0;
--to-y: 0;
--exit-x: -25vw;
--exit-y: 0;
}
#slides .slide.t-R{
--from-x: -30vw;
--from-y: 0;
--to-x: 0;
--to-y: 0;
--exit-x: 25vw;
--exit-y: 0;
}
/* C: screen center, max-width 50% */
#slides .slide.t-C{
left:50%;
top:50%;
transform: translate(-50%, -50%) scale(0.05);
}
#slides .slide.t-C .oval > video{
max-width: min(var(--c-max-w), calc(100vw - var(--c-margin)*2));
max-height: calc(100vh - var(--c-margin)*2);
}
#slides .slide.t-C.play{
animation: popHoldOutCenter var(--dur, 2s) ease-in-out forwards;
}
/* L: left side, bottom aligned */
#slides .slide.t-L{
left: var(--lr-margin);
bottom: var(--lr-margin);
transform: translate(var(--from-x), var(--from-y)) scale(0.05);
}
#slides .slide.t-L .oval > video{
max-width: min(var(--lr-max-w), calc(100vw - var(--lr-margin)*2));
max-height: min(var(--lr-max-h), calc(100vh - var(--lr-margin)*2));
}
#slides .slide.t-L.play{
animation: popHoldOutSide var(--dur, 2s) ease-in-out forwards;
}
/* R: right side, top aligned */
#slides .slide.t-R{
right: var(--lr-margin);
top: var(--lr-margin);
transform: translate(var(--from-x), var(--from-y)) scale(0.05);
}
#slides .slide.t-R .oval > video{
max-width: min(var(--lr-max-w), calc(100vw - var(--lr-margin)*2));
max-height: min(var(--lr-max-h), calc(100vh - var(--lr-margin)*2));
}
#slides .slide.t-R.play{
animation: popHoldOutSide var(--dur, 2s) ease-in-out forwards;
}
/* ------------------------------------------------------------
Targets: LL / CC / RR (absolute 2s appear + 2s disappear)
Total time is still controlled by --dur on element.
------------------------------------------------------------ */
/* same motion vars as L */
#slides .slide.t-LL{
--from-x: 30vw;
--from-y: 0;
--to-x: 0;
--to-y: 0;
--exit-x: -25vw;
--exit-y: 0;
}
/* same motion vars as R */
#slides .slide.t-RR{
--from-x: -30vw;
--from-y: 0;
--to-x: 0;
--to-y: 0;
--exit-x: 25vw;
--exit-y: 0;
}
/* CC: screen center, max-width 50% */
#slides .slide.t-CC{
left:50%;
top:50%;
transform: translate(-50%, -50%) scale(0.05);
}
#slides .slide.t-CC .oval > video{
max-width: min(var(--c-max-w), calc(100vw - var(--c-margin)*2));
max-height: calc(100vh - var(--c-margin)*2);
}
#slides .slide.t-CC.play{
animation:
popInCenter var(--edge) ease-out forwards,
holdCenter var(--hold) linear forwards var(--edge),
popOutCenter var(--edge) ease-in forwards calc(var(--edge) + var(--hold));
}
/* LL: left side, bottom aligned */
#slides .slide.t-LL{
left: var(--lr-margin);
bottom: var(--lr-margin);
transform: translate(var(--from-x), var(--from-y)) scale(0.05);
}
#slides .slide.t-LL .oval > video{
max-width: min(var(--lr-max-w), calc(100vw - var(--lr-margin)*2));
max-height: min(var(--lr-max-h), calc(100vh - var(--lr-margin)*2));
}
#slides .slide.t-LL.play{
animation:
popInSide var(--edge) ease-out forwards,
holdSide var(--hold) linear forwards var(--edge),
popOutSide var(--edge) ease-in forwards calc(var(--edge) + var(--hold));
}
/* RR: right side, top aligned */
#slides .slide.t-RR{
right: var(--lr-margin);
top: var(--lr-margin);
transform: translate(var(--from-x), var(--from-y)) scale(0.05);
}
#slides .slide.t-RR .oval > video{
max-width: min(var(--lr-max-w), calc(100vw - var(--lr-margin)*2));
max-height: min(var(--lr-max-h), calc(100vh - var(--lr-margin)*2));
}
#slides .slide.t-RR.play{
animation:
popInSide var(--edge) ease-out forwards,
holdSide var(--hold) linear forwards var(--edge),
popOutSide var(--edge) ease-in forwards calc(var(--edge) + var(--hold));
}
/* ------------------------------------------------------------
Animations (regular)
------------------------------------------------------------ */
@keyframes popHoldOutCenter{
0% { opacity:0; transform: translate(-50%, -50%) scale(0.05); }
12% { opacity:0.95; transform: translate(-50%, -50%) scale(1.00); }
80% { opacity:0.85; transform: translate(-50%, -50%) scale(1.00); }
100% { opacity:0; transform: translate(-50%, -50%) scale(1.35); }
}
/* for L/R: no translate centering (anchored to corner-ish) */
@keyframes popHoldOutSide{
/* start: screen center */
0%{
opacity:0;
transform: translate(var(--from-x), var(--from-y)) scale(0.05);
}
/* arrive to side anchor */
12%{
opacity:0.95;
transform: translate(var(--to-x), var(--to-y)) scale(1);
}
/* main hold */
80%{
opacity:0.95;
transform: translate(var(--to-x), var(--to-y)) scale(1);
}
/* exit outward */
100%{
opacity:0;
transform: translate(var(--exit-x), var(--exit-y)) scale(1.35);
}
}
/* ------------------------------------------------------------
Animations (absolute edges)
Note: keyframes stay % based, but durations are absolute via --edge.
------------------------------------------------------------ */
@keyframes popInCenter{
from{
opacity:0;
transform: translate(-50%, -50%) scale(0.05);
}
to{
opacity:0.95;
transform: translate(-50%, -50%) scale(1.00);
}
}
@keyframes holdCenter{
from, to{
opacity:0.85;
transform: translate(-50%, -50%) scale(1.00);
}
}
@keyframes popOutCenter{
to{
opacity:0;
transform: translate(-50%, -50%) scale(1.35);
}
}
@keyframes popInSide{
from{
opacity:0;
transform: translate(var(--from-x), var(--from-y)) scale(0.05);
}
to{
opacity:0.95;
transform: translate(var(--to-x), var(--to-y)) scale(1.00);
}
}
@keyframes holdSide{
from, to{
opacity:0.95;
transform: translate(var(--to-x), var(--to-y)) scale(1.00);
}
}
@keyframes popOutSide{
to{
opacity:0;
transform: translate(var(--exit-x), var(--exit-y)) scale(1.35);
}
}
/* Reduce motion */
@media (prefers-reduced-motion: reduce){
#slides .slide{
animation:none !important;
opacity:1;
transform:none !important;
}
}
</style>
<div id="slides" aria-label="Slides"></div>
<div id="preload" aria-hidden="true"></div>
<script>
// ------------------------------
// Core refs
// ------------------------------
const audio = document.getElementById("music");
const slidesLayer = document.getElementById("slides");
const preloadLayer = document.getElementById("preload");
let happy = false;
let slidesStarted = false;
// ------------------------------
// Utils
// ------------------------------
// 中文 ✨ 祈祷打印:按词数延迟输出
// ✨ FR : Impression “prière” avec temporisation proportionnelle
console.pray = function (text) {
const lines = String(text || "").split("\n");
let delay = 500;
lines.forEach((line) => {
const wc = line.trim() ? line.trim().split(/\s+/).length : 0;
setTimeout(() => console.log(line), delay);
delay += Math.max(1, wc) * 100;
});
};
// ------------------------------
// Sources + GET overrides
// ------------------------------
window.APP_CONFIG = {
Sources: {
{%- assign sources = markdown.yaml.Sources -%}
{%- if sources -%}
{%- for s in sources -%}
"{{ s[0] }}": "{{ s[1] }}"{%- unless forloop.last -%},{%- endunless -%}
{%- endfor -%}
{%- endif -%}
}
};
function applySourceOverridesFromQuery(sources) {
const params = new URLSearchParams(window.location.search);
Object.keys(sources || {}).forEach((key) => {
if (!params.has(key)) return;
const value = params.get(key);
if (!value) return;
try {
const u = new URL(value, window.location.href);
sources[key] = u.href;
console.log(`[override] ${key} -> ${u.href}`);
} catch (e) {
console.warn(`[override] invalid url for ${key}:`, value);
}
});
return sources;
}
applySourceOverridesFromQuery(window.APP_CONFIG.Sources);
function buildVideoUrl(sourceKey, filename) {
const sources = window.APP_CONFIG?.Sources || {};
const base = sources[sourceKey] || "";
if (!base) return String(filename || "");
const b = String(base);
const f = String(filename || "");
if (b.endsWith("/") && f.startsWith("/")) return b + f.slice(1);
if (!b.endsWith("/") && !f.startsWith("/")) return b + "/" + f;
return b + f;
}
function normalizeTarget(t) {
const x = String(t || "C").toUpperCase();
return ["C", "L", "R", "CC", "LL", "RR"].includes(x) ? x : "C";
}
// ------------------------------
// Schedule
// Example:
// - 0 L 30 HolyWar 1.mp4 cut0 v1
// Doc: "<showup> <target> <duration> <source> <filename> cut<cut> v<velocity>"
// ------------------------------
const SCHEDULE = [
{%- assign slides = markdown.yaml.Slides -%}
{%- if slides -%}
{%- for spec in slides -%}
{%- assign s = spec | strip -%}
{%- assign parts = s | split: ' ' -%}
{%- assign showup_s = parts[0] | plus: 0 -%}
{%- assign target = parts[1] -%}
{%- assign dur_s = parts[2] | plus: 0 -%}
{%- assign source_key = parts[3] -%}
{%- assign filename = parts[4] -%}
{%- assign cut_token = parts[5] | default: 'cut0' -%}
{%- assign vel_token = parts[6] | default: 'v1' -%}
{%- assign cut_s_str = cut_token | remove: 'cut' -%}
{%- assign vel_str = vel_token | remove: 'v' -%}
{
showupMs: {{ showup_s | times: 1000 }},
durationMs: {{ dur_s | times: 1000 }},
sourceKey: "{{ source_key }}",
filename: "{{ filename }}",
cutMs: {{ cut_s_str | times: 1000 }},
speed: {{ vel_str | default: 1 }},
target: "{{ target }}"
}{%- unless forloop.last -%},{%- endunless -%}
{%- endfor -%}
{%- endif -%}
]
.map(x => ({ ...x, target: normalizeTarget(x.target) }))
.sort((a, b) => a.showupMs - b.showupMs);
// ------------------------------
// Preload + cloning strategy
// Goals:
// - preload once per URL (one hidden base element in DOM)
// - for each slide instance, create a NEW video element (clone) so slides can overlap
// - clones also start hidden/offscreen until attached to a slide
// ------------------------------
const PRELOAD_LEAD_MS = 60000;
// base (hidden) video per URL, used only for warming network/cache
const baseVideoByUrl = new Map();
function hideVideoOffscreen(v) {
v.style.position = "fixed";
v.style.left = "-10000px";
v.style.top = "-10000px";
v.style.width = "1px";
v.style.height = "1px";
v.style.opacity = "0";
v.style.pointerEvents = "none";
}
function resetVideoInlineVisibility(v) {
v.style.position = "";
v.style.left = "";
v.style.top = "";
v.style.width = "";
v.style.height = "";
v.style.opacity = "";
v.style.pointerEvents = "";
}
function ensureBaseVideo(url, idx) {
const key = idx + ":" + url;
if (baseVideoByUrl.has(key)) return baseVideoByUrl.get(key);
const v = document.createElement("video");
v.src = url;
v.muted = true;
v.playsInline = true;
v.preload = "auto";
v.loop = false;
v.autoplay = true;
hideVideoOffscreen(v);
preloadLayer.appendChild(v);
try { v.load(); } catch (_) {}
baseVideoByUrl.set(key, v);
return v;
}
function preloadUrl(url, idx) {
ensureBaseVideo(url, idx);
}
// Create a fresh playable instance (clone)
// 中文 ✨ 每个 slide 都要独立 video 实例,支持并行播放
// ✨ FR : Chaque slide doit avoir sa propre instance vidéo (lecture en parallèle)
function createVideoInstance(url, idx) {
// ensure preload exists first (so network cache is warm)
v = ensureBaseVideo(url, idx);
v.src = url;
v.muted = true;
v.playsInline = true;
v.preload = "auto";
v.loop = true;
v.autoplay = false;
// start hidden/offscreen until attached
hideVideoOffscreen(v);
preloadLayer.appendChild(v);
try { v.load(); } catch (_) {}
return v;
}
// ------------------------------
// Slide rendering (audio-driven, supports overlaps)
// ------------------------------
// Track which slides are currently mounted (can be multiple)
const activeSlides = new Map(); // idx -> { el, v, endMs }
function isActiveAt(s, tMs) {
return tMs >= s.showupMs && tMs < (s.showupMs + s.durationMs);
}
function startVideoFrom(v, { cutMs, speed }) {
const cutS = Math.max(0, Number(cutMs || 0)) / 1000;
const sp = Number(speed || 1);
try { v.pause(); } catch (_) {}
try { v.playbackRate = sp; } catch (_) {}
try { v.currentTime = cutS; } catch (_) {}
const onMeta = () => {
try { v.playbackRate = sp; } catch (_) {}
try {
if (cutS > 0 && isFinite(v.duration)) {
v.currentTime = Math.min(Math.max(0, cutS), Math.max(0, v.duration - 0.05));
} else {
v.currentTime = cutS;
}
} catch (_) {}
v.removeEventListener("loadedmetadata", onMeta);
};
v.addEventListener("loadedmetadata", onMeta);
Promise.resolve().then(() => v.play().catch(() => {}));
}
function mountSlide(idx) {
const item = SCHEDULE[idx];
console.log("start slide", item);
const url = buildVideoUrl(item.sourceKey, item.filename);
// fresh instance for parallel playback
const v = createVideoInstance(url, idx);
// wrapper: new element => CSS animation restarts
const el = document.createElement("div");
el.className = `slide t-${item.target}`;
el.style.setProperty("--dur", (Math.max(300, item.durationMs) / 1000) + "s");
const inner = document.createElement("div");
inner.className = "oval";
inner.appendChild(v);
el.appendChild(inner);
// attach visible
resetVideoInlineVisibility(v);
slidesLayer.appendChild(el);
// 1) Do NOT start animation yet. Wait for stable layout.
// 中文 ✨ 等 metadata,避免视频尺寸后到导致 translate(-50%) 跳动
// ✨ FR : Attendre metadata pour stabiliser la taille avant l’animation
const startVisual = () => {
// 2) force layout now that dimensions exist
void el.offsetWidth;
// 3) next frame -> add .play to start CSS animation
requestAnimationFrame(() => {
// extra frame helps on Safari/iOS
requestAnimationFrame(() => el.classList.add("play"));
});
};
if (v.readyState >= 1) {
startVisual();
} else {
v.addEventListener("loadedmetadata", startVisual, { once: true });
}
// start playback (ok to start now; visual animation waits for metadata anyway)
startVideoFrom(v, { cutMs: item.cutMs, speed: item.speed });
activeSlides.set(idx, { el, v, endMs: item.showupMs + item.durationMs });
}
function unmountSlide(idx) {
const rec = activeSlides.get(idx);
if (!rec) return;
const { el, v } = rec;
try { v.pause(); } catch (_) {}
hideVideoOffscreen(v);
preloadLayer.appendChild(v);
el.remove();
activeSlides.delete(idx);
}
// -------------------------------------------
// GET ?start=<seconds> : start audio from there + ignore previous slides
// 中文 ✨ 读取 start 参数(秒),从该位置开始播放,并忽略之前的幻灯片
// ✨ FR : Lire le paramètre start (secondes), démarrer l’audio à ce point et ignorer les slides précédents
// -------------------------------------------
const START_AT_SEC = (() => {
const p = new URLSearchParams(window.location.search);
if (!p.has("start")) return null;
const raw = p.get("start");
const n = Number(raw);
return Number.isFinite(n) && n >= 0 ? n : null;
})();
// 只渲染从这个时间点之后的 slides
// ✨ Ne rendre que les slides après ce seuil temporel
const START_AT_MS = START_AT_SEC != null ? START_AT_SEC * 1000 : null;
function schedulePreloads() {
SCHEDULE.forEach((item, idx) => {
if (item.showupMs < START_AT_MS)
return;
const url = buildVideoUrl(item.sourceKey, item.filename);
const whenMs = Math.max(0, item.showupMs - PRELOAD_LEAD_MS - START_AT_MS);
setTimeout(() => preloadUrl(url, idx), whenMs);
});
}
// -------------------------------------------
// Helper: active check with "ignore previous slides"
// -------------------------------------------
function isActiveAtWithStart(s, tMs) {
if (START_AT_MS) {
// ignore all slides that end before start
if ((s.showupMs) <= START_AT_MS) return false;
// also ignore any time before start
if (tMs < START_AT_MS) return false;
}
return isActiveAt(s, tMs);
}
// Call this once, right after you set audio.src (and before play if possible)
function applyStartOffsetToAudioOnce() {
if (START_AT_SEC == null) return;
let applied = false;
const apply = () => {
if (applied) return;
applied = true;
try {
// clamp if duration is known
if (isFinite(audio.duration) && audio.duration > 0) {
audio.currentTime = Math.min(Math.max(0, START_AT_SEC), Math.max(0, audio.duration - 0.05));
} else {
audio.currentTime = START_AT_SEC;
}
} catch (_) {}
// If any slides were mounted before (shouldn't happen if you call early), clean them
try {
for (const idx of Array.from(activeSlides.keys())) unmountSlide(idx);
} catch (_) {}
};
// If metadata is already available, apply immediately; else wait.
if (audio.readyState >= 1) apply();
else audio.addEventListener("loadedmetadata", apply, { once: true });
// Safety: in some browsers seeking is allowed only after canplay
audio.addEventListener("canplay", apply, { once: true });
}
function tickSlides() {
if (!slidesStarted) return;
const tSec = Number(audio.currentTime || 0);
const tMs = tSec * 1000;
// show time if admin mode
updateAdminHud(tSec);
// 1) mount any slides that should be active but aren't mounted
for (let i = 0; i < SCHEDULE.length; i++) {
const s = SCHEDULE[i];
if (isActiveAtWithStart(s, tMs) && !activeSlides.has(i)) {
mountSlide(i);
}
}
// 2) unmount slides that are no longer active
for (const [idx, rec] of activeSlides.entries()) {
const s = SCHEDULE[idx];
if (!isActiveAtWithStart(s, tMs)) unmountSlide(idx);
}
requestAnimationFrame(tickSlides);
}
function startSlidesByAudio() {
if (slidesStarted) return;
console.log("startSlidesByAudio");
slidesStarted = true;
// if start param is present and audio is currently before it, don't show slides yet
// (tickSlides already enforces that, but this avoids flash if anything was mounted)
if (START_AT_MS != null) {
for (const idx of Array.from(activeSlides.keys())) unmountSlide(idx);
}
requestAnimationFrame(tickSlides);
}
// admin mode: ?admin=admin -> show current seconds at bottom-center
// 中文 ✨ admin=admin 时显示当前音频秒数(底部居中)
// ✨ FR : si admin=admin, afficher les secondes courantes (bas-centre)
const ADMIN_MODE = (() => {
const p = new URLSearchParams(window.location.search);
return p.get("admin") === "admin";
})();
let adminHudEl = null;
function ensureAdminHud() {
if (!ADMIN_MODE) return null;
if (adminHudEl) return adminHudEl;
adminHudEl = document.createElement("div");
adminHudEl.id = "admin-hud";
adminHudEl.style.position = "fixed";
adminHudEl.style.left = "50%";
adminHudEl.style.bottom = "12px";
adminHudEl.style.transform = "translateX(-50%)";
adminHudEl.style.zIndex = "9999";
adminHudEl.style.pointerEvents = "none";
adminHudEl.style.padding = "6px 10px";
adminHudEl.style.borderRadius = "10px";
adminHudEl.style.background = "rgba(0,0,0,0.55)";
adminHudEl.style.backdropFilter = "blur(6px)";
adminHudEl.style.webkitBackdropFilter = "blur(6px)";
adminHudEl.style.color = "#fff";
adminHudEl.style.font = "600 33px/1.2 system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif";
adminHudEl.style.letterSpacing = "0.02em";
document.body.appendChild(adminHudEl);
return adminHudEl;
}
function updateAdminHud(tSec) {
if (!ADMIN_MODE) return;
const el = ensureAdminHud();
if (!el) return;
el.textContent = `t = ${tSec.toFixed(2)}s`;
}
// ------------------------------
// Audio sequence
// ------------------------------
const MAIN_URL = "{{ markdown.yaml.PowerPointMan }}";
const INTRO_SECONDS = 12;
const startTime = Date.now();
function startAudioSequence() {
const mainPreload = document.createElement("audio");
mainPreload.src = MAIN_URL;
mainPreload.preload = "auto";
try { mainPreload.load(); } catch (_) {}
let switched = false;
function switchToMain() {
if (switched) return;
switched = true;
audio.pause();
audio.src = MAIN_URL;
audio.loop = false;
applyStartOffsetToAudioOnce();
try { audio.currentTime = 0; } catch (_) {}
audio.play().catch(() => {});
audio.addEventListener("ended", next, { once: true });
}
try { audio.currentTime = 0; } catch (_) {}
console.log("Start Intro");
ensureAdminHud();
const onTime = () => {
if (!switched && (Date.now() - startTime) >= INTRO_SECONDS * 1000) {
startSlidesByAudio();
switchToMain();
audio.removeEventListener("timeupdate", onTime);
}
};
audio.addEventListener("timeupdate", onTime);
audio.addEventListener("ended", () => {
if (!switched) {
startSlidesByAudio();
switchToMain();
}
}, { once: true });
audio.addEventListener("error", () => {
if (!switched) {
startSlidesByAudio();
switchToMain();
}
}, { once: true });
audio.play().catch(err => console.log("Autoplay blocked:", err));
}
// ------------------------------
// Click bootstrap
// ------------------------------
document.addEventListener("click", function () {
if (happy) return;
schedulePreloads();
startAudioSequence();
console.pray(`{{ markdown.RAW | replace: "`", "\`" }}`);
happy = true;
}, { once: true });
</script>
<script>
function next() {
const urlParams = new URLSearchParams(window.location.search);
const witch = urlParams.get("debug") || "{{ markdown.yaml.Next }}";
window.location.href = witch;
}
</script></body><!--
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣄
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠟⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⠆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣭⡆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣹⠄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⡁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣀⣤⠤⢤⣀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣠⠴⠒⢋⣉⣀⣠⣄⣀⣈⡇
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣸⡆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣴⣾⣯⠴⠚⠉⠉⠀⠀⠀⠀⣤⠏⣿
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡿⡇⠁⠀⠀⠀⠀⡄⠀⠀⠀⠀⠀⠀⠀⠀⣠⣴⡿⠿⢛⠁⠁⣸⠀⠀⠀⠀⠀⣤⣾⠵⠚⠁
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠰⢦⡀⠀⣠⠀⡇⢧⠀⠀⢀⣠⡾⡇⠀⠀⠀⠀⠀⣠⣴⠿⠋⠁⠀⠀⠀⠀⠘⣿⠀⣀⡠⠞⠛⠁⠂⠁⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡈⣻⡦⣞⡿⣷⠸⣄⣡⢾⡿⠁⠀⠀⠀⣀⣴⠟⠋⠁⠀⠀⠀⠀⠐⠠⡤⣾⣙⣶⡶⠃⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣂⡷⠰⣔⣾⣖⣾⡷⢿⣐⣀⣀⣤⢾⣋⠁⠀⠀⠀⣀⢀⣀⣀⣀⣀⠀⢀⢿⠑⠃--></html><!--
⠀⠀⠀⠀⠀⠀⠠⡦⠴⠴⠤⠦⠤⠤⠤⠤⠤⠴⠶⢾⣽⣙⠒⢺⣿⣿⣿⣿⢾⠶⣧⡼⢏⠑⠚⠋⠉⠉⡉⡉⠉⠉⠹⠈⠁⠉⠀⠨⢾⡂
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠂⠀⠀⠀⠂⠐⠀⠀⠀⠈⣇⡿⢯⢻⣟⣇⣷⣞⡛⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣠⣆⠀⠀⠀⠀⢠⡷⡛⣛⣼⣿⠟⠙⣧⠅⡄⠀⠀⠀⠀⠀⠀⠰⡆⠀⠀⠀⠀⢠⣾⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣴⢶⠏⠉⠀⠀⠀⠀⠀⠿⢠⣴⡟⡗⡾⡒⠖⠉⠏⠁⠀⠀⠀⠀⣀⢀⣠⣧⣀⣀⠀⠀⠀⠚⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⣠⢴⣿⠟⠁⠀⠀⠀⠀⠀⠀⠀⣠⣷⢿⠋⠁⣿⡏⠅⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠙⣿⢭⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⢀⡴⢏⡵⠛⠀⠀⠀⠀⠀⠀⠀⣀⣴⠞⠛⠀⠀⠀⠀⢿⠀⠂⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠂⢿⠘⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⣀⣼⠛⣲⡏⠁⠀⠀⠀⠀⠀⢀⣠⡾⠋⠉⠀⠀⠀⠀⠀⠀⢾⡅⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⡴⠟⠀⢰⡯⠄⠀⠀⠀⠀⣠⢴⠟⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⣹⠆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⡾⠁⠁⠀⠘⠧⠤⢤⣤⠶⠏⠙⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢾⡃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠘⣇⠂⢀⣀⣀⠤⠞⠋⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣼⠇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠈⠉⠉⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠾⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢼⡆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢰⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠛-->
Title Author PORTAL FlooPowder
Товарищ Горбачёв, срочное сообщение ТАСС: в СССР закончилась водка!
Ivan Yelizariev
Кто на лавочке сидел,
Кто на улицу глядел,
Толя пел,
Борис молчал,
Николай ногой качал.

Дело было вечером,
Делать было нечего.

Галка села на заборе,
Кот забрался на чердак.
Тут сказал ребятам Боря
Просто так:
— А у меня в кармане гвоздь!
А у вас?
— А у нас сегодня гость!
А у вас?
— А у нас сегодня кошка
Родила вчера котят.
Котята выросли немножко,
А есть из блюдца не хотят!

— А у нас в квартире газ!
А у вас?

— А у нас —
                                                                                                                                
                                                                                                                                
                                                       ▐▓▐▐                                                                     
    ▐██████▐                                         ▐███░░                                                            ██       
    ▐█▏▐▐▐█▐                                        ▐██▐                                                               ██       
    ▐█▏   █▐           ▐▐                           ▓█ ▐▐              ▐▐               ▐▐              ▐▐             ██       
    ▐█▏   █▐         ▐████▐         ▐█▐  ▐█▏        █▏████▐         ▐█████▐           ▓████▐          ▓████▐           ██       
    ▐█▏   █▐        ▐██▐▐██▐        ▐█▐  ▐█▏        ███▐▐██▐        ▐░▐▐▐██▐         ▓█░▐▐▐▏         ▓█░▐▐▐▏           ██       
    ▐█▏   █▐        ▓▎▐  ▐█▏        ▐█▐  ▐█▏        █▎▐  ▐█▏             ▐█▏        ▐█▏             ▐█▏                ██       
    ▐█▏   █▐        █▎    █▒        ▐██▓▓██▏        █▎    █▒         ▐▓████▏        ▐█▏             ▐█▏                ██       
    ▐█▏   █▐        ██    ██        ▐██████▏        ██    ██        ▐██░▐▐█▏        ▐█▐             ▐█▐                █▒       
    ▐█▐   █▐        █▎    █▒        ▐█▐  ▐█▏        █▎    █▒        ▓█   ▐█▏        ▐█▏             ▐█▏                ▐▐       
    ▓█▐   █▐        ▓▎▐  ▐█▏        ▐█▐  ▐█▏        ▓▎▐  ▐█▏        █▒   ▐█▏        ▐█▏             ▐█▏                ▐▐       
   ▐███▓▓▓██▐       ▐██▐▐▓█▐        ▐█▐  ▐█▏        ▐██▐▐▓█▐        ██▐▐▐██▏         ███▐▐▓▏         ███▐▐▓▏           ██       
   ▓████████▐        ▐████▐         ▐█▐  ▐█▏         ▐████▐         ▐████▐█▏          ▐████░          ▐████░           ██       
   ▓█      █▐          ▐▐                              ▐▐             ▐▐                ▐▐              ▐▐                      
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment