Skip to content

Instantly share code, notes, and snippets.

@yelizariev
Last active July 31, 2026 08:37
Show Gist options
  • Select an option

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

Select an option

Save yelizariev/ab5b8d2890a0819259425906be945460 to your computer and use it in GitHub Desktop.
ХрущёвкаФон
Title Эхо Войны
Author Ivan Yelizariev
VHS https://moses.lamourism.com/exodus/BookOfNumbers.25:4-13.mp4
TV
001 002 003 005 007 013 017 019 023 029
name video
СМС от Сталина: «Ты еблан, каждый десятый в Сталинградской битве был украинцем»
PORTAL https://gist.mydream42.com/yelizariev/ab5b8d2890a0819259425906be945460/XXX/tv.html
Next https://rabota.mydream42.com/

image

All the world's a stage, the old bard said,
And we are players strutting through the light;
We speak the lines that countless others read,
Then vanish softly with the falling night.

We make our entrance, wear the masks we're given,
Trade tears for laughter, triumph, love, and shame;
We bow before the silent gaze of heaven,
Then leave behind no more than just a name.

Yet one lone soul abandoned every role,
Improvising where no script was known;
He saw the truth that waited for his soul—
No hidden door, no exit of his own.

He sought the hall where silent strangers gazed,
Believing they were free beyond the play;
But every seat became another stage,
Where every watcher knew just what to say.

He turned to flee through every open door;
Each doorway led where other actors trod.
The stage was larger than he'd dreamed before—
The world itself had never left the boards.

Two exits only waited at the end:
To bless the ones who broke him, and be free;
Or curse them all—and to the darkness bend.
The choice, not fate, revealed who he would be.

Title Работа? А что за работа?
Author Ivan Yelizariev
Image https://mao.lamourism.com/%E5%8C%97%E4%BA%AC%E9%A2%90%E5%92%8C%E5%9B%AD.jpg
PORTAL https://gist.odooism.com/yelizariev/f0e89c48583ab94cb751aa1b36eaf509/v3/Boss.html
JukeBOX https://mao.lamourism.com/phonecall.mp3
FlooPowder https://tv.mydream42.com
Next https://china.mydream42.com/

image We failed to teach your grandmother how to use a smartphone.

So we taught her Cold War television to receive video calls from the future.

Imagine it: flickering cathode rays in a Khrushchev-era apartment suddenly coming alive. Your grandmother’s ancient Soviet set — the same one that once broadcast grainy propaganda and black-and-white parades — now stares back at you in real time. No apps. No touchscreens. Just her, the humming tube, and your face appearing like a ghost from a timeline that should not exist.

A little forbidden technology. A little radioactive nostalgia. A spark of the kind of engineering madness that once made empires tremble.

Generational connection — restored through machines built for a war that never ended.

