From: Danilo M. Date: Wed, 15 Apr 2026 22:24:58 +0000 (+0200) Subject: Complete matrix rain background effect implementation X-Git-Tag: release_22042026-1342~230 X-Git-Url: https://git.danix.xyz/?a=commitdiff_plain;h=4574c4cc0cbc26bdec9cf7d0872ae7401c7cc76b;p=danix.xyz-2.git Complete matrix rain background effect implementation - Add canvas-based matrix rain animation with ASCII + katakana characters - Implement per-column animation with varied drop speeds (2-4 frame throttle) - Theme-aware colors: purple and green accents with live switching - Homepage: 28% opacity (dark) / 35% opacity (light) for prominent hero effect - Inner pages: 13% opacity (dark) / 18% opacity (light) for subtle side gutters - Respect prefers-reduced-motion system setting - Add opaque background to content grids to block rain under text - Add .content-grid class to differentiate single pages from list pages - Add solid background to article list item cards - Update article list item with bg-bg class for readability - Z-index stack: canvas (z-1), content grid (z-9), main content (z-10) Files modified: - matrix-rain.js: new IIFE animation script with MutationObserver for theme switching - baseof.html: add canvas element and script tag with guard - main.css: add canvas positioning, opacity rules, content grid background - _default/single.html: add max-w-7xl and .content-grid class - articles/single.html: add max-w-7xl and .content-grid class - is/list.html: add max-w-7xl and .content-grid class - article-list-item.html: add bg-bg class for solid background Co-Authored-By: Claude Haiku 4.5 --- diff --git a/themes/danix-xyz-hacker/assets/css/main.css b/themes/danix-xyz-hacker/assets/css/main.css index 2b8cbde..53b2410 100644 --- a/themes/danix-xyz-hacker/assets/css/main.css +++ b/themes/danix-xyz-hacker/assets/css/main.css @@ -286,3 +286,46 @@ html.theme-light .prose-invert blockquote { transition-duration: 0.01ms !important; } } + +/* Matrix rain canvas background */ +#matrix-rain { + position: fixed; + inset: 0; + width: 100%; + height: 100%; + pointer-events: none; + z-index: 1; +} + +/* Dark theme: 13% opacity (inner pages) */ +html.theme-dark #matrix-rain { + opacity: 0.13; +} + +/* Light theme: 18% opacity (inner pages) */ +html.theme-light #matrix-rain { + opacity: 0.18; +} + +/* Homepage: more prominent background */ +html.theme-dark body[data-page-kind="home"] #matrix-rain { + opacity: 0.28; +} + +html.theme-light body[data-page-kind="home"] #matrix-rain { + opacity: 0.35; +} + +/* Reduced motion: hide canvas entirely */ +@media (prefers-reduced-motion: reduce) { + #matrix-rain { + display: none; + } +} + +/* Content grid background — blocks rain under text, visible in gutters (single pages only) */ +.grid.md\:grid-cols-3.gap-8.max-w-7xl.content-grid { + position: relative; + z-index: 10; + background-color: var(--bg); +} diff --git a/themes/danix-xyz-hacker/assets/css/main.min.css b/themes/danix-xyz-hacker/assets/css/main.min.css index 74161bc..9eabce5 100644 --- a/themes/danix-xyz-hacker/assets/css/main.min.css +++ b/themes/danix-xyz-hacker/assets/css/main.min.css @@ -2062,6 +2062,55 @@ html.theme-light .prose-invert blockquote { } } +/* Matrix rain canvas background */ + +#matrix-rain { + position: fixed; + inset: 0; + width: 100%; + height: 100%; + pointer-events: none; + z-index: 1; +} + +/* Dark theme: 13% opacity (inner pages) */ + +html.theme-dark #matrix-rain { + opacity: 0.13; +} + +/* Light theme: 18% opacity (inner pages) */ + +html.theme-light #matrix-rain { + opacity: 0.18; +} + +/* Homepage: more prominent background */ + +html.theme-dark body[data-page-kind="home"] #matrix-rain { + opacity: 0.28; +} + +html.theme-light body[data-page-kind="home"] #matrix-rain { + opacity: 0.35; +} + +/* Reduced motion: hide canvas entirely */ + +@media (prefers-reduced-motion: reduce) { + #matrix-rain { + display: none; + } +} + +/* Content grid background — blocks rain under text, visible in gutters (single pages only) */ + +.grid.md\:grid-cols-3.gap-8.max-w-7xl.content-grid { + position: relative; + z-index: 10; + background-color: var(--bg); +} + .hover\:bg-surface:hover { background-color: var(--surface); } diff --git a/themes/danix-xyz-hacker/assets/js/matrix-rain.js b/themes/danix-xyz-hacker/assets/js/matrix-rain.js new file mode 100644 index 0000000..53c55d8 --- /dev/null +++ b/themes/danix-xyz-hacker/assets/js/matrix-rain.js @@ -0,0 +1,157 @@ +// Matrix rain background effect +(function() { + // Bail out if user prefers reduced motion + if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) { + return; + } + + // Canvas and context + let canvas = document.getElementById('matrix-rain'); + if (!canvas) return; + const ctx = canvas.getContext('2d'); + + // State + let columns = []; + let frameCount = 0; + let colors = { accent: '#a855f7', accent2: '#00ff88', bg: '#060b10', head: '#ffffff' }; + + // Character set: 30% ASCII, 70% katakana + const ASCII = Array.from({ length: 94 }, (_, i) => String.fromCharCode(33 + i)); + const KATA = Array.from({ length: 96 }, (_, i) => String.fromCodePoint(0x30a0 + i)); + const CHARS = shuffle([...ASCII, ...KATA, ...KATA, ...KATA]); + + // Utility: shuffle array + function shuffle(arr) { + for (let i = arr.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [arr[i], arr[j]] = [arr[j], arr[i]]; + } + return arr; + } + + // Utility: convert hex or rgb color to rgba string + function hexToRgba(color, alpha) { + const rgbMatch = color.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/); + if (rgbMatch) { + return `rgba(${rgbMatch[1]}, ${rgbMatch[2]}, ${rgbMatch[3]}, ${alpha})`; + } + const hex = color.replace('#', ''); + const r = parseInt(hex.substring(0, 2), 16); + const g = parseInt(hex.substring(2, 4), 16); + const b = parseInt(hex.substring(4, 6), 16); + return `rgba(${r}, ${g}, ${b}, ${alpha})`; + } + + // Sample CSS variables based on current theme + function sampleColors() { + const style = getComputedStyle(document.documentElement); + const isDark = document.documentElement.classList.contains('theme-dark'); + colors.accent = style.getPropertyValue('--accent').trim(); + colors.accent2 = style.getPropertyValue('--accent2').trim(); + colors.bg = style.getPropertyValue('--bg').trim(); + // Head char: bright white in dark mode, deep purple-black in light mode + colors.head = isDark ? '#ffffff' : '#1a0533'; + } + + // Resize canvas to window dimensions + function resizeCanvas() { + canvas.width = window.innerWidth; + canvas.height = window.innerHeight; + ctx.font = '14px "JetBrains Mono", monospace'; + ctx.textBaseline = 'top'; + initColumns(); + } + + // Initialize columns for the current canvas width + function initColumns() { + columns = []; + const columnWidth = 14; + const columnCount = Math.floor(canvas.width / columnWidth); + + for (let i = 0; i < columnCount; i++) { + columns.push({ + x: i * columnWidth, + y: -Math.floor(Math.random() * 40), // stagger start above viewport + speed: 2 + Math.floor(Math.random() * 3), // 2-4 frames between drops + color: Math.random() < 0.6 ? 'accent2' : 'accent', // 60% green, 40% purple + charIndex: Math.floor(Math.random() * CHARS.length), + length: 8 + Math.floor(Math.random() * 13), // trail length 8-20 + }); + } + } + + // Set up MutationObserver for theme switching + function setupThemeObserver() { + const observer = new MutationObserver(function(mutations) { + for (const m of mutations) { + if (m.attributeName === 'class') { + sampleColors(); + break; + } + } + }); + observer.observe(document.documentElement, { + attributes: true, + attributeFilter: ['class'], + }); + } + + // Main animation loop + function drawFrame() { + frameCount++; + + // Fade layer: semi-transparent background fill + ctx.fillStyle = hexToRgba(colors.bg, 0.085); + ctx.fillRect(0, 0, canvas.width, canvas.height); + + // Draw each column + for (const col of columns) { + // Skip if not time to drop yet (per-column throttle) + if (frameCount % col.speed !== 0) continue; + + // Draw explicit trail in column color + ctx.fillStyle = colors[col.color]; + for (let i = 1; i <= col.length; i++) { + const trailY = (col.y - i) * 14; + if (trailY < 0) continue; + const trailCharIndex = (col.charIndex - i + CHARS.length) % CHARS.length; + ctx.fillText(CHARS[trailCharIndex], col.x, trailY); + } + + // Draw head character (bright) + ctx.fillStyle = colors.head; + const headCharIndex = col.charIndex % CHARS.length; + ctx.fillText(CHARS[headCharIndex], col.x, col.y * 14); + + // Advance column + col.y++; + col.charIndex = (col.charIndex + 1) % CHARS.length; + + // Reset when scrolled off screen + if (col.y * 14 > canvas.height + col.length * 14) { + col.y = -Math.floor(Math.random() * 20); + col.charIndex = Math.floor(Math.random() * CHARS.length); + col.color = Math.random() < 0.6 ? 'accent2' : 'accent'; + } + } + + requestAnimationFrame(drawFrame); + } + + // Initialize + sampleColors(); + resizeCanvas(); + setupThemeObserver(); + + // Debounced resize handler + let resizeTimer; + window.addEventListener('resize', function() { + clearTimeout(resizeTimer); + resizeTimer = setTimeout(resizeCanvas, 150); + }); + + // Start animation when fonts are ready + document.fonts.ready.then(function() { + requestAnimationFrame(drawFrame); + }); +})(); diff --git a/themes/danix-xyz-hacker/layouts/_default/baseof.html b/themes/danix-xyz-hacker/layouts/_default/baseof.html index 7e8338a..efeba38 100644 --- a/themes/danix-xyz-hacker/layouts/_default/baseof.html +++ b/themes/danix-xyz-hacker/layouts/_default/baseof.html @@ -27,7 +27,7 @@ {{ $chroma := resources.Get "css/chroma-custom.css" | minify }} - + {{ if eq .Kind "page" }}
+ + + {{ end }} + + + {{ with resources.Get "js/matrix-rain.js" }} + {{ $s := . | minify }} + + {{ end }} diff --git a/themes/danix-xyz-hacker/layouts/_default/single.html b/themes/danix-xyz-hacker/layouts/_default/single.html index 16d519f..5707968 100644 --- a/themes/danix-xyz-hacker/layouts/_default/single.html +++ b/themes/danix-xyz-hacker/layouts/_default/single.html @@ -1,6 +1,6 @@ {{ define "main" }}
-
+
diff --git a/themes/danix-xyz-hacker/layouts/articles/single.html b/themes/danix-xyz-hacker/layouts/articles/single.html index 93abdb6..fe2ff6e 100644 --- a/themes/danix-xyz-hacker/layouts/articles/single.html +++ b/themes/danix-xyz-hacker/layouts/articles/single.html @@ -2,7 +2,7 @@ {{ $articleType := .Params.type | default "life" }} {{ $template := printf "article-types/%s.html" $articleType }}
-
+
diff --git a/themes/danix-xyz-hacker/layouts/is/list.html b/themes/danix-xyz-hacker/layouts/is/list.html index 7535a37..1184b18 100644 --- a/themes/danix-xyz-hacker/layouts/is/list.html +++ b/themes/danix-xyz-hacker/layouts/is/list.html @@ -1,6 +1,6 @@ {{ define "main" }}
-
+
diff --git a/themes/danix-xyz-hacker/layouts/partials/article-list-item.html b/themes/danix-xyz-hacker/layouts/partials/article-list-item.html index 70d530c..29f8d2b 100644 --- a/themes/danix-xyz-hacker/layouts/partials/article-list-item.html +++ b/themes/danix-xyz-hacker/layouts/partials/article-list-item.html @@ -13,7 +13,7 @@ {{ end }} {{ end }} -
+
{{ if $imageURL }}