summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-04-10 12:01:49 +0200
committerDanilo M. <danix@danix.xyz>2026-04-10 12:01:49 +0200
commit70fab005093cd92bfbde1bfa3380c3dc873bdfcf (patch)
tree94c2aa4e0c827b9aea57e66210fd616717234d88
parentc42150058196f5affad5c6c590e99dd2fc7321c3 (diff)
downloaddanixxyz-theme-70fab005093cd92bfbde1bfa3380c3dc873bdfcf.tar.gz
danixxyz-theme-70fab005093cd92bfbde1bfa3380c3dc873bdfcf.zip
feat: align homepage to mockup-a with centered hero, terminal animation, article grid
Restructure hero layout with centered .hero-container (max-width 1080px), update hero text (prompt "welcome to", button "About Me" → /is/), add terminal widget title bar and staggered fade-in animation, replace scroll indicator with animated line. Add section header (eyebrow + title) above articles, new vertical .article-card grid layout with solid type badges (sharp corners, sharp badges), implement scroll reveal stagger (90ms per sibling). Update terminal color palette to use proper CSS variables. Remove ambient glow from hero. Changes follow THEMING-STANDARD: semantic color variables, mobile-first responsive design, prefers-reduced-motion support, WCAG AA accessibility. - hero.html: new .hero-container wrapper, typed terminal content, scroll line - hero.css: restructure layout, add color classes, stagger animation, scroll pulse - home.html: add section header, .articles-grid, pass context="home" flag - post-card.html: context-conditional rendering (homepage vertical vs. other horizontal) - card.css: new .article-card, .articles-grid, .article-* styles - main.css: add section utilities, .reveal/.revealed base states - variables.css: add --terminal-prompt, --terminal-text, --terminal-accent - scroll-reveal.js: add 90ms per-item stagger with cleanup - 404.css: remove hardcoded terminal color fallbacks Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
-rw-r--r--assets/css/components/404.css4
-rw-r--r--assets/css/components/card.css99
-rw-r--r--assets/css/components/hero.css132
-rw-r--r--assets/css/main.css37
-rw-r--r--assets/css/variables.css5
-rw-r--r--assets/js/scroll-reveal.js36
-rw-r--r--layouts/_partials/hero.html65
-rw-r--r--layouts/_partials/post-card.html44
-rw-r--r--layouts/home.html49
9 files changed, 349 insertions, 122 deletions
diff --git a/assets/css/components/404.css b/assets/css/components/404.css
index d2c93b0..3db099f 100644
--- a/assets/css/components/404.css
+++ b/assets/css/components/404.css
@@ -222,7 +222,7 @@
padding: 1rem;
font-family: var(--font-mono);
font-size: 0.75rem;
- color: var(--terminal-text, #c4d6e8);
+ color: var(--terminal-text);
line-height: 1.6;
}
@@ -232,7 +232,7 @@
}
.terminal-prompt {
- color: var(--terminal-prompt, #00ff88);
+ color: var(--terminal-prompt);
}
#terminal-files {
diff --git a/assets/css/components/card.css b/assets/css/components/card.css
index 6a8cfaf..3d218bc 100644
--- a/assets/css/components/card.css
+++ b/assets/css/components/card.css
@@ -153,3 +153,102 @@
height: 150px;
}
}
+
+/* ─── Homepage article cards ─────────────────────────────── */
+
+.articles-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
+ gap: 1.5rem;
+ margin-bottom: 2rem;
+}
+
+@media (max-width: 768px) {
+ .articles-grid { grid-template-columns: 1fr; }
+}
+
+.article-card {
+ background: var(--surface);
+ border: 1px solid var(--border);
+ border-radius: 0;
+ overflow: hidden;
+ display: flex;
+ flex-direction: column;
+ transition: var(--transition);
+}
+
+.article-card:hover {
+ transform: translate(0, -4px);
+ box-shadow: 0 8px 30px var(--accent-glow);
+ border-color: var(--accent);
+}
+
+/* Badge background set via inline style in template using var(--type-*) */
+.article-type {
+ font-family: var(--font-mono);
+ font-size: var(--fs-badge);
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: 0.1em;
+ padding: 0.4rem 1rem;
+ color: var(--bg);
+ width: fit-content;
+ border-radius: 3px;
+ margin: 1rem 1rem 0;
+}
+
+.article-content {
+ padding: 1rem 1rem 1.5rem;
+ display: flex;
+ flex-direction: column;
+ flex: 1;
+}
+
+.article-title {
+ font-family: var(--font-head);
+ font-size: 1.1rem;
+ font-weight: 700;
+ line-height: 1.3;
+ margin-bottom: 0.5rem;
+ letter-spacing: -0.025em;
+}
+
+.article-title a { color: var(--text); text-decoration: none; }
+.article-title a:hover { color: var(--accent); }
+
+.article-excerpt {
+ color: var(--text-dim);
+ font-size: 0.9rem;
+ line-height: 1.6;
+ flex: 1;
+ margin-bottom: 1rem;
+ display: -webkit-box;
+ -webkit-line-clamp: 3;
+ -webkit-box-orient: vertical;
+ overflow: hidden;
+}
+
+.article-meta {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding-top: 1rem;
+ border-top: 1px solid var(--border);
+ font-family: var(--font-mono);
+ font-size: 0.75rem;
+ color: var(--muted);
+ text-transform: uppercase;
+ letter-spacing: 0.08em;
+ margin-top: auto;
+}
+
+/* .article-read named distinctly from .article-link (used in 404.css) */
+.article-read {
+ color: var(--accent);
+ text-decoration: none;
+ font-weight: 600;
+ letter-spacing: 0.05em;
+ transition: color var(--duration-fast) ease;
+}
+
+.article-read:hover { color: var(--accent2); }
diff --git a/assets/css/components/hero.css b/assets/css/components/hero.css
index 3596274..b9ecb24 100644
--- a/assets/css/components/hero.css
+++ b/assets/css/components/hero.css
@@ -2,13 +2,25 @@
.hero {
position: relative;
- min-height: 100vh;
+ height: 100vh;
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ align-items: stretch;
+ padding: 6rem 2rem 2rem;
+ overflow: hidden;
+}
+
+.hero-container {
+ position: relative;
+ z-index: 2;
display: flex;
align-items: center;
- justify-content: center;
- padding: 2rem;
gap: 3rem;
- overflow: hidden;
+ max-width: 1080px;
+ width: 100%;
+ margin: 0 auto;
+ flex: 1;
}
#matrix-canvas {
@@ -25,8 +37,6 @@ html.theme-light #matrix-canvas {
.hero-left {
flex: 1;
min-width: 0;
- position: relative;
- z-index: 2;
}
.hero-prompt {
@@ -190,11 +200,9 @@ html.theme-light #matrix-canvas {
flex: 0 0 auto;
width: 320px;
display: none;
- position: relative;
- z-index: 2;
}
-@media (min-width: 1200px) {
+@media (min-width: 900px) {
.hero-right {
display: block;
}
@@ -225,19 +233,60 @@ html.theme-light #matrix-canvas {
display: inline-block;
}
+.terminal-title {
+ margin-left: auto;
+ font-family: var(--font-mono);
+ font-size: 0.7rem;
+ color: var(--text-dim);
+}
+
.terminal-content {
padding: 1rem;
font-family: var(--font-mono);
- color: var(--terminal-text, #c4d6e8);
+ color: var(--terminal-text);
+ font-size: 0.75rem;
+ line-height: 1.7;
}
-.terminal-content div {
- white-space: pre-wrap;
+.terminal-content .tl {
+ white-space: nowrap;
word-wrap: break-word;
}
+/* Terminal color classes */
+.tc-dim { color: var(--muted); }
+.tc-ok { color: var(--accent2); }
+.tc-key { color: var(--accent); }
+
+/* Terminal stagger animation */
+@keyframes terminal-fade-in {
+ from {
+ opacity: 0;
+ transform: translateY(4px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+.tl {
+ opacity: 0;
+ animation: terminal-fade-in 0.4s ease forwards;
+}
+
+.tl-d1 { animation-delay: 0ms; }
+.tl-d2 { animation-delay: 150ms; }
+.tl-d3 { animation-delay: 300ms; }
+.tl-d4 { animation-delay: 450ms; }
+.tl-d5 { animation-delay: 600ms; }
+.tl-d6 { animation-delay: 750ms; }
+.tl-d7 { animation-delay: 900ms; }
+.tl-d8 { animation-delay: 1050ms; }
+.tl-d9 { animation-delay: 1200ms; }
+
.terminal-prompt {
- color: var(--terminal-prompt, #00ff88);
+ color: var(--terminal-prompt);
}
/* Scroll Indicator */
@@ -250,44 +299,38 @@ html.theme-light #matrix-canvas {
flex-direction: column;
align-items: center;
gap: 0.5rem;
- font-size: 0.75rem;
+ font-family: var(--font-mono);
+ font-size: 0.7rem;
+ letter-spacing: 0.1em;
+ text-transform: uppercase;
color: var(--text-dim);
- animation: bounce 2s infinite;
z-index: 2;
}
-.scroll-indicator svg {
- color: var(--accent);
+.scroll-line {
+ width: 1px;
+ height: 40px;
+ background: var(--accent);
+ animation: scroll-line-pulse 2s ease-in-out infinite;
}
-@keyframes bounce {
- 0%, 100% { transform: translateX(-50%) translateY(0); }
- 50% { transform: translateX(-50%) translateY(-8px); }
-}
-
-/* Ambient glow behind hero */
-.hero::before {
- content: '';
- position: absolute;
- top: 50%;
- left: 50%;
- width: 600px;
- height: 600px;
- background: radial-gradient(circle, rgba(168, 85, 247, 0.15) 0%, transparent 70%);
- transform: translate(-50%, -50%);
- pointer-events: none;
- z-index: 1;
+@keyframes scroll-line-pulse {
+ 0%, 100% { opacity: 0.3; transform: scaleY(1); }
+ 50% { opacity: 1; transform: scaleY(1.15); }
}
/* Mobile */
@media (max-width: 900px) {
.hero {
- flex-direction: column;
- min-height: auto;
- justify-content: flex-start;
+ height: auto;
+ min-height: 100vh;
padding-top: 6rem;
}
+ .hero-container {
+ flex-direction: column;
+ }
+
.hero-tagline {
max-width: 100%;
}
@@ -299,15 +342,8 @@ html.theme-light #matrix-canvas {
@media (prefers-reduced-motion: reduce) {
.hero-name.is-glitching::before,
- .hero-name.is-glitching::after {
- animation: none;
- }
-
- .scroll-indicator {
- animation: none;
- }
-
- .cursor {
- animation: none;
- }
+ .hero-name.is-glitching::after { animation: none; }
+ .cursor { animation: none; }
+ .scroll-line { animation: none; opacity: 0.6; }
+ .tl { animation: none; opacity: 1; }
}
diff --git a/assets/css/main.css b/assets/css/main.css
index ad78700..44b4cd1 100644
--- a/assets/css/main.css
+++ b/assets/css/main.css
@@ -110,6 +110,43 @@ a:hover {
padding: 0 1.5rem;
}
+/* Section layout helpers */
+.section-header { margin-bottom: 3rem; }
+
+.section-eyebrow {
+ font-family: var(--font-mono);
+ font-size: 0.75rem;
+ letter-spacing: 0.16em;
+ text-transform: uppercase;
+ color: var(--accent);
+ margin-bottom: 0.5rem;
+}
+
+.section-title {
+ font-family: var(--font-head);
+ font-size: clamp(1.5rem, 4vw, 2.5rem);
+ font-weight: 800;
+ color: var(--text);
+ line-height: 1.1;
+}
+
+/* Scroll-reveal base states (used by scroll-reveal.js) */
+.reveal {
+ opacity: 0;
+ transform: translateY(28px);
+ transition: opacity 0.6s cubic-bezier(0.16, 1, 0.3, 1),
+ transform 0.6s cubic-bezier(0.16, 1, 0.3, 1);
+}
+
+.reveal.revealed {
+ opacity: 1;
+ transform: translateY(0);
+}
+
+@media (prefers-reduced-motion: reduce) {
+ .reveal { opacity: 1; transform: none; transition: none; }
+}
+
main {
min-height: calc(100vh - 200px);
}
diff --git a/assets/css/variables.css b/assets/css/variables.css
index 371999a..173e12c 100644
--- a/assets/css/variables.css
+++ b/assets/css/variables.css
@@ -20,6 +20,11 @@
--type-link: #38bdf8; /* cyan */
--type-photo: #ec4899; /* pink */
+ /* Terminal palette (referenced by hero.css and 404.css) */
+ --terminal-prompt: var(--accent2);
+ --terminal-text: var(--text);
+ --terminal-accent: var(--type-link);
+
/* Typography */
--font-body: 'IBM Plex Sans', system-ui, sans-serif;
--font-mono: 'JetBrains Mono', 'Courier New', monospace;
diff --git a/assets/js/scroll-reveal.js b/assets/js/scroll-reveal.js
index ab099c0..026d1be 100644
--- a/assets/js/scroll-reveal.js
+++ b/assets/js/scroll-reveal.js
@@ -1,6 +1,7 @@
/**
* scroll-reveal.js
- * IntersectionObserver for revealing elements on scroll
+ * IntersectionObserver for revealing elements on scroll.
+ * Adds 90ms stagger delay per sibling index within each reveal-group.
*/
export function initScrollReveal() {
@@ -11,16 +12,29 @@ export function initScrollReveal() {
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
- if (entry.isIntersecting) {
- entry.target.classList.add('revealed');
- observer.unobserve(entry.target);
- }
+ if (!entry.isIntersecting) return;
+
+ const el = entry.target;
+ const parent = el.parentElement;
+ const siblings = parent
+ ? Array.from(parent.querySelectorAll(':scope > .reveal'))
+ : [];
+ const index = siblings.indexOf(el);
+ const delay = index >= 0 ? index * 90 : 0;
+
+ el.style.transitionDelay = delay + 'ms';
+ el.classList.add('revealed');
+
+ // Remove inline delay after transition so hover transitions are unaffected
+ const cleanup = () => {
+ el.style.transitionDelay = '';
+ el.removeEventListener('transitionend', cleanup);
+ };
+ el.addEventListener('transitionend', cleanup);
+
+ observer.unobserve(el);
});
- }, {
- threshold: 0.1,
- });
+ }, { threshold: 0.1 });
- revealElements.forEach((el) => {
- observer.observe(el);
- });
+ revealElements.forEach((el) => observer.observe(el));
}
diff --git a/layouts/_partials/hero.html b/layouts/_partials/hero.html
index ac8a4e1..1be5f71 100644
--- a/layouts/_partials/hero.html
+++ b/layouts/_partials/hero.html
@@ -1,39 +1,40 @@
<section class="hero" role="region" aria-label="Hero">
- <div class="hero-left">
- <div class="hero-prompt">// Featured</div>
- <h1 class="hero-name" data-text="{{ .Site.Params.author }}">{{ .Site.Params.author }}</h1>
- <div class="hero-role" id="typed" data-phrases='{{ .Site.Params.typingPhrases | jsonify }}'></div>
- <p class="hero-tagline">{{ .Site.Params.description }}</p>
- <div class="hero-buttons">
- <a href="/articles/" class="btn btn-primary">Read Articles</a>
- <a href="#articles" class="btn btn-outline">Explore</a>
- </div>
- </div>
-
- <div class="hero-right">
- <div class="hero-terminal">
- <div class="terminal-bar">
- <span class="terminal-dot" style="background: #ff6b6b;"></span>
- <span class="terminal-dot" style="background: #ffd93d;"></span>
- <span class="terminal-dot" style="background: #6bcf7f;"></span>
+ <div class="hero-container">
+ <div class="hero-left">
+ <div class="hero-prompt">welcome to</div>
+ <h1 class="hero-name" data-text="{{ .Site.Params.author }}">{{ .Site.Params.author }}</h1>
+ <div class="hero-role" id="typed" data-phrases='{{ .Site.Params.typingPhrases | jsonify }}'></div>
+ <p class="hero-tagline">{{ .Site.Params.description }}</p>
+ <div class="hero-buttons">
+ <a href="/articles/" class="btn btn-primary">Read Articles</a>
+ <a href="/is/" class="btn btn-outline">About Me</a>
</div>
- <div class="terminal-content">
- <div>$ <span class="terminal-prompt">whoami</span></div>
- <div>danilo</div>
- <div>$ <span class="terminal-prompt">pwd</span></div>
- <div>/home/danilo/web</div>
- <div>$ <span class="terminal-prompt">ls -la</span></div>
- <div>total 48</div>
- <div>drwxr-xr-x 5 danilo staff 160 Apr 9 2026 .</div>
- <div>-rw-r--r-- 1 danilo staff 4.2K blog.md</div>
+ </div>
+ <div class="hero-right">
+ <div class="hero-terminal">
+ <div class="terminal-bar">
+ <span class="terminal-dot" style="background: #ff5f57;"></span>
+ <span class="terminal-dot" style="background: #febc2e;"></span>
+ <span class="terminal-dot" style="background: #28c840;"></span>
+ <span class="terminal-title">root@danix.xyz</span>
+ </div>
+ <div class="terminal-content">
+ <div class="tl tl-d1"><span class="tc-dim">$ </span><span class="tc-ok">whoami</span></div>
+ <div class="tl tl-d2">danilo</div>
+ <div class="tl tl-d3"><span class="tc-dim">$ </span><span class="tc-ok">cat roles.txt</span></div>
+ <div class="tl tl-d4"><span class="tc-key">Security</span> &amp; Web Dev</div>
+ <div class="tl tl-d5"><span class="tc-key">WordPress</span> Developer</div>
+ <div class="tl tl-d6"><span class="tc-key">Bash</span> Enthusiast</div>
+ <div class="tl tl-d7"><span class="tc-dim">$ </span><span class="tc-ok">uptime</span></div>
+ <div class="tl tl-d8">up 4 years, still learning</div>
+ <div class="tl tl-d9"><span class="tc-dim">$ </span><span class="tc-ok">_</span></div>
+ </div>
</div>
</div>
- </div>
+ </div><!-- /.hero-container -->
- <div class="scroll-indicator">
- <span>Scroll to explore</span>
- <svg width="20" height="20" viewBox="0 0 20 20" fill="none">
- <path d="M10 3v10M6 9l4 4 4-4" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
- </svg>
+ <div class="scroll-indicator" aria-hidden="true">
+ <span>scroll</span>
+ <div class="scroll-line"></div>
</div>
</section>
diff --git a/layouts/_partials/post-card.html b/layouts/_partials/post-card.html
index 7dd8c9a..f3a1362 100644
--- a/layouts/_partials/post-card.html
+++ b/layouts/_partials/post-card.html
@@ -1,16 +1,30 @@
-{{ $featured := .featured }}
-<article class="post-card {{ if $featured }}featured{{ end }}">
- {{ if .image }}
- <img src="{{ .image }}" alt="{{ .title }}" class="post-card-image">
- {{ else }}
- <div class="post-card-image" style="background: linear-gradient(135deg, var(--color-{{ .type }}), var(--bg2));"></div>
- {{ end }}
- <div class="post-card-body">
- <div class="post-type-badge {{ .type }}">{{ .type }}</div>
- <h3 class="post-card-title"><a href="{{ .url }}">{{ .title }}</a></h3>
- <p class="post-card-excerpt">{{ .description }}</p>
- <div class="post-card-meta">
- <span>{{ dateFormat "Jan 2, 2006" .date }}</span>
+{{ if eq .context "home" }}
+ <article class="article-card reveal" data-type="{{ .type }}">
+ <div class="article-type" style="background: var(--type-{{ .type }});">{{ .type }}</div>
+ <div class="article-content">
+ <h3 class="article-title"><a href="{{ .url }}">{{ .title }}</a></h3>
+ <p class="article-excerpt">{{ .description }}</p>
+ <div class="article-meta">
+ <span>{{ dateFormat "Jan 2, 2006" .date }}</span>
+ <a href="{{ .url }}" class="article-read">Read →</a>
+ </div>
</div>
- </div>
-</article>
+ </article>
+{{ else }}
+ {{ $featured := .featured }}
+ <article class="post-card {{ if $featured }}featured{{ end }}">
+ {{ if .image }}
+ <img src="{{ .image }}" alt="{{ .title }}" class="post-card-image">
+ {{ else }}
+ <div class="post-card-image" style="background: linear-gradient(135deg, var(--color-{{ .type }}), var(--bg2));"></div>
+ {{ end }}
+ <div class="post-card-body">
+ <div class="post-type-badge {{ .type }}">{{ .type }}</div>
+ <h3 class="post-card-title"><a href="{{ .url }}">{{ .title }}</a></h3>
+ <p class="post-card-excerpt">{{ .description }}</p>
+ <div class="post-card-meta">
+ <span>{{ dateFormat "Jan 2, 2006" .date }}</span>
+ </div>
+ </div>
+ </article>
+{{ end }}
diff --git a/layouts/home.html b/layouts/home.html
index 6328ee5..54d8087 100644
--- a/layouts/home.html
+++ b/layouts/home.html
@@ -3,25 +3,46 @@
{{ define "main" }}
{{ partial "hero.html" . }}
- <section id="articles" class="section-container reveal-group">
+ <section id="articles" class="section-container reveal-group"
+ style="background: var(--bg2); padding: 6rem 2rem;">
+
+ <div class="section-header">
+ <p class="section-eyebrow">// latest posts</p>
+ <h2 class="section-title">Recent Articles</h2>
+ </div>
+
{{ $articlesSection := .Site.GetPage "/articles" }}
{{ if $articlesSection }}
{{ $articles := $articlesSection.Pages }}
- {{ $articles := sort $articles "Date" "desc" }}
- {{ range first 6 $articles }}
- {{ $type := .Params.type }}
- {{ if not $type }}{{ $type = "article" }}{{ end }}
-
- {{ $excerpt := .Params.excerpt }}
- {{ if not $excerpt }}
- {{ $excerpt = .Summary | plainify | truncate 150 }}
- {{ end }}
+ {{ $articles = sort $articles "Date" "desc" }}
+ <div class="articles-grid">
+ {{ range first 6 $articles }}
+ {{ $type := .Params.type }}
+ {{ if not $type }}{{ $type = "article" }}{{ end }}
- {{ $data := dict "title" .Title "type" $type "description" $excerpt "date" .Date "url" .RelPermalink "image" .Params.image "featured" .Params.featured }}
- {{ partial "post-card.html" $data }}
- {{ end }}
+ {{ $excerpt := .Params.excerpt }}
+ {{ if not $excerpt }}
+ {{ $excerpt = .Summary | plainify | truncate 150 }}
+ {{ end }}
+
+ {{ $data := dict
+ "title" .Title
+ "type" $type
+ "description" $excerpt
+ "date" .Date
+ "url" .RelPermalink
+ "image" .Params.image
+ "featured" .Params.featured
+ "context" "home"
+ }}
+ {{ partial "post-card.html" $data }}
+ {{ end }}
+ </div>
{{ end }}
- <a href="/articles/" class="btn btn-outline">View All Articles</a>
+ <div style="text-align: center; margin-top: 3rem;">
+ <a href="/articles/" class="btn btn-outline">View All Articles</a>
+ </div>
+
</section>
{{ end }}