summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-04-16 00:24:58 +0200
committerDanilo M. <danix@danix.xyz>2026-04-16 00:24:58 +0200
commit4574c4cc0cbc26bdec9cf7d0872ae7401c7cc76b (patch)
tree49b159e220cc75f7aed3dfd9a5d8549606a0ded1
parent455b5bf0a8cfba658446cc6f3fd2c5964b45d507 (diff)
downloaddanixxyz-4574c4cc0cbc26bdec9cf7d0872ae7401c7cc76b.tar.gz
danixxyz-4574c4cc0cbc26bdec9cf7d0872ae7401c7cc76b.zip
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 <noreply@anthropic.com>
-rw-r--r--themes/danix-xyz-hacker/assets/css/main.css43
-rw-r--r--themes/danix-xyz-hacker/assets/css/main.min.css49
-rw-r--r--themes/danix-xyz-hacker/assets/js/matrix-rain.js157
-rw-r--r--themes/danix-xyz-hacker/layouts/_default/baseof.html11
-rw-r--r--themes/danix-xyz-hacker/layouts/_default/single.html2
-rw-r--r--themes/danix-xyz-hacker/layouts/articles/single.html2
-rw-r--r--themes/danix-xyz-hacker/layouts/is/list.html2
-rw-r--r--themes/danix-xyz-hacker/layouts/partials/article-list-item.html2
8 files changed, 263 insertions, 5 deletions
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 }}
<link rel="stylesheet" href="{{ $chroma.RelPermalink }}">
</head>
-<body class="bg-bg text-text antialiased">
+<body class="bg-bg text-text antialiased" data-page-kind="{{ if .IsHome }}home{{ else }}other{{ end }}">
<!-- Reading progress bar (only on single pages/articles) -->
{{ if eq .Kind "page" }}
<div
@@ -48,6 +48,9 @@
z-index: -1;
"></div>
+ <!-- Matrix rain canvas background -->
+ <canvas id="matrix-rain" aria-hidden="true"></canvas>
+
<!-- Theme toggle & language toggle (before Alpine loads to prevent flash) -->
<script>
(function() {
@@ -93,5 +96,11 @@
{{ $progressScript := resources.Get "js/reading-progress.js" | minify }}
<script src="{{ $progressScript.RelPermalink }}"></script>
{{ end }}
+
+ <!-- Matrix rain background effect -->
+ {{ with resources.Get "js/matrix-rain.js" }}
+ {{ $s := . | minify }}
+ <script src="{{ $s.RelPermalink }}"></script>
+ {{ end }}
</body>
</html>
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" }}
<article class="mx-auto px-4 py-12">
- <div class="grid md:grid-cols-3 gap-8 max-w-7xl mx-auto">
+ <div class="grid md:grid-cols-3 gap-8 max-w-7xl mx-auto content-grid">
<!-- Article section -->
<div class="md:col-span-2">
<!-- Article header -->
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 }}
<article class="mx-auto px-4 py-12">
- <div class="grid md:grid-cols-3 gap-8 max-w-7xl mx-auto">
+ <div class="grid md:grid-cols-3 gap-8 max-w-7xl mx-auto content-grid">
<!-- Article section -->
<div class="md:col-span-2">
<!-- Article header -->
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" }}
<div class="mx-auto px-4 py-12">
- <div class="grid md:grid-cols-3 gap-8">
+ <div class="grid md:grid-cols-3 gap-8 max-w-7xl mx-auto content-grid">
<!-- Article section -->
<div class="md:col-span-2">
<!-- Article header -->
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 }}
-<article class="border border-border/30 rounded-lg overflow-hidden hover:border-accent/50 transition-all duration-200 group">
+<article class="border border-border/30 rounded-lg overflow-hidden hover:border-accent/50 transition-all duration-200 group bg-bg">
<!-- Thumbnail -->
{{ if $imageURL }}
<a href="{{ .RelPermalink }}" class="block overflow-hidden bg-surface/50 relative" tabindex="-1">