more project sectioning
This commit is contained in:
parent
16e9bb1147
commit
2943ace31b
5 changed files with 401 additions and 70 deletions
214
src/components/Carousel.astro
Normal file
214
src/components/Carousel.astro
Normal file
|
|
@ -0,0 +1,214 @@
|
||||||
|
---
|
||||||
|
// ──────────────────────────────────────────────────────────────
|
||||||
|
// Types & Props
|
||||||
|
// ──────────────────────────────────────────────────────────────
|
||||||
|
import type { ImageMetadata } from "astro";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
/** Array of images for the carousel */
|
||||||
|
images: {
|
||||||
|
/** Astro image metadata – we only need the `src` field */
|
||||||
|
src: ImageMetadata;
|
||||||
|
/** Alt text for the image (required for accessibility) */
|
||||||
|
alt: string;
|
||||||
|
}[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const { images } = Astro.props;
|
||||||
|
|
||||||
|
// ──────────────────────────────────────────────────────────────
|
||||||
|
// Unique ID – needed so multiple carousels on the same page don’t
|
||||||
|
// clash when we query the DOM from the <script> block.
|
||||||
|
// ──────────────────────────────────────────────────────────────
|
||||||
|
const carouselId = `carousel-${Math.random().toString(36).slice(2, 11)}`;
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- ──────────────────────────────────────────────────────────────
|
||||||
|
Carousel markup – all styling is done with Tailwind classes.
|
||||||
|
The outer <section> gets the proper ARIA roles/labels.
|
||||||
|
────────────────────────────────────────────────────────────── -->
|
||||||
|
<section
|
||||||
|
id={carouselId}
|
||||||
|
class="relative overflow-hidden rounded-lg"
|
||||||
|
role="region"
|
||||||
|
aria-roledescription="carousel"
|
||||||
|
aria-label="Image carousel"
|
||||||
|
data-carousel-id={carouselId}
|
||||||
|
>
|
||||||
|
<!-- Slides -->
|
||||||
|
<div class="relative h-0 pb-[56.25%]">
|
||||||
|
<!-- 16:9 ratio placeholder -->
|
||||||
|
{
|
||||||
|
images.map((image, i) => (
|
||||||
|
<img
|
||||||
|
src={image.src.src}
|
||||||
|
alt={image.alt}
|
||||||
|
class={`
|
||||||
|
absolute inset-0 w-full h-full object-cover
|
||||||
|
transition-opacity duration-500 ease-in-out
|
||||||
|
${i === 0 ? "opacity-100" : "opacity-0"}
|
||||||
|
carousel-slide
|
||||||
|
`}
|
||||||
|
data-index={i}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Navigation Buttons -->
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="absolute left-4 top-1/2 -translate-y-1/2
|
||||||
|
flex h-10 w-10 items-center justify-center
|
||||||
|
rounded-full bg-white/70 backdrop-blur-sm
|
||||||
|
text-gray-800 hover:bg-white focus:outline-none
|
||||||
|
focus-visible:ring-2 focus-visible:ring-indigo-600
|
||||||
|
transition-colors"
|
||||||
|
aria-label="Previous slide"
|
||||||
|
data-dir="prev"
|
||||||
|
>
|
||||||
|
<!-- Heroicon: chevron‑left -->
|
||||||
|
<svg
|
||||||
|
class="h-5 w-5"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke="currentColor"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M15 19l-7-7 7-7"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
<span class="sr-only">Previous</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="absolute right-4 top-1/2 -translate-y-1/2
|
||||||
|
flex h-10 w-10 items-center justify-center
|
||||||
|
rounded-full bg-white/70 backdrop-blur-sm
|
||||||
|
text-gray-800 hover:bg-white focus:outline-none
|
||||||
|
focus-visible:ring-2 focus-visible:ring-indigo-600
|
||||||
|
transition-colors"
|
||||||
|
aria-label="Next slide"
|
||||||
|
data-dir="next"
|
||||||
|
>
|
||||||
|
<!-- Heroicon: chevron‑right -->
|
||||||
|
<svg
|
||||||
|
class="h-5 w-5"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke="currentColor"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M9 5l7 7-7 7"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
<span class="sr-only">Next</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Live region for screen‑reader announcements -->
|
||||||
|
<div
|
||||||
|
class="sr-only"
|
||||||
|
aria-live="polite"
|
||||||
|
aria-atomic="true"
|
||||||
|
data-live-announcer
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- ──────────────────────────────────────────────────────────────
|
||||||
|
Tailwind utilities are already available in the project.
|
||||||
|
No extra CSS is required – everything lives in the classes above.
|
||||||
|
────────────────────────────────────────────────────────────── -->
|
||||||
|
|
||||||
|
<script define:vars={{ carouselId }}>
|
||||||
|
// -----------------------------------------------------------------
|
||||||
|
// Carousel logic – runs once the component is in the DOM.
|
||||||
|
// -----------------------------------------------------------------
|
||||||
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
const carousel = document.getElementById(carouselId);
|
||||||
|
if (!carousel) return;
|
||||||
|
|
||||||
|
const slides = carousel.querySelectorAll(".carousel-slide");
|
||||||
|
const prevBtn = carousel.querySelector('[data-dir="prev"]');
|
||||||
|
const nextBtn = carousel.querySelector('[data-dir="next"]');
|
||||||
|
const liveAnnouncer = carousel.querySelector("[data-live-announcer]");
|
||||||
|
|
||||||
|
let current = 0;
|
||||||
|
const total = slides.length;
|
||||||
|
let autoRotateTimer;
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------
|
||||||
|
// Helper – show a slide by index (with fade transition)
|
||||||
|
// -----------------------------------------------------------------
|
||||||
|
const showSlide = (index) => {
|
||||||
|
slides[current].classList.remove("opacity-100");
|
||||||
|
slides[current].classList.add("opacity-0");
|
||||||
|
|
||||||
|
current = index;
|
||||||
|
|
||||||
|
slides[current].classList.remove("opacity-0");
|
||||||
|
slides[current].classList.add("opacity-100");
|
||||||
|
|
||||||
|
// Update screen‑reader announcement
|
||||||
|
if (liveAnnouncer) {
|
||||||
|
liveAnnouncerContent = `Slide ${current + 1} of ${total}`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------
|
||||||
|
// Navigation button handlers
|
||||||
|
// -----------------------------------------------------------------
|
||||||
|
const goPrev = () => {
|
||||||
|
const prev = (current - 1 + total) % total;
|
||||||
|
showSlide(prev);
|
||||||
|
};
|
||||||
|
const goNext = () => {
|
||||||
|
const next = (current + 1) % total;
|
||||||
|
showSlide(next);
|
||||||
|
};
|
||||||
|
|
||||||
|
prevBtn?.addEventListener("click", goPrev);
|
||||||
|
nextBtn?.addEventListener("click", goNext);
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------
|
||||||
|
// Keyboard navigation (left / right arrows)
|
||||||
|
// -----------------------------------------------------------------
|
||||||
|
carousel.addEventListener("keydown", (e) => {
|
||||||
|
if (e.key === "ArrowLeft") {
|
||||||
|
e.preventDefault();
|
||||||
|
goPrev();
|
||||||
|
} else if (e.key === "ArrowRight") {
|
||||||
|
e.preventDefault();
|
||||||
|
goNext();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------
|
||||||
|
// Auto‑rotate (pause on hover / focus)
|
||||||
|
// -----------------------------------------------------------------
|
||||||
|
const startAutoRotate = () => {
|
||||||
|
stopAutoRotate(); // safety
|
||||||
|
autoRotateTimer = window.setInterval(goNext, 5000);
|
||||||
|
};
|
||||||
|
const stopAutoRotate = () => {
|
||||||
|
if (autoRotateTimer) {
|
||||||
|
clearInterval(autoRotateTimer);
|
||||||
|
autoRotateTimer = undefined;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Start rotating when the component mounts
|
||||||
|
startAutoRotate();
|
||||||
|
|
||||||
|
// Pause when the user hovers or focuses inside the carousel
|
||||||
|
carousel.addEventListener("mouseenter", stopAutoRotate);
|
||||||
|
carousel.addEventListener("mouseleave", startAutoRotate);
|
||||||
|
carousel.addEventListener("focusin", stopAutoRotate);
|
||||||
|
carousel.addEventListener("focusout", startAutoRotate);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
---
|
---
|
||||||
import { Picture } from "astro:assets";
|
import { Icon } from "astro-icon/components";
|
||||||
import { projects } from "../consts";
|
import { projects } from "../consts";
|
||||||
|
import Carousel from "./Carousel.astro";
|
||||||
|
import ThreeColumnSection from "./ThreeColumnSection.astro";
|
||||||
---
|
---
|
||||||
|
|
||||||
<section class="py-16 bg-mytheme-200/70 dark:bg-mytheme-700/50">
|
<section class="py-16 bg-mytheme-200/70 dark:bg-mytheme-700/50">
|
||||||
|
|
@ -10,31 +12,95 @@ import { projects } from "../consts";
|
||||||
Software I developed
|
Software I developed
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div
|
<div class="space-y-16">
|
||||||
class="max-w-7xl mx-auto
|
{
|
||||||
grid grid-cols-1 lg:grid-cols-[auto,1fr,auto]
|
projects.map((project) => (
|
||||||
items-center md:items-start
|
<ThreeColumnSection maxWidth="8xl" py="py-8">
|
||||||
gap-8
|
{/* ---------- LEFT: Project details ---------- */}
|
||||||
py-8 md:py-12"
|
<div
|
||||||
>
|
slot="left"
|
||||||
<div class="w-72 md:block">project details</div>
|
class="max-w-xs justify-self-center lg:justify-self-end"
|
||||||
|
>
|
||||||
|
<article class="space-y-4">
|
||||||
|
{/* Title + optional company */}
|
||||||
|
<h3 class="text-2xl font-semibold text-slate-900 dark:text-slate-100">
|
||||||
|
{project.title}
|
||||||
|
|
||||||
<div class="max-w-2xl px-4 order-2 md:order-none">
|
{/* Duration */}
|
||||||
{
|
<p class="text-sm text-slate-500 dark:text-slate-400 flex items-center mt-1">
|
||||||
projects.map((project, index) => (
|
<Icon name="mdi:calendar" class="mr-2" />
|
||||||
<div class="">
|
{project.duration}
|
||||||
-- TODO: make it rotate through images, create new component for
|
</p>
|
||||||
this
|
</h3>
|
||||||
<Picture
|
|
||||||
src={project.images[0].src}
|
{/* Description */}
|
||||||
alt={project.images[0].alt}
|
<p class="text-base text-slate-800 dark:text-slate-200">
|
||||||
class="w-full"
|
{project.description}
|
||||||
/>
|
</p>
|
||||||
|
|
||||||
|
{/* Tech stack */}
|
||||||
|
<div class="flex flex-wrap gap-2 mt-2">
|
||||||
|
{project.tech_stack.map((tech) => (
|
||||||
|
<span class="px-2 py-0.5 text-xs font-medium bg-slate-200 dark:bg-slate-600 text-slate-800 dark:text-slate-100 rounded">
|
||||||
|
{tech}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Deliverables (optional) */}
|
||||||
|
{project.deliverables?.length && (
|
||||||
|
<ul class="list-disc list-inside text-sm text-slate-700 dark:text-slate-300 mt-3">
|
||||||
|
{project.deliverables.map((item) => (
|
||||||
|
<li class="mb-1">{item}</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Links */}
|
||||||
|
<div class="flex space-x-4 mt-4">
|
||||||
|
{project.live_url && (
|
||||||
|
<a
|
||||||
|
href={project.live_url}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
class="inline-flex items-center gap-1.5 rounded px-3 py-1.5 text-sm font-medium
|
||||||
|
transition-colors duration-150
|
||||||
|
bg-slate-100 hover:bg-slate-200 focus-visible:outline focus-visible:outline-2
|
||||||
|
focus-visible:outline-offset-2 focus-visible:outline-slate-500
|
||||||
|
dark:bg-slate-800 dark:hover:bg-slate-700 dark:focus-visible:outline-slate-400"
|
||||||
|
>
|
||||||
|
<Icon name="mdi:aspect-ratio" /> Live
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
{project.repo_url && (
|
||||||
|
<a
|
||||||
|
href={project.repo_url}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
class="inline-flex items-center gap-1.5 rounded px-3 py-1.5 text-sm font-medium
|
||||||
|
transition-colors duration-150
|
||||||
|
bg-slate-100 hover:bg-slate-200 focus-visible:outline focus-visible:outline-2
|
||||||
|
focus-visible:outline-offset-2 focus-visible:outline-slate-500
|
||||||
|
dark:bg-slate-800 dark:hover:bg-slate-700 dark:focus-visible:outline-slate-400"
|
||||||
|
>
|
||||||
|
<Icon name="mdi:github" /> Repo
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
</div>
|
</div>
|
||||||
))
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="w-48 md-w-64 md:block hidden"></div>
|
{/* ---------- CENTER: Carousel ---------- */}
|
||||||
|
<div slot="center" class="w-full">
|
||||||
|
<div class="rounded-lg overflow-hidden shadow-lg">
|
||||||
|
<Carousel images={project.images} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* ---------- RIGHT: Empty for now ---------- */}
|
||||||
|
<div slot="right" class="hidden lg:block" />
|
||||||
|
</ThreeColumnSection>
|
||||||
|
))
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
|
||||||
49
src/components/ThreeColumnSection.astro
Normal file
49
src/components/ThreeColumnSection.astro
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
---
|
||||||
|
export interface Props {
|
||||||
|
bgClass?: string;
|
||||||
|
containerClass?: string;
|
||||||
|
maxWidth?: "sm" | "md" | "lg" | "xl" | "2xl" | "4xl" | "6xl" | "8xl";
|
||||||
|
py?: string;
|
||||||
|
reverseOnMobile?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
bgClass = "",
|
||||||
|
containerClass = "",
|
||||||
|
maxWidth = "8xl",
|
||||||
|
py = "py-8 md:py-12",
|
||||||
|
reverseOnMobile = false,
|
||||||
|
} = Astro.props;
|
||||||
|
|
||||||
|
const maxWidthClasses = {
|
||||||
|
sm: "max-w-sm",
|
||||||
|
md: "max-w-md",
|
||||||
|
lg: "max-w-lg",
|
||||||
|
xl: "max-w-xl",
|
||||||
|
"2xl": "max-w-2xl",
|
||||||
|
"4xl": "max-w-4xl",
|
||||||
|
"6xl": "max-w-6xl",
|
||||||
|
"8xl": "max-w-8xl",
|
||||||
|
};
|
||||||
|
---
|
||||||
|
|
||||||
|
<section class={`${bgClass} ${py}`}>
|
||||||
|
<div
|
||||||
|
class={`${maxWidthClasses[maxWidth]} mx-auto grid grid-cols-1 lg:grid-cols-[1fr_640px_1fr] items-center md:items-start gap-8 px-4 ${containerClass}`}
|
||||||
|
>
|
||||||
|
<!-- Left Column (Hidden on mobile, visible on desktop) -->
|
||||||
|
<div class={`${reverseOnMobile ? "order-3 lg:order-1" : "order-1"}`}>
|
||||||
|
<slot name="left" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Center Column (Main content) -->
|
||||||
|
<div class={`order-1 lg:order-2`}>
|
||||||
|
<slot name="center" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Right Column -->
|
||||||
|
<div class={`${reverseOnMobile ? "order-1 lg:order-3" : "order-3"}`}>
|
||||||
|
<slot name="right" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
@ -45,29 +45,30 @@ export const projects: Project[] = [
|
||||||
// complexity: 4,
|
// complexity: 4,
|
||||||
// company: "TV1 GmbH",
|
// company: "TV1 GmbH",
|
||||||
// },
|
// },
|
||||||
// {
|
{
|
||||||
// title: "VIDEO.TAXI Meetings",
|
title: "VIDEO.TAXI Meetings",
|
||||||
// live_url: "https://meetings.video.taxi/",
|
live_url: "https://meetings.video.taxi/",
|
||||||
// description:
|
description:
|
||||||
// "Records, transcribes, and translates meetings (Webex, Teams, Zoom). Interfaces with the GraphQL API of VIDEO.TAXI. Architecure, development, and deployment all done by me. Greenfield project.",
|
"Records, transcribes, and translates meetings (Webex, Teams, Zoom). Interfaces with the GraphQL API of VIDEO.TAXI. Architecure, development, and deployment all done by me. Greenfield project.",
|
||||||
// tech_stack: [
|
tech_stack: [
|
||||||
// "Svelte",
|
"Svelte",
|
||||||
// "TypeScript",
|
"TypeScript",
|
||||||
// "Tailwind CSS",
|
"Tailwind CSS",
|
||||||
// "Express",
|
"Express",
|
||||||
// "GraphQL",
|
"GraphQL",
|
||||||
// "PostgreSQL",
|
"PostgreSQL",
|
||||||
// "Docker",
|
"Docker",
|
||||||
// "OpenAPI",
|
"OpenAPI",
|
||||||
// ],
|
],
|
||||||
// duration: "2024 - Present",
|
duration: "2024 - Present",
|
||||||
// company: "TV1 GmbH",
|
company: "TV1 GmbH",
|
||||||
// deliverables: [
|
deliverables: [
|
||||||
// "Live Updating Dashboard",
|
"Live Updating Dashboard",
|
||||||
// "Meeting Bots for Teams, Zoom and Webex",
|
"Meeting Bots for Teams, Zoom and Webex",
|
||||||
// "Keycloak integration",
|
"Keycloak integration",
|
||||||
// ],
|
],
|
||||||
// },
|
images: [],
|
||||||
|
},
|
||||||
// {
|
// {
|
||||||
// title: "Netbox PDU Plugin",
|
// title: "Netbox PDU Plugin",
|
||||||
// repo_url: "https://github.com/AlexDaichendt/axians-netbox-plugin-pdu",
|
// repo_url: "https://github.com/AlexDaichendt/axians-netbox-plugin-pdu",
|
||||||
|
|
@ -107,7 +108,7 @@ export const projects: Project[] = [
|
||||||
// company: "TV1 GmbH",
|
// company: "TV1 GmbH",
|
||||||
// },
|
// },
|
||||||
{
|
{
|
||||||
title: "Discretize -- Gear Optimizer",
|
title: "Discretize: Gear Optimizer",
|
||||||
live_url: "https://optimizer.discretize.eu/",
|
live_url: "https://optimizer.discretize.eu/",
|
||||||
repo_url: "https://github.com/discretize/discretize-gear-optimizer",
|
repo_url: "https://github.com/discretize/discretize-gear-optimizer",
|
||||||
description:
|
description:
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,10 @@
|
||||||
---
|
---
|
||||||
import BaseLayout from "../layouts/BaseLayout.astro";
|
|
||||||
import me from "../assets/me.jpg";
|
import me from "../assets/me.jpg";
|
||||||
import { Image } from "astro:assets";
|
|
||||||
import Picture from "../components/Picture.astro";
|
|
||||||
import Link from "../components/Link.astro";
|
import Link from "../components/Link.astro";
|
||||||
import { projects } from "../consts";
|
import Picture from "../components/Picture.astro";
|
||||||
import ProjectSection from "../components/ProjectSection.astro";
|
import ProjectSection from "../components/ProjectSection.astro";
|
||||||
|
import ThreeColumnSection from "../components/ThreeColumnSection.astro";
|
||||||
|
import BaseLayout from "../layouts/BaseLayout.astro";
|
||||||
|
|
||||||
const love = [
|
const love = [
|
||||||
{
|
{
|
||||||
|
|
@ -59,16 +58,10 @@ const skills = {
|
||||||
subtitle="Software Engineer, Linux Enthusiast, Lightweight Systems Advocate"
|
subtitle="Software Engineer, Linux Enthusiast, Lightweight Systems Advocate"
|
||||||
className="w-full py-16 flex flex-col"
|
className="w-full py-16 flex flex-col"
|
||||||
>
|
>
|
||||||
<section
|
<ThreeColumnSection reverseOnMobile={true}>
|
||||||
class="max-w-8xl mx-auto
|
<div slot="left"></div>
|
||||||
grid grid-cols-1 lg:grid-cols-[auto,1fr,auto]
|
|
||||||
items-center md:items-start
|
|
||||||
gap-8
|
|
||||||
py-8 md:py-12"
|
|
||||||
>
|
|
||||||
<div class="w-72 hidden md:block"></div>
|
|
||||||
|
|
||||||
<div class="max-w-2xl px-4 order-2 md:order-none">
|
<div slot="center" class="max-w-2xl">
|
||||||
<p class="mb-4">
|
<p class="mb-4">
|
||||||
I am a privacy-first software engineer passionate about building web
|
I am a privacy-first software engineer passionate about building web
|
||||||
applications that are efficient, user-friendly, and respectful of your
|
applications that are efficient, user-friendly, and respectful of your
|
||||||
|
|
@ -86,13 +79,18 @@ const skills = {
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="px-4 order-1 md:order-none">
|
<div slot="right" class="flex justify-center md:justify-start items-start">
|
||||||
<Picture src={me} alt="me" class="w-48 md:w-64 h-auto mx-auto" />
|
<Picture
|
||||||
|
src={me}
|
||||||
|
alt="My profile picture"
|
||||||
|
class="w-48 md:w-64 h-auto rounded-lg"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</ThreeColumnSection>
|
||||||
|
|
||||||
<section class="py-16 bg-mytheme-200/70 dark:bg-mytheme-700/50">
|
<ThreeColumnSection bgClass="bg-mytheme-200/70 dark:bg-mytheme-700/50">
|
||||||
<div class="max-w-2xl mx-auto px-4">
|
<div slot="left"></div>
|
||||||
|
<div slot="center" class="max-w-2xl">
|
||||||
<h2
|
<h2
|
||||||
class="text-3xl md:text-4xl font-bold mb-12 text-center text-slate-800 dark:text-slate-100"
|
class="text-3xl md:text-4xl font-bold mb-12 text-center text-slate-800 dark:text-slate-100"
|
||||||
>
|
>
|
||||||
|
|
@ -111,10 +109,12 @@ const skills = {
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
<div slot="right"></div>
|
||||||
|
</ThreeColumnSection>
|
||||||
|
|
||||||
<section class="py-16">
|
<ThreeColumnSection>
|
||||||
<div class="max-w-2xl mx-auto px-4">
|
<div slot="left"></div>
|
||||||
|
<div slot="center">
|
||||||
<h2
|
<h2
|
||||||
class="text-3xl md:text-4xl font-bold mb-12 text-center text-slate-800 dark:text-slate-100"
|
class="text-3xl md:text-4xl font-bold mb-12 text-center text-slate-800 dark:text-slate-100"
|
||||||
>
|
>
|
||||||
|
|
@ -139,7 +139,8 @@ const skills = {
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
<div slot="right"></div>
|
||||||
|
</ThreeColumnSection>
|
||||||
|
|
||||||
<ProjectSection />
|
<ProjectSection />
|
||||||
</BaseLayout>
|
</BaseLayout>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue