Add open-source VFX pipeline guide and enhance site structure
- Introduced a comprehensive guide on building a production-ready VFX pipeline using open-source tools, detailing cost breakdowns and workflows. - Added new sections for experience, featured projects, skills, and contact information, enhancing the overall site structure and user navigation. - Updated components to utilize dynamic content from new markdown files, improving maintainability and scalability of the site. - Enhanced the contact page with structured data for better user interaction and accessibility.
This commit is contained in:
parent
cb11e09adb
commit
ffb140ac7f
@ -1,4 +1,29 @@
|
||||
---
|
||||
interface Props {
|
||||
sectionTitle: string;
|
||||
sectionSubtitle: string;
|
||||
sectionLabel: string;
|
||||
description: string;
|
||||
entries: Array<{
|
||||
systemId: string;
|
||||
status: string;
|
||||
dates: string;
|
||||
company: string;
|
||||
role: string;
|
||||
tags?: string[];
|
||||
description: string;
|
||||
achievements?: Array<{
|
||||
label: string;
|
||||
text: string;
|
||||
}>;
|
||||
link?: {
|
||||
url: string;
|
||||
text: string;
|
||||
};
|
||||
}>;
|
||||
}
|
||||
|
||||
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">
|
||||
|
||||
@ -6,136 +31,122 @@
|
||||
<div class="grid grid-cols-1 lg:grid-cols-12 gap-12 mb-20">
|
||||
<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">Experience</span>
|
||||
<span class="block text-transparent text-stroke animate-on-scroll slide-up stagger-1">History</span>
|
||||
</h2>
|
||||
<span class="block animate-on-scroll slide-up">{sectionTitle}</span>
|
||||
<span class="block text-transparent text-stroke animate-on-scroll slide-up stagger-1">{sectionSubtitle}</span>
|
||||
</h2>
|
||||
</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">/// Career Timeline</div>
|
||||
<div class="font-mono text-xs text-slate-500 uppercase tracking-widest mb-4">{sectionLabel}</div>
|
||||
<p class="text-slate-400 text-base leading-relaxed border-l border-brand-accent pl-6 animate-on-scroll fade-in stagger-2">
|
||||
Bridging creative vision with technical execution. From running a dedicated VFX studio to high-end freelance supervision.
|
||||
{description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- "Rack Mount" Layout -->
|
||||
<div class="flex flex-col gap-6">
|
||||
<!-- First Entry (Full Width) -->
|
||||
{entries[0] && (() => {
|
||||
const entry = entries[0];
|
||||
return (
|
||||
<div class="group relative border border-white/10 bg-white/[0.02] hover:border-brand-accent/50 hover:bg-white/[0.04] transition-all duration-500 overflow-hidden animate-on-scroll slide-up stagger-1">
|
||||
<!-- Active Indicator Strip -->
|
||||
<div class="absolute top-0 left-0 w-1 h-full bg-brand-accent opacity-100"></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>
|
||||
|
||||
<!-- Unit 01: Biohazard (Active System) -->
|
||||
<div class="group relative border border-white/10 bg-white/[0.02] hover:border-brand-accent/50 hover:bg-white/[0.04] transition-all duration-500 overflow-hidden animate-on-scroll slide-up stagger-1">
|
||||
<!-- Active Indicator Strip -->
|
||||
<div class="absolute top-0 left-0 w-1 h-full bg-brand-accent opacity-100"></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>
|
||||
|
||||
<!-- Technical Header -->
|
||||
<div class="flex items-center justify-between px-8 py-4 border-b border-white/5 bg-white/[0.02]">
|
||||
<div class="flex items-center gap-4">
|
||||
<span class="font-mono text-[10px] text-brand-accent uppercase tracking-widest">SYS.01 /// ACTIVE</span>
|
||||
<div class="h-px w-12 bg-white/10"></div>
|
||||
</div>
|
||||
<span class="font-mono text-[10px] text-slate-500 uppercase tracking-widest">2022 — PRESENT</span>
|
||||
</div>
|
||||
|
||||
<div class="p-8 lg:p-12 grid grid-cols-1 lg:grid-cols-12 gap-12">
|
||||
<div class="lg:col-span-4">
|
||||
<h3 class="text-4xl font-bold text-white uppercase tracking-tight mb-2">Biohazard VFX</h3>
|
||||
<span class="text-sm font-mono text-slate-400">Founder & Owner</span>
|
||||
|
||||
<div class="mt-8 flex flex-wrap gap-2">
|
||||
<span class="px-2 py-1 text-[10px] font-mono uppercase border border-white/10 text-slate-400">Studio Lead</span>
|
||||
<span class="px-2 py-1 text-[10px] font-mono uppercase border border-white/10 text-slate-400">Pipeline Arch</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="lg:col-span-8 flex flex-col justify-between">
|
||||
<p class="text-slate-300 leading-relaxed font-light text-lg mb-8">
|
||||
Founded and lead a VFX studio specializing in high-end commercial and music video work.
|
||||
Delivered projects for Post Malone, ENHYPEN, and Nike. Architected a custom pipeline combining cloud and self-hosted infrastructure.
|
||||
</p>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 border-t border-white/5 pt-8">
|
||||
<div>
|
||||
<h4 class="text-[10px] font-mono text-slate-500 uppercase tracking-widest mb-2">Key Achievement</h4>
|
||||
<p class="text-sm text-slate-400">Designed 7-plate reconciliation workflows for ENHYPEN (projection mapping live action onto CAD).</p>
|
||||
<!-- Technical Header -->
|
||||
<div class="flex items-center justify-between px-8 py-4 border-b border-white/5 bg-white/[0.02]">
|
||||
<div class="flex items-center gap-4">
|
||||
<span class="font-mono text-[10px] text-brand-accent uppercase tracking-widest">{entry.systemId} /// {entry.status}</span>
|
||||
<div class="h-px w-12 bg-white/10"></div>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="text-[10px] font-mono text-slate-500 uppercase tracking-widest mb-2">System Impact</h4>
|
||||
<p class="text-sm text-slate-400">Developed QA systems for AI-generated assets, transforming mid-tier output into production-ready deliverables.</p>
|
||||
<span class="font-mono text-[10px] text-slate-500 uppercase tracking-widest">{entry.dates}</span>
|
||||
</div>
|
||||
|
||||
<div class="p-8 lg:p-12 grid grid-cols-1 lg:grid-cols-12 gap-12">
|
||||
<div class="lg:col-span-4">
|
||||
<h3 class="text-4xl font-bold text-white uppercase tracking-tight mb-2">{entry.company}</h3>
|
||||
<span class="text-sm font-mono text-slate-400">{entry.role}</span>
|
||||
|
||||
{entry.tags && entry.tags.length > 0 && (
|
||||
<div class="mt-8 flex flex-wrap gap-2">
|
||||
{entry.tags.map((tag) => (
|
||||
<span class="px-2 py-1 text-[10px] font-mono uppercase border border-white/10 text-slate-400">{tag}</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div class="lg:col-span-8 flex flex-col justify-between">
|
||||
<p class="text-slate-300 leading-relaxed font-light text-lg mb-8">
|
||||
{entry.description}
|
||||
</p>
|
||||
|
||||
{entry.achievements && entry.achievements.length > 0 && (
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 border-t border-white/5 pt-8">
|
||||
{entry.achievements.map((achievement) => (
|
||||
<div>
|
||||
<h4 class="text-[10px] font-mono text-slate-500 uppercase tracking-widest mb-2">{achievement.label}</h4>
|
||||
<p class="text-sm text-slate-400">{achievement.text}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{entry.link && (
|
||||
<div class="mt-8 flex justify-end">
|
||||
<a href={entry.link.url} target={entry.link.url.startsWith('http') ? '_blank' : undefined} class="inline-flex items-center gap-3 text-xs font-bold uppercase tracking-widest text-white hover:text-brand-accent transition-colors">
|
||||
{entry.link.text} <svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14"/><path d="m12 5 7 7-7 7"/></svg>
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-8 flex justify-end">
|
||||
<a href="https://biohazardvfx.com" target="_blank" class="inline-flex items-center gap-3 text-xs font-bold uppercase tracking-widest text-white hover:text-brand-accent transition-colors">
|
||||
Visit Studio Uplink <svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14"/><path d="m12 5 7 7-7 7"/></svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})()}
|
||||
|
||||
<!-- Split Row for Remaining Entries -->
|
||||
{entries.length > 1 && (
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
{entries.slice(1).map((entry, index) => (
|
||||
<div class={`group relative border border-white/10 bg-white/[0.02] hover:border-white/30 transition-all duration-500 overflow-hidden animate-on-scroll slide-up stagger-${index + 2}`}>
|
||||
<!-- Inactive Indicator Strip -->
|
||||
<div class="absolute top-0 left-0 w-1 h-full bg-slate-700 opacity-50 group-hover:bg-white transition-colors"></div>
|
||||
|
||||
<!-- Technical Header -->
|
||||
<div class="flex items-center justify-between px-8 py-4 border-b border-white/5 bg-white/[0.02]">
|
||||
<div class="flex items-center gap-4">
|
||||
<span class="font-mono text-[10px] text-slate-500 uppercase tracking-widest">{entry.systemId} /// {entry.status}</span>
|
||||
</div>
|
||||
<span class="font-mono text-[10px] text-slate-500 uppercase tracking-widest">{entry.dates}</span>
|
||||
</div>
|
||||
|
||||
<div class="p-8 lg:p-10 flex flex-col h-full">
|
||||
<div class="mb-6">
|
||||
<h3 class="text-2xl font-bold text-white uppercase tracking-tight mb-1">{entry.company}</h3>
|
||||
<span class="text-xs font-mono text-slate-400">{entry.role}</span>
|
||||
</div>
|
||||
<p class="text-slate-400 leading-relaxed font-light text-sm mb-8 flex-grow">
|
||||
{entry.description}
|
||||
</p>
|
||||
{entry.link && (
|
||||
<div class="pt-6 border-t border-white/5">
|
||||
<a href={entry.link.url} class="inline-flex items-center gap-3 text-xs font-bold uppercase tracking-widest text-slate-300 hover:text-white transition-colors">
|
||||
{entry.link.text} <svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14"/><path d="m12 5 7 7-7 7"/></svg>
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
{entry.tags && entry.tags.length > 0 && (
|
||||
<div class="pt-6 border-t border-white/5 flex flex-wrap gap-2">
|
||||
{entry.tags.map((tag) => (
|
||||
<span class="px-2 py-1 text-[10px] font-mono uppercase border border-white/10 text-slate-500">{tag}</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Split Row for Stink & Freelance -->
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
|
||||
<!-- Unit 02: Stinkfilms -->
|
||||
<div class="group relative border border-white/10 bg-white/[0.02] hover:border-white/30 transition-all duration-500 overflow-hidden animate-on-scroll slide-up stagger-2">
|
||||
<!-- Inactive Indicator Strip -->
|
||||
<div class="absolute top-0 left-0 w-1 h-full bg-slate-700 opacity-50 group-hover:bg-white transition-colors"></div>
|
||||
|
||||
<!-- Technical Header -->
|
||||
<div class="flex items-center justify-between px-8 py-4 border-b border-white/5 bg-white/[0.02]">
|
||||
<div class="flex items-center gap-4">
|
||||
<span class="font-mono text-[10px] text-slate-500 uppercase tracking-widest">SYS.02 /// ARCHIVED</span>
|
||||
</div>
|
||||
<span class="font-mono text-[10px] text-slate-500 uppercase tracking-widest">SUMMER 2024</span>
|
||||
</div>
|
||||
|
||||
<div class="p-8 lg:p-10 flex flex-col h-full">
|
||||
<div class="mb-6">
|
||||
<h3 class="text-2xl font-bold text-white uppercase tracking-tight mb-1">Stinkfilms</h3>
|
||||
<span class="text-xs font-mono text-slate-400">VFX Supervisor</span>
|
||||
</div>
|
||||
<p class="text-slate-400 leading-relaxed font-light text-sm mb-8 flex-grow">
|
||||
Led Biohazard VFX team (60+ artists) alongside director Felix Brady to create a brand film for G-Star Raw.
|
||||
Managed full CG environments in Blender/Houdini.
|
||||
</p>
|
||||
<div class="pt-6 border-t border-white/5">
|
||||
<a href="/blog/gstar-raw-olympics/" class="inline-flex items-center gap-3 text-xs font-bold uppercase tracking-widest text-slate-300 hover:text-white transition-colors">
|
||||
Access Case Data <svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14"/><path d="m12 5 7 7-7 7"/></svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Unit 03: Freelance -->
|
||||
<div class="group relative border border-white/10 bg-white/[0.02] hover:border-white/30 transition-all duration-500 overflow-hidden animate-on-scroll slide-up stagger-3">
|
||||
<!-- Background Process Indicator Strip -->
|
||||
<div class="absolute top-0 left-0 w-1 h-full bg-slate-700 opacity-50 group-hover:bg-white transition-colors"></div>
|
||||
|
||||
<!-- Technical Header -->
|
||||
<div class="flex items-center justify-between px-8 py-4 border-b border-white/5 bg-white/[0.02]">
|
||||
<div class="flex items-center gap-4">
|
||||
<span class="font-mono text-[10px] text-slate-500 uppercase tracking-widest">SYS.03 /// DAEMON</span>
|
||||
</div>
|
||||
<span class="font-mono text-[10px] text-slate-500 uppercase tracking-widest">2016 — PRESENT</span>
|
||||
</div>
|
||||
|
||||
<div class="p-8 lg:p-10 flex flex-col h-full">
|
||||
<div class="mb-6">
|
||||
<h3 class="text-2xl font-bold text-white uppercase tracking-tight mb-1">Freelance</h3>
|
||||
<span class="text-xs font-mono text-slate-400">Senior Compositor</span>
|
||||
</div>
|
||||
<p class="text-slate-400 leading-relaxed font-light text-sm mb-8 flex-grow">
|
||||
Taking on select freelance compositing and 3D work alongside studio operations.
|
||||
Clients include Abyss Digital, Atlantic, Interscope.
|
||||
</p>
|
||||
<div class="pt-6 border-t border-white/5 flex flex-wrap gap-2">
|
||||
<span class="px-2 py-1 text-[10px] font-mono uppercase border border-white/10 text-slate-500">Nuke</span>
|
||||
<span class="px-2 py-1 text-[10px] font-mono uppercase border border-white/10 text-slate-500">Flame</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@ -1,8 +1,25 @@
|
||||
---
|
||||
interface Props {
|
||||
role: string;
|
||||
client: string;
|
||||
year: string;
|
||||
region: string;
|
||||
projectTitle: string;
|
||||
projectSubtitle: string;
|
||||
projectDescription: string;
|
||||
stats: Array<{
|
||||
label: string;
|
||||
value: string;
|
||||
}>;
|
||||
videoUrl: string;
|
||||
linkUrl: string;
|
||||
}
|
||||
|
||||
const { role, client, year, region, projectTitle, projectSubtitle, projectDescription, stats, videoUrl, linkUrl } = Astro.props;
|
||||
---
|
||||
<section id="work" class="relative overflow-hidden group min-h-[100dvh] flex flex-col cursor-pointer">
|
||||
<!-- Main Link Overlay -->
|
||||
<a href="/blog/gstar-raw-olympics/" class="absolute inset-0 z-30" aria-label="View G-Star Raw Olympics Case Study"></a>
|
||||
<a href={linkUrl} class="absolute inset-0 z-30" aria-label={`View ${projectTitle} ${projectSubtitle} Case Study`}></a>
|
||||
|
||||
<!-- Video Background -->
|
||||
<div class="absolute inset-0 z-0">
|
||||
@ -13,7 +30,7 @@
|
||||
playsinline
|
||||
class="w-full h-full object-cover opacity-70 transition-opacity duration-700 group-hover:opacity-100"
|
||||
>
|
||||
<source src="https://media.nicholai.work/FF_PUFF_GStar_DC_v08_4608x3164.mp4" type="video/mp4" />
|
||||
<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>
|
||||
@ -30,19 +47,19 @@
|
||||
<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>
|
||||
<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">VFX Sup</span>
|
||||
<span class="text-xl md:text-2xl font-bold text-white 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">Stink</span>
|
||||
<span class="text-xl md:text-2xl font-bold text-white 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">2024</span>
|
||||
<span class="text-xl md:text-2xl font-bold text-white 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">Global</span>
|
||||
<span class="text-xl md:text-2xl font-bold text-white uppercase tracking-tight">{region}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -51,7 +68,7 @@
|
||||
<!-- Side Vertical Title (Optional, unobtrusive) -->
|
||||
<div class="hidden lg:block absolute -left-8 top-1/2 -translate-y-1/2 origin-left -rotate-90">
|
||||
<h2 class="text-6xl font-bold text-transparent text-stroke uppercase tracking-tighter opacity-20 select-none">
|
||||
G-Star Raw
|
||||
{projectTitle}
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
@ -63,33 +80,22 @@
|
||||
<!-- 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">
|
||||
G-Star <span class="text-transparent text-stroke">Olympics</span>
|
||||
{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">
|
||||
Full CG environment production for the 2024 Olympic Campaign.
|
||||
Orchestrated procedural city generation and AI-enhanced lighting workflows.
|
||||
{projectDescription}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Technical Stats (Mini-Table) -->
|
||||
<div class="lg:col-span-5">
|
||||
<div class="grid grid-cols-2 gap-x-8 gap-y-4 font-mono text-xs">
|
||||
<div class="border-l border-brand-accent/30 pl-3">
|
||||
<span class="block text-slate-500 text-[10px] uppercase mb-1">Shot Count</span>
|
||||
<span class="block text-white font-bold">12 Sequences</span>
|
||||
</div>
|
||||
<div class="border-l border-brand-accent/30 pl-3">
|
||||
<span class="block text-slate-500 text-[10px] uppercase mb-1">Resolution</span>
|
||||
<span class="block text-white font-bold">4K DCI</span>
|
||||
</div>
|
||||
<div class="border-l border-brand-accent/30 pl-3">
|
||||
<span class="block text-slate-500 text-[10px] uppercase mb-1">Pipeline</span>
|
||||
<span class="block text-white font-bold">Houdini / Solaris</span>
|
||||
</div>
|
||||
<div class="border-l border-brand-accent/30 pl-3">
|
||||
<span class="block text-slate-500 text-[10px] uppercase mb-1">Render</span>
|
||||
<span class="block text-white font-bold">Karma XPU</span>
|
||||
</div>
|
||||
{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>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@ -1,4 +1,14 @@
|
||||
---
|
||||
interface Props {
|
||||
headlineLine1: string;
|
||||
headlineLine2: string;
|
||||
portfolioYear: string;
|
||||
location: string;
|
||||
locationLabel: string;
|
||||
bio: string;
|
||||
}
|
||||
|
||||
const { headlineLine1, headlineLine2, portfolioYear, location, locationLabel, bio } = Astro.props;
|
||||
---
|
||||
<section id="hero" class="relative w-full h-[100dvh] overflow-hidden bg-brand-dark">
|
||||
<!-- Background Image (Portrait) -->
|
||||
@ -29,11 +39,11 @@
|
||||
<!-- 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">
|
||||
Portfolio 2026
|
||||
{portfolioYear}
|
||||
</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">Location</span>
|
||||
Colorado Springs, CO<br>
|
||||
<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>
|
||||
</div>
|
||||
@ -41,12 +51,12 @@
|
||||
<!-- 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">
|
||||
<span class="block intro-element opacity-0 translate-y-10 transition-all duration-1000 ease-out delay-100">VISUAL</span>
|
||||
<span class="block text-brand-accent opacity-0 translate-y-10 transition-all duration-1000 ease-out delay-200 intro-element">ALCHEMIST</span>
|
||||
<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">
|
||||
I am a problem solver who loves visual effects. Creating end-to-end visual content for clients like Post Malone, Stinkfilms, and Adidas. Bridging creative vision with technical execution.
|
||||
{bio}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
@ -1,5 +1,27 @@
|
||||
---
|
||||
import { Image } from 'astro:assets';
|
||||
|
||||
interface Props {
|
||||
sectionTitle: string;
|
||||
sectionSubtitle: string;
|
||||
description: string;
|
||||
skills: Array<{
|
||||
id: string;
|
||||
domain: string;
|
||||
tools: string;
|
||||
proficiency: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
const { sectionTitle, sectionSubtitle, description, skills } = Astro.props;
|
||||
|
||||
// Image map for skill data attributes
|
||||
const imageMap: Record<string, string> = {
|
||||
"01": "compositing",
|
||||
"02": "3d",
|
||||
"03": "ai",
|
||||
"04": "dev"
|
||||
};
|
||||
---
|
||||
|
||||
<section id="skills" class="bg-brand-dark py-32 lg:py-48 overflow-hidden relative cursor-default">
|
||||
@ -10,16 +32,16 @@ import { Image } from 'astro:assets';
|
||||
<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">Technical</span>
|
||||
<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">Arsenal</span>
|
||||
<span class="animate-on-scroll slide-up stagger-1 block text-stroke text-transparent">{sectionSubtitle}</span>
|
||||
</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">
|
||||
A comprehensive suite of tools and workflows designed for high-fidelity visual production and pipeline automation.
|
||||
{description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@ -35,75 +57,32 @@ import { Image } from 'astro:assets';
|
||||
<div class="col-span-6 md:col-span-2 hidden md:block text-right">Proficiency</div>
|
||||
</div>
|
||||
|
||||
<!-- Row 1: Compositing -->
|
||||
<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" data-image="compositing">
|
||||
<div class="col-span-2 md:col-span-1 text-brand-accent font-mono text-sm relative overflow-hidden">
|
||||
<span class="block group-hover:-translate-y-full transition-transform duration-500">01</span>
|
||||
<span class="absolute top-0 left-0 translate-y-full group-hover:translate-y-0 transition-transform duration-500">01</span>
|
||||
</div>
|
||||
<div class="col-span-10 md:col-span-4 relative overflow-hidden">
|
||||
<h3 class="text-3xl md:text-5xl font-bold text-white uppercase tracking-tighter group-hover:text-brand-accent transition-colors duration-300">Compositing</h3>
|
||||
<!-- Reveal Swipe Effect -->
|
||||
<div class="absolute inset-0 bg-brand-accent transform scale-x-0 group-hover:scale-x-100 transition-transform duration-500 origin-left mix-blend-difference pointer-events-none opacity-0 group-hover:opacity-100"></div>
|
||||
</div>
|
||||
<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">
|
||||
Nuke/NukeX • ComfyUI • After Effects • Photoshop
|
||||
</div>
|
||||
<div class="col-span-6 md:col-span-2 text-right hidden md:block">
|
||||
<span class="inline-block px-3 py-1 border border-brand-accent/50 text-brand-accent text-[10px] font-bold uppercase tracking-widest bg-brand-accent/5">Expert</span>
|
||||
</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";
|
||||
|
||||
<!-- Row 2: 3D Generalist -->
|
||||
<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" data-image="3d">
|
||||
<div class="col-span-2 md:col-span-1 text-brand-accent font-mono text-sm relative overflow-hidden">
|
||||
<span class="block group-hover:-translate-y-full transition-transform duration-500">02</span>
|
||||
<span class="absolute top-0 left-0 translate-y-full group-hover:translate-y-0 transition-transform duration-500">02</span>
|
||||
</div>
|
||||
<div class="col-span-10 md:col-span-4 relative overflow-hidden">
|
||||
<h3 class="text-3xl md:text-5xl font-bold text-white uppercase tracking-tighter group-hover:text-brand-accent transition-colors duration-300">3D Generalist</h3>
|
||||
</div>
|
||||
<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">
|
||||
Houdini • Blender • Maya • USD • Solaris
|
||||
</div>
|
||||
<div class="col-span-6 md:col-span-2 text-right hidden md:block">
|
||||
<span class="inline-block px-3 py-1 border border-white/20 text-slate-300 text-[10px] font-bold uppercase tracking-widest">Advanced</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Row 3: AI / ML -->
|
||||
<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" data-image="ai">
|
||||
<div class="col-span-2 md:col-span-1 text-brand-accent font-mono text-sm relative overflow-hidden">
|
||||
<span class="block group-hover:-translate-y-full transition-transform duration-500">03</span>
|
||||
<span class="absolute top-0 left-0 translate-y-full group-hover:translate-y-0 transition-transform duration-500">03</span>
|
||||
</div>
|
||||
<div class="col-span-10 md:col-span-4 relative overflow-hidden">
|
||||
<h3 class="text-3xl md:text-5xl font-bold text-white uppercase tracking-tighter group-hover:text-brand-accent transition-colors duration-300">AI Integration</h3>
|
||||
</div>
|
||||
<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">
|
||||
Stable Diffusion • LoRA • Datasets • Python
|
||||
</div>
|
||||
<div class="col-span-6 md:col-span-2 text-right hidden md:block">
|
||||
<span class="inline-block px-3 py-1 border border-brand-accent/50 text-brand-accent text-[10px] font-bold uppercase tracking-widest bg-brand-accent/5">Specialist</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Row 4: Development -->
|
||||
<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" data-image="dev">
|
||||
<div class="col-span-2 md:col-span-1 text-brand-accent font-mono text-sm relative overflow-hidden">
|
||||
<span class="block group-hover:-translate-y-full transition-transform duration-500">04</span>
|
||||
<span class="absolute top-0 left-0 translate-y-full group-hover:translate-y-0 transition-transform duration-500">04</span>
|
||||
</div>
|
||||
<div class="col-span-10 md:col-span-4 relative overflow-hidden">
|
||||
<h3 class="text-3xl md:text-5xl font-bold text-white uppercase tracking-tighter group-hover:text-brand-accent transition-colors duration-300">Development</h3>
|
||||
</div>
|
||||
<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">
|
||||
Python • React • Docker • Linux • Pipeline
|
||||
</div>
|
||||
<div class="col-span-6 md:col-span-2 text-right hidden md:block">
|
||||
<span class="inline-block px-3 py-1 border border-white/20 text-slate-300 text-[10px] font-bold uppercase tracking-widest">Full Stack</span>
|
||||
</div>
|
||||
</div>
|
||||
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 ${index < skills.length - 1 ? '' : ''}`} data-image={imageMap[skill.id] || "default"}>
|
||||
<div class="col-span-2 md:col-span-1 text-brand-accent font-mono text-sm relative overflow-hidden">
|
||||
<span class="block group-hover:-translate-y-full transition-transform duration-500">{skill.id}</span>
|
||||
<span class="absolute top-0 left-0 translate-y-full group-hover:translate-y-0 transition-transform duration-500">{skill.id}</span>
|
||||
</div>
|
||||
<div class="col-span-10 md:col-span-4 relative overflow-hidden">
|
||||
<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>
|
||||
{index === 0 && (
|
||||
<div class="absolute inset-0 bg-brand-accent transform scale-x-0 group-hover:scale-x-100 transition-transform duration-500 origin-left mix-blend-difference pointer-events-none opacity-0 group-hover:opacity-100"></div>
|
||||
)}
|
||||
</div>
|
||||
<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">
|
||||
{skill.tools}
|
||||
</div>
|
||||
<div class="col-span-6 md:col-span-2 text-right hidden md:block">
|
||||
<span class={`inline-block px-3 py-1 border text-[10px] font-bold uppercase tracking-widest ${proficiencyClass}`}>{skill.proficiency}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -16,4 +16,90 @@ const blog = defineCollection({
|
||||
}),
|
||||
});
|
||||
|
||||
export const collections = { blog };
|
||||
const sections = defineCollection({
|
||||
loader: glob({ base: './src/content/sections', pattern: '**/*.{md,mdx}' }),
|
||||
schema: z.object({
|
||||
// Hero section
|
||||
headlineLine1: z.string().optional(),
|
||||
headlineLine2: z.string().optional(),
|
||||
portfolioYear: z.string().optional(),
|
||||
location: z.string().optional(),
|
||||
locationLabel: z.string().optional(),
|
||||
bio: z.string().optional(),
|
||||
// Experience section
|
||||
sectionTitle: z.string().optional(),
|
||||
sectionSubtitle: z.string().optional(),
|
||||
sectionLabel: z.string().optional(),
|
||||
description: z.string().optional(),
|
||||
// Experience entries
|
||||
entries: z.array(z.object({
|
||||
systemId: z.string(),
|
||||
status: z.string(),
|
||||
dates: z.string(),
|
||||
company: z.string(),
|
||||
role: z.string(),
|
||||
tags: z.array(z.string()).optional(),
|
||||
description: z.string(),
|
||||
achievements: z.array(z.object({
|
||||
label: z.string(),
|
||||
text: z.string(),
|
||||
})).optional(),
|
||||
link: z.object({
|
||||
url: z.string(),
|
||||
text: z.string(),
|
||||
}).optional(),
|
||||
})).optional(),
|
||||
// Skills entries
|
||||
skills: z.array(z.object({
|
||||
id: z.string(),
|
||||
domain: z.string(),
|
||||
tools: z.string(),
|
||||
proficiency: z.string(),
|
||||
})).optional(),
|
||||
// Featured project
|
||||
role: z.string().optional(),
|
||||
client: z.string().optional(),
|
||||
year: z.string().optional(),
|
||||
region: z.string().optional(),
|
||||
projectTitle: z.string().optional(),
|
||||
projectSubtitle: z.string().optional(),
|
||||
projectDescription: z.string().optional(),
|
||||
stats: z.array(z.object({
|
||||
label: z.string(),
|
||||
value: z.string(),
|
||||
})).optional(),
|
||||
videoUrl: z.string().optional(),
|
||||
linkUrl: z.string().optional(),
|
||||
}),
|
||||
});
|
||||
|
||||
const pages = defineCollection({
|
||||
loader: glob({ base: './src/content/pages', pattern: '**/*.{md,mdx}' }),
|
||||
schema: z.object({
|
||||
pageTitleLine1: z.string().optional(),
|
||||
pageTitleLine2: z.string().optional(),
|
||||
availabilityText: z.string().optional(),
|
||||
email: z.string().optional(),
|
||||
location: z.string().optional(),
|
||||
locationCountry: z.string().optional(),
|
||||
coordinates: z.string().optional(),
|
||||
socialLinks: z.array(z.object({
|
||||
name: z.string(),
|
||||
url: z.string(),
|
||||
})).optional(),
|
||||
formLabels: z.object({
|
||||
name: z.string().optional(),
|
||||
email: z.string().optional(),
|
||||
subject: z.string().optional(),
|
||||
message: z.string().optional(),
|
||||
submit: z.string().optional(),
|
||||
transmissionUplink: z.string().optional(),
|
||||
}).optional(),
|
||||
subjectOptions: z.array(z.object({
|
||||
value: z.string(),
|
||||
label: z.string(),
|
||||
})).optional(),
|
||||
}),
|
||||
});
|
||||
|
||||
export const collections = { blog, sections, pages };
|
||||
|
||||
33
src/content/pages/contact.mdx
Normal file
33
src/content/pages/contact.mdx
Normal file
@ -0,0 +1,33 @@
|
||||
---
|
||||
pageTitleLine1: "Project"
|
||||
pageTitleLine2: "Inquiry"
|
||||
availabilityText: "Available for freelance commissions and studio collaborations. Currently booking Q3 2026."
|
||||
email: "nicholai@nicholai.work"
|
||||
location: "Colorado Springs, CO"
|
||||
locationCountry: "United States"
|
||||
coordinates: "38.8339° N, 104.8214° W"
|
||||
socialLinks:
|
||||
- name: "Instagram"
|
||||
url: "#"
|
||||
- name: "LinkedIn"
|
||||
url: "#"
|
||||
- name: "Vimeo"
|
||||
url: "#"
|
||||
formLabels:
|
||||
transmissionUplink: "Transmission Uplink"
|
||||
name: "/// Identification Name"
|
||||
email: "/// Return Address"
|
||||
subject: "/// Subject Protocol"
|
||||
message: "/// Message Data"
|
||||
submit: "Transmit Message"
|
||||
subjectOptions:
|
||||
- value: "project"
|
||||
label: "New Project Commission"
|
||||
- value: "collab"
|
||||
label: "Studio Collaboration"
|
||||
- value: "press"
|
||||
label: "Press / Media"
|
||||
- value: "other"
|
||||
label: "Other Inquiry"
|
||||
---
|
||||
|
||||
43
src/content/sections/experience.mdx
Normal file
43
src/content/sections/experience.mdx
Normal file
@ -0,0 +1,43 @@
|
||||
---
|
||||
sectionTitle: "Experience"
|
||||
sectionSubtitle: "History"
|
||||
sectionLabel: "/// Career Timeline"
|
||||
description: "Bridging creative vision with technical execution. From running a dedicated VFX studio to high-end freelance supervision."
|
||||
entries:
|
||||
- systemId: "SYS.01"
|
||||
status: "ACTIVE"
|
||||
dates: "2022 — PRESENT"
|
||||
company: "Biohazard VFX"
|
||||
role: "Founder & Owner"
|
||||
tags:
|
||||
- "Studio Lead"
|
||||
- "Pipeline Arch"
|
||||
description: "Founded and lead a VFX studio specializing in high-end commercial and music video work. Delivered projects for Post Malone, ENHYPEN, and Nike. Architected a custom pipeline combining cloud and self-hosted infrastructure."
|
||||
achievements:
|
||||
- label: "Key Achievement"
|
||||
text: "Designed 7-plate reconciliation workflows for ENHYPEN (projection mapping live action onto CAD)."
|
||||
- label: "System Impact"
|
||||
text: "Developed QA systems for AI-generated assets, transforming mid-tier output into production-ready deliverables."
|
||||
link:
|
||||
url: "https://biohazardvfx.com"
|
||||
text: "Visit Studio Uplink"
|
||||
- systemId: "SYS.02"
|
||||
status: "ARCHIVED"
|
||||
dates: "SUMMER 2024"
|
||||
company: "Stinkfilms"
|
||||
role: "VFX Supervisor"
|
||||
description: "Led Biohazard VFX team (60+ artists) alongside director Felix Brady to create a brand film for G-Star Raw. Managed full CG environments in Blender/Houdini."
|
||||
link:
|
||||
url: "/blog/gstar-raw-olympics/"
|
||||
text: "Access Case Data"
|
||||
- systemId: "SYS.03"
|
||||
status: "DAEMON"
|
||||
dates: "2016 — PRESENT"
|
||||
company: "Freelance"
|
||||
role: "Senior Compositor"
|
||||
description: "Taking on select freelance compositing and 3D work alongside studio operations. Clients include Abyss Digital, Atlantic, Interscope."
|
||||
tags:
|
||||
- "Nuke"
|
||||
- "Flame"
|
||||
---
|
||||
|
||||
21
src/content/sections/featured-project.mdx
Normal file
21
src/content/sections/featured-project.mdx
Normal file
@ -0,0 +1,21 @@
|
||||
---
|
||||
role: "VFX Sup"
|
||||
client: "Stink"
|
||||
year: "2024"
|
||||
region: "Global"
|
||||
projectTitle: "G-Star"
|
||||
projectSubtitle: "Olympics"
|
||||
projectDescription: "Full CG environment production for the 2024 Olympic Campaign. Orchestrated procedural city generation and AI-enhanced lighting workflows."
|
||||
stats:
|
||||
- label: "Shot Count"
|
||||
value: "12 Sequences"
|
||||
- label: "Resolution"
|
||||
value: "4K DCI"
|
||||
- label: "Pipeline"
|
||||
value: "Houdini / Solaris"
|
||||
- label: "Render"
|
||||
value: "Karma XPU"
|
||||
videoUrl: "https://media.nicholai.work/FF_PUFF_GStar_DC_v08_4608x3164.mp4"
|
||||
linkUrl: "/blog/gstar-raw-olympics/"
|
||||
---
|
||||
|
||||
9
src/content/sections/hero.mdx
Normal file
9
src/content/sections/hero.mdx
Normal file
@ -0,0 +1,9 @@
|
||||
---
|
||||
headlineLine1: "VISUAL"
|
||||
headlineLine2: "ALCHEMIST"
|
||||
portfolioYear: "Portfolio 2026"
|
||||
location: "Colorado Springs, CO"
|
||||
locationLabel: "Location"
|
||||
bio: "I am a problem solver who loves visual effects. Creating end-to-end visual content for clients like Post Malone, Stinkfilms, and Adidas. Bridging creative vision with technical execution."
|
||||
---
|
||||
|
||||
23
src/content/sections/skills.mdx
Normal file
23
src/content/sections/skills.mdx
Normal file
@ -0,0 +1,23 @@
|
||||
---
|
||||
sectionTitle: "Technical"
|
||||
sectionSubtitle: "Arsenal"
|
||||
description: "A comprehensive suite of tools and workflows designed for high-fidelity visual production and pipeline automation."
|
||||
skills:
|
||||
- id: "01"
|
||||
domain: "Compositing"
|
||||
tools: "Nuke/NukeX • ComfyUI • After Effects • Photoshop"
|
||||
proficiency: "Expert"
|
||||
- id: "02"
|
||||
domain: "3D Generalist"
|
||||
tools: "Houdini • Blender • Maya • USD • Solaris"
|
||||
proficiency: "Advanced"
|
||||
- id: "03"
|
||||
domain: "AI Integration"
|
||||
tools: "Stable Diffusion • LoRA • Datasets • Python"
|
||||
proficiency: "Specialist"
|
||||
- id: "04"
|
||||
domain: "Development"
|
||||
tools: "Python • React • Docker • Linux • Pipeline"
|
||||
proficiency: "Full Stack"
|
||||
---
|
||||
|
||||
@ -1,8 +1,13 @@
|
||||
---
|
||||
import BaseLayout from '../layouts/BaseLayout.astro';
|
||||
import { SITE_TITLE } from '../consts';
|
||||
import { getEntry } from 'astro:content';
|
||||
|
||||
const pageTitle = `Contact | ${SITE_TITLE}`;
|
||||
|
||||
// Fetch contact page content
|
||||
const contactEntry = await getEntry('pages', 'contact');
|
||||
const contactContent = contactEntry.data;
|
||||
---
|
||||
|
||||
<BaseLayout title={pageTitle} description="Get in touch for collaboration or inquiries." usePadding={false}>
|
||||
@ -22,14 +27,13 @@ const pageTitle = `Contact | ${SITE_TITLE}`;
|
||||
<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="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">
|
||||
<span class="block animate-on-scroll slide-up">Project</span>
|
||||
<span class="block text-brand-accent animate-on-scroll slide-up stagger-1">Inquiry</span>
|
||||
<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">
|
||||
Available for freelance commissions and studio collaborations.
|
||||
Currently booking Q3 2026.
|
||||
{contactContent.availabilityText}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@ -40,7 +44,7 @@ const pageTitle = `Contact | ${SITE_TITLE}`;
|
||||
<div class="lg:col-span-7 animate-on-scroll slide-up stagger-3">
|
||||
<div class="mb-8 flex items-center gap-3">
|
||||
<span class="w-2 h-2 bg-brand-accent rounded-full animate-pulse"></span>
|
||||
<span class="font-mono text-xs text-brand-accent uppercase tracking-widest">Transmission Uplink</span>
|
||||
<span class="font-mono text-xs text-brand-accent uppercase tracking-widest">{contactContent.formLabels?.transmissionUplink}</span>
|
||||
</div>
|
||||
|
||||
<form id="contact-form" class="space-y-12">
|
||||
@ -54,7 +58,7 @@ const pageTitle = `Contact | ${SITE_TITLE}`;
|
||||
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">
|
||||
/// Identification Name
|
||||
{contactContent.formLabels?.name}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
@ -68,7 +72,7 @@ const pageTitle = `Contact | ${SITE_TITLE}`;
|
||||
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">
|
||||
/// Return Address
|
||||
{contactContent.formLabels?.email}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
@ -84,28 +88,18 @@ const pageTitle = `Contact | ${SITE_TITLE}`;
|
||||
</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">
|
||||
/// Subject Protocol
|
||||
{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 class="p-1">
|
||||
<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="project">
|
||||
<span class="w-1.5 h-1.5 rounded-full bg-brand-accent opacity-0 group-hover/option:opacity-100 transition-opacity"></span>
|
||||
New Project Commission
|
||||
</div>
|
||||
<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="collab">
|
||||
<span class="w-1.5 h-1.5 rounded-full bg-brand-accent opacity-0 group-hover/option:opacity-100 transition-opacity"></span>
|
||||
Studio Collaboration
|
||||
</div>
|
||||
<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="press">
|
||||
<span class="w-1.5 h-1.5 rounded-full bg-brand-accent opacity-0 group-hover/option:opacity-100 transition-opacity"></span>
|
||||
Press / Media
|
||||
</div>
|
||||
<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="other">
|
||||
<span class="w-1.5 h-1.5 rounded-full bg-brand-accent opacity-0 group-hover/option:opacity-100 transition-opacity"></span>
|
||||
Other Inquiry
|
||||
</div>
|
||||
{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}>
|
||||
<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>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -120,13 +114,13 @@ const pageTitle = `Contact | ${SITE_TITLE}`;
|
||||
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">
|
||||
/// Message Data
|
||||
{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" class="font-mono text-xs font-bold uppercase tracking-widest text-white group-hover:text-brand-accent transition-colors">Transmit Message</span>
|
||||
<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>
|
||||
</div>
|
||||
@ -139,8 +133,8 @@ const pageTitle = `Contact | ${SITE_TITLE}`;
|
||||
<!-- 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:nicholai@nicholai.work" class="text-2xl md:text-3xl font-bold text-white hover:text-brand-accent transition-colors break-all">
|
||||
nicholai@nicholai.work
|
||||
<a href={`mailto:${contactContent.email}`} class="text-2xl md:text-3xl font-bold text-white hover:text-brand-accent transition-colors break-all">
|
||||
{contactContent.email}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@ -148,11 +142,11 @@ const pageTitle = `Contact | ${SITE_TITLE}`;
|
||||
<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">
|
||||
Colorado Springs, CO<br>
|
||||
<span class="text-slate-500 text-base">United States</span>
|
||||
{contactContent.location}<br>
|
||||
<span class="text-slate-500 text-base">{contactContent.locationCountry}</span>
|
||||
</p>
|
||||
<div class="mt-4 font-mono text-xs text-brand-accent">
|
||||
38.8339° N, 104.8214° W
|
||||
{contactContent.coordinates}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -160,24 +154,14 @@ const pageTitle = `Contact | ${SITE_TITLE}`;
|
||||
<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>
|
||||
<ul class="space-y-4">
|
||||
<li>
|
||||
<a href="#" class="flex items-center gap-4 group">
|
||||
<span class="text-slate-400 group-hover:text-white transition-colors text-lg">Instagram</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>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="flex items-center gap-4 group">
|
||||
<span class="text-slate-400 group-hover:text-white transition-colors text-lg">LinkedIn</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>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="flex items-center gap-4 group">
|
||||
<span class="text-slate-400 group-hover:text-white transition-colors text-lg">Vimeo</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>
|
||||
</a>
|
||||
</li>
|
||||
{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>
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@ -680,7 +664,7 @@ const pageTitle = `Contact | ${SITE_TITLE}`;
|
||||
showResponse();
|
||||
|
||||
// Reset button state
|
||||
submitText.textContent = 'Transmit Message';
|
||||
submitText.textContent = submitText.getAttribute('data-default-text') || 'Transmit Message';
|
||||
submitBtn.disabled = false;
|
||||
|
||||
} catch (markdownError) {
|
||||
@ -713,8 +697,9 @@ const pageTitle = `Contact | ${SITE_TITLE}`;
|
||||
|
||||
// Update button to failure state
|
||||
submitText.textContent = 'Transmission Failed';
|
||||
const defaultText = submitText.getAttribute('data-default-text') || 'Transmit Message';
|
||||
setTimeout(() => {
|
||||
submitText.textContent = 'Transmit Message';
|
||||
submitText.textContent = defaultText;
|
||||
submitBtn.disabled = false;
|
||||
}, 2000);
|
||||
|
||||
|
||||
@ -4,23 +4,68 @@ import Hero from '../components/sections/Hero.astro';
|
||||
import Experience from '../components/sections/Experience.astro';
|
||||
import FeaturedProject from '../components/sections/FeaturedProject.astro';
|
||||
import Skills from '../components/sections/Skills.astro';
|
||||
import { getEntry } from 'astro:content';
|
||||
|
||||
// Fetch all section content
|
||||
const heroEntry = await getEntry('sections', 'hero');
|
||||
const experienceEntry = await getEntry('sections', 'experience');
|
||||
const skillsEntry = await getEntry('sections', 'skills');
|
||||
const featuredProjectEntry = await getEntry('sections', 'featured-project');
|
||||
|
||||
// Extract content from entries
|
||||
const heroContent = {
|
||||
headlineLine1: heroEntry.data.headlineLine1 || '',
|
||||
headlineLine2: heroEntry.data.headlineLine2 || '',
|
||||
portfolioYear: heroEntry.data.portfolioYear || '',
|
||||
location: heroEntry.data.location || '',
|
||||
locationLabel: heroEntry.data.locationLabel || '',
|
||||
bio: heroEntry.data.bio || '',
|
||||
};
|
||||
|
||||
const experienceContent = {
|
||||
sectionTitle: experienceEntry.data.sectionTitle || '',
|
||||
sectionSubtitle: experienceEntry.data.sectionSubtitle || '',
|
||||
sectionLabel: experienceEntry.data.sectionLabel || '',
|
||||
description: experienceEntry.data.description || '',
|
||||
entries: experienceEntry.data.entries || [],
|
||||
};
|
||||
|
||||
const skillsContent = {
|
||||
sectionTitle: skillsEntry.data.sectionTitle || '',
|
||||
sectionSubtitle: skillsEntry.data.sectionSubtitle || '',
|
||||
description: skillsEntry.data.description || '',
|
||||
skills: skillsEntry.data.skills || [],
|
||||
};
|
||||
|
||||
const featuredProjectContent = {
|
||||
role: featuredProjectEntry.data.role || '',
|
||||
client: featuredProjectEntry.data.client || '',
|
||||
year: featuredProjectEntry.data.year || '',
|
||||
region: featuredProjectEntry.data.region || '',
|
||||
projectTitle: featuredProjectEntry.data.projectTitle || '',
|
||||
projectSubtitle: featuredProjectEntry.data.projectSubtitle || '',
|
||||
projectDescription: featuredProjectEntry.data.projectDescription || '',
|
||||
stats: featuredProjectEntry.data.stats || [],
|
||||
videoUrl: featuredProjectEntry.data.videoUrl || '',
|
||||
linkUrl: featuredProjectEntry.data.linkUrl || '',
|
||||
};
|
||||
---
|
||||
|
||||
<BaseLayout usePadding={false}>
|
||||
<Hero />
|
||||
<Hero {...heroContent} />
|
||||
|
||||
<!-- Gradient Divider -->
|
||||
<div class="w-full my-16 lg:my-24">
|
||||
<div class="h-[1px] divider-gradient"></div>
|
||||
</div>
|
||||
|
||||
<Experience />
|
||||
<Experience {...experienceContent} />
|
||||
|
||||
<!-- Container Divider with accent hint -->
|
||||
<div class="container mx-auto px-6 lg:px-12 my-8">
|
||||
<div class="h-[1px] divider-gradient"></div>
|
||||
</div>
|
||||
|
||||
<FeaturedProject />
|
||||
<Skills />
|
||||
<FeaturedProject {...featuredProjectContent} />
|
||||
<Skills {...skillsContent} />
|
||||
</BaseLayout>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user