diff options
| -rw-r--r-- | assets/css/components/404.css | 4 | ||||
| -rw-r--r-- | assets/css/components/card.css | 99 | ||||
| -rw-r--r-- | assets/css/components/hero.css | 132 | ||||
| -rw-r--r-- | assets/css/main.css | 37 | ||||
| -rw-r--r-- | assets/css/variables.css | 5 | ||||
| -rw-r--r-- | assets/js/scroll-reveal.js | 36 | ||||
| -rw-r--r-- | layouts/_partials/hero.html | 65 | ||||
| -rw-r--r-- | layouts/_partials/post-card.html | 44 | ||||
| -rw-r--r-- | layouts/home.html | 49 |
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> & 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 }} |
