diff --git a/package.json b/package.json index 187c2e2..d4a79a8 100644 --- a/package.json +++ b/package.json @@ -50,5 +50,8 @@ "typescript-eslint": "^8.54.0", "vite": "^6.0.0", "vitest": "^4.0.18" + }, + "dependencies": { + "@lucide/svelte": "^0.563.1" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 321bf9f..5fdb8dd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,6 +7,10 @@ settings: importers: .: + dependencies: + '@lucide/svelte': + specifier: ^0.563.1 + version: 0.563.1(svelte@5.49.1) devDependencies: '@eslint/js': specifier: ^9.39.2 @@ -647,6 +651,11 @@ packages: '@keyv/serialize@1.1.1': resolution: {integrity: sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==} + '@lucide/svelte@0.563.1': + resolution: {integrity: sha512-Kt+MbnE5D9RsuI/csmf7M+HWxALe57x3A0DhQ8pPnnUpneh7zuldrYjlT+veWtk+tVnp5doQtaAAxLujzIlhBw==} + peerDependencies: + svelte: ^5 + '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -2910,6 +2919,10 @@ snapshots: '@keyv/serialize@1.1.1': {} + '@lucide/svelte@0.563.1(svelte@5.49.1)': + dependencies: + svelte: 5.49.1 + '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 diff --git a/src/app.css b/src/app.css index 1b33d34..e75a1c8 100644 --- a/src/app.css +++ b/src/app.css @@ -375,6 +375,13 @@ a { } } +.icon-button { + display: flex; + align-items: center; + justify-content: center; + gap: var(--space-xs); +} + /* PRIMARY CTA Use the CTA/button tokens (defined in BOTH modes) to guarantee contrast. This fixes the dark-mode purple/white contrast violation without changing your purple brand accents. */ diff --git a/src/lib/components/Footer.svelte b/src/lib/components/Footer.svelte index fe1d614..f226aa8 100644 --- a/src/lib/components/Footer.svelte +++ b/src/lib/components/Footer.svelte @@ -1,3 +1,9 @@ + + @@ -35,4 +51,11 @@ color: var(--color-text-tertiary); max-width: 100%; } + + .link { + display: flex; + align-items: center; + justify-content: center; + gap: var(--space-xs); + } diff --git a/src/lib/components/Hero.svelte b/src/lib/components/Hero.svelte index 06e5e86..40c9e0a 100644 --- a/src/lib/components/Hero.svelte +++ b/src/lib/components/Hero.svelte @@ -1,4 +1,6 @@ @@ -13,20 +15,22 @@
Schedule a 30-minute intro call + Download resume +
diff --git a/src/lib/components/Icon/ExternalLink.svelte b/src/lib/components/Icon/ExternalLink.svelte new file mode 100644 index 0000000..d293eb7 --- /dev/null +++ b/src/lib/components/Icon/ExternalLink.svelte @@ -0,0 +1,30 @@ + + + + + + diff --git a/src/lib/components/Icon/FiletypePdf.svelte b/src/lib/components/Icon/FiletypePdf.svelte new file mode 100644 index 0000000..e071765 --- /dev/null +++ b/src/lib/components/Icon/FiletypePdf.svelte @@ -0,0 +1,30 @@ + + + + + + diff --git a/src/lib/components/Icon/Github.svelte b/src/lib/components/Icon/Github.svelte new file mode 100644 index 0000000..b3a6dd2 --- /dev/null +++ b/src/lib/components/Icon/Github.svelte @@ -0,0 +1,30 @@ + + + + + + diff --git a/src/lib/components/Icon/LinkedIn.svelte b/src/lib/components/Icon/LinkedIn.svelte new file mode 100644 index 0000000..0ae4550 --- /dev/null +++ b/src/lib/components/Icon/LinkedIn.svelte @@ -0,0 +1,30 @@ + + + + + + diff --git a/src/lib/components/Navigation.svelte b/src/lib/components/Navigation.svelte index 16b769f..2aeb79e 100644 --- a/src/lib/components/Navigation.svelte +++ b/src/lib/components/Navigation.svelte @@ -51,17 +51,17 @@ background-color: var(--color-bg); background-color: var(--color-bg); border-bottom: 1px solid var(--color-border); - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.6); - padding: var(--space-md) 0; + padding-top: var(--space-sm); position: sticky; text-align: center; top: 0; z-index: 100; @media (max-width: 768px) { + align-items: stretch; display: flex; flex-direction: column; - align-items: stretch; + padding: var(--space-md) 0; } } @@ -281,29 +281,56 @@ } @supports (animation-timeline: scroll()) { + /* Shadow on pseudo-element; only opacity is animated (composited) */ + .nav::after { + content: ''; + position: absolute; + inset: 0; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); + pointer-events: none; + opacity: 0; + animation: nav-shadow-on-scroll linear; + animation-timeline: scroll(root block); + animation-range: 0 100px; + animation-fill-mode: both; + will-change: opacity; + } + + @keyframes nav-shadow-on-scroll { + from { + opacity: 0; + } + + to { + opacity: 1; + } + } + + @media (prefers-reduced-motion: reduce) { + .nav::after { + animation: none; + opacity: 1; + } + } + + /* Composited-only animation: opacity only (visibility/pointer-events not animated) */ .nav-back-to-top, .nav-header-logo { opacity: 0; - visibility: hidden; - pointer-events: none; animation: nav-reveal-on-scroll linear; animation-timeline: scroll(root block); animation-range: 300px 400px; animation-fill-mode: both; - will-change: opacity, visibility; + will-change: opacity; } @keyframes nav-reveal-on-scroll { from { opacity: 0; - visibility: hidden; - pointer-events: none; } to { opacity: 1; - visibility: visible; - pointer-events: auto; } } diff --git a/src/lib/components/ScheduleSection.svelte b/src/lib/components/ScheduleSection.svelte index 75f79a7..5f86b58 100644 --- a/src/lib/components/ScheduleSection.svelte +++ b/src/lib/components/ScheduleSection.svelte @@ -1,3 +1,7 @@ + +
Ready to discuss your project?

Schedule a 30-minute intro call +
@@ -32,4 +37,8 @@ line-height: var(--line-height-relaxed); max-width: 100%; } + + .btn { + margin: 0 auto; + }