Update theme variable usage and add ThemeToggle component

- Refactor component styles to use CSS custom properties for colors and backgrounds.
- Replace hard‑coded Tailwind classes with theme variables across BlogCard, BlogFilters, Footer, GridOverlay, Navigation, PostNavigation, ReadingProgress, RelatedPosts, TableOfContents, ThemeToggle, sections, layouts, pages, and global.css.
- Add ThemeToggle component for user‑controlled theme switching.
- Update global styles to define new theme variables.
- Ensure all components respect theme changes and maintain accessibility.

Hubert The Eunuch
This commit is contained in:
Nicholai 2025-12-18 17:08:52 -07:00
parent dc215c89f4
commit 2ca66ccc6d
19 changed files with 698 additions and 405 deletions

View File

@ -36,12 +36,12 @@ const isFeatured = variant === 'featured';
---
<article class:list={[
'group relative border border-white/10 bg-white/[0.02] hover:border-brand-accent/40 transition-all duration-500 overflow-hidden',
'group relative border border-[var(--theme-border-primary)] bg-[var(--theme-hover-bg)] hover:border-brand-accent/40 transition-all duration-500 overflow-hidden',
isFeatured ? 'lg:grid lg:grid-cols-2' : '',
className
]}>
<!-- Accent indicator strip -->
<div class="absolute top-0 left-0 w-1 h-full bg-slate-700 opacity-50 group-hover:bg-brand-accent group-hover:opacity-100 transition-all duration-500"></div>
<div class="absolute top-0 left-0 w-1 h-full bg-[var(--theme-text-subtle)] opacity-50 group-hover:bg-brand-accent group-hover:opacity-100 transition-all duration-500"></div>
<!-- Image section -->
<a href={href} class:list={[
@ -57,13 +57,13 @@ const isFeatured = variant === 'featured';
class="w-full h-full object-cover transition-transform duration-[1.2s] ease-out group-hover:scale-105"
/>
)}
<div class="absolute inset-0 bg-brand-dark/40 group-hover:bg-brand-dark/20 transition-colors duration-500"></div>
<div class="absolute inset-0 bg-gradient-to-t from-brand-dark/60 to-transparent"></div>
<div class="absolute inset-0 bg-[var(--theme-card-overlay)] group-hover:opacity-50 transition-opacity duration-500"></div>
<div class="absolute inset-0 bg-gradient-to-t from-[var(--theme-card-gradient)] to-transparent"></div>
<!-- Category badge overlay -->
{category && (
<div class="absolute top-4 left-4">
<span class="px-3 py-1.5 text-[10px] font-mono font-bold uppercase tracking-widest bg-brand-dark/80 border border-white/20 text-white backdrop-blur-sm">
<span class="px-3 py-1.5 text-[10px] font-mono font-bold uppercase tracking-widest bg-[var(--theme-bg-primary)]/80 border border-[var(--theme-border-strong)] text-[var(--theme-text-primary)] backdrop-blur-sm">
{category}
</span>
</div>
@ -80,8 +80,8 @@ const isFeatured = variant === 'featured';
<span class="text-[10px] font-mono text-brand-accent uppercase tracking-widest">
<FormattedDate date={pubDate} />
</span>
<span class="h-px flex-grow max-w-8 bg-white/20"></span>
<span class="text-[10px] font-mono text-slate-500 uppercase tracking-widest">
<span class="h-px flex-grow max-w-8 bg-[var(--theme-border-strong)]"></span>
<span class="text-[10px] font-mono text-[var(--theme-text-muted)] uppercase tracking-widest">
{readTime}
</span>
</div>
@ -89,7 +89,7 @@ const isFeatured = variant === 'featured';
<!-- Title -->
<a href={href}>
<h3 class:list={[
'font-bold text-white uppercase tracking-tight mb-3 group-hover:text-brand-accent transition-colors duration-300 leading-tight',
'font-bold text-[var(--theme-text-primary)] uppercase tracking-tight mb-3 group-hover:text-brand-accent transition-colors duration-300 leading-tight',
isFeatured ? 'text-3xl lg:text-4xl' : isCompact ? 'text-lg' : 'text-xl lg:text-2xl'
]}>
{title}
@ -98,7 +98,7 @@ const isFeatured = variant === 'featured';
<!-- Description -->
<p class:list={[
'text-slate-400 font-light leading-relaxed',
'text-[var(--theme-text-secondary)] font-light leading-relaxed',
isFeatured ? 'text-base lg:text-lg line-clamp-3 mb-8' : isCompact ? 'text-sm line-clamp-2 mb-4' : 'text-sm line-clamp-2 mb-6'
]}>
{description}
@ -108,7 +108,7 @@ const isFeatured = variant === 'featured';
{tags && tags.length > 0 && !isCompact && (
<div class="flex flex-wrap gap-2 mb-6">
{tags.slice(0, 4).map((tag) => (
<span class="px-2 py-1 text-[10px] font-mono uppercase border border-white/10 text-slate-500 group-hover:border-white/20 transition-colors">
<span class="px-2 py-1 text-[10px] font-mono uppercase border border-[var(--theme-border-primary)] text-[var(--theme-text-muted)] group-hover:border-[var(--theme-border-strong)] transition-colors">
{tag}
</span>
))}
@ -118,14 +118,14 @@ const isFeatured = variant === 'featured';
<!-- Read link -->
<div class:list={[
'flex items-center',
isFeatured ? 'mt-auto pt-6 border-t border-white/10' : 'mt-auto'
isFeatured ? 'mt-auto pt-6 border-t border-[var(--theme-border-primary)]' : 'mt-auto'
]}>
<a
href={href}
class="inline-flex items-center gap-3 text-xs font-bold uppercase tracking-widest text-slate-500 group-hover:text-white transition-all duration-300"
class="inline-flex items-center gap-3 text-xs font-bold uppercase tracking-widest text-[var(--theme-text-muted)] group-hover:text-[var(--theme-text-primary)] transition-all duration-300"
>
Read Article
<span class="block w-6 h-[1px] bg-slate-600 group-hover:bg-brand-accent group-hover:w-10 transition-all duration-300"></span>
<span class="block w-6 h-[1px] bg-[var(--theme-text-subtle)] group-hover:bg-brand-accent group-hover:w-10 transition-all duration-300"></span>
<svg
xmlns="http://www.w3.org/2000/svg"
width="12"
@ -145,4 +145,3 @@ const isFeatured = variant === 'featured';
</div>
</div>
</article>

View File

@ -12,13 +12,13 @@ const { categories, class: className = '' } = Astro.props;
<div class="flex flex-col lg:flex-row lg:items-center lg:justify-between gap-6 mb-10">
<!-- Category chips -->
<div class="flex flex-wrap items-center gap-1">
<span class="text-[10px] font-mono text-slate-500 uppercase tracking-widest mr-4">
<span class="text-[10px] font-mono text-[var(--theme-text-muted)] uppercase tracking-widest mr-4">
/// SECTOR SELECT
</span>
<button
type="button"
data-category="all"
class="filter-chip active px-4 py-2 text-[10px] font-mono font-bold uppercase tracking-widest border-b-2 border-brand-accent text-white bg-white/5 transition-all duration-300 hover:bg-white/10"
class="filter-chip active px-4 py-2 text-[10px] font-mono font-bold uppercase tracking-widest border-b-2 border-brand-accent text-[var(--theme-text-primary)] bg-[var(--theme-hover-bg-strong)] transition-all duration-300 hover:bg-[var(--theme-hover-bg-strong)]"
>
All
</button>
@ -26,7 +26,7 @@ const { categories, class: className = '' } = Astro.props;
<button
type="button"
data-category={category}
class="filter-chip px-4 py-2 text-[10px] font-mono font-bold uppercase tracking-widest border-b-2 border-transparent text-slate-500 hover:text-white hover:border-brand-accent/50 hover:bg-white/5 transition-all duration-300"
class="filter-chip px-4 py-2 text-[10px] font-mono font-bold uppercase tracking-widest border-b-2 border-transparent text-[var(--theme-text-muted)] hover:text-[var(--theme-text-primary)] hover:border-brand-accent/50 hover:bg-[var(--theme-hover-bg-strong)] transition-all duration-300"
>
{category}
</button>
@ -42,12 +42,12 @@ const { categories, class: className = '' } = Astro.props;
type="text"
id="blog-search"
placeholder="SEARCH_DATABASE..."
class="w-full pl-6 pr-4 py-2 text-sm font-mono bg-transparent border-b border-slate-700 text-white placeholder:text-slate-600 focus:border-brand-accent focus:outline-none transition-colors duration-300 uppercase"
class="w-full pl-6 pr-4 py-2 text-sm font-mono bg-transparent border-b border-[var(--theme-text-subtle)] text-[var(--theme-text-primary)] placeholder:text-[var(--theme-text-subtle)] focus:border-brand-accent focus:outline-none transition-colors duration-300 uppercase"
/>
<button
type="button"
id="clear-search"
class="absolute right-0 top-1/2 -translate-y-1/2 text-slate-500 hover:text-brand-accent transition-colors hidden"
class="absolute right-0 top-1/2 -translate-y-1/2 text-[var(--theme-text-muted)] hover:text-brand-accent transition-colors hidden"
>
<span class="font-mono text-xs">[CLR]</span>
</button>
@ -55,11 +55,11 @@ const { categories, class: className = '' } = Astro.props;
</div>
<!-- Results count -->
<div class="flex items-center gap-4 pb-6 border-b border-white/10 mb-8">
<span class="text-[10px] font-mono text-slate-500 uppercase tracking-widest">
<div class="flex items-center gap-4 pb-6 border-b border-[var(--theme-border-primary)] mb-8">
<span class="text-[10px] font-mono text-[var(--theme-text-muted)] uppercase tracking-widest">
<span id="results-count">0</span> ARTICLES
</span>
<span class="h-px flex-grow bg-white/5"></span>
<span class="h-px flex-grow bg-[var(--theme-border-secondary)]"></span>
</div>
</div>
@ -122,13 +122,13 @@ const { categories, class: className = '' } = Astro.props;
// Update active state
// Reset all to inactive state
filterChips.forEach((c) => {
c.classList.remove('active', 'border-brand-accent', 'text-white', 'bg-white/5');
c.classList.add('border-transparent', 'text-slate-500');
c.classList.remove('active', 'border-brand-accent', 'text-[var(--theme-text-primary)]', 'bg-[var(--theme-hover-bg-strong)]');
c.classList.add('border-transparent', 'text-[var(--theme-text-muted)]');
});
// Set clicked to active state
chipEl.classList.add('active', 'border-brand-accent', 'text-white', 'bg-white/5');
chipEl.classList.remove('border-transparent', 'text-slate-500');
chipEl.classList.add('active', 'border-brand-accent', 'text-[var(--theme-text-primary)]', 'bg-[var(--theme-hover-bg-strong)]');
chipEl.classList.remove('border-transparent', 'text-[var(--theme-text-muted)]');
filterPosts();
});
@ -173,8 +173,7 @@ const { categories, class: className = '' } = Astro.props;
<style>
.filter-chip.active {
border-color: var(--color-brand-accent);
color: white;
background-color: rgba(255, 77, 0, 0.05);
color: var(--theme-text-primary);
background-color: var(--theme-hover-bg-strong);
}
</style>

View File

@ -2,10 +2,10 @@
const today = new Date();
---
<footer class="container mx-auto px-6 lg:px-12 py-32 lg:py-40 relative overflow-hidden border-t border-white/5">
<footer class="container mx-auto px-6 lg:px-12 py-32 lg:py-40 relative overflow-hidden border-t border-[var(--theme-border-secondary)]">
<div class="grid grid-cols-1 md:grid-cols-2 gap-16 lg:gap-24 items-end relative z-10">
<div class="animate-on-scroll slide-right">
<h2 class="text-5xl md:text-7xl lg:text-8xl font-bold uppercase leading-[0.95] tracking-tighter mb-10 text-white group cursor-pointer">
<h2 class="text-5xl md:text-7xl lg:text-8xl font-bold uppercase leading-[0.95] tracking-tighter mb-10 text-[var(--theme-text-primary)] group cursor-pointer">
Let's<br>
<span class="text-stroke group-hover:text-brand-accent transition-all duration-500 ease-out">Build</span><br>
Reality.
@ -18,28 +18,28 @@ const today = new Date();
<div class="md:text-right animate-on-scroll slide-left stagger-2">
<div class="mb-14">
<p class="text-xs font-bold uppercase text-slate-500 mb-6 tracking-widest">Social Uplink</p>
<p class="text-xs font-bold uppercase text-[var(--theme-text-muted)] mb-6 tracking-widest">Social Uplink</p>
<ul class="space-y-3">
<li>
<a href="https://nicholai.work" class="text-white hover:text-brand-accent text-lg font-mono transition-colors duration-300 inline-block">
<a href="https://nicholai.work" class="text-[var(--theme-text-primary)] hover:text-brand-accent text-lg font-mono transition-colors duration-300 inline-block">
nicholai.work
</a>
</li>
<li>
<a href="https://instagram.com/nicholai.exe/" class="text-white hover:text-brand-accent text-lg font-mono transition-colors duration-300 inline-block">
<a href="https://instagram.com/nicholai.exe/" class="text-[var(--theme-text-primary)] hover:text-brand-accent text-lg font-mono transition-colors duration-300 inline-block">
@nicholai.exe
</a>
</li>
<li>
<a href="https://www.linkedin.com/in/nicholai-vogel-7a6b85112/" class="text-white hover:text-brand-accent text-lg font-mono transition-colors duration-300 inline-block">
<a href="https://www.linkedin.com/in/nicholai-vogel-7a6b85112/" class="text-[var(--theme-text-primary)] hover:text-brand-accent text-lg font-mono transition-colors duration-300 inline-block">
LinkedIn
</a>
</li>
</ul>
</div>
<div class="flex md:justify-end items-end gap-3 text-[10px] text-slate-600 font-mono uppercase tracking-wide">
<div class="flex md:justify-end items-end gap-3 text-[10px] text-[var(--theme-text-muted)] font-mono uppercase tracking-wide">
<span>&copy; {today.getFullYear()} Nicholai Vogel</span>
<span class="text-slate-700">/</span>
<span class="text-[var(--theme-text-subtle)]">/</span>
<span>V7 SYSTEM</span>
</div>
</div>
@ -47,6 +47,6 @@ const today = new Date();
<!-- Decorative huge text bg -->
<div class="absolute -bottom-8 lg:-bottom-12 left-1/2 -translate-x-1/2 w-full text-center pointer-events-none select-none">
<span class="text-[12rem] md:text-[18rem] lg:text-[22rem] font-extrabold text-white/[0.02] uppercase leading-none whitespace-nowrap tracking-tighter">VOGEL</span>
<span class="text-[12rem] md:text-[18rem] lg:text-[22rem] font-extrabold text-[var(--theme-text-primary)] opacity-[var(--theme-decorative-opacity)] uppercase leading-none whitespace-nowrap tracking-tighter transition-opacity duration-300">VOGEL</span>
</div>
</footer>

View File

@ -5,17 +5,16 @@
<!-- 12 Column Guide (Visual Only - Low Opacity) -->
<div
class="fixed inset-0 container mx-auto px-6 lg:px-12 grid grid-cols-4 md:grid-cols-12 gap-4 pointer-events-none z-0 opacity-10 border-x border-white/5">
<div class="border-r border-white/5 h-full hidden md:block"></div>
<div class="border-r border-white/5 h-full hidden md:block"></div>
<div class="border-r border-white/5 h-full hidden md:block"></div>
<div class="border-r border-white/5 h-full hidden md:block"></div>
<div class="border-r border-white/5 h-full hidden md:block"></div>
<div class="border-r border-white/5 h-full hidden md:block"></div>
<div class="border-r border-white/5 h-full hidden md:block"></div>
<div class="border-r border-white/5 h-full hidden md:block"></div>
<div class="border-r border-white/5 h-full hidden md:block"></div>
<div class="border-r border-white/5 h-full hidden md:block"></div>
<div class="border-r border-white/5 h-full hidden md:block"></div>
class="fixed inset-0 container mx-auto px-6 lg:px-12 grid grid-cols-4 md:grid-cols-12 gap-4 pointer-events-none z-0 opacity-10 border-x border-[var(--theme-border-secondary)]">
<div class="border-r border-[var(--theme-border-secondary)] h-full hidden md:block"></div>
<div class="border-r border-[var(--theme-border-secondary)] h-full hidden md:block"></div>
<div class="border-r border-[var(--theme-border-secondary)] h-full hidden md:block"></div>
<div class="border-r border-[var(--theme-border-secondary)] h-full hidden md:block"></div>
<div class="border-r border-[var(--theme-border-secondary)] h-full hidden md:block"></div>
<div class="border-r border-[var(--theme-border-secondary)] h-full hidden md:block"></div>
<div class="border-r border-[var(--theme-border-secondary)] h-full hidden md:block"></div>
<div class="border-r border-[var(--theme-border-secondary)] h-full hidden md:block"></div>
<div class="border-r border-[var(--theme-border-secondary)] h-full hidden md:block"></div>
<div class="border-r border-[var(--theme-border-secondary)] h-full hidden md:block"></div>
<div class="border-r border-[var(--theme-border-secondary)] h-full hidden md:block"></div>
</div>

View File

@ -1,21 +1,23 @@
---
---
---
import ThemeToggle from './ThemeToggle.astro';
---
<nav class="fixed top-0 left-0 w-full z-50 px-6 lg:px-12 py-6 lg:py-8 flex justify-between items-center backdrop-blur-md bg-brand-dark/80 border-b border-white/5">
<!-- Left side - can be empty or have subtle branding -->
<div class="hidden md:block">
<a href="/" class="text-[10px] font-mono text-slate-600 tracking-widest uppercase hover:text-brand-accent transition-colors duration-300">NV / 2026</a>
<nav class="fixed top-0 left-0 w-full z-50 px-6 lg:px-12 py-6 lg:py-8 flex justify-between items-center backdrop-blur-md bg-[var(--theme-overlay)] border-b border-[var(--theme-border-secondary)]">
<!-- Left side - branding and theme toggle -->
<div class="flex items-center gap-6">
<a href="/" class="text-[10px] font-mono text-[var(--theme-text-muted)] tracking-widest uppercase hover:text-brand-accent transition-colors duration-300">NV / 2026</a>
<div class="hidden md:block">
<ThemeToggle />
</div>
</div>
<!-- Right side navigation -->
<div class="flex items-center gap-8 lg:gap-12 ml-auto">
<div class="flex items-center gap-6 lg:gap-10 ml-auto">
<div class="hidden md:flex items-center gap-10 lg:gap-12">
<a href="/"
class:list={[
"relative text-xs font-semibold uppercase tracking-[0.15em] transition-all duration-300 py-2 group",
Astro.url.pathname === '/' ? "text-white" : "text-slate-500 hover:text-white"
Astro.url.pathname === '/' ? "text-[var(--theme-text-primary)]" : "text-[var(--theme-text-muted)] hover:text-[var(--theme-text-primary)]"
]}>
<span class="relative z-10">Home</span>
<span class:list={[
@ -26,7 +28,7 @@
<a href="/blog"
class:list={[
"relative text-xs font-semibold uppercase tracking-[0.15em] transition-all duration-300 py-2 group",
Astro.url.pathname.startsWith('/blog') ? "text-white" : "text-slate-500 hover:text-white"
Astro.url.pathname.startsWith('/blog') ? "text-[var(--theme-text-primary)]" : "text-[var(--theme-text-muted)] hover:text-[var(--theme-text-primary)]"
]}>
<span class="relative z-10">Blog</span>
<span class:list={[
@ -41,34 +43,36 @@
"hidden md:block border px-5 lg:px-6 py-2.5 lg:py-3 text-xs font-bold uppercase tracking-[0.15em] transition-all duration-300",
Astro.url.pathname.startsWith('/contact')
? "border-brand-accent bg-brand-accent text-brand-dark"
: "border-slate-600 text-white hover:border-brand-accent hover:bg-brand-accent hover:text-brand-dark"
: "border-[var(--theme-border-strong)] text-[var(--theme-text-primary)] hover:border-brand-accent hover:bg-brand-accent hover:text-brand-dark"
]}>
Let's Talk
</a>
</div>
<!-- Mobile menu button -->
<button
id="mobile-menu-toggle"
class="md:hidden p-2 text-slate-400 hover:text-white transition-colors z-[60]"
aria-label="Toggle menu"
aria-expanded="false"
>
<!-- Hamburger icon -->
<svg id="menu-icon-open" class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M4 6h16M4 12h16M4 18h16"></path>
</svg>
<!-- Close icon (hidden by default) -->
<svg id="menu-icon-close" class="w-6 h-6 hidden" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M6 18L18 6M6 6l12 12"></path>
</svg>
</button>
<div class="md:hidden flex items-center">
<button
id="mobile-menu-toggle"
class="p-2 text-[var(--theme-text-muted)] hover:text-[var(--theme-text-primary)] transition-colors z-[60]"
aria-label="Toggle menu"
aria-expanded="false"
>
<!-- Hamburger icon -->
<svg id="menu-icon-open" class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M4 6h16M4 12h16M4 18h16"></path>
</svg>
<!-- Close icon (hidden by default) -->
<svg id="menu-icon-close" class="w-6 h-6 hidden" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M6 18L18 6M6 6l12 12"></path>
</svg>
</button>
</div>
</nav>
<!-- Mobile Menu Overlay -->
<div
id="mobile-menu"
class="fixed inset-0 z-40 bg-brand-dark/98 backdrop-blur-xl transform translate-x-full transition-transform duration-300 ease-out md:hidden"
class="fixed inset-0 z-40 bg-[var(--theme-overlay-heavy)] backdrop-blur-xl transform translate-x-full transition-transform duration-300 ease-out md:hidden"
>
<!-- Menu Content -->
<div class="flex flex-col justify-center items-center h-full px-8">
@ -76,19 +80,19 @@
<nav class="flex flex-col items-center gap-8 mb-12">
<a
href="/"
class="mobile-nav-link text-3xl font-bold uppercase tracking-wider text-white hover:text-brand-accent transition-colors duration-300"
class="mobile-nav-link text-3xl font-bold uppercase tracking-wider text-[var(--theme-text-primary)] hover:text-brand-accent transition-colors duration-300"
>
Home
</a>
<a
href="/blog"
class="mobile-nav-link text-3xl font-bold uppercase tracking-wider text-white hover:text-brand-accent transition-colors duration-300"
class="mobile-nav-link text-3xl font-bold uppercase tracking-wider text-[var(--theme-text-primary)] hover:text-brand-accent transition-colors duration-300"
>
Blog
</a>
<a
href="/contact"
class="mobile-nav-link text-3xl font-bold uppercase tracking-wider text-white hover:text-brand-accent transition-colors duration-300"
class="mobile-nav-link text-3xl font-bold uppercase tracking-wider text-[var(--theme-text-primary)] hover:text-brand-accent transition-colors duration-300"
>
Contact
</a>
@ -97,16 +101,21 @@
<!-- CTA Button -->
<a
href="/contact"
class="border border-brand-accent px-8 py-4 text-sm font-bold uppercase tracking-[0.2em] text-brand-accent hover:bg-brand-accent hover:text-brand-dark transition-all duration-300"
class="border border-brand-accent px-8 py-4 text-sm font-bold uppercase tracking-[0.2em] text-brand-accent hover:bg-brand-accent hover:text-brand-dark transition-all duration-300 mb-8"
>
Let's Talk
</a>
<!-- Decorative Elements -->
<div class="absolute bottom-12 left-8 right-8">
<div class="flex justify-between items-center text-[10px] font-mono text-slate-600 uppercase tracking-widest">
<span>NV / 2026</span>
<span>Menu</span>
<div class="absolute bottom-12 left-8 right-8 flex justify-between items-center">
<div class="flex flex-col gap-2">
<div class="text-[10px] font-mono text-[var(--theme-text-muted)] uppercase tracking-widest">
NV / 2026
</div>
<ThemeToggle />
</div>
<div class="text-[10px] font-mono text-[var(--theme-text-muted)] uppercase tracking-widest self-end">
Menu
</div>
</div>
</div>

View File

@ -17,12 +17,12 @@ const { prevPost, nextPost } = Astro.props;
---
{(prevPost || nextPost) && (
<nav class="post-navigation mt-20 pt-12 border-t border-white/10" aria-label="Post navigation">
<nav class="post-navigation mt-20 pt-12 border-t border-[var(--theme-border-primary)]" aria-label="Post navigation">
<div class="flex items-center gap-4 mb-8">
<span class="text-[10px] font-mono text-slate-500 uppercase tracking-widest font-bold">
<span class="text-[10px] font-mono text-[var(--theme-text-muted)] uppercase tracking-widest font-bold">
/// NEXT_IN_SEQUENCE
</span>
<span class="h-px flex-grow bg-white/10"></span>
<span class="h-px flex-grow bg-[var(--theme-border-primary)]"></span>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
@ -30,24 +30,24 @@ const { prevPost, nextPost } = Astro.props;
{prevPost ? (
<a
href={prevPost.href}
class="group relative flex items-center gap-6 p-6 border border-white/10 bg-white/[0.02] hover:border-brand-accent/40 hover:bg-white/[0.04] transition-all duration-500 overflow-hidden"
class="group relative flex items-center gap-6 p-6 border border-[var(--theme-border-primary)] bg-[var(--theme-hover-bg)] hover:border-brand-accent/40 hover:bg-[var(--theme-hover-bg-strong)] transition-all duration-500 overflow-hidden"
>
<div class="absolute top-0 left-0 w-[2px] h-full bg-slate-700 opacity-50 group-hover:bg-brand-accent group-hover:opacity-100 transition-all duration-500"></div>
<div class="absolute top-0 left-0 w-[2px] h-full bg-[var(--theme-text-subtle)] opacity-50 group-hover:bg-brand-accent group-hover:opacity-100 transition-all duration-500"></div>
<div class="absolute inset-0 bg-brand-accent/5 translate-x-[-100%] group-hover:translate-x-0 transition-transform duration-500 pointer-events-none"></div>
<!-- Content -->
<div class="flex-grow min-w-0 z-10">
<span class="text-[10px] font-mono text-slate-500 uppercase tracking-widest mb-2 block group-hover:text-brand-accent transition-colors">
<span class="text-[10px] font-mono text-[var(--theme-text-muted)] uppercase tracking-widest mb-2 block group-hover:text-brand-accent transition-colors">
&lt; PREV_FILE
</span>
<h4 class="text-sm font-bold text-white uppercase tracking-tight truncate group-hover:text-brand-accent transition-colors">
<h4 class="text-sm font-bold text-[var(--theme-text-primary)] uppercase tracking-tight truncate group-hover:text-brand-accent transition-colors">
{prevPost.title}
</h4>
</div>
<!-- Thumbnail -->
{prevPost.heroImage && (
<div class="hidden sm:block flex-shrink-0 w-12 h-12 overflow-hidden border border-white/10 z-10 grayscale group-hover:grayscale-0 transition-all duration-500">
<div class="hidden sm:block flex-shrink-0 w-12 h-12 overflow-hidden border border-[var(--theme-border-primary)] z-10 grayscale group-hover:grayscale-0 transition-all duration-500">
<Image
src={prevPost.heroImage}
alt=""
@ -59,8 +59,8 @@ const { prevPost, nextPost } = Astro.props;
)}
</a>
) : (
<div class="border border-white/5 bg-white/[0.01] p-6 flex items-center justify-center">
<span class="text-[10px] font-mono text-slate-600 uppercase tracking-widest">/// START_OF_ARCHIVE</span>
<div class="border border-[var(--theme-border-secondary)] bg-[var(--theme-hover-bg)] p-6 flex items-center justify-center">
<span class="text-[10px] font-mono text-[var(--theme-text-subtle)] uppercase tracking-widest">/// START_OF_ARCHIVE</span>
</div>
)}
@ -68,14 +68,14 @@ const { prevPost, nextPost } = Astro.props;
{nextPost ? (
<a
href={nextPost.href}
class="group relative flex items-center gap-6 p-6 border border-white/10 bg-white/[0.02] hover:border-brand-accent/40 hover:bg-white/[0.04] transition-all duration-500 overflow-hidden"
class="group relative flex items-center gap-6 p-6 border border-[var(--theme-border-primary)] bg-[var(--theme-hover-bg)] hover:border-brand-accent/40 hover:bg-[var(--theme-hover-bg-strong)] transition-all duration-500 overflow-hidden"
>
<div class="absolute top-0 right-0 w-[2px] h-full bg-slate-700 opacity-50 group-hover:bg-brand-accent group-hover:opacity-100 transition-all duration-500"></div>
<div class="absolute top-0 right-0 w-[2px] h-full bg-[var(--theme-text-subtle)] opacity-50 group-hover:bg-brand-accent group-hover:opacity-100 transition-all duration-500"></div>
<div class="absolute inset-0 bg-brand-accent/5 translate-x-[100%] group-hover:translate-x-0 transition-transform duration-500 pointer-events-none"></div>
<!-- Thumbnail -->
{nextPost.heroImage && (
<div class="hidden sm:block flex-shrink-0 w-12 h-12 overflow-hidden border border-white/10 z-10 grayscale group-hover:grayscale-0 transition-all duration-500">
<div class="hidden sm:block flex-shrink-0 w-12 h-12 overflow-hidden border border-[var(--theme-border-primary)] z-10 grayscale group-hover:grayscale-0 transition-all duration-500">
<Image
src={nextPost.heroImage}
alt=""
@ -88,20 +88,19 @@ const { prevPost, nextPost } = Astro.props;
<!-- Content -->
<div class="flex-grow min-w-0 text-right z-10">
<span class="text-[10px] font-mono text-slate-500 uppercase tracking-widest mb-2 block group-hover:text-brand-accent transition-colors">
<span class="text-[10px] font-mono text-[var(--theme-text-muted)] uppercase tracking-widest mb-2 block group-hover:text-brand-accent transition-colors">
NEXT_FILE &gt;
</span>
<h4 class="text-sm font-bold text-white uppercase tracking-tight truncate group-hover:text-brand-accent transition-colors">
<h4 class="text-sm font-bold text-[var(--theme-text-primary)] uppercase tracking-tight truncate group-hover:text-brand-accent transition-colors">
{nextPost.title}
</h4>
</div>
</a>
) : (
<div class="border border-white/5 bg-white/[0.01] p-6 flex items-center justify-center">
<span class="text-[10px] font-mono text-slate-600 uppercase tracking-widest">/// END_OF_ARCHIVE</span>
<div class="border border-[var(--theme-border-secondary)] bg-[var(--theme-hover-bg)] p-6 flex items-center justify-center">
<span class="text-[10px] font-mono text-[var(--theme-text-subtle)] uppercase tracking-widest">/// END_OF_ARCHIVE</span>
</div>
)}
</div>
</nav>
)}

View File

@ -2,13 +2,13 @@
// Reading progress bar that tracks scroll position
---
<div id="reading-progress-container" class="fixed top-0 left-0 w-full h-[3px] z-[100] bg-brand-dark/50">
<div id="reading-progress-container" class="fixed top-0 left-0 w-full h-[3px] z-[100] bg-[var(--theme-bg-primary)]/50">
<div id="reading-progress-bar" class="h-full bg-brand-accent w-0 transition-[width] duration-100 ease-out shadow-[0_0_10px_rgba(221,65,50,0.5)]"></div>
</div>
<div id="reading-status" class="fixed top-4 right-4 z-[90] hidden lg:flex items-center gap-3 px-3 py-1 bg-brand-dark/80 backdrop-blur-md border border-white/10 opacity-0 transition-opacity duration-300 pointer-events-none">
<div id="reading-status" class="fixed top-4 right-4 z-[90] hidden lg:flex items-center gap-3 px-3 py-1 bg-[var(--theme-overlay)] backdrop-blur-md border border-[var(--theme-border-primary)] opacity-0 transition-opacity duration-300 pointer-events-none">
<div class="w-1.5 h-1.5 bg-brand-accent rounded-full animate-pulse"></div>
<span class="text-[9px] font-mono text-slate-400 uppercase tracking-widest">READING_BUFFER: <span id="progress-text" class="text-white">0%</span></span>
<span class="text-[9px] font-mono text-[var(--theme-text-secondary)] uppercase tracking-widest">READING_BUFFER: <span id="progress-text" class="text-[var(--theme-text-primary)]">0%</span></span>
</div>
<script>
@ -69,4 +69,3 @@
// Re-initialize on Astro page transitions
document.addEventListener('astro:page-load', initReadingProgress);
</script>

View File

@ -21,12 +21,12 @@ const { posts, class: className = '' } = Astro.props;
---
{posts.length > 0 && (
<section class:list={['related-posts mt-20 pt-12 border-t border-white/10', className]}>
<section class:list={['related-posts mt-20 pt-12 border-t border-[var(--theme-border-primary)]', className]}>
<div class="flex items-center gap-4 mb-8">
<span class="text-[10px] font-mono text-slate-500 uppercase tracking-widest font-bold">
<span class="text-[10px] font-mono text-[var(--theme-text-muted)] uppercase tracking-widest font-bold">
/// RELATED_ARCHIVES
</span>
<span class="h-px flex-grow bg-white/10"></span>
<span class="h-px flex-grow bg-[var(--theme-border-primary)]"></span>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
@ -45,4 +45,3 @@ const { posts, class: className = '' } = Astro.props;
</div>
</section>
)}

View File

@ -17,10 +17,10 @@ const tocHeadings = headings.filter((h) => h.depth === 2 || h.depth === 3);
{tocHeadings.length > 0 && (
<nav class:list={['toc', className]} data-toc aria-label="Table of contents">
<div class="flex items-center gap-3 mb-6">
<span class="text-[10px] font-mono text-slate-500 uppercase tracking-widest font-bold">
<span class="text-[10px] font-mono text-[var(--theme-text-muted)] uppercase tracking-widest font-bold">
/// CONTENTS
</span>
<span class="h-px flex-grow bg-white/10"></span>
<span class="h-px flex-grow bg-[var(--theme-border-primary)]"></span>
</div>
<ul class="space-y-3">
{tocHeadings.map((heading) => (
@ -29,15 +29,15 @@ const tocHeadings = headings.filter((h) => h.depth === 2 || h.depth === 3);
href={`#${heading.slug}`}
data-toc-link={heading.slug}
class:list={[
'toc-link block text-sm transition-all duration-300 hover:text-white',
'toc-link block text-sm transition-all duration-300 hover:text-[var(--theme-text-primary)]',
heading.depth === 2
? 'text-slate-400 font-medium'
: 'text-slate-500 pl-4 text-xs',
? 'text-[var(--theme-text-secondary)] font-medium'
: 'text-[var(--theme-text-muted)] pl-4 text-xs',
]}
>
<span class="flex items-center gap-2">
{heading.depth === 2 && (
<span class="w-1.5 h-1.5 bg-slate-600 toc-indicator transition-colors duration-300"></span>
<span class="w-1.5 h-1.5 bg-[var(--theme-text-subtle)] toc-indicator transition-colors duration-300"></span>
)}
{heading.text}
</span>
@ -78,23 +78,23 @@ const tocHeadings = headings.filter((h) => h.depth === 2 || h.depth === 3);
if (activeHeading && currentActive !== activeHeading) {
// Remove active state from all links
tocLinks.forEach((link) => {
link.classList.remove('text-brand-accent', 'text-white');
link.classList.add('text-slate-400');
link.classList.remove('text-brand-accent', 'text-[var(--theme-text-primary)]');
link.classList.add('text-[var(--theme-text-secondary)]');
const indicator = link.querySelector('.toc-indicator');
if (indicator) {
indicator.classList.remove('bg-brand-accent');
indicator.classList.add('bg-slate-600');
indicator.classList.add('bg-[var(--theme-text-subtle)]');
}
});
// Add active state to current link
const activeLink = document.querySelector(`[data-toc-link="${activeHeading.id}"]`);
if (activeLink) {
activeLink.classList.remove('text-slate-400');
activeLink.classList.remove('text-[var(--theme-text-secondary)]');
activeLink.classList.add('text-brand-accent');
const indicator = activeLink.querySelector('.toc-indicator');
if (indicator) {
indicator.classList.remove('bg-slate-600');
indicator.classList.remove('bg-[var(--theme-text-subtle)]');
indicator.classList.add('bg-brand-accent');
}
}

View File

@ -0,0 +1,87 @@
---
---
<div class="theme-toggle-group flex items-center gap-3 ml-2 select-none" role="group" aria-label="Theme selector">
<div class="flex items-center text-[var(--theme-text-subtle)] opacity-50">
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round">
<polyline points="15 10 20 15 15 20"></polyline>
<path d="M4 4v7a4 4 0 0 0 4 4h12"></path>
</svg>
</div>
<div class="flex items-center gap-2.5">
<button
type="button"
class="theme-toggle-dark w-2.5 h-2.5 rounded-full bg-[#000] border border-white/20 hover:border-brand-accent/50 transition-all duration-300 relative group cursor-pointer"
aria-label="Dark theme"
title="Dark theme"
>
<span class="absolute -inset-1 border border-brand-accent rounded-full opacity-0 group-[.is-current-theme]:opacity-100 transition-opacity duration-300"></span>
</button>
<button
type="button"
class="theme-toggle-light w-2.5 h-2.5 rounded-full bg-[#efefef] border border-black/10 hover:border-brand-accent/50 transition-all duration-300 relative group cursor-pointer"
aria-label="Light theme"
title="Light theme"
>
<span class="absolute -inset-1 border border-brand-accent rounded-full opacity-0 group-[.is-current-theme]:opacity-100 transition-opacity duration-300"></span>
</button>
</div>
</div>
<script>
function initThemeToggle() {
const groups = document.querySelectorAll('.theme-toggle-group');
function getTheme(): 'dark' | 'light' {
const stored = localStorage.getItem('theme');
if (stored === 'light' || stored === 'dark') {
return stored;
}
return 'dark';
}
function updateAllToggles(theme: 'dark' | 'light') {
groups.forEach(group => {
const darkBtn = group.querySelector('.theme-toggle-dark');
const lightBtn = group.querySelector('.theme-toggle-light');
darkBtn?.classList.remove('is-current-theme');
lightBtn?.classList.remove('is-current-theme');
if (theme === 'dark') {
darkBtn?.classList.add('is-current-theme');
} else {
lightBtn?.classList.add('is-current-theme');
}
});
}
function setTheme(theme: 'dark' | 'light') {
if (document.documentElement.getAttribute('data-theme') === theme) return;
document.documentElement.classList.add('theme-transition');
document.documentElement.setAttribute('data-theme', theme);
localStorage.setItem('theme', theme);
updateAllToggles(theme);
setTimeout(() => {
document.documentElement.classList.remove('theme-transition');
}, 300);
}
const currentTheme = getTheme();
updateAllToggles(currentTheme);
groups.forEach(group => {
const darkBtn = group.querySelector('.theme-toggle-dark');
const lightBtn = group.querySelector('.theme-toggle-light');
darkBtn?.addEventListener('click', () => setTheme('dark'));
lightBtn?.addEventListener('click', () => setTheme('light'));
});
}
initThemeToggle();
document.addEventListener('astro:page-load', initThemeToggle);
</script>

View File

@ -25,87 +25,126 @@ interface Props {
const { sectionTitle, sectionSubtitle, sectionLabel, description, entries } = Astro.props;
---
<section id="experience" class="container mx-auto px-6 lg:px-12 py-32 border-t border-white/10">
<!-- Section Header -->
<div class="grid grid-cols-1 lg:grid-cols-12 gap-12 mb-24 lg:mb-32 group cursor-pointer">
<div class="lg:col-span-8">
<h2 class="text-6xl md:text-8xl lg:text-9xl font-bold uppercase tracking-tighter leading-[0.85] text-white">
<span class="block animate-on-scroll slide-up">{sectionTitle}</span>
<span class="block animate-on-scroll slide-up stagger-1 text-stroke group-hover:text-brand-accent transition-all duration-500 ease-out">{sectionSubtitle}</span>
</h2>
</div>
<div class="lg:col-span-4 flex items-end">
<p class="text-slate-400 text-lg leading-relaxed animate-on-scroll slide-up stagger-2 border-l-2 border-brand-accent pl-6">
{description}
</p>
</div>
</div>
<!-- Experience List -->
<div class="w-full border-t border-white/10">
{entries.map((entry, index) => (
<a
href={entry.link?.url || '#'}
class="group block border-b border-white/10 py-10 hover:border-brand-accent/30 transition-colors duration-300"
>
<!-- Entry Header -->
<div class="flex items-baseline justify-between mb-6">
<div class="flex-grow">
<h3 class="text-3xl md:text-5xl font-bold text-white uppercase tracking-tighter group-hover:text-brand-accent transition-colors duration-300">
{entry.company}
</h3>
<p class="text-sm font-mono text-slate-400 mt-2">
{entry.role}
</p>
</div>
<div class="hidden md:block text-right ml-12 flex-shrink-0">
<div class="text-sm font-mono text-brand-accent">{String(index + 1).padStart(2, '0')}</div>
</div>
<section id="experience" class="w-full py-32 border-t border-[var(--theme-border-primary)] bg-[var(--theme-bg-primary)] overflow-hidden">
<div class="container mx-auto px-6 lg:px-12">
<!-- Section Header -->
<div class="grid grid-cols-1 lg:grid-cols-12 gap-12 mb-24 lg:mb-32">
<div class="lg:col-span-8 group cursor-default">
<div class="flex items-center gap-3 mb-6 intro-element animate-on-scroll fade-in">
<div class="w-2 h-2 bg-brand-accent animate-pulse"></div>
<span class="font-mono text-[10px] uppercase tracking-[0.3em] text-brand-accent">SYS.RECORDS /// WORK_HISTORY</span>
</div>
<h2 class="text-5xl md:text-7xl lg:text-8xl font-bold uppercase tracking-tighter leading-[0.85] text-[var(--theme-text-primary)]">
<span class="block">{sectionTitle}</span>
<span class="block text-brand-accent">
{sectionSubtitle}
</span>
</h2>
</div>
<div class="lg:col-span-4 flex flex-col justify-end">
<div class="font-mono text-[10px] text-[var(--theme-text-subtle)] uppercase tracking-widest mb-4 animate-on-scroll fade-in stagger-2 flex items-center gap-2">
<span class="w-8 h-px bg-brand-accent/30"></span>
DATA_ARCHIVE_V7
</div>
<p class="text-[var(--theme-text-secondary)] text-lg leading-relaxed animate-on-scroll slide-up stagger-2 border-l border-brand-accent/30 pl-6">
{description}
</p>
</div>
</div>
<!-- Entry Content Grid -->
<div class="grid grid-cols-1 lg:grid-cols-12 gap-8 lg:gap-12">
<!-- Left Column: Metadata -->
<div class="lg:col-span-4 space-y-6">
<div>
<div class="text-[10px] font-mono uppercase tracking-widest text-slate-500 mb-2">Period</div>
<div class="font-bold text-white">{entry.dates}</div>
</div>
{entry.tags && entry.tags.length > 0 && (
<div>
<div class="text-[10px] font-mono uppercase tracking-widest text-slate-500 mb-2">Stack</div>
<div class="text-slate-400 font-mono text-xs md:text-sm tracking-wide group-hover:text-white transition-colors duration-300">
{entry.tags.join(', ')}
<!-- Experience List -->
<div class="w-full border-t border-[var(--theme-border-primary)]">
{entries.map((entry, index) => (
<div class="group relative border-b border-[var(--theme-border-primary)] hover:bg-white/[0.01] transition-all duration-500 overflow-hidden">
<!-- Industrial Side Accent -->
<div class="absolute left-0 top-0 bottom-0 w-1 bg-brand-accent transform -translate-x-full group-hover:translate-x-0 transition-transform duration-500 ease-out"></div>
<a
href={entry.link?.url || '#'}
class="block py-12 lg:py-16 px-4 lg:px-8"
>
<div class="grid grid-cols-1 lg:grid-cols-12 gap-12 items-start">
<!-- Left: Header & Role -->
<div class="lg:col-span-4">
<div class="flex items-center gap-4 mb-4">
<span class="font-mono text-xs text-brand-accent opacity-50 group-hover:opacity-100 transition-opacity">[{entry.systemId || `EXP.0${index + 1}`}]</span>
<div class="flex items-center gap-2">
<div class={`w-1.5 h-1.5 rounded-full ${index === 0 ? 'bg-brand-accent animate-pulse' : 'bg-[var(--theme-text-subtle)]'}`}></div>
<span class="font-mono text-[9px] uppercase tracking-widest text-[var(--theme-text-muted)]">{index === 0 ? 'PRODUCTION_LIVE' : 'PRODUCTION_WRAP'}</span>
</div>
</div>
<h3 class="text-4xl md:text-5xl lg:text-6xl font-bold text-[var(--theme-text-primary)] uppercase tracking-tighter group-hover:text-brand-accent transition-colors duration-300 mb-4">
{entry.company}
</h3>
<p class="font-mono text-xs uppercase tracking-[0.2em] text-[var(--theme-text-secondary)]">
{entry.role}
</p>
</div>
)}
</div>
<!-- Right Column: Description -->
<div class="lg:col-span-8">
<p class="text-slate-400 leading-relaxed mb-6 group-hover:text-white transition-colors duration-300">
{entry.description}
</p>
<!-- Middle: Metadata -->
<div class="lg:col-span-3 space-y-8 pt-2">
<div>
<div class="text-[10px] font-mono uppercase tracking-[0.2em] text-[var(--theme-text-muted)] mb-3 flex items-center gap-2">
<span class="text-brand-accent">/</span> DUR.TIMELINE
</div>
<div class="font-mono text-sm text-[var(--theme-text-primary)]">{entry.dates}</div>
</div>
{entry.achievements && entry.achievements.length > 0 && (
<div class="space-y-4">
{entry.achievements.map((achievement) => (
<div class="border-l-2 border-white/10 pl-4">
<div class="text-[10px] font-mono uppercase tracking-widest text-slate-500 mb-1">
{achievement.label}
{entry.tags && entry.tags.length > 0 && (
<div>
<div class="text-[10px] font-mono uppercase tracking-[0.2em] text-[var(--theme-text-muted)] mb-3 flex items-center gap-2">
<span class="text-brand-accent">/</span> PIP.TOOLSET
</div>
<div class="text-sm text-slate-400 group-hover:text-white transition-colors duration-300">
{achievement.text}
<div class="flex flex-wrap gap-2">
{entry.tags.map(tag => (
<span class="text-[9px] font-mono uppercase tracking-wider px-2 py-1 border border-[var(--theme-border-secondary)] text-[var(--theme-text-muted)] group-hover:border-brand-accent/30 group-hover:text-[var(--theme-text-secondary)] transition-all">
{tag}
</span>
))}
</div>
</div>
))}
)}
</div>
)}
</div>
<!-- Right: Content -->
<div class="lg:col-span-5 pt-2">
<div class="text-[10px] font-mono uppercase tracking-[0.2em] text-[var(--theme-text-muted)] mb-4 flex items-center gap-2">
<span class="text-brand-accent">/</span> LOG.PRODUCTION_MANIFEST
</div>
<p class="text-[var(--theme-text-secondary)] leading-relaxed mb-8 group-hover:text-[var(--theme-text-primary)] transition-colors duration-300">
{entry.description}
</p>
{entry.achievements && entry.achievements.length > 0 && (
<div class="space-y-6">
{entry.achievements.map((achievement) => (
<div class="relative pl-6 py-1 group/item">
<!-- Minimalist bullet -->
<div class="absolute left-0 top-3 w-3 h-px bg-brand-accent/40 group-hover/item:w-5 transition-all"></div>
<div class="text-[9px] font-mono uppercase tracking-widest text-brand-accent/60 mb-1">
{achievement.label}
</div>
<div class="text-sm text-[var(--theme-text-secondary)] group-hover:text-[var(--theme-text-primary)] transition-colors">
{achievement.text}
</div>
</div>
))}
</div>
)}
{entry.link && (
<div class="mt-8 flex items-center gap-2 font-mono text-[10px] text-brand-accent uppercase tracking-widest opacity-0 group-hover:opacity-100 translate-x-[-10px] group-hover:translate-x-0 transition-all duration-500">
<span>View Project</span>
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="square" stroke-linejoin="miter">
<path d="M5 12h14M12 5l7 7-7 7"/>
</svg>
</div>
)}
</div>
</div>
</a>
</div>
</a>
))}
))}
</div>
</div>
</section>

View File

@ -33,33 +33,33 @@ const { role, client, year, region, projectTitle, projectSubtitle, projectDescri
<source src={videoUrl} type="video/mp4" />
</video>
<!-- Cinematic Letterboxing / Gradient Vignette -->
<div class="absolute inset-0 bg-gradient-to-b from-brand-dark/80 via-transparent to-brand-dark/80 pointer-events-none"></div>
<div class="absolute inset-0 bg-gradient-to-r from-brand-dark/40 via-transparent to-brand-dark/40 pointer-events-none"></div>
<div class="absolute inset-0 bg-gradient-to-b from-[var(--theme-hero-gradient-top)] via-transparent to-[var(--theme-hero-gradient-top)] pointer-events-none transition-colors duration-500"></div>
<div class="absolute inset-0 bg-gradient-to-r from-[var(--theme-hero-gradient-side)] via-transparent to-[var(--theme-hero-gradient-side)] pointer-events-none transition-colors duration-500"></div>
<!-- Subtle Grid Overlay -->
<div class="absolute inset-0 bg-[linear-gradient(rgba(255,255,255,0.02)_1px,transparent_1px),linear-gradient(90deg,rgba(255,255,255,0.02)_1px,transparent_1px)] bg-[size:60px_60px] pointer-events-none opacity-30"></div>
<div class="absolute inset-0 bg-[linear-gradient(var(--theme-grid-line)_1px,transparent_1px),linear-gradient(90deg,var(--theme-grid-line)_1px,transparent_1px)] bg-[size:60px_60px] pointer-events-none opacity-30"></div>
</div>
<!-- Main Content Container - Spaced to frame the video -->
<div class="container mx-auto px-6 lg:px-12 relative z-10 flex-1 flex flex-col justify-between py-12 lg:py-16 pointer-events-none">
<!-- TOP HUD: Telemetry Data -->
<div class="grid grid-cols-2 md:grid-cols-4 gap-8 border-t border-white/20 pt-6 animate-on-scroll slide-up">
<div class="grid grid-cols-2 md:grid-cols-4 gap-8 border-t border-[var(--theme-border-strong)] pt-6 animate-on-scroll slide-up">
<div>
<span class="text-[9px] font-mono text-brand-accent uppercase tracking-widest block mb-1">/// Role</span>
<span class="text-xl md:text-2xl font-bold text-white uppercase tracking-tight">{role}</span>
<span class="text-xl md:text-2xl font-bold text-[var(--theme-text-primary)] uppercase tracking-tight">{role}</span>
</div>
<div>
<span class="text-[9px] font-mono text-brand-accent uppercase tracking-widest block mb-1">/// Client</span>
<span class="text-xl md:text-2xl font-bold text-white uppercase tracking-tight">{client}</span>
<span class="text-xl md:text-2xl font-bold text-[var(--theme-text-primary)] uppercase tracking-tight">{client}</span>
</div>
<div>
<span class="text-[9px] font-mono text-brand-accent uppercase tracking-widest block mb-1">/// Year</span>
<span class="text-xl md:text-2xl font-bold text-white uppercase tracking-tight">{year}</span>
<span class="text-xl md:text-2xl font-bold text-[var(--theme-text-primary)] uppercase tracking-tight">{year}</span>
</div>
<div class="text-right md:text-left">
<span class="text-[9px] font-mono text-brand-accent uppercase tracking-widest block mb-1">/// Region</span>
<span class="text-xl md:text-2xl font-bold text-white uppercase tracking-tight">{region}</span>
<span class="text-xl md:text-2xl font-bold text-[var(--theme-text-primary)] uppercase tracking-tight">{region}</span>
</div>
</div>
@ -74,15 +74,15 @@ const { role, client, year, region, projectTitle, projectSubtitle, projectDescri
</div>
<!-- BOTTOM HUD: Project Details & Stats -->
<div class="border-b border-white/20 pb-6 animate-on-scroll slide-up stagger-1">
<div class="border-b border-[var(--theme-border-strong)] pb-6 animate-on-scroll slide-up stagger-1">
<div class="grid grid-cols-1 lg:grid-cols-12 gap-8 items-end">
<!-- Title & Description -->
<div class="lg:col-span-7">
<h2 class="text-5xl md:text-7xl font-bold uppercase text-white mb-4 tracking-tighter leading-none">
<h2 class="text-5xl md:text-7xl font-bold uppercase text-[var(--theme-text-primary)] mb-4 tracking-tighter leading-none">
{projectTitle} <span class="text-transparent text-stroke">{projectSubtitle}</span>
</h2>
<p class="text-slate-300 font-light max-w-lg text-sm md:text-base leading-relaxed">
<p class="text-[var(--theme-text-secondary)] font-light max-w-lg text-sm md:text-base leading-relaxed">
{projectDescription}
</p>
</div>
@ -92,8 +92,8 @@ const { role, client, year, region, projectTitle, projectSubtitle, projectDescri
<div class="grid grid-cols-2 gap-x-8 gap-y-4 font-mono text-xs">
{stats.map((stat) => (
<div class="border-l border-brand-accent/30 pl-3">
<span class="block text-slate-500 text-[10px] uppercase mb-1">{stat.label}</span>
<span class="block text-white font-bold">{stat.value}</span>
<span class="block text-[var(--theme-text-muted)] text-[10px] uppercase mb-1">{stat.label}</span>
<span class="block text-[var(--theme-text-primary)] font-bold">{stat.value}</span>
</div>
))}
</div>

View File

@ -13,7 +13,10 @@ interface Props {
const { headlineLine1, headlineLine2, portfolioYear, location, locationLabel, bio } = Astro.props;
---
<section id="hero" class="relative w-full h-[100dvh] overflow-hidden bg-brand-dark">
<section id="hero" class="relative w-full h-[100dvh] overflow-hidden bg-[var(--theme-bg-primary)]">
<!-- Industrial Scanlines -->
<div class="absolute inset-0 z-1 pointer-events-none opacity-[0.03] bg-[linear-gradient(rgba(18,16,16,0)_50%,rgba(0,0,0,0.25)_50%),linear-gradient(90deg,rgba(255,0,0,0.06),rgba(0,255,0,0.02),rgba(0,0,112,0.06))] bg-[length:100%_2px,3px_100%]"></div>
<!-- Background Image (Portrait) - Optimized with AVIF/WebP -->
<div class="absolute top-0 right-0 w-full md:w-1/2 h-full z-0">
<div class="relative w-full h-full">
@ -23,20 +26,30 @@ const { headlineLine1, headlineLine2, portfolioYear, location, locationLabel, bi
widths={[640, 1024, 1600]}
sizes="(max-width: 768px) 100vw, 50vw"
alt="Nicholai Vogel portrait"
class="w-full h-full object-cover object-center opacity-0 mix-blend-luminosity md:opacity-0 transition-opacity duration-[2000ms] ease-out delay-500 intro-element"
class="w-full h-full object-cover object-center opacity-0 mix-blend-luminosity transition-opacity duration-[2500ms] ease-out delay-700 intro-element"
id="hero-portrait"
loading="eager"
decoding="sync"
/>
<div class="absolute inset-0 bg-gradient-to-l from-transparent via-brand-dark/50 to-brand-dark"></div>
<div class="absolute inset-0 bg-gradient-to-t from-brand-dark via-transparent to-transparent"></div>
<div class="absolute inset-0 bg-gradient-to-l from-transparent via-[var(--theme-hero-gradient-side)] to-[var(--theme-bg-primary)] transition-colors duration-500"></div>
<div class="absolute inset-0 bg-gradient-to-t from-[var(--theme-bg-primary)] via-transparent to-transparent transition-colors duration-500"></div>
<!-- Technical Overlay Elements -->
<div class="absolute bottom-12 right-12 hidden lg:flex flex-col items-end gap-1 font-mono text-[9px] text-brand-accent/40 uppercase tracking-[0.3em] intro-element opacity-0 delay-1000">
<span>COORD: 38.8339° N, 104.8214° W</span>
<span>ELV: 1,839M</span>
<div class="flex gap-2 mt-2">
<div class="w-8 h-px bg-brand-accent/20"></div>
<div class="w-2 h-px bg-brand-accent/40"></div>
</div>
</div>
</div>
</div>
<!-- The 100 Squares Grid Overlay -->
<div id="grid-container" class="absolute inset-0 z-10 w-full h-full grid grid-cols-10 grid-rows-10 pointer-events-none">
{Array.from({ length: 100 }).map((_, i) => (
<div class="grid-cell w-full h-full border border-white/5 opacity-0 transition-all duration-500 ease-out" data-index={i}></div>
<div class="grid-cell w-full h-full border border-[var(--theme-border-secondary)] opacity-0 transition-all duration-500 ease-out" data-index={i}></div>
))}
</div>
@ -46,38 +59,55 @@ const { headlineLine1, headlineLine2, portfolioYear, location, locationLabel, bi
<!-- Top Metadata -->
<div class="flex justify-between items-start w-full intro-element opacity-0 translate-y-4 transition-all duration-1000 ease-out delay-300">
<div class="font-mono text-xs uppercase tracking-widest text-slate-500">
{portfolioYear}
<div class="flex items-center gap-3">
<div class="w-1.5 h-1.5 bg-brand-accent animate-pulse"></div>
<div class="font-mono text-[10px] uppercase tracking-[0.2em] text-[var(--theme-text-muted)]">
<span class="text-brand-accent mr-1">SYS.PRTF</span> / {portfolioYear}
</div>
</div>
<div class="font-mono text-xs text-slate-500 text-right tracking-wide">
<span class="block text-slate-600 mb-1 uppercase tracking-widest">{locationLabel}</span>
{location}<br>
<span id="clock" class="text-brand-accent">00:00:00 MST</span>
<div class="font-mono text-[10px] text-[var(--theme-text-muted)] text-right tracking-[0.15em] uppercase">
<div class="mb-1 flex items-center justify-end gap-2">
<span class="text-[var(--theme-text-subtle)]">{locationLabel}</span>
<span class="text-brand-accent font-bold">///</span>
</div>
<div class="text-[var(--theme-text-secondary)]">{location}</div>
<div id="clock" class="text-brand-accent mt-0.5">00:00:00 MST</div>
</div>
</div>
<!-- Main Heading & Description -->
<div class="max-w-5xl">
<h1 class="text-6xl md:text-8xl lg:text-9xl tracking-tighter leading-[0.9] font-bold text-white mix-blend-overlay opacity-90 mb-8 perspective-text">
<h1 class="text-6xl md:text-8xl lg:text-9xl tracking-tighter leading-[0.85] font-bold text-[var(--theme-text-primary)] mb-8 perspective-text">
<span class="block intro-element opacity-0 translate-y-10 transition-all duration-1000 ease-out delay-100">{headlineLine1}</span>
<span class="block text-brand-accent opacity-0 translate-y-10 transition-all duration-1000 ease-out delay-200 intro-element">{headlineLine2}</span>
</h1>
<p class="font-mono text-sm md:text-base max-w-lg text-slate-400 font-light leading-relaxed intro-element opacity-0 translate-y-6 transition-all duration-1000 ease-out delay-500">
<p class="font-mono text-sm md:text-base max-w-lg text-[var(--theme-text-secondary)] font-light leading-relaxed intro-element opacity-0 translate-y-6 transition-all duration-1000 ease-out delay-500">
{bio}
</p>
</div>
<!-- Bottom Navigation -->
<div class="flex justify-between items-end w-full intro-element opacity-0 transition-all duration-1000 ease-out delay-700">
<a href="#experience" class="flex items-center justify-center w-12 h-12 border border-white/10 rounded-full text-brand-accent hover:bg-brand-accent hover:text-brand-dark transition-all duration-300 group">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="group-hover:animate-bounce">
<path d="M12 5v14M19 12l-7 7-7-7"/>
</svg>
<a href="#experience" class="group flex items-center gap-6 py-2">
<div class="relative w-12 h-12 flex items-center justify-center border border-[var(--theme-border-primary)] text-brand-accent hover:border-brand-accent hover:bg-brand-accent/5 transition-all duration-300">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="square" stroke-linejoin="miter" class="group-hover:translate-y-1 transition-transform duration-300">
<path d="M7 13l5 5 5-5M12 6v12"/>
</svg>
<!-- Technical Corner Accent -->
<div class="absolute -top-px -left-px w-2 h-2 border-t border-l border-brand-accent opacity-0 group-hover:opacity-100 transition-opacity"></div>
<div class="absolute -bottom-px -right-px w-2 h-2 border-b border-r border-brand-accent opacity-0 group-hover:opacity-100 transition-opacity"></div>
</div>
<div class="flex flex-col font-mono text-[10px] uppercase tracking-[0.2em]">
<span class="text-[var(--theme-text-muted)] group-hover:text-brand-accent transition-colors">Scroll</span>
<span class="text-[var(--theme-text-subtle)] group-hover:text-[var(--theme-text-secondary)] transition-colors">To Explore</span>
</div>
</a>
<div class="text-right font-mono text-xs text-slate-500 tracking-widest">
SCROLL TO EXPLORE
<div class="hidden md:block text-right font-mono text-[10px] text-[var(--theme-text-muted)] tracking-[0.2em] uppercase">
<span class="text-[var(--theme-text-subtle)]">STATUS:</span> <span class="text-brand-accent">READY_FOR_INPUT</span><br>
<span class="text-[var(--theme-text-subtle)]">INDEX:</span> 00.01 / 05.00
</div>
</div>
</div>
@ -91,7 +121,7 @@ const { headlineLine1, headlineLine2, portfolioYear, location, locationLabel, bi
}
/* Fade out */
.grid-cell {
/* Slightly faster fade-out for a snappier feel */
/* Snappier fade-out */
transition: opacity 0.6s ease-out, background-color 0.6s ease-out;
}

View File

@ -24,46 +24,50 @@ const imageMap: Record<string, string> = {
};
---
<section id="skills" class="bg-brand-dark py-32 lg:py-48 overflow-hidden relative cursor-default">
<section id="skills" class="bg-[var(--theme-bg-primary)] py-32 lg:py-48 overflow-hidden relative cursor-default">
<div class="container mx-auto px-6 lg:px-12 relative z-10">
<!-- Header Section -->
<div class="mb-24 lg:mb-32 grid grid-cols-1 lg:grid-cols-12 gap-12">
<div class="lg:col-span-8">
<h2 class="text-6xl md:text-8xl lg:text-9xl font-bold uppercase tracking-tighter leading-[0.85] text-white">
<span class="block relative overflow-hidden">
<span class="animate-on-scroll slide-up block">{sectionTitle}</span>
</span>
<span class="block relative overflow-hidden">
<span class="animate-on-scroll slide-up stagger-1 block text-stroke text-transparent">{sectionSubtitle}</span>
</span>
<div class="lg:col-span-8 group cursor-default">
<div class="flex items-center gap-3 mb-6 intro-element animate-on-scroll fade-in">
<div class="w-2 h-2 bg-brand-accent animate-pulse"></div>
<span class="font-mono text-[10px] uppercase tracking-[0.3em] text-brand-accent">SYS.TOOLSET /// PIPELINE_CAPABILITIES</span>
</div>
<h2 class="text-5xl md:text-7xl lg:text-8xl font-bold uppercase tracking-tighter leading-[0.85] text-[var(--theme-text-primary)]">
<span class="block">{sectionTitle}</span>
<span class="block text-brand-accent">{sectionSubtitle}</span>
</h2>
</div>
<div class="lg:col-span-4 flex items-end">
<p class="text-slate-400 text-lg leading-relaxed animate-on-scroll slide-up stagger-2 border-l-2 border-brand-accent pl-6">
<div class="lg:col-span-4 flex flex-col justify-end">
<div class="font-mono text-[10px] text-[var(--theme-text-subtle)] uppercase tracking-widest mb-4 flex items-center gap-2">
<span class="w-8 h-px bg-brand-accent/30"></span>
TECH_STACK_MANIFEST
</div>
<p class="text-[var(--theme-text-secondary)] text-lg leading-relaxed border-l border-brand-accent/30 pl-6">
{description}
</p>
</div>
</div>
<!-- Skills Data Grid -->
<div class="w-full border-t border-white/10" id="skills-table">
<div class="w-full border-t border-[var(--theme-border-primary)]" id="skills-table">
<!-- Table Header -->
<div class="grid grid-cols-12 gap-4 py-6 border-b border-white/10 text-[10px] font-mono uppercase tracking-widest text-slate-500 select-none">
<div class="col-span-2 md:col-span-1">/// ID</div>
<div class="col-span-10 md:col-span-4">Domain</div>
<div class="col-span-12 md:col-span-5 hidden md:block">Stack</div>
<div class="col-span-6 md:col-span-2 hidden md:block text-right">Proficiency</div>
<div class="grid grid-cols-12 gap-4 py-8 border-b border-[var(--theme-border-primary)] text-[10px] font-mono uppercase tracking-[0.2em] text-[var(--theme-text-muted)] select-none">
<div class="col-span-2 md:col-span-1">/// ID.TAG</div>
<div class="col-span-10 md:col-span-4">DOMAIN.SPECIALIZATION</div>
<div class="col-span-12 md:col-span-5 hidden md:block">PIP.TOOLSET</div>
<div class="col-span-6 md:col-span-2 hidden md:block text-right">LVL.STATUS</div>
</div>
{skills.map((skill, index) => {
const proficiencyClass = skill.proficiency === "Expert" || skill.proficiency === "Specialist"
? "border-brand-accent/50 text-brand-accent bg-brand-accent/5"
: "border-white/20 text-slate-300";
: "border-[var(--theme-border-strong)] text-[var(--theme-text-secondary)]";
return (
<div class={`skill-row group relative grid grid-cols-12 gap-4 py-10 border-b border-white/10 items-center transition-colors duration-300 hover:border-brand-accent/30 overflow-hidden ${index < skills.length - 1 ? '' : ''}`} data-image={imageMap[skill.id] || "default"}>
<div class={`skill-row group relative grid grid-cols-12 gap-4 py-10 border-b border-[var(--theme-border-primary)] items-center transition-colors duration-300 hover:border-brand-accent/30 overflow-hidden`} data-image={imageMap[skill.id] || "default"}>
<!-- Hover Background Effect -->
<div class="absolute inset-0 bg-brand-accent/5 opacity-0 group-hover:opacity-100 transition-opacity duration-300 pointer-events-none"></div>
@ -75,13 +79,13 @@ const imageMap: Record<string, string> = {
<!-- Main Content (Domain) -->
<div class="col-span-10 md:col-span-4 relative z-10">
<h3 class="text-3xl md:text-5xl font-bold text-white uppercase tracking-tighter group-hover:text-brand-accent transition-colors duration-300">{skill.domain}</h3>
<h3 class="text-3xl md:text-5xl font-bold text-[var(--theme-text-primary)] uppercase tracking-tighter group-hover:text-brand-accent transition-colors duration-300">{skill.domain}</h3>
<!-- Scan line effect for ALL items on hover -->
<div class="absolute bottom-0 left-0 h-[1px] w-full bg-brand-accent transform scale-x-0 group-hover:scale-x-100 transition-transform duration-700 ease-out origin-left opacity-0 group-hover:opacity-100"></div>
</div>
<!-- Tools Stack -->
<div class="col-span-12 md:col-span-5 text-slate-400 font-mono text-xs md:text-sm tracking-wide group-hover:text-white transition-colors duration-300 z-10">
<div class="col-span-12 md:col-span-5 text-[var(--theme-text-secondary)] font-mono text-xs md:text-sm tracking-wide group-hover:text-[var(--theme-text-primary)] transition-colors duration-300 z-10">
{skill.tools}
</div>

View File

@ -29,9 +29,17 @@ const {
---
<!DOCTYPE html>
<html lang="en" class="scroll-smooth">
<html lang="en" class="scroll-smooth" data-theme="dark">
<head>
<meta name="x-nicholai-marker" content={HTML_MARKER} />
<!-- Theme initialization script - runs before page render to prevent flash -->
<script is:inline>
(function() {
const stored = localStorage.getItem('theme');
const theme = (stored === 'light' || stored === 'dark') ? stored : 'dark';
document.documentElement.setAttribute('data-theme', theme);
})();
</script>
<BaseHead
title={title}
description={description}
@ -43,7 +51,7 @@ const {
<slot name="head" />
</head>
<body class="antialiased selection:bg-brand-accent selection:text-brand-dark bg-brand-dark text-white">
<body class="antialiased selection:bg-brand-accent selection:text-brand-dark">
<!-- Only hydrate custom cursor on devices that can actually benefit from it -->
<CustomCursor client:media="(pointer: fine) and (hover: hover)" />
<GridOverlay />
@ -180,4 +188,3 @@ const {
</script>
</body>
</html>

View File

@ -103,7 +103,7 @@ const articleSchema = {
<div class="lg:col-span-8 lg:col-start-3">
<!-- Back Navigation -->
<div class="mb-12">
<a href="/blog" class="inline-flex items-center gap-3 px-5 py-3 border border-slate-700 bg-brand-dark/50 text-xs font-mono font-bold uppercase tracking-widest text-slate-400 hover:border-brand-accent hover:text-white hover:bg-brand-accent/5 transition-all duration-300 group backdrop-blur-sm">
<a href="/blog" class="inline-flex items-center gap-3 px-5 py-3 border border-[var(--theme-text-subtle)] bg-[var(--theme-overlay)] text-xs font-mono font-bold uppercase tracking-widest text-[var(--theme-text-secondary)] hover:border-brand-accent hover:text-[var(--theme-text-primary)] hover:bg-brand-accent/5 transition-all duration-300 group backdrop-blur-sm">
<span class="text-brand-accent group-hover:-translate-x-1 transition-transform duration-300">&lt;</span>
<span>RETURN_TO_ARCHIVE</span>
</a>
@ -115,30 +115,30 @@ const articleSchema = {
<!-- Text Content -->
<div class="order-2 lg:order-1 relative z-10">
<!-- Metadata -->
<div class="flex flex-wrap items-center gap-4 text-[10px] font-mono text-slate-500 uppercase tracking-widest mb-8 border-b border-white/10 pb-4">
<div class="flex flex-wrap items-center gap-4 text-[10px] font-mono text-[var(--theme-text-muted)] uppercase tracking-widest mb-8 border-b border-[var(--theme-border-primary)] pb-4">
<div class="flex items-center gap-2">
<div class="w-1.5 h-1.5 bg-brand-accent shadow-[0_0_10px_rgba(221,65,50,0.5)] rounded-full animate-pulse"></div>
<span class="text-brand-accent font-bold">SYS.LOG</span>
</div>
<span class="text-slate-700">/</span>
<span class="text-[var(--theme-text-subtle)]">/</span>
<FormattedDate date={pubDate} />
<span class="text-slate-700">/</span>
<span class="text-[var(--theme-text-subtle)]">/</span>
<span>{readTime}</span>
</div>
{category && (
<div class="mb-6">
<span class="inline-block px-3 py-1.5 text-[10px] font-mono font-bold uppercase tracking-[0.2em] bg-white/5 border border-white/10 text-brand-accent hover:bg-brand-accent/10 transition-colors cursor-default">
<span class="inline-block px-3 py-1.5 text-[10px] font-mono font-bold uppercase tracking-[0.2em] bg-[var(--theme-hover-bg-strong)] border border-[var(--theme-border-primary)] text-brand-accent hover:bg-brand-accent/10 transition-colors cursor-default">
/// {category}
</span>
</div>
)}
<h1 class="text-4xl md:text-5xl lg:text-6xl font-bold text-white uppercase leading-[0.9] tracking-tighter mb-8 break-words text-balance">
<h1 class="text-4xl md:text-5xl lg:text-6xl font-bold text-[var(--theme-text-primary)] uppercase leading-[0.9] tracking-tighter mb-8 break-words text-balance">
{title}
</h1>
<p class="text-base md:text-lg text-slate-400 leading-relaxed font-light mb-8 border-l-2 border-brand-accent pl-6">
<p class="text-base md:text-lg text-[var(--theme-text-secondary)] leading-relaxed font-light mb-8 border-l-2 border-brand-accent pl-6">
{description}
</p>
@ -146,7 +146,7 @@ const articleSchema = {
{tags && tags.length > 0 && (
<div class="flex flex-wrap gap-2">
{tags.map((tag) => (
<span class="px-2 py-1 text-[9px] font-mono uppercase bg-brand-dark border border-slate-800 text-slate-500 hover:border-brand-accent/50 hover:text-white transition-colors cursor-default">
<span class="px-2 py-1 text-[9px] font-mono uppercase bg-[var(--theme-bg-primary)] border border-[var(--theme-border-primary)] text-[var(--theme-text-muted)] hover:border-brand-accent/50 hover:text-[var(--theme-text-primary)] transition-colors cursor-default">
#{tag}
</span>
))}
@ -157,7 +157,7 @@ const articleSchema = {
<!-- Hero Image -->
{heroImage && (
<div class="order-1 lg:order-2">
<div class="relative aspect-[4/3] lg:aspect-square overflow-hidden border border-white/10 bg-white/[0.02] group">
<div class="relative aspect-[4/3] lg:aspect-square overflow-hidden border border-[var(--theme-border-primary)] bg-[var(--theme-hover-bg)] group">
<!-- Tech corners -->
<div class="absolute top-0 left-0 w-2 h-2 border-t border-l border-brand-accent z-20"></div>
<div class="absolute top-0 right-0 w-2 h-2 border-t border-r border-brand-accent z-20"></div>
@ -188,30 +188,30 @@ const articleSchema = {
</div>
<!-- Author Footer -->
<footer class="mt-24 pt-10 border-t border-white/10">
<footer class="mt-24 pt-10 border-t border-[var(--theme-border-primary)]">
<div class="flex flex-col md:flex-row md:items-center md:justify-between gap-6">
<div>
<p class="text-[10px] font-mono text-brand-accent uppercase tracking-widest mb-2 flex items-center gap-2">
<span class="w-1.5 h-1.5 bg-brand-accent rounded-full animate-pulse"></span>
/// END TRANSMISSION
</p>
<p class="text-slate-400 text-sm font-mono">
<p class="text-[var(--theme-text-secondary)] text-sm font-mono">
LOG_DATE: <FormattedDate date={pubDate} />
{updatedDate && (
<span class="text-slate-500"> // UPDATED: <FormattedDate date={updatedDate} /></span>
<span class="text-[var(--theme-text-muted)]"> // UPDATED: <FormattedDate date={updatedDate} /></span>
)}
</p>
</div>
<!-- Share Links -->
<div class="flex items-center gap-6">
<span class="text-[10px] font-mono text-slate-500 uppercase tracking-widest">DATA_UPLINK:</span>
<span class="text-[10px] font-mono text-[var(--theme-text-muted)] uppercase tracking-widest">DATA_UPLINK:</span>
<div class="flex items-center gap-2">
<a
href={`https://twitter.com/intent/tweet?text=${encodeURIComponent(title)}&url=${encodeURIComponent(Astro.url.href)}`}
target="_blank"
rel="noopener noreferrer"
class="w-10 h-10 flex items-center justify-center border border-white/10 text-slate-400 hover:border-brand-accent hover:text-brand-accent transition-all duration-300"
class="w-10 h-10 flex items-center justify-center border border-[var(--theme-border-primary)] text-[var(--theme-text-secondary)] hover:border-brand-accent hover:text-brand-accent transition-all duration-300"
aria-label="Share on Twitter"
>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
@ -222,7 +222,7 @@ const articleSchema = {
href={`https://www.linkedin.com/shareArticle?mini=true&url=${encodeURIComponent(Astro.url.href)}&title=${encodeURIComponent(title)}`}
target="_blank"
rel="noopener noreferrer"
class="w-10 h-10 flex items-center justify-center border border-white/10 text-slate-400 hover:border-brand-accent hover:text-brand-accent transition-all duration-300"
class="w-10 h-10 flex items-center justify-center border border-[var(--theme-border-primary)] text-[var(--theme-text-secondary)] hover:border-brand-accent hover:text-brand-accent transition-all duration-300"
aria-label="Share on LinkedIn"
>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
@ -234,7 +234,7 @@ const articleSchema = {
<button
type="button"
onclick="navigator.clipboard.writeText(window.location.href)"
class="w-10 h-10 flex items-center justify-center border border-white/10 text-slate-400 hover:border-brand-accent hover:text-brand-accent transition-all duration-300"
class="w-10 h-10 flex items-center justify-center border border-[var(--theme-border-primary)] text-[var(--theme-text-secondary)] hover:border-brand-accent hover:text-brand-accent transition-all duration-300"
aria-label="Copy link"
>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
@ -254,8 +254,8 @@ const articleSchema = {
<RelatedPosts posts={relatedPosts} />
<!-- Back to Blog -->
<div class="mt-20 pt-10 border-t border-white/10 flex justify-center lg:justify-start">
<a href="/blog" class="inline-flex items-center gap-4 px-8 py-4 border border-slate-600 text-xs font-bold uppercase tracking-widest text-white hover:border-brand-accent hover:bg-brand-accent hover:text-brand-dark transition-all duration-300 group">
<div class="mt-20 pt-10 border-t border-[var(--theme-border-primary)] flex justify-center lg:justify-start">
<a href="/blog" class="inline-flex items-center gap-4 px-8 py-4 border border-[var(--theme-border-strong)] text-xs font-bold uppercase tracking-widest text-[var(--theme-text-primary)] hover:border-brand-accent hover:bg-brand-accent hover:text-brand-dark transition-all duration-300 group">
<span class="font-mono transition-transform duration-300 group-hover:-translate-x-1">&lt;</span>
ACCESS_FULL_ARCHIVE
</a>

View File

@ -31,7 +31,7 @@ const categories = [...new Set(allPosts.map((post) => post.data.category).filter
<section class="container mx-auto px-6 lg:px-12">
<!-- Back Navigation -->
<div class="mb-12">
<a href="/" class="inline-flex items-center gap-3 px-5 py-3 border border-slate-700 bg-brand-dark/50 text-xs font-mono font-bold uppercase tracking-widest text-slate-400 hover:border-brand-accent hover:text-white hover:bg-brand-accent/5 transition-all duration-300 group backdrop-blur-sm">
<a href="/" class="inline-flex items-center gap-3 px-5 py-3 border border-[var(--theme-border-primary)] bg-[var(--theme-overlay)] text-xs font-mono font-bold uppercase tracking-widest text-[var(--theme-text-secondary)] hover:border-brand-accent hover:text-[var(--theme-text-primary)] hover:bg-brand-accent/5 transition-all duration-300 group backdrop-blur-sm">
<span class="text-brand-accent group-hover:-translate-x-1 transition-transform duration-300">&lt;</span>
<span>RETURN_TO_HOME</span>
</a>
@ -40,14 +40,21 @@ const categories = [...new Set(allPosts.map((post) => post.data.category).filter
<!-- Page Header -->
<div class="grid grid-cols-1 lg:grid-cols-12 gap-8 lg:gap-12 mb-16 lg:mb-24">
<div class="lg:col-span-8">
<h1 class="text-6xl md:text-8xl lg:text-9xl font-bold uppercase tracking-tighter leading-[0.85]">
<span class="block text-white animate-on-scroll slide-up">BLOG</span>
<span class="block text-transparent text-stroke animate-on-scroll slide-up stagger-1">ARCHIVE</span>
<div class="flex items-center gap-3 mb-6">
<div class="w-2 h-2 bg-brand-accent animate-pulse"></div>
<span class="font-mono text-[10px] uppercase tracking-[0.3em] text-brand-accent">SYS.LOG /// PRODUCTION_ARCHIVE</span>
</div>
<h1 class="text-5xl md:text-7xl lg:text-8xl font-bold uppercase tracking-tighter leading-[0.85]">
<span class="block text-[var(--theme-text-primary)]">BLOG</span>
<span class="block text-brand-accent">ARCHIVE</span>
</h1>
</div>
<div class="lg:col-span-4 flex flex-col justify-end pb-4">
<div class="font-mono text-xs text-slate-500 uppercase tracking-widest mb-4">/// THOUGHTS & PROCESS</div>
<p class="text-slate-400 text-base leading-relaxed border-l border-brand-accent pl-6 animate-on-scroll fade-in stagger-2">
<div class="lg:col-span-4 flex flex-col justify-end">
<div class="font-mono text-[10px] text-[var(--theme-text-subtle)] uppercase tracking-widest mb-4 flex items-center gap-2">
<span class="w-8 h-px bg-brand-accent/30"></span>
THOUGHTS & PROCESS
</div>
<p class="text-[var(--theme-text-secondary)] text-lg leading-relaxed border-l border-brand-accent/30 pl-6">
Deep dives into VFX production, technical pipelines, and creative process. Sharing lessons from the front lines of visual effects.
</p>
</div>
@ -61,10 +68,10 @@ const categories = [...new Set(allPosts.map((post) => post.data.category).filter
<span class="text-[10px] font-mono text-brand-accent uppercase tracking-widest font-bold">
SYS.BLOG /// FEATURED
</span>
<span class="h-px flex-grow bg-white/10"></span>
<span class="h-px flex-grow bg-[var(--theme-border-secondary)]"></span>
</div>
<article class="group relative border border-white/10 bg-white/[0.02] hover:border-brand-accent/40 transition-all duration-500 overflow-hidden">
<article class="group relative border border-[var(--theme-border-primary)] bg-[var(--theme-hover-bg)] hover:border-brand-accent/40 transition-all duration-500 overflow-hidden">
<!-- Accent indicator strip -->
<div class="absolute top-0 left-0 w-1 h-full bg-brand-accent"></div>
<div class="absolute top-0 left-0 w-full h-1 bg-brand-accent opacity-0 group-hover:opacity-100 transition-opacity duration-500"></div>
@ -81,14 +88,14 @@ const categories = [...new Set(allPosts.map((post) => post.data.category).filter
class="w-full h-full object-cover transition-transform duration-[1.2s] ease-out group-hover:scale-105"
/>
)}
<div class="absolute inset-0 bg-brand-dark/30 group-hover:bg-brand-dark/10 transition-colors duration-500"></div>
<div class="absolute inset-0 bg-gradient-to-r from-transparent via-transparent to-brand-dark/80 hidden lg:block"></div>
<div class="absolute inset-0 bg-gradient-to-t from-brand-dark/60 to-transparent lg:hidden"></div>
<div class="absolute inset-0 bg-[var(--theme-card-overlay)] group-hover:opacity-50 transition-opacity duration-500"></div>
<div class="absolute inset-0 bg-gradient-to-r from-transparent via-transparent to-[var(--theme-card-gradient)] hidden lg:block"></div>
<div class="absolute inset-0 bg-gradient-to-t from-[var(--theme-card-gradient)] to-transparent lg:hidden"></div>
<!-- Category badge -->
{featuredPost.data.category && (
<div class="absolute top-6 left-6">
<span class="px-4 py-2 text-[10px] font-mono font-bold uppercase tracking-widest bg-brand-dark/80 border border-brand-accent/50 text-brand-accent backdrop-blur-sm">
<span class="px-4 py-2 text-[10px] font-mono font-bold uppercase tracking-widest bg-[var(--theme-overlay)] border border-brand-accent/50 text-brand-accent backdrop-blur-sm">
{featuredPost.data.category}
</span>
</div>
@ -105,21 +112,21 @@ const categories = [...new Set(allPosts.map((post) => post.data.category).filter
<span class="text-[10px] font-mono text-brand-accent uppercase tracking-widest">
<FormattedDate date={featuredPost.data.pubDate} />
</span>
<span class="h-px w-8 bg-white/20"></span>
<span class="text-[10px] font-mono text-slate-500 uppercase tracking-widest">
<span class="h-px w-8 bg-[var(--theme-border-strong)]"></span>
<span class="text-[10px] font-mono text-[var(--theme-text-muted)] uppercase tracking-widest">
5 min read
</span>
</div>
<!-- Title -->
<a href={`/blog/${featuredPost.id}/`}>
<h2 class="text-3xl lg:text-4xl xl:text-5xl font-bold text-white uppercase tracking-tight mb-6 group-hover:text-brand-accent transition-colors duration-300 leading-tight">
<h2 class="text-3xl lg:text-4xl xl:text-5xl font-bold text-[var(--theme-text-primary)] uppercase tracking-tight mb-6 group-hover:text-brand-accent transition-colors duration-300 leading-tight">
{featuredPost.data.title}
</h2>
</a>
<!-- Description -->
<p class="text-slate-400 text-base lg:text-lg font-light leading-relaxed mb-8 line-clamp-3">
<p class="text-[var(--theme-text-secondary)] text-base lg:text-lg font-light leading-relaxed mb-8 line-clamp-3">
{featuredPost.data.description}
</p>
@ -127,7 +134,7 @@ const categories = [...new Set(allPosts.map((post) => post.data.category).filter
{featuredPost.data.tags && featuredPost.data.tags.length > 0 && (
<div class="flex flex-wrap gap-2 mb-8">
{featuredPost.data.tags.slice(0, 5).map((tag: string) => (
<span class="px-3 py-1.5 text-[10px] font-mono uppercase border border-white/10 text-slate-500 group-hover:border-white/20 transition-colors">
<span class="px-3 py-1.5 text-[10px] font-mono uppercase border border-[var(--theme-border-primary)] text-[var(--theme-text-muted)] group-hover:border-[var(--theme-border-strong)] transition-colors">
{tag}
</span>
))}
@ -135,13 +142,13 @@ const categories = [...new Set(allPosts.map((post) => post.data.category).filter
)}
<!-- Read link -->
<div class="pt-6 border-t border-white/10">
<div class="pt-6 border-t border-[var(--theme-border-primary)]">
<a
href={`/blog/${featuredPost.id}/`}
class="inline-flex items-center gap-4 text-xs font-bold uppercase tracking-widest text-white hover:text-brand-accent transition-all duration-300 group/link"
class="inline-flex items-center gap-4 text-xs font-bold uppercase tracking-widest text-[var(--theme-text-primary)] hover:text-brand-accent transition-all duration-300 group/link"
>
Read Full Article
<span class="block w-8 h-[1px] bg-white/30 group-hover/link:bg-brand-accent group-hover/link:w-12 transition-all duration-300"></span>
<span class="block w-8 h-[1px] bg-[var(--theme-border-strong)] group-hover/link:bg-brand-accent group-hover/link:w-12 transition-all duration-300"></span>
<svg
xmlns="http://www.w3.org/2000/svg"
width="14"
@ -168,10 +175,10 @@ const categories = [...new Set(allPosts.map((post) => post.data.category).filter
<!-- Latest Section with Filters -->
<div class="mb-16 lg:mb-24">
<div class="flex items-center gap-4 mb-8">
<span class="text-[10px] font-mono text-slate-500 uppercase tracking-widest font-bold">
<span class="text-[10px] font-mono text-[var(--theme-text-muted)] uppercase tracking-widest font-bold">
/// LATEST TRANSMISSIONS
</span>
<span class="h-px flex-grow bg-white/10"></span>
<span class="h-px flex-grow bg-[var(--theme-border-secondary)]"></span>
</div>
<!-- Filters Component -->
@ -202,10 +209,10 @@ const categories = [...new Set(allPosts.map((post) => post.data.category).filter
<!-- Empty state (hidden by default, shown via JS when no results) -->
<div id="no-results" class="hidden text-center py-20">
<div class="text-slate-500 font-mono text-sm uppercase tracking-widest mb-4">
<div class="text-[var(--theme-text-muted)] font-mono text-sm uppercase tracking-widest mb-4">
/// NO MATCHING ARTICLES FOUND
</div>
<p class="text-slate-400 text-sm">
<p class="text-[var(--theme-text-secondary)] text-sm">
Try adjusting your search or filter criteria.
</p>
</div>

View File

@ -16,7 +16,7 @@ const contactContent = contactEntry.data;
<div class="fixed inset-0 z-0 pointer-events-none">
<div class="w-full h-full grid grid-cols-12 gap-4 opacity-[0.03]">
{Array.from({ length: 12 }).map((_) => (
<div class="h-full border-r border-white"></div>
<div class="h-full border-r border-[var(--theme-text-primary)]"></div>
))}
</div>
</div>
@ -24,15 +24,15 @@ const contactContent = contactEntry.data;
<section class="relative z-10 min-h-screen flex flex-col pt-32 lg:pt-48 pb-20 px-6 lg:px-12">
<!-- Page Header -->
<div class="grid grid-cols-1 lg:grid-cols-12 gap-12 mb-20 lg:mb-32 border-b border-white/10 pb-12">
<div class="grid grid-cols-1 lg:grid-cols-12 gap-12 mb-20 lg:mb-32 border-b border-[var(--theme-border-primary)] pb-12">
<div class="lg:col-span-8">
<h1 class="text-6xl md:text-8xl lg:text-9xl font-bold uppercase tracking-tighter leading-[0.85] text-white mb-8">
<h1 class="text-6xl md:text-8xl lg:text-9xl font-bold uppercase tracking-tighter leading-[0.85] text-[var(--theme-text-primary)] mb-8">
<span class="block animate-on-scroll slide-up">{contactContent.pageTitleLine1}</span>
<span class="block text-brand-accent animate-on-scroll slide-up stagger-1">{contactContent.pageTitleLine2}</span>
</h1>
</div>
<div class="lg:col-span-4 flex flex-col justify-end">
<p class="font-mono text-sm text-slate-400 leading-relaxed mb-8 border-l border-brand-accent pl-6 animate-on-scroll fade-in stagger-2">
<p class="font-mono text-sm text-[var(--theme-text-secondary)] leading-relaxed mb-8 border-l border-brand-accent pl-6 animate-on-scroll fade-in stagger-2">
{contactContent.availabilityText}
</p>
</div>
@ -53,11 +53,11 @@ const contactContent = contactEntry.data;
type="text"
id="name"
name="name"
class="block w-full bg-transparent border-b border-white/20 py-4 text-xl text-white focus:outline-none focus:border-brand-accent transition-colors duration-300 placeholder-transparent peer"
class="block w-full bg-transparent border-b border-[var(--theme-border-strong)] py-4 text-xl text-[var(--theme-text-primary)] focus:outline-none focus:border-brand-accent transition-colors duration-300 placeholder-transparent peer"
placeholder="Name"
required
/>
<label for="name" class="absolute left-0 top-4 text-slate-500 text-sm font-mono uppercase tracking-widest transition-all duration-300 peer-focus:-top-6 peer-focus:text-xs peer-focus:text-brand-accent peer-valid:-top-6 peer-valid:text-xs peer-valid:text-slate-400 pointer-events-none">
<label for="name" class="absolute left-0 top-4 text-[var(--theme-text-muted)] text-sm font-mono uppercase tracking-widest transition-all duration-300 peer-focus:-top-6 peer-focus:text-xs peer-focus:text-brand-accent peer-valid:-top-6 peer-valid:text-xs peer-valid:text-[var(--theme-text-secondary)] pointer-events-none">
{contactContent.formLabels?.name}
</label>
</div>
@ -67,11 +67,11 @@ const contactContent = contactEntry.data;
type="email"
id="email"
name="email"
class="block w-full bg-transparent border-b border-white/20 py-4 text-xl text-white focus:outline-none focus:border-brand-accent transition-colors duration-300 placeholder-transparent peer"
class="block w-full bg-transparent border-b border-[var(--theme-border-strong)] py-4 text-xl text-[var(--theme-text-primary)] focus:outline-none focus:border-brand-accent transition-colors duration-300 placeholder-transparent peer"
placeholder="Email"
required
/>
<label for="email" class="absolute left-0 top-4 text-slate-500 text-sm font-mono uppercase tracking-widest transition-all duration-300 peer-focus:-top-6 peer-focus:text-xs peer-focus:text-brand-accent peer-valid:-top-6 peer-valid:text-xs peer-valid:text-slate-400 pointer-events-none">
<label for="email" class="absolute left-0 top-4 text-[var(--theme-text-muted)] text-sm font-mono uppercase tracking-widest transition-all duration-300 peer-focus:-top-6 peer-focus:text-xs peer-focus:text-brand-accent peer-valid:-top-6 peer-valid:text-xs peer-valid:text-[var(--theme-text-secondary)] pointer-events-none">
{contactContent.formLabels?.email}
</label>
</div>
@ -80,22 +80,22 @@ const contactContent = contactEntry.data;
<div class="group relative" id="custom-select">
<input type="hidden" name="subject" id="subject-input" required>
<button type="button" id="select-trigger" class="block w-full text-left bg-transparent border-b border-white/20 py-4 text-xl text-white focus:outline-none focus:border-brand-accent transition-colors duration-300 flex justify-between items-center group-hover:border-white/40">
<button type="button" id="select-trigger" class="block w-full text-left bg-transparent border-b border-[var(--theme-border-strong)] py-4 text-xl text-[var(--theme-text-primary)] focus:outline-none focus:border-brand-accent transition-colors duration-300 flex justify-between items-center group-hover:border-[var(--theme-border-primary)]">
<span id="select-value" class="text-transparent">Select</span> <!-- Hidden placeholder text to keep height -->
<div class="text-brand-accent transform transition-transform duration-300" id="select-arrow">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6"/></svg>
</div>
</button>
<label id="select-label" class="absolute left-0 top-4 text-slate-500 text-sm font-mono uppercase tracking-widest transition-all duration-300 pointer-events-none">
<label id="select-label" class="absolute left-0 top-4 text-[var(--theme-text-muted)] text-sm font-mono uppercase tracking-widest transition-all duration-300 pointer-events-none">
{contactContent.formLabels?.subject}
</label>
<!-- Dropdown Menu -->
<div id="select-options" class="absolute left-0 top-full w-full bg-brand-dark border border-white/20 shadow-2xl z-50 hidden opacity-0 transform translate-y-2 transition-all duration-200 origin-top mt-2">
<div id="select-options" class="absolute left-0 top-full w-full bg-[var(--theme-bg-primary)] border border-[var(--theme-border-strong)] shadow-2xl z-50 hidden opacity-0 transform translate-y-2 transition-all duration-200 origin-top mt-2">
<div class="p-1">
{contactContent.subjectOptions?.map((option) => (
<div class="option px-5 py-4 hover:bg-white/5 cursor-pointer text-white text-lg font-light transition-colors flex items-center gap-3 group/option" data-value={option.value}>
<div class="option px-5 py-4 hover:bg-[var(--theme-hover-bg-strong)] cursor-pointer text-[var(--theme-text-primary)] text-lg font-light transition-colors flex items-center gap-3 group/option" data-value={option.value}>
<span class="w-1.5 h-1.5 rounded-full bg-brand-accent opacity-0 group-hover/option:opacity-100 transition-opacity"></span>
{option.label}
</div>
@ -109,19 +109,19 @@ const contactContent = contactEntry.data;
id="message"
name="message"
rows="4"
class="block w-full bg-transparent border-b border-white/20 py-4 text-xl text-white focus:outline-none focus:border-brand-accent transition-colors duration-300 placeholder-transparent peer resize-none"
class="block w-full bg-transparent border-b border-[var(--theme-border-strong)] py-4 text-xl text-[var(--theme-text-primary)] focus:outline-none focus:border-brand-accent transition-colors duration-300 placeholder-transparent peer resize-none"
placeholder="Message"
required
></textarea>
<label for="message" class="absolute left-0 top-4 text-slate-500 text-sm font-mono uppercase tracking-widest transition-all duration-300 peer-focus:-top-6 peer-focus:text-xs peer-focus:text-brand-accent peer-valid:-top-6 peer-valid:text-xs peer-valid:text-slate-400 pointer-events-none">
<label for="message" class="absolute left-0 top-4 text-[var(--theme-text-muted)] text-sm font-mono uppercase tracking-widest transition-all duration-300 peer-focus:-top-6 peer-focus:text-xs peer-focus:text-brand-accent peer-valid:-top-6 peer-valid:text-xs peer-valid:text-[var(--theme-text-secondary)] pointer-events-none">
{contactContent.formLabels?.message}
</label>
</div>
<div class="pt-8">
<button type="submit" id="submit-btn" class="group relative inline-flex items-center justify-center gap-4 px-8 py-4 bg-transparent border border-white/20 hover:border-brand-accent hover:bg-brand-accent/5 transition-all duration-300 disabled:opacity-50 disabled:cursor-not-allowed">
<span id="submit-text" data-default-text={contactContent.formLabels?.submit} class="font-mono text-xs font-bold uppercase tracking-widest text-white group-hover:text-brand-accent transition-colors">{contactContent.formLabels?.submit}</span>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="text-slate-500 group-hover:text-brand-accent group-hover:translate-x-1 transition-all"><line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/></svg>
<button type="submit" id="submit-btn" class="group relative inline-flex items-center justify-center gap-4 px-8 py-4 bg-transparent border border-[var(--theme-border-strong)] hover:border-brand-accent hover:bg-brand-accent/5 transition-all duration-300 disabled:opacity-50 disabled:cursor-not-allowed">
<span id="submit-text" data-default-text={contactContent.formLabels?.submit} class="font-mono text-xs font-bold uppercase tracking-widest text-[var(--theme-text-primary)] group-hover:text-brand-accent transition-colors">{contactContent.formLabels?.submit}</span>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="text-[var(--theme-text-muted)] group-hover:text-brand-accent group-hover:translate-x-1 transition-all"><line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/></svg>
</button>
</div>
</form>
@ -131,19 +131,19 @@ const contactContent = contactEntry.data;
<div class="lg:col-span-5 space-y-16 animate-on-scroll slide-left stagger-4">
<!-- Data Block 1 -->
<div class="relative pl-6 border-l border-white/10">
<h3 class="font-mono text-xs text-slate-500 uppercase tracking-widest mb-4">Direct Link</h3>
<a href={`mailto:${contactContent.email}`} class="text-2xl md:text-3xl font-bold text-white hover:text-brand-accent transition-colors break-all">
<div class="relative pl-6 border-l border-[var(--theme-border-primary)]">
<h3 class="font-mono text-xs text-[var(--theme-text-muted)] uppercase tracking-widest mb-4">Direct Link</h3>
<a href={`mailto:${contactContent.email}`} class="text-2xl md:text-3xl font-bold text-[var(--theme-text-primary)] hover:text-brand-accent transition-colors break-all">
{contactContent.email}
</a>
</div>
<!-- Data Block 2 -->
<div class="relative pl-6 border-l border-white/10">
<h3 class="font-mono text-xs text-slate-500 uppercase tracking-widest mb-4">Coordinates</h3>
<p class="text-xl text-white font-light">
<div class="relative pl-6 border-l border-[var(--theme-border-primary)]">
<h3 class="font-mono text-xs text-[var(--theme-text-muted)] uppercase tracking-widest mb-4">Coordinates</h3>
<p class="text-xl text-[var(--theme-text-primary)] font-light">
{contactContent.location}<br>
<span class="text-slate-500 text-base">{contactContent.locationCountry}</span>
<span class="text-[var(--theme-text-muted)] text-base">{contactContent.locationCountry}</span>
</p>
<div class="mt-4 font-mono text-xs text-brand-accent">
{contactContent.coordinates}
@ -151,14 +151,14 @@ const contactContent = contactEntry.data;
</div>
<!-- Data Block 3 -->
<div class="relative pl-6 border-l border-white/10">
<h3 class="font-mono text-xs text-slate-500 uppercase tracking-widest mb-4">Social Feed</h3>
<div class="relative pl-6 border-l border-[var(--theme-border-primary)]">
<h3 class="font-mono text-xs text-[var(--theme-text-muted)] uppercase tracking-widest mb-4">Social Feed</h3>
<ul class="space-y-4">
{contactContent.socialLinks?.map((link) => (
<li>
<a href={link.url} class="flex items-center gap-4 group">
<span class="text-slate-400 group-hover:text-white transition-colors text-lg">{link.name}</span>
<svg class="w-4 h-4 text-slate-600 group-hover:text-brand-accent transition-colors transform group-hover:translate-x-1 group-hover:-translate-y-1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="7" y1="17" x2="17" y2="7"/><polyline points="7 7 17 7 17 17"/></svg>
<span class="text-[var(--theme-text-secondary)] group-hover:text-[var(--theme-text-primary)] transition-colors text-lg">{link.name}</span>
<svg class="w-4 h-4 text-[var(--theme-text-subtle)] group-hover:text-brand-accent transition-colors transform group-hover:translate-x-1 group-hover:-translate-y-1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="7" y1="17" x2="17" y2="7"/><polyline points="7 7 17 7 17 17"/></svg>
</a>
</li>
))}
@ -174,7 +174,7 @@ const contactContent = contactEntry.data;
<div id="toast-container" class="fixed top-6 right-6 z-50 pointer-events-none"></div>
<!-- Full-Screen Modal for Loading and Response -->
<div id="transmission-modal" class="fixed inset-0 z-[100] flex items-center justify-center bg-brand-dark/95 backdrop-blur-xl opacity-0 pointer-events-none transition-opacity duration-500">
<div id="transmission-modal" class="fixed inset-0 z-[100] flex items-center justify-center bg-[var(--theme-overlay-heavy)] backdrop-blur-xl opacity-0 pointer-events-none transition-opacity duration-500">
<!-- Loading State -->
<div id="loading-state" class="text-center">
@ -200,7 +200,7 @@ const contactContent = contactEntry.data;
<!-- Loading Text -->
<div class="space-y-4">
<h2 class="text-3xl md:text-5xl font-bold text-white uppercase tracking-tight">
<h2 class="text-3xl md:text-5xl font-bold text-[var(--theme-text-primary)] uppercase tracking-tight">
<span id="loading-text">Transmitting</span>
</h2>
<div class="flex items-center justify-center gap-2">
@ -208,15 +208,15 @@ const contactContent = contactEntry.data;
<div class="w-2 h-2 bg-brand-accent rounded-full animate-bounce delay-100"></div>
<div class="w-2 h-2 bg-brand-accent rounded-full animate-bounce delay-200"></div>
</div>
<p class="font-mono text-sm text-slate-400 uppercase tracking-widest">Signal Processing</p>
<p class="font-mono text-sm text-[var(--theme-text-secondary)] uppercase tracking-widest">Signal Processing</p>
</div>
</div>
<!-- Response State (hidden initially) -->
<div id="response-state" class="hidden w-full h-full absolute inset-0 z-10 flex flex-col items-center justify-center p-6 opacity-0 transition-all duration-700">
<!-- Close button -->
<button id="close-modal" class="absolute top-8 right-8 z-50 p-3 border border-white/20 hover:border-brand-accent hover:bg-brand-accent/10 transition-all duration-300 group">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="text-white group-hover:text-brand-accent transition-colors">
<button id="close-modal" class="absolute top-8 right-8 z-50 p-3 border border-[var(--theme-border-strong)] hover:border-brand-accent hover:bg-brand-accent/10 transition-all duration-300 group">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="text-[var(--theme-text-primary)] group-hover:text-brand-accent transition-colors">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
@ -251,15 +251,15 @@ const contactContent = contactEntry.data;
</BaseLayout>
<style>
/* Custom autofill styles to match dark theme */
/* Custom autofill styles to match theme */
input:-webkit-autofill,
input:-webkit-autofill:hover,
input:-webkit-autofill:focus,
textarea:-webkit-autofill,
textarea:-webkit-autofill:hover,
textarea:-webkit-autofill:focus {
-webkit-text-fill-color: white;
-webkit-box-shadow: 0 0 0px 1000px #0B0D11 inset;
-webkit-text-fill-color: var(--theme-text-primary);
-webkit-box-shadow: 0 0 0px 1000px var(--theme-bg-primary) inset;
transition: background-color 5000s ease-in-out 0s;
}
@ -267,13 +267,13 @@ const contactContent = contactEntry.data;
.label-active {
top: -1.5rem !important;
font-size: 0.75rem !important;
color: #94A3B8 !important;
color: var(--theme-text-secondary) !important;
}
/* Dropdown open state */
.dropdown-open #select-arrow {
transform: rotate(180deg);
color: white;
color: var(--theme-text-primary);
}
/* Custom Animations */
@ -322,28 +322,29 @@ const contactContent = contactEntry.data;
}
.custom-scrollbar::-webkit-scrollbar-track {
background: rgba(255, 255, 255, 0.05);
background: var(--theme-hover-bg-strong);
}
.custom-scrollbar::-webkit-scrollbar-thumb {
background: rgba(0, 255, 255, 0.3);
background: var(--color-brand-accent);
border-radius: 4px;
opacity: 0.3;
}
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
background: rgba(0, 255, 255, 0.5);
opacity: 0.5;
}
/* Response Content Prose Styles - Enhanced Readability */
.prose-response {
color: white;
color: var(--theme-text-primary);
text-align: left;
}
.prose-response h1,
.prose-response h2,
.prose-response h3 {
color: white;
color: var(--theme-text-primary);
margin-top: 1.5em;
margin-bottom: 0.75em;
font-weight: 800;
@ -354,7 +355,7 @@ const contactContent = contactEntry.data;
.prose-response h1 {
font-size: 3.5rem;
background: linear-gradient(to right, #fff, #94a3b8);
background: linear-gradient(to right, var(--theme-text-primary), var(--theme-text-secondary));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
margin-bottom: 0.5em;
@ -366,53 +367,53 @@ const contactContent = contactEntry.data;
.prose-response h3 {
font-size: 1.75rem;
color: #ff4d00;
color: var(--color-brand-accent);
}
.prose-response p {
margin-bottom: 1.5em;
line-height: 1.8;
color: rgba(255, 255, 255, 0.9);
font-size: 1.5rem; /* Increased size significantly */
color: var(--theme-text-secondary);
font-size: 1.5rem;
font-weight: 300;
max-width: 65ch;
margin-right: auto;
}
.prose-response strong {
color: #ff4d00;
color: var(--color-brand-accent);
font-weight: 600;
}
.prose-response em {
font-style: italic;
color: #94a3b8;
color: var(--theme-text-muted);
}
/* Blockquote for signature or special text */
.prose-response blockquote {
border-left: none; /* Removed standard border */
border-left: none;
margin: 3em 0 1em;
padding: 0;
color: #ff4d00;
color: var(--color-brand-accent);
font-family: 'Courier New', monospace;
font-size: 1rem;
text-transform: uppercase;
letter-spacing: 0.2em;
display: inline-block;
border-top: 1px solid rgba(255, 77, 0, 0.3);
border-top: 1px solid rgba(221, 65, 50, 0.3);
padding-top: 2em;
}
.prose-response a {
color: #ff4d00;
color: var(--color-brand-accent);
text-decoration: underline;
text-underline-offset: 4px;
transition: all 0.3s;
}
.prose-response a:hover {
color: white;
color: var(--theme-text-primary);
text-decoration-thickness: 2px;
}
@ -461,14 +462,14 @@ const contactContent = contactEntry.data;
// Update UI
selectValue.textContent = text;
selectValue.classList.remove('text-transparent');
selectValue.classList.add('text-white');
selectValue.classList.add('text-[var(--theme-text-primary)]');
// Update Data
hiddenInput.value = value;
// Update Label Style
selectLabel.classList.add('label-active');
selectLabel.classList.add('text-brand-accent'); // Highlight when selected
selectLabel.classList.add('text-brand-accent');
closeDropdown();
});

View File

@ -109,33 +109,117 @@
}
@utility text-stroke {
-webkit-text-stroke: 1px rgba(255, 255, 255, 0.15);
-webkit-text-stroke: 1px var(--theme-text-stroke);
color: transparent;
}
@utility text-stroke-dark {
-webkit-text-stroke: 1px var(--theme-text-stroke-inverted);
color: transparent;
}
@utility skill-tag {
@apply text-[10px] font-mono font-bold uppercase tracking-wider px-3 py-2 border border-slate-700 text-slate-400 hover:border-brand-accent hover:text-white transition-all duration-300 cursor-default select-none;
@apply text-[10px] font-mono font-bold uppercase tracking-wider px-3 py-2 border border-[var(--theme-border-strong)] text-[var(--theme-text-secondary)] hover:border-brand-accent hover:text-[var(--theme-text-primary)] transition-all duration-300 cursor-default select-none;
}
@utility btn-primary {
@apply bg-brand-accent text-brand-dark px-8 py-4 text-xs font-bold uppercase tracking-widest hover:bg-white transition-all duration-300 inline-block;
@apply bg-brand-accent text-brand-dark px-8 py-4 text-xs font-bold uppercase tracking-widest hover:bg-[var(--theme-text-primary)] hover:text-[var(--theme-bg-primary)] transition-all duration-300 inline-block;
}
@utility btn-ghost {
@apply border border-slate-600 text-white px-8 py-4 text-xs font-bold uppercase tracking-widest hover:border-brand-accent hover:bg-brand-accent/5 transition-all duration-300 inline-block;
@apply border border-[var(--theme-border-strong)] text-[var(--theme-text-primary)] px-8 py-4 text-xs font-bold uppercase tracking-widest hover:border-brand-accent hover:bg-brand-accent/5 transition-all duration-300 inline-block;
}
@utility grid-overlay {
background-size: 100px 100px;
background-image: linear-gradient(to right, rgba(255, 255, 255, 0.03) 1px, transparent 1px);
background-image: linear-gradient(to right, var(--theme-grid-line) 1px, transparent 1px);
pointer-events: none;
z-index: 0;
}
/* ===== THEME SYSTEM ===== */
/* Dark mode (default) */
:root {
--vh-full: 100vh;
--vh-full: 100dvh;
/* Theme colors - Dark mode defaults */
--theme-bg-primary: #0B0D11;
--theme-bg-secondary: #151921;
--theme-bg-tertiary: #1E293B;
--theme-text-primary: #ffffff;
--theme-text-secondary: #94A3B8;
--theme-text-muted: #64748B;
--theme-text-subtle: #475569;
--theme-border-primary: rgba(255, 255, 255, 0.1);
--theme-border-secondary: rgba(255, 255, 255, 0.05);
--theme-border-strong: rgba(255, 255, 255, 0.2);
--theme-overlay: rgba(11, 13, 17, 0.8);
--theme-overlay-heavy: rgba(11, 13, 17, 0.98);
--theme-grid-line: rgba(255, 255, 255, 0.03);
--theme-hover-bg: rgba(255, 255, 255, 0.02);
--theme-hover-bg-strong: rgba(255, 255, 255, 0.05);
--theme-text-stroke: rgba(255, 255, 255, 0.15);
--theme-text-stroke-inverted: rgba(0, 0, 0, 0.15);
--theme-scrollbar-track: #0B0D11;
--theme-scrollbar-thumb: #334155;
--theme-code-bg: rgba(255, 77, 0, 0.1);
--theme-blockquote-bg: linear-gradient(135deg, rgba(255, 77, 0, 0.05), rgba(21, 25, 33, 0.8));
--theme-divider: rgba(255, 255, 255, 0.1);
--theme-decorative-opacity: 0.02;
--theme-card-overlay: rgba(11, 13, 17, 0.3);
--theme-card-gradient: rgba(11, 13, 17, 0.6);
--theme-hero-gradient-top: rgba(11, 13, 17, 0.8);
--theme-hero-gradient-side: rgba(11, 13, 17, 0.4);
}
/* Light mode */
[data-theme="light"] {
--theme-bg-primary: #FAFAFA;
--theme-bg-secondary: #F1F3F5;
--theme-bg-tertiary: #E9ECEF;
--theme-text-primary: #0B0D11;
--theme-text-secondary: #475569;
--theme-text-muted: #64748B;
--theme-text-subtle: #94A3B8;
--theme-border-primary: rgba(0, 0, 0, 0.1);
--theme-border-secondary: rgba(0, 0, 0, 0.05);
--theme-border-strong: rgba(0, 0, 0, 0.15);
--theme-overlay: rgba(250, 250, 250, 0.9);
--theme-overlay-heavy: rgba(250, 250, 250, 0.98);
--theme-grid-line: rgba(0, 0, 0, 0.04);
--theme-hover-bg: rgba(0, 0, 0, 0.02);
--theme-hover-bg-strong: rgba(0, 0, 0, 0.05);
--theme-text-stroke: rgba(0, 0, 0, 0.2);
--theme-text-stroke-inverted: rgba(255, 255, 255, 0.15);
--theme-scrollbar-track: #FAFAFA;
--theme-scrollbar-thumb: #CBD5E1;
--theme-code-bg: rgba(221, 65, 50, 0.08);
--theme-blockquote-bg: linear-gradient(135deg, rgba(221, 65, 50, 0.05), rgba(241, 243, 245, 0.9));
--theme-divider: rgba(0, 0, 0, 0.08);
--theme-decorative-opacity: 0.04;
--theme-card-overlay: rgba(250, 250, 250, 0.15);
--theme-card-gradient: rgba(250, 250, 250, 0.4);
--theme-hero-gradient-top: rgba(250, 250, 250, 0.5);
--theme-hero-gradient-side: rgba(250, 250, 250, 0.2);
}
/* Theme transition for smooth switching */
html.theme-transition,
html.theme-transition *,
html.theme-transition *::before,
html.theme-transition *::after {
transition: background-color 0.3s ease,
border-color 0.3s ease,
color 0.3s ease,
box-shadow 0.3s ease !important;
}
/* Base Styles */
body {
background-color: var(--color-brand-dark);
color: #ffffff;
background-color: var(--theme-bg-primary);
color: var(--theme-text-primary);
overflow-x: hidden;
}
@ -150,23 +234,17 @@ html {
}
}
/* Mobile viewport height fix - uses dvh with vh fallback */
:root {
--vh-full: 100vh;
--vh-full: 100dvh;
}
/* Custom Scrollbar */
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-track {
background: var(--color-brand-dark);
background: var(--theme-scrollbar-track);
}
::-webkit-scrollbar-thumb {
background: #334155;
background: var(--theme-scrollbar-thumb);
transition: background 0.3s ease;
}
@ -295,7 +373,7 @@ html {
.cursor-outline {
width: 40px;
height: 40px;
border: 1px solid rgba(255, 77, 0, 0.5);
border: 1px solid rgba(221, 65, 50, 0.5);
z-index: 99999;
transition: width 0.3s cubic-bezier(0.16, 1, 0.3, 1),
height 0.3s cubic-bezier(0.16, 1, 0.3, 1),
@ -315,7 +393,7 @@ a:hover~.cursor-outline,
button:hover~.cursor-outline {
width: 60px;
height: 60px;
background-color: rgba(255, 77, 0, 0.05);
background-color: rgba(221, 65, 50, 0.05);
border-color: var(--color-brand-accent);
}
@ -348,15 +426,15 @@ a {
.hover-border-glow:hover {
border-color: var(--color-brand-accent);
box-shadow: 0 0 20px rgba(255, 77, 0, 0.1);
box-shadow: 0 0 20px rgba(221, 65, 50, 0.1);
}
/* Gradient divider */
.divider-gradient {
background: linear-gradient(to right,
transparent,
rgba(255, 255, 255, 0.1) 20%,
rgba(255, 255, 255, 0.1) 80%,
var(--theme-divider) 20%,
var(--theme-divider) 80%,
transparent);
}
@ -364,19 +442,19 @@ a {
.divider-accent {
background: linear-gradient(to right,
transparent,
rgba(255, 77, 0, 0.2) 50%,
rgba(221, 65, 50, 0.2) 50%,
transparent);
}
/* ===== PROSE / MARKDOWN STYLES ===== */
.prose-custom {
color: #94A3B8;
color: var(--theme-text-secondary);
line-height: 1.8;
font-size: 1.0625rem;
}
.prose-custom h2 {
color: #ffffff;
color: var(--theme-text-primary);
font-size: 1.75rem;
font-weight: 700;
text-transform: uppercase;
@ -384,7 +462,7 @@ a {
margin-top: 3.5rem;
margin-bottom: 1.25rem;
padding-bottom: 0.75rem;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
border-bottom: 1px solid var(--theme-border-primary);
position: relative;
scroll-margin-top: 6rem;
}
@ -398,7 +476,7 @@ a {
}
.prose-custom h3 {
color: #ffffff;
color: var(--theme-text-primary);
font-size: 1.25rem;
font-weight: 600;
text-transform: uppercase;
@ -409,7 +487,7 @@ a {
}
.prose-custom h4 {
color: #ffffff;
color: var(--theme-text-primary);
font-size: 1.1rem;
font-weight: 600;
margin-top: 1.5rem;
@ -429,17 +507,17 @@ a {
}
.prose-custom a:hover {
color: #ffffff;
color: var(--theme-text-primary);
border-bottom-color: var(--color-brand-accent);
}
.prose-custom strong {
color: #ffffff;
color: var(--theme-text-primary);
font-weight: 600;
}
.prose-custom em {
color: #CBD5E1;
color: var(--theme-text-secondary);
font-style: italic;
}
@ -492,14 +570,14 @@ a {
.prose-custom blockquote {
position: relative;
border-left: 3px solid var(--color-brand-accent);
background: linear-gradient(135deg, rgba(255, 77, 0, 0.05), rgba(21, 25, 33, 0.8));
background: var(--theme-blockquote-bg);
padding: 1.5rem 1.5rem 1.5rem 2rem;
margin: 2.5rem 0;
font-style: italic;
color: #CBD5E1;
border-right: 1px solid rgba(255, 255, 255, 0.05);
border-top: 1px solid rgba(255, 255, 255, 0.05);
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
color: var(--theme-text-secondary);
border-right: 1px solid var(--theme-border-secondary);
border-top: 1px solid var(--theme-border-secondary);
border-bottom: 1px solid var(--theme-border-secondary);
}
.prose-custom blockquote::before {
@ -507,7 +585,7 @@ a {
position: absolute;
top: -0.75rem;
left: 1rem;
background: var(--color-brand-dark);
background: var(--theme-bg-primary);
padding: 0 0.5rem;
font-family: var(--font-mono);
font-size: 0.625rem;
@ -528,19 +606,19 @@ a {
/* Enhanced Code - Inline */
.prose-custom code {
color: var(--color-brand-accent);
background-color: rgba(255, 77, 0, 0.1);
background-color: var(--theme-code-bg);
padding: 0.2rem 0.5rem;
border-radius: 0;
font-family: var(--font-mono);
font-size: 0.85em;
border: 1px solid rgba(255, 77, 0, 0.2);
border: 1px solid rgba(221, 65, 50, 0.2);
}
/* Enhanced Code Blocks - Terminal Style */
.prose-custom pre {
position: relative;
background-color: var(--color-brand-panel);
border: 1px solid rgba(255, 255, 255, 0.1);
background-color: var(--theme-bg-secondary);
border: 1px solid var(--theme-border-primary);
padding: 0;
margin: 2.5rem 0;
overflow: hidden;
@ -549,14 +627,14 @@ a {
.prose-custom pre::before {
content: "TERMINAL";
display: block;
background: rgba(255, 255, 255, 0.03);
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
background: var(--theme-hover-bg);
border-bottom: 1px solid var(--theme-border-primary);
padding: 0.75rem 1rem;
font-family: var(--font-mono);
font-size: 0.625rem;
font-weight: 700;
letter-spacing: 0.15em;
color: #64748B;
color: var(--theme-text-muted);
text-transform: uppercase;
}
@ -564,7 +642,7 @@ a {
display: block;
background: none;
padding: 1.5rem;
color: #CBD5E1;
color: var(--theme-text-secondary);
border: none;
overflow-x: auto;
}
@ -585,25 +663,25 @@ a {
content: "";
flex: 1;
height: 1px;
background: linear-gradient(to right, transparent, rgba(255, 77, 0, 0.3));
background: linear-gradient(to right, transparent, rgba(221, 65, 50, 0.3));
}
.prose-custom hr::after {
content: "";
flex: 1;
height: 1px;
background: linear-gradient(to left, transparent, rgba(255, 77, 0, 0.3));
background: linear-gradient(to left, transparent, rgba(221, 65, 50, 0.3));
}
/* Enhanced Images */
.prose-custom img {
border: 1px solid rgba(255, 255, 255, 0.1);
border: 1px solid var(--theme-border-primary);
margin: 2.5rem 0;
transition: border-color 0.3s ease;
}
.prose-custom img:hover {
border-color: rgba(255, 77, 0, 0.3);
border-color: rgba(221, 65, 50, 0.3);
}
/* Image Captions (for figures) */
@ -620,7 +698,7 @@ a {
font-size: 0.6875rem;
text-transform: uppercase;
letter-spacing: 0.1em;
color: #64748B;
color: var(--theme-text-muted);
margin-top: 0.75rem;
padding-left: 0.5rem;
border-left: 2px solid var(--color-brand-accent);
@ -634,7 +712,7 @@ a {
.prose-custom .video-container video {
width: 100%;
border: 1px solid rgba(255, 255, 255, 0.1);
border: 1px solid var(--theme-border-primary);
}
.prose-custom .video-container p {
@ -642,7 +720,7 @@ a {
font-size: 0.6875rem;
text-transform: uppercase;
letter-spacing: 0.1em;
color: #64748B;
color: var(--theme-text-muted);
margin-top: 0.75rem;
margin-bottom: 0;
}
@ -656,8 +734,8 @@ a {
}
.prose-custom thead {
background: rgba(255, 255, 255, 0.03);
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
background: var(--theme-hover-bg);
border-bottom: 1px solid var(--theme-border-primary);
}
.prose-custom th {
@ -666,17 +744,54 @@ a {
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.1em;
color: #64748B;
color: var(--theme-text-muted);
padding: 1rem;
text-align: left;
}
.prose-custom td {
padding: 1rem;
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
color: #94A3B8;
border-bottom: 1px solid var(--theme-border-secondary);
color: var(--theme-text-secondary);
}
.prose-custom tr:hover td {
background: rgba(255, 255, 255, 0.02);
background: var(--theme-hover-bg);
}
/* ===== THEME-AWARE UTILITY CLASSES ===== */
/* Background colors */
.bg-theme-primary {
background-color: var(--theme-bg-primary);
}
.bg-theme-secondary {
background-color: var(--theme-bg-secondary);
}
.bg-theme-tertiary {
background-color: var(--theme-bg-tertiary);
}
/* Text colors */
.text-theme-primary {
color: var(--theme-text-primary);
}
.text-theme-secondary {
color: var(--theme-text-secondary);
}
.text-theme-muted {
color: var(--theme-text-muted);
}
/* Border colors */
.border-theme-primary {
border-color: var(--theme-border-primary);
}
.border-theme-secondary {
border-color: var(--theme-border-secondary);
}