Compare commits
7 Commits
mkl.gg-main
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 1a40e6e718 | |||
| 237668fd31 | |||
| facc608d98 | |||
| c85dc91466 | |||
| d35c906e50 | |||
| 589553883a | |||
| c0c5a71b0a |
+4
-3
@@ -1,9 +1,10 @@
|
||||
import { defineConfig } from "astro/config";
|
||||
import mdx from "@astrojs/mdx";
|
||||
import sitemap from "@astrojs/sitemap";
|
||||
import tailwind from "@astrojs/tailwind";
|
||||
import mdx from "@astrojs/mdx";
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
site: "https://astro-nano-demo.vercel.app",
|
||||
integrations: [mdx(), sitemap(), tailwind()],
|
||||
});
|
||||
integrations: [sitemap(), tailwind(), mdx()]
|
||||
});
|
||||
Generated
+2521
-3040
File diff suppressed because it is too large
Load Diff
+13
-8
@@ -13,25 +13,30 @@
|
||||
"lint:fix": "eslint . --fix"
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/check": "^0.5.9",
|
||||
"@astrojs/mdx": "^2.2.0",
|
||||
"@astrojs/check": "^0.9.4",
|
||||
"@astrojs/mdx": "^4.0.6",
|
||||
"@astrojs/rss": "^4.0.5",
|
||||
"@astrojs/sitemap": "^3.1.1",
|
||||
"@astrojs/tailwind": "^5.1.0",
|
||||
"@astrojs/sitemap": "^3.2.1",
|
||||
"@astrojs/tailwind": "^5.1.4",
|
||||
"@fontsource/inter": "^5.0.17",
|
||||
"@fontsource/lora": "^5.0.16",
|
||||
"@tailwindcss/typography": "^0.5.10",
|
||||
"@tailwindcss/typography": "^0.5.16",
|
||||
"@typescript-eslint/eslint-plugin": "^7.3.1",
|
||||
"@typescript-eslint/parser": "^7.3.1",
|
||||
"astro": "^4.5.6",
|
||||
"astro": "^5.1.7",
|
||||
"astro-embed": "^0.9.0",
|
||||
"astro-light-box": "^0.1.1",
|
||||
"canvas-confetti": "^1.9.3",
|
||||
"clsx": "^2.1.0",
|
||||
"devicons-astro": "^0.3.3",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-plugin-astro": "^0.32.0",
|
||||
"eslint-plugin-jsx-a11y": "^6.8.0",
|
||||
"gsap": "^3.13.0",
|
||||
"rehype": "^13.0.2",
|
||||
"sharp": "^0.33.3",
|
||||
"tailwind-merge": "^2.2.2",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"typescript": "^5.4.2"
|
||||
"tailwindcss": "^3.4.17",
|
||||
"typescript": "^5.7.3"
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 173 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 140 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 97 KiB |
@@ -0,0 +1,120 @@
|
||||
---
|
||||
// Contact Form Component
|
||||
---
|
||||
<div id="form-message" class="hidden mb-4"></div>
|
||||
|
||||
<form id="contact-form" class="space-y-6 contact-form-wrapper">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div class="space-y-2">
|
||||
<label for="name" class="block text-sm text-black/70 dark:text-white/60">
|
||||
Name
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="name"
|
||||
name="name"
|
||||
required
|
||||
class="w-full px-4 py-3 rounded-md border border-black/5 dark:border-white/5 bg-transparent text-black dark:text-white placeholder-black/30 dark:placeholder-white/30 focus:outline-none focus:border-black/20 dark:focus:border-white/20 transition-all duration-300"
|
||||
placeholder="Your name"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<label for="email" class="block text-sm text-black/70 dark:text-white/60">
|
||||
Email
|
||||
</label>
|
||||
<input
|
||||
type="email"
|
||||
id="email"
|
||||
name="email"
|
||||
required
|
||||
class="w-full px-4 py-3 rounded-md border border-black/5 dark:border-white/5 bg-transparent text-black dark:text-white placeholder-black/30 dark:placeholder-white/30 focus:outline-none focus:border-black/20 dark:focus:border-white/20 transition-all duration-300"
|
||||
placeholder="your.email@example.com"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<label for="message" class="block text-sm text-black/70 dark:text-white/60">
|
||||
Message
|
||||
</label>
|
||||
<textarea
|
||||
id="message"
|
||||
name="message"
|
||||
required
|
||||
rows="5"
|
||||
class="w-full px-4 py-3 rounded-md border border-black/5 dark:border-white/5 bg-transparent text-black dark:text-white placeholder-black/30 dark:placeholder-white/30 focus:outline-none focus:border-black/20 dark:focus:border-white/20 transition-all duration-300 resize-none"
|
||||
placeholder="Your message..."
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button
|
||||
type="submit"
|
||||
class="px-8 py-3 rounded-md bg-black/90 dark:bg-white/90 text-white dark:text-black font-medium hover:bg-black dark:hover:bg-white focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-black/50 dark:focus:ring-white/50 transition-all duration-300 disabled:opacity-40 disabled:cursor-not-allowed"
|
||||
>
|
||||
<span id="button-text">Send Message</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const form = document.getElementById('contact-form') as HTMLFormElement;
|
||||
const buttonText = document.getElementById('button-text') as HTMLSpanElement;
|
||||
const formMessage = document.getElementById('form-message') as HTMLDivElement;
|
||||
|
||||
if (form) {
|
||||
form.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
// Get form data
|
||||
const formData = new FormData(form);
|
||||
const name = formData.get('name') as string;
|
||||
const email = formData.get('email') as string;
|
||||
const message = formData.get('message') as string;
|
||||
|
||||
// Disable button and show loading state
|
||||
const submitButton = form.querySelector('button[type="submit"]') as HTMLButtonElement;
|
||||
submitButton.disabled = true;
|
||||
buttonText.textContent = 'Sending...';
|
||||
formMessage.className = 'hidden';
|
||||
|
||||
try {
|
||||
// Send to API
|
||||
const response = await fetch('https://notify.api.standard.nz/', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name,
|
||||
email,
|
||||
message,
|
||||
destination: 'michael'
|
||||
}),
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
// Success
|
||||
formMessage.textContent = 'Message sent successfully! I\'ll get back to you soon.';
|
||||
formMessage.className = 'text-sm px-4 py-2.5 rounded-md bg-green-50 dark:bg-green-900/20 text-green-700 dark:text-green-300 border border-green-200 dark:border-green-800/30';
|
||||
form.style.display = 'none';
|
||||
} else {
|
||||
// Error response
|
||||
throw new Error('Failed to send message');
|
||||
}
|
||||
} catch (error) {
|
||||
// Network or other error
|
||||
formMessage.textContent = 'Failed to send message. Please try again or email me directly.';
|
||||
formMessage.className = 'text-sm px-4 py-2.5 rounded-md bg-red-50 dark:bg-red-900/20 text-red-700 dark:text-red-300 border border-red-200 dark:border-red-800/30';
|
||||
} finally {
|
||||
// Re-enable button
|
||||
submitButton.disabled = false;
|
||||
buttonText.textContent = 'Send Message';
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
<div class="mx-auto max-w-screen-sm px-5">
|
||||
|
||||
@@ -17,4 +17,4 @@
|
||||
|
||||
</script>
|
||||
|
||||
<span class="text-4xl" id="emoji">👋</span>
|
||||
<span class="text-5xl" id="emoji">👋</span>
|
||||
|
||||
+15
-16
@@ -1,24 +1,23 @@
|
||||
---
|
||||
import Container from "@components/Container.astro";
|
||||
import Link from "@components/Link.astro";
|
||||
import { SITE } from "@consts";
|
||||
import { SITE, SOCIALS } from "@consts";
|
||||
|
||||
// Check if we're on the homepage
|
||||
const isHomepage = Astro.url.pathname === '/';
|
||||
---
|
||||
|
||||
<header>
|
||||
<Container>
|
||||
<div class="flex flex-wrap gap-y-2 justify-between">
|
||||
<Link href="/" underline={false}>
|
||||
<div class="font-semibold">
|
||||
{SITE.NAME}
|
||||
</div>
|
||||
</Link>
|
||||
<nav class="flex gap-1">
|
||||
<Link href="/blog">
|
||||
blog
|
||||
<div class={`flex flex-wrap gap-y-2 ${isHomepage ? 'justify-end' : 'justify-between'}`}>
|
||||
{!isHomepage && (
|
||||
<Link href="/" underline={false}>
|
||||
<div class="font-semibold header-name">
|
||||
{SITE.NAME}
|
||||
</div>
|
||||
</Link>
|
||||
<span>
|
||||
{`/`}
|
||||
</span>
|
||||
)}
|
||||
<nav class="flex gap-1">
|
||||
<Link href="/work">
|
||||
work
|
||||
</Link>
|
||||
@@ -31,9 +30,9 @@ import { SITE } from "@consts";
|
||||
<span>
|
||||
{`/`}
|
||||
</span>
|
||||
<!-- <Link href="/lab">
|
||||
lab
|
||||
</Link> -->
|
||||
<Link href={SOCIALS.find(social => social.NAME === "cv")?.HREF || ""} external={true}>
|
||||
cv
|
||||
</Link>
|
||||
</nav>
|
||||
</div>
|
||||
</Container>
|
||||
|
||||
@@ -6,6 +6,7 @@ type Props = {
|
||||
external?: boolean;
|
||||
underline?: boolean;
|
||||
confetti?: boolean;
|
||||
class?: string;
|
||||
}
|
||||
|
||||
const { href, external, underline = true, confetti, ...rest } = Astro.props;
|
||||
|
||||
@@ -9,19 +9,19 @@ type Props = {
|
||||
const { heading, subheading, href, target="" } = Astro.props;
|
||||
---
|
||||
|
||||
<a href={href} target={target} class="relative group flex flex-nowrap py-3 px-4 pr-10 rounded-lg border border-black/15 dark:border-white/20 hover:bg-black/5 dark:hover:bg-white/5 hover:text-black dark:hover:text-white transition-colors duration-300 ease-in-out">
|
||||
<div class="flex flex-col flex-1 truncate">
|
||||
<div class="font-semibold">
|
||||
<a href={href} target={target} class="relative group flex flex-nowrap py-4 px-5 pr-12 rounded-xl modern-card grain-texture hover:text-black dark:hover:text-white transition-all duration-300 ease-in-out">
|
||||
<div class="flex flex-col flex-1 truncate relative z-10">
|
||||
<div class="font-semibold text-base">
|
||||
{heading}
|
||||
</div>
|
||||
<div class="text-sm">
|
||||
<div class="text-sm opacity-75 mt-1">
|
||||
{subheading}
|
||||
</div>
|
||||
</div>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
class="absolute top-1/2 right-2 -translate-y-1/2 size-5 stroke-2 fill-none stroke-current">
|
||||
class="absolute top-1/2 right-4 -translate-y-1/2 size-5 stroke-2 fill-none stroke-current z-10">
|
||||
<line x1="5" y1="12" x2="19" y2="12" class="translate-x-3 group-hover:translate-x-0 scale-x-0 group-hover:scale-x-100 transition-transform duration-300 ease-in-out" />
|
||||
<polyline points="12 5 19 12 12 19" class="-translate-x-1 group-hover:translate-x-0 transition-transform duration-300 ease-in-out" />
|
||||
</svg>
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
---
|
||||
|
||||
<div class="mx-auto max-w-screen-xl px-5">
|
||||
<slot />
|
||||
</div>
|
||||
@@ -0,0 +1,18 @@
|
||||
---
|
||||
import type { CollectionEntry } from "astro:content";
|
||||
import LinkCard from "./LinkCard.astro";
|
||||
import { dateRange } from "@lib/utils";
|
||||
|
||||
type Props = {
|
||||
entry: CollectionEntry<"work">;
|
||||
}
|
||||
|
||||
const { entry } = Astro.props;
|
||||
const href = entry.data.linkedinURL || "#";
|
||||
const heading = entry.data.company;
|
||||
const subheading = `${entry.data.role} • ${dateRange(entry.data.dateStart, entry.data.dateEnd)}`;
|
||||
---
|
||||
|
||||
<LinkCard href={href} heading={heading} subheading={subheading} target="_blank">
|
||||
|
||||
</LinkCard>
|
||||
+5
-1
@@ -2,7 +2,7 @@ import type { Site, Metadata, Socials } from "@types";
|
||||
|
||||
export const SITE: Site = {
|
||||
NAME: "Michael Rausch",
|
||||
EMAIL: "michael@rausch.nz",
|
||||
EMAIL: "m@michaelraus.ch",
|
||||
NUM_POSTS_ON_HOMEPAGE: 3,
|
||||
NUM_WORKS_ON_HOMEPAGE: 2,
|
||||
NUM_PROJECTS_ON_HOMEPAGE: 3,
|
||||
@@ -36,5 +36,9 @@ export const SOCIALS: Socials = [
|
||||
{
|
||||
NAME: "linkedin",
|
||||
HREF: "https://www.linkedin.com/in/michael-rausch-13445b8a/",
|
||||
},
|
||||
{
|
||||
NAME: "cv",
|
||||
HREF: "https://standardresume.co/r/sQUNyo7W9NsmFFG8ZvU_B",
|
||||
}
|
||||
];
|
||||
|
||||
@@ -17,6 +17,7 @@ const work = defineCollection({
|
||||
role: z.string(),
|
||||
dateStart: z.coerce.date(),
|
||||
dateEnd: z.union([z.coerce.date(), z.string()]),
|
||||
linkedinURL: z.string().optional(),
|
||||
}),
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
---
|
||||
title: "Tile Direct EasySample"
|
||||
description: "An AR cloud content management system"
|
||||
date: "2023"
|
||||
demoURL: ""
|
||||
---
|
||||
|
||||
TileDirect EasySample is a platform built to simplify how tile samples are managed and tracked. It lets sales teams check samples out to customers effortlessly, keeps tabs on inventory to prevent loss, and helps them follow up with customers at the right time—knowing exactly which tiles caught their attention. Customers can scan samples to explore more details online, see how tiles look in augmented reality, and get automated reminders for returns or care instructions.
|
||||
|
||||
The front end is built with Next.js, using shadcn/ui for styling and modern React Redux to manage state. On the backend, I used .NET Core with a PostgreSQL database, structured around an event-driven architecture. It’s hosted on Vercel, fly.io and Amazon AWS.
|
||||
|
||||
<br/>
|
||||
|
||||

|
||||
|
||||
<br/>
|
||||
|
||||

|
||||
|
||||
<br/>
|
||||
|
||||

|
||||
@@ -1,14 +0,0 @@
|
||||
---
|
||||
title: "QuickView Cloud"
|
||||
description: "An AR cloud content management system"
|
||||
date: "2024"
|
||||
demoURL: "https://actuality.nz"
|
||||
---
|
||||
|
||||

|
||||
|
||||
## Technologies
|
||||
- NodeJS
|
||||
- Firebase
|
||||
- React
|
||||
- UIKit
|
||||
@@ -0,0 +1,16 @@
|
||||
---
|
||||
title: "QuickView Cloud"
|
||||
description: "An AR cloud content management system"
|
||||
date: "2024"
|
||||
demoURL: "/quickviewdemo"
|
||||
---
|
||||
import { Tweet, Vimeo, YouTube, LinkPreview } from 'astro-embed';
|
||||
import DevIcon from 'devicons-astro';
|
||||
|
||||
<YouTube id="tRZPLgZB_II" />
|
||||
|
||||
QuickView transforms product information into interactive augmented reality (AR) experiences that can be seamlessly embedded in websites, apps, and marketing materials, allowing users to visualize products in their own spaces.
|
||||
|
||||
QuickView is built with a modern tech stack, including React, Redux, Node.js, Go, Firebase, and Pixar Universal Scene Description (USDZ).
|
||||
|
||||

|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
title: "CV"
|
||||
redirect: "https://standardresume.co/r/sQUNyo7W9NsmFFG8ZvU_B"
|
||||
---
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
company: "Standard"
|
||||
role: "Software Engineer / Director"
|
||||
dateStart: "01/01/2019"
|
||||
dateEnd: "Mothballed"
|
||||
linkedinURL: "https://www.linkedin.com/company/standard-nz"
|
||||
---
|
||||
|
||||
@@ -3,5 +3,6 @@ company: "University of Canterbury"
|
||||
role: "Software Engineer"
|
||||
dateStart: "08/01/2023"
|
||||
dateEnd: "present"
|
||||
linkedinURL: "https://www.linkedin.com/company/university-of-canterbury"
|
||||
---
|
||||
|
||||
|
||||
@@ -3,5 +3,6 @@ company: "Actuality"
|
||||
role: "Software Engineer"
|
||||
dateStart: "02/11/2022"
|
||||
dateEnd: "05/05/2023"
|
||||
linkedinURL: "https://www.linkedin.com/company/actuality"
|
||||
---
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ export function formatDate(date: Date) {
|
||||
}
|
||||
|
||||
export function readingTime(html: string) {
|
||||
if (!html) return "1 min read";
|
||||
const textOnly = html.replace(/<[^>]+>/g, "");
|
||||
const wordCount = textOnly.split(/\s+/).length;
|
||||
const readingTimeMinutes = ((wordCount / 200) + 1).toFixed();
|
||||
|
||||
+41
-46
@@ -3,7 +3,9 @@ import { getCollection } from "astro:content";
|
||||
import Container from "@components/Container.astro";
|
||||
import PageLayout from "@layouts/PageLayout.astro";
|
||||
import ArrowCard from "@components/ArrowCard.astro";
|
||||
import WorkCard from "@components/WorkCard.astro";
|
||||
import Link from "@components/Link.astro";
|
||||
import ContactForm from "@components/ContactForm.astro";
|
||||
import { dateRange } from "@lib/utils";
|
||||
import { SITE, HOME, SOCIALS } from "@consts";
|
||||
import EmojiScroller from "@components/EmojiScroller.astro";
|
||||
@@ -34,14 +36,14 @@ const work = await Promise.all(
|
||||
|
||||
<PageLayout title={HOME.TITLE} description={HOME.DESCRIPTION}>
|
||||
<Container>
|
||||
<h4 class="animate font-semibold text-black dark:text-white text-4xl font-serif">
|
||||
<h1 class="animate font-bold text-black dark:text-white text-4xl md:text-5xl font-serif leading-tight mb-5">
|
||||
Hi, I'm Michael <EmojiScroller/>
|
||||
</h4>
|
||||
</h1>
|
||||
<div class="space-y-16">
|
||||
<section>
|
||||
<article class="space-y-4">
|
||||
<p class="animate">
|
||||
I'm a software engineer at the University of Canterbury, working on <Link href="https://uconline.ac.nz" external>Tuihono UC | UC Online</Link>.
|
||||
<p class="animate text-lg">
|
||||
I'm a software engineer at the University of Canterbury, working on <Link href="https://uconline.ac.nz" external class="text-blue-600 dark:text-blue-400 hover:text-blue-700 dark:hover:text-blue-300">Tuihono UC | UC Online</Link>.
|
||||
|
||||
I specialize in full-stack development. While my primary expertise is in software engineering, I also have a strong interest in cloud infrastructure and DevOps. Based in Christchurch, New Zealand
|
||||
<br/><br/>
|
||||
@@ -50,58 +52,50 @@ const work = await Promise.all(
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<section class="animate space-y-6">
|
||||
<div class="flex flex-wrap gap-y-2 items-center justify-between">
|
||||
<h5 class="font-semibold text-black dark:text-white">
|
||||
Latest posts
|
||||
</h5>
|
||||
<Link href="/blog">
|
||||
See all posts
|
||||
</Link>
|
||||
</div>
|
||||
<ul class="flex flex-col gap-4">
|
||||
{blog.map(post => (
|
||||
<li>
|
||||
<ArrowCard entry={post} />
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</section>
|
||||
{ blog.length > 1 &&
|
||||
<section class="animate space-y-6">
|
||||
<div class="flex flex-wrap gap-y-2 items-center justify-between">
|
||||
<h4 class="font-semibold text-black dark:text-white text-xl">
|
||||
Latest posts
|
||||
</h4>
|
||||
<Link href="/blog">
|
||||
See all posts
|
||||
</Link>
|
||||
</div>
|
||||
<ul class="flex flex-col gap-4">
|
||||
{blog.map(post => (
|
||||
<li>
|
||||
<ArrowCard entry={post} />
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</section>
|
||||
}
|
||||
|
||||
|
||||
<section class="animate space-y-6">
|
||||
<div class="flex flex-wrap gap-y-2 items-center justify-between">
|
||||
<h5 class="font-semibold text-black dark:text-white">
|
||||
<h4 class="font-semibold text-black dark:text-white text-xl">
|
||||
Work Experience
|
||||
</h5>
|
||||
</h4>
|
||||
<Link href="/work">
|
||||
See all work
|
||||
</Link>
|
||||
</div>
|
||||
<ul class="flex flex-col space-y-4">
|
||||
{work.map(entry => (
|
||||
<li>
|
||||
<div class="text-sm opacity-75">
|
||||
{dateRange(entry.data.dateStart, entry.data.dateEnd)}
|
||||
</div>
|
||||
<div class="font-semibold text-black dark:text-white">
|
||||
{entry.data.company}
|
||||
</div>
|
||||
<div class="text-sm opacity-75">
|
||||
{entry.data.role}
|
||||
</div>
|
||||
<article>
|
||||
<entry.Content />
|
||||
</article>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<ul class="flex flex-col gap-4">
|
||||
{work.map(entry => (
|
||||
<li>
|
||||
<WorkCard entry={entry} />
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section class="animate space-y-6">
|
||||
<div class="flex flex-wrap gap-y-2 items-center justify-between">
|
||||
<h5 class="font-semibold text-black dark:text-white">
|
||||
<h4 class="font-semibold text-black dark:text-white text-xl">
|
||||
Recent projects
|
||||
</h5>
|
||||
</h4>
|
||||
<Link href="/projects">
|
||||
See all projects
|
||||
</Link>
|
||||
@@ -116,16 +110,17 @@ const work = await Promise.all(
|
||||
</section>
|
||||
|
||||
<section class="animate space-y-4">
|
||||
<h5 class="font-semibold text-black dark:text-white">
|
||||
<h4 class="font-semibold text-black dark:text-white text-xl">
|
||||
Let's Connect
|
||||
</h5>
|
||||
</h4>
|
||||
<article>
|
||||
<p>
|
||||
If you want to get in touch with me about something or just to say hi,
|
||||
reach out on social media or send me an email.
|
||||
</p>
|
||||
</article>
|
||||
<ul class="flex flex-wrap gap-2">
|
||||
<ContactForm />
|
||||
<ul class="flex flex-wrap gap-2 pt-5">
|
||||
{SOCIALS.map(SOCIAL => (
|
||||
<li class="flex gap-x-2 text-nowrap">
|
||||
<Link href={SOCIAL.HREF} external aria-label={`${SITE.NAME} on ${SOCIAL.NAME}`}>
|
||||
|
||||
@@ -13,7 +13,7 @@ const projects = (await getCollection("projects"))
|
||||
<PageLayout title={PROJECTS.TITLE} description={PROJECTS.DESCRIPTION}>
|
||||
<Container>
|
||||
<div class="space-y-10">
|
||||
<div class="animate font-semibold text-black dark:text-white text-4xl font-serif">
|
||||
<div class="animate font-semibold text-black dark:text-white text-5xl md:text-6xl font-serif">
|
||||
Projects
|
||||
</div>
|
||||
<ul class="animate flex flex-col gap-4">
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
---
|
||||
import PageLayout from "@layouts/PageLayout.astro";
|
||||
import WideContainer from "@components/WideContainer.astro";
|
||||
---
|
||||
|
||||
<PageLayout title={"QuickView Demo"} description={""}>
|
||||
<WideContainer>
|
||||
<iframe src="https://embedv1.quickview.co/?pid=dLFJz4mIGby5aV4kFORL" style="border: 1px solid #f0f0f0; width: 100%; min-height: 700px; border-radius: 20px;"></iframe>
|
||||
</WideContainer>
|
||||
</PageLayout>
|
||||
|
||||
@@ -19,7 +19,7 @@ const work = await Promise.all(
|
||||
<PageLayout title={WORK.TITLE} description={WORK.DESCRIPTION}>
|
||||
<Container>
|
||||
<div class="space-y-10">
|
||||
<div class="animate font-semibold text-black dark:text-white text-4xl font-serif">
|
||||
<div class="animate font-semibold text-black dark:text-white text-5xl md:text-6xl font-serif">
|
||||
Work
|
||||
</div>
|
||||
<ul class="flex flex-col space-y-4">
|
||||
|
||||
+136
-2
@@ -14,21 +14,35 @@ html.dark {
|
||||
html,
|
||||
body {
|
||||
@apply size-full;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
body {
|
||||
@apply font-sans antialiased;
|
||||
@apply flex flex-col;
|
||||
@apply bg-stone-100 dark:bg-black;
|
||||
@apply bg-stone-100;
|
||||
@apply text-black dark:text-white/75;
|
||||
}
|
||||
|
||||
.dark body {
|
||||
background: #0f0f0f;
|
||||
background-attachment: fixed;
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
header {
|
||||
@apply fixed top-0 left-0 right-0 z-50 py-5;
|
||||
@apply bg-stone-100/75 dark:bg-black/25;
|
||||
@apply bg-stone-100/75;
|
||||
@apply backdrop-blur-sm saturate-200;
|
||||
}
|
||||
|
||||
.dark header {
|
||||
background: transparent !important;
|
||||
backdrop-filter: none;
|
||||
}
|
||||
|
||||
main {
|
||||
@apply flex-1 py-32;
|
||||
}
|
||||
@@ -70,4 +84,124 @@ html #back-to-top {
|
||||
|
||||
html.scrolled #back-to-top {
|
||||
@apply opacity-100 pointer-events-auto;
|
||||
}
|
||||
|
||||
/* Grain texture effect */
|
||||
.grain-texture {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.grain-texture::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)' opacity='0.15'/%3E%3C/svg%3E");
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease-in-out;
|
||||
pointer-events: none;
|
||||
z-index: 2;
|
||||
mix-blend-mode: overlay;
|
||||
}
|
||||
|
||||
.grain-texture:hover::before {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Modern card hover effects */
|
||||
.modern-card {
|
||||
position: relative !important;
|
||||
background: rgba(255, 255, 255, 0.05) !important;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1) !important;
|
||||
transition: all 0.3s ease !important;
|
||||
}
|
||||
|
||||
.modern-card::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background:
|
||||
radial-gradient(circle at 20% 80%, #1a0a0a 0%, transparent 40%),
|
||||
radial-gradient(circle at 10% 20%, #ff8c42 0%, transparent 50%),
|
||||
radial-gradient(circle at 60% 50%, #4a9fd8 0%, transparent 60%),
|
||||
radial-gradient(circle at 100% 100%, #1a4d7a 0%, transparent 50%),
|
||||
linear-gradient(135deg, #2a1a1a 0%, #1a3a5a 100%);
|
||||
filter: blur(40px);
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
z-index: 0;
|
||||
border-radius: inherit;
|
||||
}
|
||||
|
||||
.modern-card:hover::after {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Different color variations for each card */
|
||||
li:nth-child(1) .modern-card::after {
|
||||
background:
|
||||
radial-gradient(circle at 20% 80%, #1a0a0a 0%, transparent 40%),
|
||||
radial-gradient(circle at 10% 20%, #ff8c42 0%, transparent 50%),
|
||||
radial-gradient(circle at 60% 50%, #4a9fd8 0%, transparent 60%),
|
||||
radial-gradient(circle at 100% 100%, #1a4d7a 0%, transparent 50%),
|
||||
linear-gradient(135deg, #2a1a1a 0%, #1a3a5a 100%);
|
||||
}
|
||||
|
||||
li:nth-child(2) .modern-card::after {
|
||||
background:
|
||||
radial-gradient(circle at 20% 80%, #0a1a0a 0%, transparent 40%),
|
||||
radial-gradient(circle at 10% 20%, #42ff8c 0%, transparent 50%),
|
||||
radial-gradient(circle at 60% 50%, #4ad89f 0%, transparent 60%),
|
||||
radial-gradient(circle at 100% 100%, #1a7a4d 0%, transparent 50%),
|
||||
linear-gradient(135deg, #1a2a1a 0%, #1a5a3a 100%);
|
||||
}
|
||||
|
||||
li:nth-child(3) .modern-card::after {
|
||||
background:
|
||||
radial-gradient(circle at 20% 80%, #1a0a1a 0%, transparent 40%),
|
||||
radial-gradient(circle at 10% 20%, #ff42d8 0%, transparent 50%),
|
||||
radial-gradient(circle at 60% 50%, #9f4ad8 0%, transparent 60%),
|
||||
radial-gradient(circle at 100% 100%, #7a1a6d 0%, transparent 50%),
|
||||
linear-gradient(135deg, #2a1a2a 0%, #5a1a4a 100%);
|
||||
}
|
||||
|
||||
li:nth-child(4) .modern-card::after {
|
||||
background:
|
||||
radial-gradient(circle at 20% 80%, #1a1a0a 0%, transparent 40%),
|
||||
radial-gradient(circle at 10% 20%, #ffd842 0%, transparent 50%),
|
||||
radial-gradient(circle at 60% 50%, #d8b84a 0%, transparent 60%),
|
||||
radial-gradient(circle at 100% 100%, #7a6d1a 0%, transparent 50%),
|
||||
linear-gradient(135deg, #2a2a1a 0%, #5a4a1a 100%);
|
||||
}
|
||||
|
||||
li:nth-child(5) .modern-card::after {
|
||||
background:
|
||||
radial-gradient(circle at 20% 80%, #0a0a1a 0%, transparent 40%),
|
||||
radial-gradient(circle at 10% 20%, #8c42ff 0%, transparent 50%),
|
||||
radial-gradient(circle at 60% 50%, #6a4ad8 0%, transparent 60%),
|
||||
radial-gradient(circle at 100% 100%, #4d1a7a 0%, transparent 50%),
|
||||
linear-gradient(135deg, #1a1a2a 0%, #3a1a5a 100%);
|
||||
}
|
||||
|
||||
.modern-card:hover {
|
||||
border-color: rgba(255, 255, 255, 0.2) !important;
|
||||
}
|
||||
|
||||
.dark .modern-card {
|
||||
background: rgba(255, 255, 255, 0.05) !important;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1) !important;
|
||||
}
|
||||
|
||||
.dark .modern-card:hover {
|
||||
border-color: rgba(255, 255, 255, 0.2) !important;
|
||||
}
|
||||
|
||||
/* Header name animation */
|
||||
.header-name {
|
||||
transition: opacity 0.3s ease-in-out, transform 0.3s ease-in-out;
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
Reference in New Issue
Block a user