7 Commits

Author SHA1 Message Date
Michael Rausch 1a40e6e718 UI updates 2025-10-12 00:58:56 +13:00
Michael Rausch 237668fd31 UI updates 2025-10-11 21:06:21 +13:00
Michael Rausch facc608d98 Added CV redirect 2025-01-16 01:27:57 +13:00
Michael Rausch c85dc91466 Added CV redirect 2025-01-16 01:26:15 +13:00
Michael Rausch d35c906e50 Added CV 2025-01-16 01:23:09 +13:00
Michael Rausch 589553883a Added work portfolio 2025-01-16 00:57:44 +13:00
Michael Rausch c0c5a71b0a Added work portfolio 2025-01-16 00:42:01 +13:00
30 changed files with 5178 additions and 5168 deletions
+3 -2
View File
@@ -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()]
});
+2521 -3040
View File
File diff suppressed because it is too large Load Diff
+13 -8
View File
@@ -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

+120
View File
@@ -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
View File
@@ -1,5 +1,4 @@
---
---
<div class="mx-auto max-w-screen-sm px-5">
+1 -1
View File
@@ -17,4 +17,4 @@
</script>
<span class="text-4xl" id="emoji">👋</span>
<span class="text-5xl" id="emoji">👋</span>
+15 -16
View File
@@ -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>
+1
View File
@@ -6,6 +6,7 @@ type Props = {
external?: boolean;
underline?: boolean;
confetti?: boolean;
class?: string;
}
const { href, external, underline = true, confetti, ...rest } = Astro.props;
+5 -5
View File
@@ -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>
+6
View File
@@ -0,0 +1,6 @@
---
---
<div class="mx-auto max-w-screen-xl px-5">
<slot />
</div>
+18
View File
@@ -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
View File
@@ -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",
}
];
+1
View File
@@ -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(),
}),
});
+22
View File
@@ -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. Its hosted on Vercel, fly.io and Amazon AWS.
<br/>
![EasySample](/content/es1.png)
<br/>
![EasySample](/content/es2.png)
<br/>
![EasySample](/content/es3.png)
-14
View File
@@ -1,14 +0,0 @@
---
title: "QuickView Cloud"
description: "An AR cloud content management system"
date: "2024"
demoURL: "https://actuality.nz"
---
![UC Online](/content/quickview.png)
## Technologies
- NodeJS
- Firebase
- React
- UIKit
+16
View File
@@ -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).
![QuickView](/content/quickview.png)
+4
View File
@@ -0,0 +1,4 @@
---
title: "CV"
redirect: "https://standardresume.co/r/sQUNyo7W9NsmFFG8ZvU_B"
---
+8
View File
@@ -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"
---
+1
View File
@@ -3,5 +3,6 @@ company: "Actuality"
role: "Software Engineer"
dateStart: "02/11/2022"
dateEnd: "05/05/2023"
linkedinURL: "https://www.linkedin.com/company/actuality"
---
+1
View File
@@ -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
View File
@@ -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}`}>
+1 -1
View File
@@ -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">
+11
View File
@@ -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>
+1 -1
View File
@@ -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
View File
@@ -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;
}
@@ -71,3 +85,123 @@ 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);
}
+2225 -2029
View File
File diff suppressed because it is too large Load Diff