forked from SpoTUI/SpoTUI
feat(theme): animated the ascii logo
This commit is contained in:
@@ -1,3 +1,5 @@
|
|||||||
|

|
||||||
|
|
||||||
# SpoTUI
|
# SpoTUI
|
||||||
|
|
||||||
SpoTUI is a terminal-style Spotify skin built for Spicetify.
|
SpoTUI is a terminal-style Spotify skin built for Spicetify.
|
||||||
@@ -10,7 +12,6 @@ It combines:
|
|||||||
## Screenshot
|
## Screenshot
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Terminal-inspired interface
|
- Terminal-inspired interface
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 940 KiB |
BIN
Binary file not shown.
|
Before Width: | Height: | Size: 304 KiB After Width: | Height: | Size: 326 KiB |
@@ -28,7 +28,7 @@ const style = `
|
|||||||
top: 41%;
|
top: 41%;
|
||||||
transform: translate(-50%, -50%);
|
transform: translate(-50%, -50%);
|
||||||
color: #ff8c42;
|
color: #ff8c42;
|
||||||
opacity: 0.28;
|
opacity: 1;
|
||||||
white-space: pre;
|
white-space: pre;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-family: "JetBrains Mono", "Fira Code", monospace;
|
font-family: "JetBrains Mono", "Fira Code", monospace;
|
||||||
@@ -37,6 +37,64 @@ const style = `
|
|||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
z-index: 0;
|
z-index: 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spotui-ascii-grid {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
line-height: 1;
|
||||||
|
font-size: clamp(9px, 1.4vw, 22px);
|
||||||
|
letter-spacing: 0;
|
||||||
|
font-weight: 400;
|
||||||
|
font-variant-ligatures: none;
|
||||||
|
font-kerning: none;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
user-select: none;
|
||||||
|
white-space: pre;
|
||||||
|
padding: 20px;
|
||||||
|
contain: layout style paint;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spotui-ascii-row {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
white-space: nowrap;
|
||||||
|
contain: layout style paint;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spotui-ascii-char {
|
||||||
|
display: inline-block;
|
||||||
|
font-size: clamp(9px, 1.4vw, 22px);
|
||||||
|
line-height: 1;
|
||||||
|
width: 1ch;
|
||||||
|
text-align: left;
|
||||||
|
position: relative;
|
||||||
|
text-shadow: 0 0 6px currentColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 700px) {
|
||||||
|
.spotui-ascii-grid {
|
||||||
|
font-size: clamp(5px, 1.1vw, 11px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.spotui-ascii-char {
|
||||||
|
font-size: clamp(5px, 1.1vw, 11px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 450px) {
|
||||||
|
.spotui-ascii-grid {
|
||||||
|
font-size: clamp(3.5px, 1.4vw, 7px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.spotui-ascii-char {
|
||||||
|
font-size: clamp(3.5px, 1.4vw, 7px);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#spotui-output {
|
#spotui-output {
|
||||||
@@ -178,19 +236,497 @@ body.spotui-tui-hidden #spotui-popup {
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const SPOTUI_ASCII_ART = [
|
const SPOTUI_ASCII_ART = [
|
||||||
" ██████ ██▓███ ▒█████ ▄▄▄█████▓ █ ██ ██▓",
|
" ▄████████ ▄███████▄ ▄██████▄ ███ ███ █▄ ▄█ ",
|
||||||
"▒██ ▒ ▓██░ ██▒▒██▒ ██▒▓ ██▒ ▓▒ ██ ▓██▒▓██▒",
|
" ███ ███ ███ ███ ███ ███ ▀█████████▄ ███ ███ ███ ",
|
||||||
"░ ▓██▄ ▓██░ ██▓▒▒██░ ██▒▒ ▓██░ ▒░▓██ ▒██░▒██▒",
|
" ███ █▀ ███ ███ ███ ███ ▀███▀▀██ ███ ███ ███▌ ",
|
||||||
" ▒ ██▒▒██▄█▓▒ ▒▒██ ██░░ ▓██▓ ░ ▓▓█ ░██░░██░",
|
" ███ ███ ███ ███ ███ ███ ▀ ███ ███ ███▌ ",
|
||||||
"▒██████▒▒▒██▒ ░ ░░ ████▓▒░ ▒██▒ ░ ▒▒█████▓ ░██░",
|
"▀███████████ ▀█████████▀ ███ ███ ███ ███ ███ ███▌ ",
|
||||||
"▒ ▒▓▒ ▒ ░▒▓▒░ ░ ░░ ▒░▒░▒░ ▒ ░░ ░▒▓▒ ▒ ▒ ░▓ ",
|
" ███ ███ ███ ███ ███ ███ ███ ███ ",
|
||||||
"░ ░▒ ░ ░░▒ ░ ░ ▒ ▒░ ░ ░░▒░ ░ ░ ▒ ░",
|
" ▄█ ███ ███ ███ ███ ███ ███ ███ ███ ",
|
||||||
"░ ░ ░ ░░ ░ ░ ░ ▒ ░ ░░░ ░ ░ ▒ ░",
|
" ▄████████▀ ▄████▀ ▀██████▀ ▄████▀ ████████▀ █▀ ",
|
||||||
" ░ ░ ░ ░ ░ ",
|
|
||||||
" ",
|
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const GLITCH_CHARS = "01";
|
||||||
|
const ORANGE_PALETTE = [
|
||||||
|
"#ff6a00",
|
||||||
|
"#ff7a0a",
|
||||||
|
"#ff8c1a",
|
||||||
|
"#ff9e33",
|
||||||
|
"#ffb04d",
|
||||||
|
"#ffc266",
|
||||||
|
"#ffd480",
|
||||||
|
"#ffe699",
|
||||||
|
];
|
||||||
|
|
||||||
|
function getCharColor(row, col, totalRows, totalCols) {
|
||||||
|
const normRow = row / Math.max(totalRows - 1, 1);
|
||||||
|
const normCol = col / Math.max(totalCols - 1, 1);
|
||||||
|
const mix = normRow * 0.55 + normCol * 0.45;
|
||||||
|
const idx = Math.floor(mix * (ORANGE_PALETTE.length - 1));
|
||||||
|
const frac = mix * (ORANGE_PALETTE.length - 1) - idx;
|
||||||
|
const i = Math.min(idx, ORANGE_PALETTE.length - 2);
|
||||||
|
const c1 = ORANGE_PALETTE[i];
|
||||||
|
const c2 = ORANGE_PALETTE[i + 1] || ORANGE_PALETTE[i];
|
||||||
|
const r1 = parseInt(c1.slice(1, 3), 16);
|
||||||
|
const g1 = parseInt(c1.slice(3, 5), 16);
|
||||||
|
const b1 = parseInt(c1.slice(5, 7), 16);
|
||||||
|
const r2 = parseInt(c2.slice(1, 3), 16);
|
||||||
|
const g2 = parseInt(c2.slice(3, 5), 16);
|
||||||
|
const b2 = parseInt(c2.slice(5, 7), 16);
|
||||||
|
const r = Math.round(r1 + (r2 - r1) * frac);
|
||||||
|
const g = Math.round(g1 + (g2 - g1) * frac);
|
||||||
|
const b = Math.round(b1 + (b2 - b1) * frac);
|
||||||
|
return `rgb(${r},${g},${b})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
let asciiAnimationInitialized = false;
|
||||||
|
let asciiCharData = [];
|
||||||
|
|
||||||
|
function initAsciiAnimation() {
|
||||||
|
if (asciiAnimationInitialized) return;
|
||||||
|
asciiAnimationInitialized = true;
|
||||||
|
|
||||||
|
const logo = document.getElementById("spotui-logo");
|
||||||
|
if (!logo) return;
|
||||||
|
|
||||||
|
logo.innerHTML = "";
|
||||||
|
|
||||||
|
const grid = document.createElement("div");
|
||||||
|
grid.className = "spotui-ascii-grid";
|
||||||
|
logo.appendChild(grid);
|
||||||
|
|
||||||
|
const rows = SPOTUI_ASCII_ART.length;
|
||||||
|
const cols = Math.max(...SPOTUI_ASCII_ART.map((row) => row.length));
|
||||||
|
const charData = [];
|
||||||
|
const rowSpansCache = [];
|
||||||
|
|
||||||
|
SPOTUI_ASCII_ART.forEach((line, rowIdx) => {
|
||||||
|
const rowDiv = document.createElement("div");
|
||||||
|
rowDiv.className = "spotui-ascii-row";
|
||||||
|
rowDiv.dataset.row = rowIdx;
|
||||||
|
const padded = line.padEnd(cols, " ");
|
||||||
|
const chars = [...padded];
|
||||||
|
const rowSpans = [];
|
||||||
|
chars.forEach((ch, colIdx) => {
|
||||||
|
const span = document.createElement("span");
|
||||||
|
span.className = "spotui-ascii-char";
|
||||||
|
span.textContent = ch;
|
||||||
|
span.dataset.row = rowIdx;
|
||||||
|
span.dataset.col = colIdx;
|
||||||
|
span.dataset.original = ch;
|
||||||
|
const color = getCharColor(rowIdx, colIdx, rows, cols);
|
||||||
|
span.style.color = color;
|
||||||
|
span.dataset.origColor = color;
|
||||||
|
charData.push({
|
||||||
|
row: rowIdx,
|
||||||
|
col: colIdx,
|
||||||
|
el: span,
|
||||||
|
original: ch,
|
||||||
|
color,
|
||||||
|
});
|
||||||
|
rowSpans.push(span);
|
||||||
|
rowDiv.appendChild(span);
|
||||||
|
});
|
||||||
|
rowSpansCache.push(rowSpans);
|
||||||
|
grid.appendChild(rowDiv);
|
||||||
|
});
|
||||||
|
|
||||||
|
asciiCharData = charData;
|
||||||
|
|
||||||
|
function resetGrid() {
|
||||||
|
charData.forEach(({ el, original, color }) => {
|
||||||
|
el.textContent = original;
|
||||||
|
el.style.color = color;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRowSpans(rowIdx) {
|
||||||
|
return rowSpansCache[rowIdx] || [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
|
|
||||||
|
async function decryptRow(rowIdx) {
|
||||||
|
const spans = getRowSpans(rowIdx);
|
||||||
|
if (!spans.length) return;
|
||||||
|
const chars = [...GLITCH_CHARS];
|
||||||
|
const origs = spans.map((span) => span.dataset.original || " ");
|
||||||
|
const colors = spans.map((span) => span.dataset.origColor || "#ff8c1a");
|
||||||
|
|
||||||
|
spans.forEach((span) => {
|
||||||
|
span.textContent = chars[Math.floor(Math.random() * chars.length)];
|
||||||
|
});
|
||||||
|
|
||||||
|
const indices = Array.from({ length: spans.length }, (_, i) => i);
|
||||||
|
for (let i = indices.length - 1; i > 0; i -= 1) {
|
||||||
|
const j = Math.floor(Math.random() * (i + 1));
|
||||||
|
[indices[i], indices[j]] = [indices[j], indices[i]];
|
||||||
|
}
|
||||||
|
|
||||||
|
const batchSize = 4;
|
||||||
|
for (let start = 0; start < indices.length; start += batchSize) {
|
||||||
|
const batch = indices.slice(start, start + batchSize);
|
||||||
|
batch.forEach((idx) => {
|
||||||
|
spans[idx].textContent = chars[Math.floor(Math.random() * chars.length)];
|
||||||
|
});
|
||||||
|
await sleep(8);
|
||||||
|
batch.forEach((idx) => {
|
||||||
|
spans[idx].textContent = origs[idx];
|
||||||
|
spans[idx].style.color = colors[idx];
|
||||||
|
});
|
||||||
|
await sleep(6);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function glitchRowWave(rowIdx, duration = 500) {
|
||||||
|
const spans = getRowSpans(rowIdx);
|
||||||
|
if (!spans.length) return;
|
||||||
|
const origs = spans.map((span) => span.dataset.original || " ");
|
||||||
|
const colors = spans.map((span) => span.dataset.origColor || "#ff8c1a");
|
||||||
|
const chars = [...GLITCH_CHARS];
|
||||||
|
const steps = 8;
|
||||||
|
for (let step = 0; step < steps; step += 1) {
|
||||||
|
spans.forEach((span) => {
|
||||||
|
const randIdx = Math.floor(Math.random() * chars.length);
|
||||||
|
span.textContent = chars[randIdx];
|
||||||
|
const hue = 20 + Math.random() * 35;
|
||||||
|
span.style.color = `hsl(${hue}, 100%, ${50 + Math.random() * 30}%)`;
|
||||||
|
});
|
||||||
|
await sleep(Math.floor(duration / steps));
|
||||||
|
}
|
||||||
|
spans.forEach((span, i) => {
|
||||||
|
span.textContent = origs[i] || " ";
|
||||||
|
span.style.color = colors[i] || "#ff8c1a";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function burstGlitch(duration = 800) {
|
||||||
|
const centerRow = Math.floor(rows / 2);
|
||||||
|
const centerCol = Math.floor(cols / 2);
|
||||||
|
const withDist = charData.map((entry) => {
|
||||||
|
const dr = entry.row - centerRow;
|
||||||
|
const dc = entry.col - centerCol;
|
||||||
|
return { ...entry, dist: Math.sqrt(dr * dr + dc * dc) };
|
||||||
|
});
|
||||||
|
const maxDist = Math.max(...withDist.map((entry) => entry.dist), 1);
|
||||||
|
const chars = [...GLITCH_CHARS];
|
||||||
|
const steps = 8;
|
||||||
|
for (let step = 0; step < steps; step += 1) {
|
||||||
|
const progress = step / steps;
|
||||||
|
withDist.forEach(({ el, original, color, dist }) => {
|
||||||
|
const norm = dist / maxDist;
|
||||||
|
const threshold = progress * 1.1;
|
||||||
|
if (norm < threshold + 0.12 && norm > threshold - 0.12) {
|
||||||
|
if (Math.random() < 0.75) {
|
||||||
|
const randIdx = Math.floor(Math.random() * chars.length);
|
||||||
|
el.textContent = chars[randIdx];
|
||||||
|
el.style.color = `hsl(${20 + Math.random() * 35}, 100%, ${50 + Math.random() * 30}%)`;
|
||||||
|
}
|
||||||
|
} else if (norm < threshold - 0.12) {
|
||||||
|
el.textContent = original;
|
||||||
|
el.style.color = color;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await sleep(Math.floor(duration / steps));
|
||||||
|
}
|
||||||
|
resetGrid();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function pulseGlitch(duration = 1200) {
|
||||||
|
const centerRow = Math.floor(rows / 2);
|
||||||
|
const centerCol = Math.floor(cols / 2);
|
||||||
|
const withDist = charData.map((entry) => {
|
||||||
|
const dr = entry.row - centerRow;
|
||||||
|
const dc = entry.col - centerCol;
|
||||||
|
return { ...entry, dist: Math.sqrt(dr * dr + dc * dc) };
|
||||||
|
});
|
||||||
|
const maxDist = Math.max(...withDist.map((entry) => entry.dist), 1);
|
||||||
|
const chars = [...GLITCH_CHARS];
|
||||||
|
const waves = 3;
|
||||||
|
const stepsPerWave = 10;
|
||||||
|
|
||||||
|
for (let wave = 0; wave < waves; wave += 1) {
|
||||||
|
for (let step = 0; step < stepsPerWave; step += 1) {
|
||||||
|
const progress = step / stepsPerWave;
|
||||||
|
const threshold = progress * 1.0;
|
||||||
|
withDist.forEach(({ el, original, color, dist }) => {
|
||||||
|
const norm = dist / maxDist;
|
||||||
|
if (norm < threshold + 0.1 && norm > threshold - 0.1) {
|
||||||
|
if (Math.random() < 0.7) {
|
||||||
|
const randIdx = Math.floor(Math.random() * chars.length);
|
||||||
|
el.textContent = chars[randIdx];
|
||||||
|
el.style.color = `hsl(${20 + Math.random() * 35}, 100%, ${55 + Math.random() * 25}%)`;
|
||||||
|
}
|
||||||
|
} else if (norm < threshold - 0.1 && wave === waves - 1) {
|
||||||
|
el.textContent = original;
|
||||||
|
el.style.color = color;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await sleep(Math.floor(duration / (waves * stepsPerWave)));
|
||||||
|
}
|
||||||
|
await sleep(40);
|
||||||
|
}
|
||||||
|
resetGrid();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function implosionGlitch(duration = 900) {
|
||||||
|
const centerRow = Math.floor(rows / 2);
|
||||||
|
const centerCol = Math.floor(cols / 2);
|
||||||
|
const withDist = charData.map((entry) => {
|
||||||
|
const dr = entry.row - centerRow;
|
||||||
|
const dc = entry.col - centerCol;
|
||||||
|
return { ...entry, dist: Math.sqrt(dr * dr + dc * dc) };
|
||||||
|
});
|
||||||
|
const maxDist = Math.max(...withDist.map((entry) => entry.dist), 1);
|
||||||
|
const chars = [...GLITCH_CHARS];
|
||||||
|
const steps = 10;
|
||||||
|
|
||||||
|
withDist.forEach(({ el }) => {
|
||||||
|
const randIdx = Math.floor(Math.random() * chars.length);
|
||||||
|
el.textContent = chars[randIdx];
|
||||||
|
el.style.color = `hsl(${20 + Math.random() * 35}, 100%, ${45 + Math.random() * 35}%)`;
|
||||||
|
});
|
||||||
|
|
||||||
|
for (let step = 0; step < steps; step += 1) {
|
||||||
|
const progress = step / steps;
|
||||||
|
const threshold = 1.0 - progress * 1.1;
|
||||||
|
withDist.forEach(({ el, original, color, dist }) => {
|
||||||
|
const norm = dist / maxDist;
|
||||||
|
if (norm <= threshold) {
|
||||||
|
el.textContent = original;
|
||||||
|
el.style.color = color;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await sleep(Math.floor(duration / steps));
|
||||||
|
}
|
||||||
|
resetGrid();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function spiralGlitch(duration = 1000) {
|
||||||
|
const centerRow = Math.floor(rows / 2);
|
||||||
|
const centerCol = Math.floor(cols / 2);
|
||||||
|
const withAngle = charData.map((entry) => {
|
||||||
|
const dr = entry.row - centerRow;
|
||||||
|
const dc = entry.col - centerCol;
|
||||||
|
const angle = Math.atan2(dc, dr);
|
||||||
|
const dist = Math.sqrt(dr * dr + dc * dc);
|
||||||
|
return { ...entry, angle, dist };
|
||||||
|
});
|
||||||
|
const chars = [...GLITCH_CHARS];
|
||||||
|
const steps = 36;
|
||||||
|
const wedgeWidth = 0.5;
|
||||||
|
|
||||||
|
for (let step = 0; step < steps; step += 1) {
|
||||||
|
const sweepAngle = (step / steps) * Math.PI * 2 - Math.PI;
|
||||||
|
withAngle.forEach(({ el, original, color, angle, dist }) => {
|
||||||
|
let diff = Math.abs(angle - sweepAngle);
|
||||||
|
if (diff > Math.PI) diff = Math.PI * 2 - diff;
|
||||||
|
if (diff < wedgeWidth && dist > 0.1) {
|
||||||
|
const randIdx = Math.floor(Math.random() * chars.length);
|
||||||
|
el.textContent = chars[randIdx];
|
||||||
|
el.style.color = `hsl(${20 + Math.random() * 35}, 100%, ${55 + Math.random() * 25}%)`;
|
||||||
|
} else {
|
||||||
|
el.textContent = original;
|
||||||
|
el.style.color = color;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await sleep(Math.floor(duration / steps));
|
||||||
|
}
|
||||||
|
resetGrid();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fuzzWaveGlitch(duration = 1000) {
|
||||||
|
const centerRow = Math.floor(rows / 2);
|
||||||
|
const centerCol = Math.floor(cols / 2);
|
||||||
|
const withDist = charData.map((entry) => {
|
||||||
|
const dr = entry.row - centerRow;
|
||||||
|
const dc = entry.col - centerCol;
|
||||||
|
return { ...entry, dist: Math.sqrt(dr * dr + dc * dc) };
|
||||||
|
});
|
||||||
|
const maxDist = Math.max(...withDist.map((entry) => entry.dist), 1);
|
||||||
|
const chars = [...GLITCH_CHARS];
|
||||||
|
const steps = 20;
|
||||||
|
const bandWidth = 0.25;
|
||||||
|
|
||||||
|
for (let step = 0; step < steps; step += 1) {
|
||||||
|
const progress = step / steps;
|
||||||
|
const targetNorm = progress * 1.0;
|
||||||
|
withDist.forEach(({ el, original, color, dist }) => {
|
||||||
|
const norm = dist / maxDist;
|
||||||
|
const distanceFromTarget = Math.abs(norm - targetNorm);
|
||||||
|
if (distanceFromTarget < bandWidth && Math.random() < 0.65) {
|
||||||
|
const randIdx = Math.floor(Math.random() * chars.length);
|
||||||
|
el.textContent = chars[randIdx];
|
||||||
|
el.style.color = `hsl(${20 + Math.random() * 35}, 100%, ${50 + Math.random() * 30}%)`;
|
||||||
|
} else if (distanceFromTarget > bandWidth * 1.5) {
|
||||||
|
el.textContent = original;
|
||||||
|
el.style.color = color;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await sleep(Math.floor(duration / steps));
|
||||||
|
}
|
||||||
|
resetGrid();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function staticGlitch(duration = 600) {
|
||||||
|
const chars = [...GLITCH_CHARS];
|
||||||
|
const steps = 6;
|
||||||
|
for (let step = 0; step < steps; step += 1) {
|
||||||
|
charData.forEach(({ el }) => {
|
||||||
|
if (Math.random() < 0.8) {
|
||||||
|
el.textContent = chars[Math.floor(Math.random() * chars.length)];
|
||||||
|
el.style.color = `hsl(${20 + Math.random() * 35}, 100%, ${50 + Math.random() * 30}%)`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await sleep(Math.floor(duration / steps));
|
||||||
|
}
|
||||||
|
resetGrid();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function horizontalBand(direction = 1, duration = 800) {
|
||||||
|
const chars = [...GLITCH_CHARS];
|
||||||
|
const start = direction === 1 ? 0 : rows - 1;
|
||||||
|
const totalSteps = rows + 2;
|
||||||
|
for (let step = 0; step <= totalSteps; step += 1) {
|
||||||
|
resetGrid();
|
||||||
|
const bandCenter = start + direction * step;
|
||||||
|
const bandTop = Math.max(0, bandCenter - 1);
|
||||||
|
const bandBottom = Math.min(rows - 1, bandCenter + 1);
|
||||||
|
for (let row = bandTop; row <= bandBottom; row += 1) {
|
||||||
|
const spans = getRowSpans(row);
|
||||||
|
spans.forEach((span) => {
|
||||||
|
span.textContent = chars[Math.floor(Math.random() * chars.length)];
|
||||||
|
span.style.color = `hsl(${20 + Math.random() * 35}, 100%, ${50 + Math.random() * 30}%)`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
await sleep(Math.floor(duration / totalSteps));
|
||||||
|
}
|
||||||
|
resetGrid();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function verticalSlice(direction = 1, duration = 800) {
|
||||||
|
const chars = [...GLITCH_CHARS];
|
||||||
|
const start = direction === 1 ? 0 : cols - 1;
|
||||||
|
const totalSteps = cols + 2;
|
||||||
|
for (let step = 0; step <= totalSteps; step += 1) {
|
||||||
|
resetGrid();
|
||||||
|
const bandCenter = start + direction * step;
|
||||||
|
const bandLeft = Math.max(0, bandCenter - 1);
|
||||||
|
const bandRight = Math.min(cols - 1, bandCenter + 1);
|
||||||
|
charData.forEach(({ el, col }) => {
|
||||||
|
if (col >= bandLeft && col <= bandRight) {
|
||||||
|
el.textContent = chars[Math.floor(Math.random() * chars.length)];
|
||||||
|
el.style.color = `hsl(${20 + Math.random() * 35}, 100%, ${50 + Math.random() * 30}%)`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await sleep(Math.floor(duration / totalSteps));
|
||||||
|
}
|
||||||
|
resetGrid();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function stageWaveDown() {
|
||||||
|
for (let row = 0; row < rows; row += 1) {
|
||||||
|
await glitchRowWave(row, 300);
|
||||||
|
await sleep(20);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function stageWaveUp() {
|
||||||
|
for (let row = rows - 1; row >= 0; row -= 1) {
|
||||||
|
await glitchRowWave(row, 260);
|
||||||
|
await sleep(15);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function stageDecrypt() {
|
||||||
|
const chars = [...GLITCH_CHARS];
|
||||||
|
charData.forEach(({ el }) => {
|
||||||
|
const randIdx = Math.floor(Math.random() * chars.length);
|
||||||
|
el.textContent = chars[randIdx];
|
||||||
|
});
|
||||||
|
for (let row = 0; row < rows; row += 1) {
|
||||||
|
await decryptRow(row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function stageBurst() {
|
||||||
|
await burstGlitch(900);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function stagePulse() {
|
||||||
|
await pulseGlitch(1200);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function stageImplosion() {
|
||||||
|
await implosionGlitch(900);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function stageSpiral() {
|
||||||
|
await spiralGlitch(1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function stageFuzzWave() {
|
||||||
|
await fuzzWaveGlitch(1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function stageStatic() {
|
||||||
|
await staticGlitch(700);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function stageHSlashDown() {
|
||||||
|
await horizontalBand(1, 800);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function stageHSlashUp() {
|
||||||
|
await horizontalBand(-1, 800);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function stageVSlashRight() {
|
||||||
|
await verticalSlice(1, 800);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function stageVSlashLeft() {
|
||||||
|
await verticalSlice(-1, 800);
|
||||||
|
}
|
||||||
|
|
||||||
|
const stageFunctions = [
|
||||||
|
stageWaveDown,
|
||||||
|
stageWaveUp,
|
||||||
|
stageDecrypt,
|
||||||
|
stageBurst,
|
||||||
|
stagePulse,
|
||||||
|
stageImplosion,
|
||||||
|
stageSpiral,
|
||||||
|
stageFuzzWave,
|
||||||
|
stageStatic,
|
||||||
|
stageHSlashDown,
|
||||||
|
stageHSlashUp,
|
||||||
|
stageVSlashRight,
|
||||||
|
stageVSlashLeft,
|
||||||
|
];
|
||||||
|
|
||||||
|
function shuffleArray(array) {
|
||||||
|
for (let index = array.length - 1; index > 0; index -= 1) {
|
||||||
|
const j = Math.floor(Math.random() * (index + 1));
|
||||||
|
[array[index], array[j]] = [array[j], array[index]];
|
||||||
|
}
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function runLoop() {
|
||||||
|
while (true) {
|
||||||
|
const shuffled = shuffleArray([...stageFunctions]);
|
||||||
|
for (const stageFn of shuffled) {
|
||||||
|
await stageFn();
|
||||||
|
await sleep(700 + Math.random() * 400);
|
||||||
|
}
|
||||||
|
resetGrid();
|
||||||
|
await sleep(300);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
runLoop().catch(console.error);
|
||||||
|
}
|
||||||
|
|
||||||
let tuiMode = "command";
|
let tuiMode = "command";
|
||||||
|
|
||||||
@@ -343,16 +879,7 @@ function createTerminal() {
|
|||||||
box.id = "spotui-tui";
|
box.id = "spotui-tui";
|
||||||
setTuiMode("command");
|
setTuiMode("command");
|
||||||
box.innerHTML = `
|
box.innerHTML = `
|
||||||
<div id="spotui-logo"> ██████ ██▓███ ▒█████ ▄▄▄█████▓ █ ██ ██▓
|
<div id="spotui-logo"></div>
|
||||||
▒██ ▒ ▓██░ ██▒▒██▒ ██▒▓ ██▒ ▓▒ ██ ▓██▒▓██▒
|
|
||||||
░ ▓██▄ ▓██░ ██▓▒▒██░ ██▒▒ ▓██░ ▒░▓██ ▒██░▒██▒
|
|
||||||
▒ ██▒▒██▄█▓▒ ▒▒██ ██░░ ▓██▓ ░ ▓▓█ ░██░░██░
|
|
||||||
▒██████▒▒▒██▒ ░ ░░ ████▓▒░ ▒██▒ ░ ▒▒█████▓ ░██░
|
|
||||||
▒ ▒▓▒ ▒ ░▒▓▒░ ░ ░░ ▒░▒░▒░ ▒ ░░ ░▒▓▒ ▒ ▒ ░▓
|
|
||||||
░ ░▒ ░ ░░▒ ░ ░ ▒ ▒░ ░ ░░▒░ ░ ░ ▒ ░
|
|
||||||
░ ░ ░ ░░ ░ ░ ░ ▒ ░ ░░░ ░ ░ ▒ ░
|
|
||||||
░ ░ ░ ░ ░
|
|
||||||
</div>
|
|
||||||
<div id="spotui-output">SpoTUI v1.0
|
<div id="spotui-output">SpoTUI v1.0
|
||||||
|
|
||||||
Type /help
|
Type /help
|
||||||
@@ -364,6 +891,7 @@ Type /help
|
|||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
document.body.appendChild(box);
|
document.body.appendChild(box);
|
||||||
|
initAsciiAnimation();
|
||||||
|
|
||||||
const input = document.getElementById("spotui-input");
|
const input = document.getElementById("spotui-input");
|
||||||
input.addEventListener("keydown", async (e) => {
|
input.addEventListener("keydown", async (e) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user