Now available as a simple service.
(For now.)

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>{{ markdown.yaml.Title }}</title>
<link rel="icon" type="image/png" href="{{ markdown.yaml.Icon | default: 'https://thepiratecircus.com/favicon.ico' }}"/>
<meta property="og:title" content="Whatever you think it is, it's not" />
<meta property="og:image" content="https://gist.github.com/user-attachments/assets/c64786b8-a32f-4dde-ab08-26cd9fad963a">
<meta property="og:image:type" content="image/png">
<link href="https://fonts.googleapis.com/css?family=Inconsolata&display=swap" rel="stylesheet">
<script src="https://npmcdn.com/regl@1.3.9/dist/regl.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gl-matrix/2.7.1/gl-matrix-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/onecolor/3.1.0/one-color-all.js"></script>
<style>
html, body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
overflow: hidden;
background: #000;
}
body {
display: flex;
justify-content: center;
align-items: center;
background: radial-gradient(circle at 50% 20%, #002a2a, #000);
box-sizing: border-box;
}
.container {
position: relative;
width: 100%;
max-width: 1520px;
aspect-ratio: 4 / 3;
border-radius: 35px;
display: flex;
align-items: center;
justify-content: center;
}
canvas {
display: block;
height: 80% !important;
border-radius: 5px;
box-shadow: 0 0 50px 35px rgba(0, 0, 0, 0.95);
}
/* Вертикальные экраны */
@media (max-aspect-ratio: 4/3) {
.container {
max-height: 94vh;
}
}
</style>
</head>
<body>
<div class="container">
<canvas id="canvas" width="640" height="480"></canvas>
</div>
<video
id="video"
style="display:none"
playsinline
preload="auto"
crossorigin="anonymous">
<source src="{{ markdown.yaml.VHS }}" type="video/mp4">
</video>
<script>
let videoStarted=false;
const TV = {{ markdown.yaml.TV }};
function updateTV(url, start) {
if (!url) return;
videoStarted = false;
const video = document.getElementById('video');
if (!video) return;
let source = video.querySelector('source');
if (source) {
source.src = url;
}
video.load();
start = start || 0;
video.addEventListener("loadedmetadata", function onLoaded() {
video.removeEventListener("loadedmetadata", onLoaded);
video.currentTime = parseFloat(start);
});
}
function handleHash() {
const tv = window.location.hash.substring(1);
if (tv && TV[tv]) {
updateTV(TV[tv].video, TV[tv].start);
if (TV[tv].name) {
document.title = TV[tv].name;
}
}
}
document.addEventListener('DOMContentLoaded', function() {
const urlParams = new URLSearchParams(window.location.search);
const testUrl = urlParams.get('test');
if (testUrl) {
updateTV(testUrl)
return
}
handleHash();
});
window.addEventListener('hashchange', () => {
handleHash();
});
console.pray = function (text) {
const lines = text.split('\n'); // разбиваем текст на строки
let delay = 500;
lines.forEach((line, index) => {
const wordCount = line.trim().split(/\s+/).length;
setTimeout(() => {
console.log(line);
}, delay);
delay += wordCount * 100; // добавляем задержку для следующей строки
});
};
function next(){
window.location.href="{{ markdown.yaml.Next }}";
}
function fullscreen() {
if (!document.fullscreenElement &&
!document.mozFullScreenElement &&
!document.webkitFullscreenElement &&
!document.msFullscreenElement) {
try {
if (document.documentElement.requestFullscreen) {
document.documentElement.requestFullscreen();
} else if (document.documentElement.mozRequestFullScreen) {
document.documentElement.mozRequestFullScreen();
} else if (document.documentElement.webkitRequestFullscreen) {
document.documentElement.webkitRequestFullscreen();
} else if (document.documentElement.msRequestFullscreen) {
document.documentElement.msRequestFullscreen();
}
} catch (error) {
console.warn("Fullscreen request failed:", error);
}
}
}
const onecolor = one.color;
function hex2vector(cssHex) {
const pc = onecolor(cssHex);
return vec3.fromValues(
pc.red(),
pc.green(),
pc.blue()
);
}
const charW = 6;
const charH = 10;
const bufferCW = 80;
const bufferCH = 24;
const bufferW = bufferCW * charW;
const bufferH = bufferCH * charH;
const textureW = 512;
const textureH = 256;
const consolePad = 8; // in texels
const consoleW = bufferW + consolePad * 2;
const consoleH = bufferH + consolePad * 2;
const bufferCanvas = document.createElement('canvas');
bufferCanvas.width = bufferW;
bufferCanvas.height = bufferH;
// document.body.appendChild(bufferCanvas);
const bufferContext = bufferCanvas.getContext('2d');
const video=document.getElementById("video");
document.addEventListener("click",()=>{
if(videoStarted) return;
fullscreen();
videoStarted=true;
video.play();
console.pray(`{{ markdown.RAW | replace: "`", "\`" }}`)
});
video.onended=next;
bufferContext.fillStyle = '#000';
bufferContext.fillRect(0, 0, bufferW, bufferH);
function charRange(start, end) {
return Array.apply(null, new Array(end - start)).map((_, index) => {
return String.fromCharCode(start + index);
});
}
const characterSet = ([]
.concat(charRange(0x30, 0x3a)) // ASCII digits
.concat(charRange(0x40, 0x5b)) // ASCII uppercase and @
.concat(charRange(0x30a0, 0x30ff)) // kanji
);
// const bannerSet = [
// '❤', '☠', '☣', '☻', '⚇', '⚿', '⛯'
// ];
const bannerImgSet = [
new Image(),
new Image(),
new Image(),
new Image()
];
bannerImgSet[0].src = 'data:image/svg+xml;utf8,' + encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 80" x="0px" y="0px"><g><path d="M26,44H38a7.00818,7.00818,0,0,0,7-7V26H19V37A7.00818,7.00818,0,0,0,26,44Zm8.419-9.81348,7-5a.99968.99968,0,1,1,1.1621,1.627l-7,5a.99968.99968,0,1,1-1.1621-1.627ZM21.186,29.419a.99814.99814,0,0,1,1.395-.23243l7,5a.99968.99968,0,1,1-1.1621,1.627l-7-5A.9994.9994,0,0,1,21.186,29.419Z"/><path d="M13,53v3h6V50H16A3.00328,3.00328,0,0,0,13,53Z"/><rect x="23" y="10" width="2" height="14"/><path d="M45,24V9A5,5,0,0,0,35,9V24h2V9a.99974.99974,0,0,1,1-1h4a.99974.99974,0,0,1,1,1V24Z"/><path d="M29,24V9A5,5,0,0,0,19,9V24h2V9a.99974.99974,0,0,1,1-1h4a.99974.99974,0,0,1,1,1V24Z"/><rect x="39" y="10" width="2" height="14"/><rect x="13" y="58" width="6" height="2"/><rect x="45" y="58" width="6" height="2"/><path d="M40,46H37v5a.99974.99974,0,0,1-1,1H28a.99974.99974,0,0,1-1-1V46H24a3.00328,3.00328,0,0,0-3,3V60h2V57a.99974.99974,0,0,1,1-1H40a.99974.99974,0,0,1,1,1v3h2V49A3.00328,3.00328,0,0,0,40,46Z"/><rect x="33" y="58" width="6" height="2"/><path d="M48,50H45v6h6V53A3.00328,3.00328,0,0,0,48,50Z"/><rect x="29" y="46" width="2" height="4"/><rect x="33" y="46" width="2" height="4"/><rect x="25" y="58" width="6" height="2"/></g></svg>');
bannerImgSet[1].src = 'data:image/svg+xml;utf8,' + encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 80" x="0px" y="0px"><path d="M20.67,28.71c6.92,0,7.67-4.23,7.67-6a7.67,7.67,0,0,0-15.33,0C13,24.48,13.75,28.71,20.67,28.71Zm0-9.71a3.67,3.67,0,0,1,3.67,3.67c0,.77,0,2-3.67,2S17,23.43,17,22.67A3.67,3.67,0,0,1,20.67,19Z"/><path d="M42.67,28.71c6.92,0,7.67-4.23,7.67-6a7.67,7.67,0,1,0-15.33,0C35,24.48,35.75,28.71,42.67,28.71Zm0-9.71a3.67,3.67,0,0,1,3.67,3.67c0,.77,0,2-3.67,2S39,23.43,39,22.67A3.67,3.67,0,0,1,42.67,19Z"/><path d="M25.14,34.5c0,2.49,4,3.21,6.5,3.21s6.5-.73,6.5-3.21-4-4.5-6.5-4.5S25.14,32,25.14,34.5Z"/><path d="M42,2H22A16,16,0,0,0,6,18V47a9,9,0,0,0,7.2,8.82A9,9,0,0,0,22,63H42a9,9,0,0,0,8.8-7.18A9,9,0,0,0,58,47V18A16,16,0,0,0,42,2ZM10,18A12,12,0,0,1,22,6H42A12,12,0,0,1,54,18V37.5A1.5,1.5,0,0,1,52.5,39,5.52,5.52,0,0,0,47,44.5V47H44V45a2,2,0,0,0-4,0v2H34V45a2,2,0,0,0-4,0v2H24V45a2,2,0,0,0-4,0v2H17V44.5A5.51,5.51,0,0,0,11.5,39a1.47,1.47,0,0,1-1.07-.46,1.45,1.45,0,0,1-.43-1V18Zm0,29V42.77a5.47,5.47,0,0,0,1.5.23A1.5,1.5,0,0,1,13,44.5v7.08A5,5,0,0,1,10,47Zm36.94,7.72A5,5,0,0,1,42,59H22a5,5,0,0,1-5-4.37A3.51,3.51,0,0,1,17,54V51h3v2a2,2,0,0,0,4,0V51h6v2a2,2,0,0,0,4,0V51h6v2a2,2,0,0,0,4,0V51h3v3A3.85,3.85,0,0,1,46.94,54.72ZM51,51.58V44.5a1.47,1.47,0,0,1,.46-1.07,1.45,1.45,0,0,1,1-.43,5.47,5.47,0,0,0,1.5-.21V47A5,5,0,0,1,51,51.58Z"/></svg>');
bannerImgSet[2].src = 'data:image/svg+xml;utf8,' + encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" x="0px" y="0px" viewBox="0 0 100 125"><symbol viewBox="-27.299 -39.024 54.598 78.047"><path fill="none" d="M11.157,34.02c-1.275-1.85-3.408-3.026-5.733-3.026c-2.13,0-4.118,0.985-5.424,2.607 c-1.306-1.622-3.294-2.607-5.423-2.607c-2.325,0-4.457,1.176-5.734,3.026c-1.275-1.85-3.408-3.026-5.733-3.026 c-1.913,0-3.648,0.776-4.908,2.029v-66.474c1.26,1.253,2.995,2.029,4.908,2.029c2.325,0,4.458-1.176,5.733-3.026 c1.277,1.85,3.409,3.026,5.734,3.026c2.129,0,4.117-0.986,5.423-2.607c1.306,1.622,3.294,2.607,5.424,2.607 c2.325,0,4.458-1.176,5.733-3.026c1.277,1.85,3.409,3.026,5.734,3.026c1.913,0,3.647-0.776,4.907-2.028v66.473 c-1.26-1.253-2.994-2.028-4.907-2.028C14.567,30.995,12.435,32.17,11.157,34.02z"/><path fill="#000000" stroke="#000000" stroke-width="3" stroke-miterlimit="10" d="M-20.463-37.524 c0.391,1.615,1.839,2.822,3.573,2.822c1.733,0,3.181-1.207,3.572-2.822h4.323c0.391,1.615,1.839,2.822,3.573,2.822 s3.182-1.207,3.573-2.822h3.701c0.391,1.615,1.839,2.822,3.573,2.822s3.182-1.207,3.573-2.822h4.322 c0.391,1.615,1.839,2.822,3.573,2.822s3.182-1.207,3.573-2.822h5.335v75.047h-5.266c-0.219-1.825-1.759-3.25-3.641-3.25 s-3.423,1.425-3.641,3.25H9.065c-0.219-1.825-1.759-3.25-3.641-3.25s-3.423,1.425-3.641,3.25h-3.564 c-0.219-1.825-1.759-3.25-3.641-3.25s-3.423,1.425-3.641,3.25h-4.186c-0.219-1.825-1.758-3.25-3.641-3.25 c-1.883,0-3.423,1.425-3.641,3.25h-5.267v-75.047H-20.463z"/></symbol><symbol viewBox="-30.282 -48.056 60.563 94.113"><path fill="#000000" stroke="#000000" stroke-width="6" stroke-miterlimit="10" d="M16.006,43.056 c-3.233-4.883-8.77-8.108-15.065-8.108s-11.832,3.225-15.065,8.108h-10.584v-88.113h10.584c3.233,4.883,8.77,8.108,15.065,8.108 s11.832-3.224,15.065-8.108H26.59v88.113H16.006z"/><line fill="none" stroke="#000000" stroke-width="5" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-dasharray="7,8" x1="27.781" y1="-20.755" x2="-27.782" y2="-20.755"/></symbol><symbol viewBox="-24.712 -42.834 54.356 85.667"><path fill="#000000" stroke="#000000" stroke-width="5" stroke-miterlimit="10" d="M17.181,40.333h-4.475 c-0.466-2.355-2.539-4.131-5.031-4.131c-2.491,0-4.565,1.776-5.03,4.131h-4.883c-0.466-2.355-2.539-4.131-5.031-4.131 c-2.491,0-4.565,1.776-5.03,4.131h-4.883c-0.466-2.355-2.539-4.131-5.03-4.131v-72.023c2.622,0,4.76-1.974,5.069-4.512h4.806 c0.309,2.539,2.447,4.512,5.069,4.512c2.622,0,4.76-1.974,5.069-4.512h4.806c0.309,2.539,2.447,4.512,5.069,4.512 c2.622,0,4.76-1.974,5.069-4.512h4.398c0.309,2.539,2.447,4.512,5.069,4.512v72.023C19.72,36.202,17.647,37.978,17.181,40.333z"/><line fill="none" stroke="#000000" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-dasharray="7,6" x1="-22.644" y1="-17.395" x2="27.644" y2="-17.395"/></symbol><symbol viewBox="-23.971 -42.094 47.944 84.188"><g><path fill="#000000" stroke="#000000" stroke-width="3.521" stroke-miterlimit="10" d="M17.182,40.333h-4.475 c-0.466-2.355-2.539-4.131-5.031-4.131c-2.491,0-4.565,1.776-5.03,4.131h-4.883c-0.466-2.355-2.539-4.131-5.031-4.131 c-2.491,0-4.565,1.776-5.03,4.131h-4.883c-0.466-2.355-2.539-4.131-5.03-4.131v-72.023c2.622,0,4.76-1.974,5.069-4.512h4.806 c0.309,2.539,2.447,4.512,5.069,4.512c2.622,0,4.76-1.974,5.069-4.512h4.806c0.309,2.539,2.447,4.512,5.069,4.512 c2.622,0,4.76-1.974,5.069-4.512h4.398c0.309,2.539,2.447,4.512,5.069,4.512v72.023C19.721,36.202,17.648,37.978,17.182,40.333z"/></g><polygon fill="#000000" stroke="#000000" stroke-width="3.8113" stroke-miterlimit="10" points="5.14,17.02 11.952,13.853 8.431,7.217 7.524,-0.241 0.124,1.057 -7.248,-0.385 -8.301,7.053 -11.951,13.62 -5.201,16.919 -0.084,22.42 "/></symbol><symbol id="a" viewBox="-21.087 -31.266 42.17 72.87"><path fill="#000000" stroke="#000000" stroke-width="5" stroke-miterlimit="10" d="M6.373,39.104h5.21c0-3.87,3.13-7,7-7v-53.87 c-3.87,0-7-3.14-7-7h-5.21"/><path fill="#000000" stroke="#000000" stroke-width="5" stroke-miterlimit="10" d="M-6.377-28.766h-5.21c0,3.86-3.13,7-7,7v53.87 c3.87,0,7,3.13,7,7h5.21"/><line fill="none" stroke="#000000" stroke-width="5" stroke-miterlimit="10" x1="2.373" y1="39.104" x2="-2.377" y2="39.104"/><line fill="none" stroke="#000000" stroke-width="5" stroke-miterlimit="10" x1="2.373" y1="-28.766" x2="-2.377" y2="-28.766"/><g><path stroke="#000000" stroke-width="0.9" stroke-miterlimit="10" d="M-9.901-10.118c0-0.994-0.806-1.8-1.8-1.8 s-1.8,0.806-1.8,1.8s0.806,1.8,1.8,1.8S-9.901-9.124-9.901-10.118z"/><path stroke="#000000" stroke-width="0.9" stroke-miterlimit="10" d="M-2.101-10.118c0-0.994-0.806-1.8-1.8-1.8 s-1.8,0.806-1.8,1.8s0.806,1.8,1.8,1.8S-2.101-9.124-2.101-10.118z"/><path stroke="#000000" stroke-width="0.9" stroke-miterlimit="10" d="M5.699-10.118c0-0.994-0.806-1.8-1.8-1.8s-1.8,0.806-1.8,1.8 s0.806,1.8,1.8,1.8S5.699-9.124,5.699-10.118z"/><path stroke="#000000" stroke-width="0.9" stroke-miterlimit="10" d="M13.499-10.118c0-0.994-0.806-1.8-1.8-1.8 s-1.8,0.806-1.8,1.8s0.806,1.8,1.8,1.8S13.499-9.124,13.499-10.118z"/></g></symbol><symbol viewBox="-21.086 -31.266 42.17 72.87"><use xlink:href="#a" width="42.17" height="72.87" x="-21.087" y="-31.266" transform="matrix(1 0 0 1 6.996482e-004 -2.671033e-004)" overflow="visible"/><line fill="none" stroke="#000000" stroke-width="4" stroke-miterlimit="10" x1="9.224" y1="13.75" x2="-9.223" y2="13.75"/></symbol><g display="none"><path display="inline" fill="#000000" d="M828.792,474.743H-409.006c-2.084,0-3.789-1.705-3.789-3.789V-716.211 c0-2.084,1.705-3.789,3.789-3.789H828.792c2.084,0,3.789,1.705,3.789,3.789V470.954 C832.582,473.038,830.877,474.743,828.792,474.743z"/></g><g><g><path d="M50,91.998c23.195,0,41.998-18.803,41.998-41.998C91.998,26.805,73.195,8.002,50,8.002 C26.805,8.002,8.002,26.805,8.002,50C8.002,73.195,26.805,91.998,50,91.998z M31.914,67.307l2.067-4.598 c0.255-0.569,0.734-1.003,1.318-1.213c0.24-0.075,0.479-0.12,0.719-0.12c0.374,0,0.734,0.09,1.063,0.27 c3.145,1.677,7.369,2.681,11.293,2.681c4.613,0,7.968-1.887,7.968-4.478c0-1.678-0.974-3.7-8.148-6.036 c-7.773-2.441-15.697-6.006-15.697-14.004c0-6.066,4.418-10.814,11.817-12.686l1.123-0.285v-5.018 c0-1.228,1.018-2.232,2.247-2.232h5.317c1.243,0,2.247,1.003,2.247,2.232v4.508l1.318,0.165c3.16,0.374,5.961,1.153,8.537,2.367 c0.539,0.255,0.959,0.704,1.153,1.273c0.21,0.554,0.18,1.183-0.075,1.707l-2.142,4.538c-0.374,0.779-1.168,1.288-2.037,1.288 c-0.315,0-0.629-0.075-0.929-0.21c-1.393-0.644-4.658-2.127-9.87-2.127c-4.733,0-6.845,1.827-6.845,3.655 c0,2.037,2.202,3.415,9.091,5.692c7.339,2.396,14.828,6.006,14.828,14.693c0,4.688-3.25,10.994-12.417,13.3l-1.123,0.299v5.197 c0,1.243-1.018,2.247-2.247,2.247h-5.407c-1.243,0-2.247-1.003-2.247-2.247v-4.763l-1.333-0.135 c-4.134-0.464-8.028-1.588-10.649-3.1C31.839,69.598,31.45,68.37,31.914,67.307z"/></g></g></svg>');
bannerImgSet[3].src = 'data:image/svg+xml;utf8,' + encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 44 55" x="0px" y="0px"><circle cx="22" cy="22" r="15.25" transform="translate(-9.11 22) rotate(-45)" style="" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="3px"/><line x1="18.41" y1="14.99" x2="19.9" y2="18.82" style="" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="3px"/><line x1="25.92" y1="14.99" x2="24.44" y2="18.82" style="" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="3px"/><line x1="16.29" y1="18.84" x2="27.71" y2="18.84" style="" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="3px"/><line x1="16.29" y1="24.41" x2="27.71" y2="24.41" style="" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="3px"/><line x1="22" y1="18.84" x2="22" y2="29.01" style="" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="3px"/></svg>');
// pseudo-random
// credit: https://gist.github.com/blixt/f17b47c62508be59987b
const SEED_OFFSET = new Date().getTime();
function randomize(seed) {
const intSeed = seed % 2147483647;
const safeSeed = intSeed > 0 ? intSeed : intSeed + 2147483646;
return safeSeed * 16807 % 2147483647;
}
function getRandomizedFraction(seed) {
return (seed - 1) / 2147483646;
}
// main character trail state
function createTrail() {
const cx = Math.floor(Math.random() * bufferCW);
const cy = 0;
const cvy = 5 + Math.random() * 15;
return [ cx, cy, cvy ];
}
const trails = Array.apply(null, new Array(30)).map((_, index) => {
return createTrail();
});
function updateWorld(delta) {
trails.forEach((trail, index) => {
trail[1] += trail[2] * delta;
if (trail[1] > bufferCH) {
trails[index] = createTrail();
}
});
}
// "warm up" the state by simulating the world for a bit
Array.apply(null, new Array(100)).forEach(() => {
updateWorld(0.1);
});
let fadeCountdown = 0;
let bannerCountdown = 8.0;
let bannerChoice = 0;
function renderWorld(delta) {
if(videoStarted && video.readyState>=2 && !video.ended){
const vw=video.videoWidth;
const vh=video.videoHeight;
const scale=Math.max(bufferW/vw,bufferH/vh);
const w=vw*scale;
const h=vh*scale;
const x=(bufferW-w)/2;
const y=(bufferH-h)/2;
bufferContext.drawImage(video,x,y,w,h);
}
// fade screen every few frames
// (not every frame, for long trails without rounding artifacts)
fadeCountdown -= delta;
if (fadeCountdown < 0) {
bufferContext.fillStyle = 'rgba(0, 0, 0, 0.5)';
bufferContext.fillRect(0, 0, bufferW, bufferH);
fadeCountdown += 0.2;
}
// redraw
bufferContext.textAlign = 'center';
bufferContext.font = '12px "Inconsolata"';
trails.forEach((trail, index) => {
const k = index / trails.length;
const charY = Math.floor(trail[1]);
// randomize based on character position
const charSeed = index + (trail[0] + charY * bufferCW) * 50;
const outSeed = randomize(charSeed * 1500 + SEED_OFFSET);
const char = characterSet[Math.floor(getRandomizedFraction(outSeed) * characterSet.length)];
bufferContext.fillStyle = `hsl(${180 + k * 120}, 100%, 60%)`;
bufferContext.fillText(
char,
(trail[0] + 0.5) * charW, // center inside character box
charY * charH + charH,
charW // restrict width, but allow a tiny bit of spillover
);
});
// fade screen every few frames
// (not every frame, for long trails without rounding artifacts)
bannerCountdown -= delta;
if (bannerCountdown < 1.5) {
bufferContext.fillStyle = `hsla(${180 + Math.random() * 220}, 100%, 30%, 0.4)`;
bufferContext.fillRect(0, 0, bufferW, bufferH);
if (bannerCountdown > 0.8) {
bannerChoice = Math.floor(Math.random() * bannerImgSet.length);
}
bufferContext.globalAlpha = 0.6;
const bannerImg = bannerImgSet[bannerChoice];
bufferContext.drawImage(bannerImg, 0, 10, bufferW, bufferH + 40);
bufferContext.globalAlpha = 1;
// bufferContext.fillStyle = `hsla(${100 + Math.random() * 220}, 100%, 10%, 1)`;
// bufferContext.font = '250px sans-serif';
// bufferContext.save();
// bufferContext.scale(2, 1);
// bufferContext.fillText(
// bannerSet[Math.floor(Math.random() * bannerSet.length)],
// 240 / 2, 200
// );
// bufferContext.restore();
}
if (bannerCountdown < 0) {
bannerCountdown += 30 + Math.random() * 10;
}
}
document.addEventListener('DOMContentLoaded', function() {
// init WebGL
const regl = createREGL({
canvas: document.body.querySelector('canvas'),
attributes: { antialias: true, alpha: false, preserveDrawingBuffer: true }
});
const spriteTexture = regl.texture({
width: 512,
height: 256,
mag: 'linear'
});
const termFgColor = hex2vector('#fee');
const termBgColor = hex2vector('#002a2a');
const quadCommand = regl({
vert: `
precision mediump float;
attribute vec3 position;
varying vec2 uvPosition;
void main() {
uvPosition = position.xy * vec2(0.5, -0.5) + vec2(0.5);
gl_Position = vec4(
vec2(-1.0, 1.0) + (position.xy - vec2(-1.0, 1.0)) * 1.0,
0.0,
1.0
);
}
`,
frag: `
precision mediump float;
varying vec2 uvPosition;
uniform sampler2D sprite;
uniform float time;
uniform vec3 bgColor;
uniform vec3 fgColor;
#define textureW ${textureW + '.0'}
#define textureH ${textureH + '.0'}
#define consoleW ${consoleW + '.0'}
#define consoleH ${consoleH + '.0'}
#define consolePadUVW ${consolePad / consoleW}
#define consolePadUVH ${consolePad / consoleH}
#define charUVW ${charW / consoleW}
#define charUVH ${charH / consoleH}
void main() {
// @todo use uniform
vec2 consoleWH = vec2(consoleW, consoleH);
// @todo use uniforms
float glitchLine = mod(0.8 + time * 0.07, 1.0);
float glitchFlutter = mod(time * 40.0, 1.0); // timed to be slightly out of sync from main frame rate
float glitchAmount = 0.06 + glitchFlutter * 0.01;
float glitchDistance = 0.04 + glitchFlutter * 0.15;
vec2 center = uvPosition - vec2(0.5);
float factor = dot(center, center) * 0.2;
vec2 distortedUVPosition = uvPosition + center * (1.0 - factor) * factor;
vec2 fromEdge = vec2(0.5, 0.5) - abs(distortedUVPosition - vec2(0.5, 0.5));
if (fromEdge.x > 0.0 && fromEdge.y > 0.0) {
vec2 fromEdgePixel = min(0.2 * consoleWH * fromEdge, vec2(1.0, 1.0));
// simulate 2x virtual pixel size, for crisp display on low-res
vec2 inTexel = mod(distortedUVPosition * consoleWH * 0.5, vec2(1.0));
float distToGlitch = glitchLine - (distortedUVPosition.y - inTexel.y / consoleH);
float glitchOffsetLinear = step(0.0, distToGlitch) * max(0.0, glitchDistance - distToGlitch) / glitchDistance;
float glitchOffset = glitchOffsetLinear * glitchOffsetLinear;
vec2 inTexelOffset = inTexel - 0.5;
float scanlineAmount = inTexelOffset.y * inTexelOffset.y / 0.25;
// float intensity = 8.0 - scanlineAmount * 5.0 + glitchOffset * 2.0; // ray intensity is over-amped by default
float intensity = 3.5 - scanlineAmount * 2.5 + glitchOffset * 0.6;
vec2 uvAdjustment = inTexelOffset * vec2(0.0, .5 / consoleH); // remove vertical texel interpolation
distortedUVPosition.x -= glitchOffset * glitchAmount + 0.011 * (glitchFlutter * glitchFlutter * glitchFlutter);
vec4 sourcePixel = texture2D(
sprite,
(distortedUVPosition - uvAdjustment) * consoleWH / vec2(textureW, textureH)
);
vec3 pixelRGB = sourcePixel.rgb * sourcePixel.a;
// multiply by source alpha as well
float screenFade = 1.0 - dot(center, center) * 1.8;
float edgeFade = fromEdgePixel.x * fromEdgePixel.y;
gl_FragColor = vec4(edgeFade * screenFade * mix(
bgColor,
fgColor,
// intensity * pixelRGB + glitchOffset * 1.5
intensity * pixelRGB + glitchOffset * 0.4
) * (1.0 - 0.2 * scanlineAmount), 0.2);
} else {
gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0);
}
}
`,
attributes: {
position: regl.buffer([
[ -1, -1, 0 ],
[ 1, -1, 0 ],
[ -1, 1, 0 ],
[ 1, 1, 0 ]
])
},
uniforms: {
time: regl.context('time'),
camera: regl.prop('camera'),
sprite: spriteTexture,
bgColor: regl.prop('bgColor'),
fgColor: regl.prop('fgColor')
},
primitive: 'triangle strip',
count: 4,
depth: {
enable: false
},
blend: {
enable: true,
func: {
src: 'src alpha',
dst: 'one minus src alpha'
}
}
});
regl.clear({
depth: 1,
color: [ 0, 0, 0, 1 ]
});
// main loop
let currentTime = performance.now();
function rafBody() {
// measure time
const newTime = performance.now();
const delta = Math.min(0.05, (newTime - currentTime) / 1000); // apply limiter to avoid frame skips
currentTime = newTime;
updateWorld(delta);
renderWorld(delta);
regl.poll();
spriteTexture.subimage(bufferContext, consolePad, consolePad);
quadCommand({
bgColor: termBgColor,
fgColor: termFgColor
});
requestAnimationFrame(rafBody);
}
// kickstart the loop
rafBody();
}); // DOMContentLoaded
</script>
</body>
</html>
<!-- Copyright: https://codepen.io/unframework/pen/pqorjJ -->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment