Custom Code Experiment •
After Truth, Comes Advertising
Source Code View HTML / CSS / JavaScript
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>After Truth, Comes Advertising</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background: #000;
overflow: hidden;
font-family: ui-monospace, "Cascadia Code", "Roboto Mono", "Courier New", monospace;
touch-action: none;
-webkit-tap-highlight-color: transparent;
}
canvas {
display: block;
background: #000;
cursor: pointer;
}
</style>
</head>
<body>
<canvas id="glitchCanvas"></canvas>
<script>
class GlitchArt {
constructor(canvasId) {
this.canvas = document.getElementById(canvasId);
this.ctx = this.canvas.getContext('2d', { alpha: false });
this.dpr = Math.min(window.devicePixelRatio || 1, 2);
this.config = {
text: "AFTER TRUTH COMES ADVERTISING",
mobileBreakpoint: 768,
maxParticles: 100,
maxScanlines: 15,
maxNoise: 800,
phaseDuration: 300,
};
this.state = {
time: 0,
lastTime: performance.now(),
glitchIntensity: 0,
clickBoost: 0,
phase: 'calm',
cssWidth: 0,
cssHeight: 0,
particles: [],
scanlines: [],
noise: [],
isRunning: true,
};
this.init();
this.bindEvents();
this.animate();
}
get isMobile() {
return window.matchMedia(`(max-width: ${this.config.mobileBreakpoint}px) and (hover: none)`).matches;
}
get performanceMultiplier() {
return this.isMobile ? 0.6 : 1;
}
init() {
this.resize();
this.initElements();
}
resize() {
this.state.cssWidth = window.innerWidth;
this.state.cssHeight = window.innerHeight;
this.canvas.width = this.state.cssWidth * this.dpr;
this.canvas.height = this.state.cssHeight * this.dpr;
this.canvas.style.width = `${this.state.cssWidth}px`;
this.canvas.style.height = `${this.state.cssHeight}px`;
this.ctx.setTransform(this.dpr, 0, 0, this.dpr, 0, 0);
this.config.maxParticles = this.isMobile ? 60 : 120;
this.config.maxScanlines = this.isMobile ? 8 : 15;
this.config.maxNoise = this.isMobile ? 300 : 600;
this.initElements();
}
initElements() {
const { cssWidth, cssHeight } = this.state;
this.state.particles = Array.from({ length: this.config.maxParticles }, () => ({
x: Math.random() * cssWidth,
y: Math.random() * cssHeight,
vx: (Math.random() - 0.5) * 2,
vy: (Math.random() - 0.5) * 2,
life: Math.random() * 100 + 50,
maxLife: Math.random() * 100 + 50,
size: Math.random() * 2 + 0.5,
}));
this.state.scanlines = Array.from({ length: this.config.maxScanlines }, () => ({
y: Math.random() * cssHeight,
speed: Math.random() * 2 + 0.5,
intensity: Math.random() * 0.5,
width: Math.random() * 3 + 1,
}));
this.state.noise = Array.from({ length: this.config.maxNoise }, () => ({
x: Math.random() * cssWidth,
y: Math.random() * cssHeight,
size: Math.random() * 3 + 0.5,
life: Math.random() * 60,
maxLife: Math.random() * 60,
}));
}
getGlitchPhase() {
const cycle = this.state.time / this.config.phaseDuration;
const phaseIndex = Math.floor(cycle) % 4;
const phases = ['calm', 'building', 'peak', 'decay'];
const intensityMap = {
calm: 0.1 + Math.sin(this.state.time * 0.02) * 0.1,
building: 0.3 + (this.state.time % this.config.phaseDuration) / this.config.phaseDuration * 0.4,
peak: 0.8 + Math.random() * 0.4,
decay: 0.7 - (this.state.time % this.config.phaseDuration) / this.config.phaseDuration * 0.5,
};
return { phase: phases[phaseIndex], intensity: intensityMap[phases[phaseIndex]] };
}
drawVignette() {
const { cssWidth, cssHeight } = this.state;
const gradient = this.ctx.createRadialGradient(
cssWidth / 2, cssHeight / 2, 0,
cssWidth / 2, cssHeight / 2, Math.max(cssWidth, cssHeight) * 0.6
);
gradient.addColorStop(0, 'rgba(10, 10, 10, 0)');
gradient.addColorStop(0.7, 'rgba(0, 0, 0, 0.5)');
gradient.addColorStop(1, 'rgba(0, 0, 0, 0.9)');
this.ctx.fillStyle = gradient;
this.ctx.fillRect(0, 0, cssWidth, cssHeight);
}
drawChromaticAberration(str, x, y, fontSize) {
const offset = this.state.glitchIntensity * 4;
const distance = Math.hypot(x - this.state.cssWidth / 2, y - this.state.cssHeight / 2);
const distortion = offset * (1 + distance * 0.0005);
this.ctx.font = `${fontSize}px ui-monospace, "Cascadia Code", "Roboto Mono", monospace`;
this.ctx.textAlign = 'center';
this.ctx.textBaseline = 'middle';
this.ctx.fillStyle = `rgba(255, 0, 0, ${0.6 * this.state.glitchIntensity})`;
this.ctx.fillText(str, x - distortion, y);
this.ctx.fillStyle = `rgba(0, 255, 0, ${0.8 * this.state.glitchIntensity})`;
this.ctx.fillText(str, x, y);
this.ctx.fillStyle = `rgba(0, 100, 255, ${0.7 * this.state.glitchIntensity})`;
this.ctx.fillText(str, x + distortion, y);
this.ctx.fillStyle = `rgba(255, 255, 255, ${0.9 - this.state.glitchIntensity * 0.3})`;
this.ctx.fillText(str, x, y);
}
drawText() {
this.ctx.save();
const { cssWidth, cssHeight } = this.state;
const centerX = cssWidth / 2;
const centerY = cssHeight / 2;
const words = this.config.text.split(' ');
const baseFontSize = this.isMobile ? 20 : 36;
const lineSpacing = this.isMobile ? 32 : 56;
words.forEach((word, idx) => {
const wordY = centerY + (idx - (words.length - 1) / 2) * lineSpacing;
const wave = Math.sin(this.state.time * 0.03 + idx) * baseFontSize * 0.1;
const fontSize = baseFontSize + wave;
const xOffset = Math.sin(this.state.time * 0.08 + idx * 2) * this.state.glitchIntensity * 15;
const yOffset = Math.cos(this.state.time * 0.12 + idx * 2) * this.state.glitchIntensity * 8;
const randX = this.state.glitchIntensity > 0.7 ? (Math.random() - 0.5) * this.state.glitchIntensity * 20 : 0;
const randY = this.state.glitchIntensity > 0.7 ? (Math.random() - 0.5) * this.state.glitchIntensity * 10 : 0;
const finalX = centerX + xOffset + randX;
const finalY = wordY + yOffset + randY;
if (this.state.glitchIntensity > 0.3) {
this.drawChromaticAberration(word.toUpperCase(), finalX, finalY, fontSize);
} else {
this.ctx.font = `${fontSize}px ui-monospace, "Cascadia Code", "Roboto Mono", monospace`;
this.ctx.textAlign = 'center';
this.ctx.textBaseline = 'middle';
this.ctx.fillStyle = '#e0e0e0';
this.ctx.fillText(word.toUpperCase(), finalX, finalY);
}
if (this.state.glitchIntensity > 0.6 && Math.random() < 0.3) {
this.ctx.strokeStyle = `rgba(255, 0, 255, ${this.state.glitchIntensity * 0.4})`;
this.ctx.lineWidth = 1;
this.ctx.strokeText(word.toUpperCase(), finalX + 1, finalY + 1);
}
});
this.ctx.restore();
}
drawParticles() {
const { cssWidth, cssHeight } = this.state;
this.state.particles.forEach(p => {
if (p.life > 0) {
p.x += p.vx * this.performanceMultiplier;
p.y += p.vy * this.performanceMultiplier;
p.x = ((p.x % cssWidth) + cssWidth) % cssWidth;
p.y = ((p.y % cssHeight) + cssHeight) % cssHeight;
const alpha = (p.life / p.maxLife) * 0.8;
this.ctx.fillStyle = `rgba(0, 255, 200, ${alpha})`;
this.ctx.fillRect(p.x, p.y, p.size, p.size);
p.life--;
} else {
p.x = Math.random() * cssWidth;
p.y = Math.random() * cssHeight;
p.life = p.maxLife;
}
});
}
drawScanlines() {
this.state.scanlines.forEach(line => {
const alpha = line.intensity * this.state.glitchIntensity * 0.3;
this.ctx.fillStyle = `rgba(0, 255, 100, ${alpha})`;
this.ctx.fillRect(0, line.y, this.state.cssWidth, line.width);
line.y += line.speed * this.performanceMultiplier;
if (line.y > this.state.cssHeight) {
line.y = -line.width;
line.intensity = Math.random() * 0.5;
}
});
}
drawNoise() {
const { cssWidth, cssHeight } = this.state;
this.state.noise.forEach(n => {
if (n.life > 0) {
this.ctx.fillStyle = `rgba(${Math.random() * 255}, ${Math.random() * 255}, ${Math.random() * 255}, 0.6)`;
this.ctx.fillRect(n.x, n.y, n.size, n.size);
n.life--;
} else {
n.x = Math.random() * cssWidth;
n.y = Math.random() * cssHeight;
n.life = n.maxLife;
}
});
}
drawFrameBuffer() {
if (this.state.glitchIntensity < 0.7 || Math.random() > 0.1) return;
const { cssWidth, cssHeight } = this.state;
const corruptionHeight = Math.random() * 30 + 10;
const startY = Math.random() * (cssHeight - corruptionHeight);
const hue = Math.floor(Math.random() * 360);
this.ctx.fillStyle = `hsla(${hue}, 100%, 50%, ${this.state.glitchIntensity * 0.2})`;
this.ctx.fillRect(0, startY, cssWidth, corruptionHeight);
}
applyDataMoshing(imageData) {
if (this.state.glitchIntensity < 0.4) return;
const { data, width, height } = imageData;
const glitchRows = Math.floor(height * this.state.glitchIntensity * 0.01);
for (let i = 0; i < glitchRows; i++) {
const sourceRow = Math.floor(Math.random() * height) * width * 4;
const targetRow = Math.floor(Math.random() * height) * width * 4;
for (let x = 0; x < width * 4; x += 4) {
const sourceIdx = sourceRow + x;
const targetIdx = targetRow + x;
if (sourceIdx < data.length && targetIdx < data.length) {
data[targetIdx] = data[sourceIdx];
data[targetIdx + 1] = data[sourceIdx + 1];
data[targetIdx + 2] = data[sourceIdx + 2];
}
}
}
}
applyPixelSort(imageData) {
if (this.state.glitchIntensity < 0.5) return;
const { data, width, height } = imageData;
const bandHeight = Math.floor(Math.random() * 80 + 20);
const startY = Math.floor(Math.random() * (height - bandHeight));
for (let y = 0; y < bandHeight; y++) {
const rowStart = ((y + startY) * width + Math.floor(Math.random() * width * 0.3)) * 4;
const rowEnd = rowStart + Math.floor(width * 0.4) * 4;
const pixels = [];
for (let i = rowStart; i < rowEnd && i < data.length; i += 4) {
const brightness = (data[i] + data[i + 1] + data[i + 2]) / 3;
pixels.push({ index: i, brightness });
}
pixels.sort((a, b) => a.brightness - b.brightness);
pixels.forEach((pixel, idx) => {
const targetIdx = rowStart + idx * 4;
if (targetIdx < data.length) {
data[targetIdx] = data[pixel.index];
data[targetIdx + 1] = data[pixel.index + 1];
data[targetIdx + 2] = data[pixel.index + 2];
data[targetIdx + 3] = data[pixel.index + 3];
}
});
}
}
applyBitCrush(imageData) {
if (this.state.glitchIntensity < 0.6) return;
const { data } = imageData;
const reduction = Math.min(Math.floor(this.state.glitchIntensity * 4), 7);
const mask = 0xff << reduction;
for (let i = 0; i < data.length; i += 4) {
data[i] &= mask;
data[i + 1] &= mask;
data[i + 2] &= mask;
}
}
bindEvents() {
window.addEventListener('resize', () => this.resize());
document.addEventListener('visibilitychange', () => {
this.state.isRunning = !document.hidden;
if (this.state.isRunning) {
this.state.lastTime = performance.now();
this.animate();
}
});
this.canvas.addEventListener('click', () => {
// NEW: Add to click boost instead of directly to intensity
this.state.clickBoost = Math.min(this.state.clickBoost + 0.5, 0.6);
});
window.addEventListener('orientationchange', () => {
setTimeout(() => this.resize(), 100);
});
}
animate() {
if (!this.state.isRunning) return;
const now = performance.now();
const deltaTime = (now - this.state.lastTime) / 16.67;
this.state.lastTime = now;
// 1. Clear canvas
this.ctx.fillStyle = 'rgba(0, 0, 0, 0.12)';
this.ctx.fillRect(0, 0, this.state.cssWidth, this.state.cssHeight);
// 2. Calculate glitch phase (target intensity)
const phaseInfo = this.getGlitchPhase();
// NEW: Decay click boost over time
this.state.clickBoost *= 0.95;
this.state.clickBoost = Math.max(0, this.state.clickBoost - 0.01); // Also linear decay
// NEW: Combine phase intensity with click boost
this.state.glitchIntensity = Math.min(phaseInfo.intensity + this.state.clickBoost, 1.2);
// 3. Draw base layers
this.drawVignette();
this.drawParticles();
this.drawText();
this.drawScanlines();
this.drawNoise();
this.drawFrameBuffer();
// 4. Capture canvas for pixel manipulation
let imageData = null;
const needsImageData = this.state.glitchIntensity > 0.4;
if (needsImageData) {
imageData = this.ctx.getImageData(0, 0, this.canvas.width, this.canvas.height);
}
// 5. Apply pixel-manipulation effects
if (imageData) {
this.applyDataMoshing(imageData);
this.applyPixelSort(imageData);
this.applyBitCrush(imageData);
this.ctx.putImageData(imageData, 0, 0);
}
this.state.time += deltaTime;
requestAnimationFrame(() => this.animate());
}
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
const art = new GlitchArt('glitchCanvas');
});
} else {
const art = new GlitchArt('glitchCanvas');
}
</script>
</body>
</html>