From 0444067c2de0438bf4a491889ce326069e281cc4 Mon Sep 17 00:00:00 2001 From: Steffen Illium Date: Sun, 14 Sep 2025 22:49:23 +0200 Subject: [PATCH] refined design --- .dockerignore | 19 ++ AGENTS.md | 4 +- Dockerfile | 42 +++- README.md | 3 +- app/blog/[slug]/page.tsx | 80 -------- app/blog/page.tsx | 49 ----- app/connect/page.tsx | 24 +-- app/experience/[slug]/page.tsx | 35 +++- app/experience/page.tsx | 37 +--- app/layout.tsx | 2 +- app/not-found.js | 55 ++++++ app/page.tsx | 7 +- app/projects/[slug]/page.tsx | 49 ----- app/projects/page.tsx | 47 ----- app/publications/page.tsx | 6 +- app/research/[slug]/page.tsx | 9 +- app/research/page.tsx | 48 ++--- app/resume.tsx | 9 +- app/sitemap.ts | 61 ++++++ app/status/page.tsx | 33 ++++ app/tags/[tag]/page.tsx | 7 +- app/tags/page.tsx | 2 +- components/card-research.tsx | 80 ++++++++ components/container-footer.tsx | 38 ++++ components/container-header.tsx | 93 ++++----- components/container-references.tsx | 2 +- components/filterable-experience-list.tsx | 132 +++++++++++++ components/filterable-research-list.tsx | 135 +++++++++++++ components/footer.tsx | 9 - components/list-item-research.tsx | 82 ++++++++ components/list-item.tsx | 9 +- components/magicui/shiny-button.tsx | 72 ------- components/mdx.tsx | 43 ++-- components/navbar.tsx | 1 - components/project-card.tsx | 1 - components/publication-card.tsx | 4 +- components/theme-provider.tsx | 3 +- components/types.tsx | 9 + .../blog/2022-01-22-wireguard-ui-nat-vps.mdx | 9 - content/experience/2018-02-01-innomi.mdx | 2 +- .../2018-02-15-computer_achitecture.mdx | 2 +- content/experience/2018-10-15-IOT.mdx | 2 +- content/experience/2018-10-30-Python.mdx | 2 +- content/experience/2019-02-24-dw.mdx | 2 +- .../experience/2019-02-24-water-networks.mdx | 2 +- .../experience/2019-05-01-seminar-TIMS.mdx | 2 +- .../experience/2019-05-01-seminar-VTIMS.mdx | 2 +- content/experience/2019-10-15-OpenMunich.mdx | 2 +- .../2019-10-15-operating_systems.mdx | 2 +- content/experience/2020-04-15-IOS.mdx | 2 +- content/experience/2020-05-01-FIKS.mdx | 2 +- content/experience/2022-10-15-android.mdx | 2 +- .../2023-10-15-server-administration.mdx | 2 +- ...18-11-01-learned-trajectory-annotation.mdx | 1 + .../2019-07-01-neural-self-replication.mdx | 1 + .../2019-07-05-deep-audio-baselines.mdx | 1 + .../2020-03-01-soccer-team-vectors.mdx | 1 + .../2020-05-01-3d-primitive-segmentation.mdx | 1 + .../2020-06-01-peoc-ood-detection.mdx | 1 + .../2020-07-15-av-meantime-coverage.mdx | 1 + .../2020-10-25-surgical-mask-detection.mdx | 1 + .../2021-03-01-anomalous-sound-features.mdx | 1 + .../2021-03-02-sound-anomaly-transfer.mdx | 2 +- .../2021-03-03-acoustic-leak-detection.mdx | 1 + ...04-primate-vocalization-classification.mdx | 1 + .../2021-03-05-audio-vision-transformer.mdx | 2 +- .../2021-03-06-tasked-self-replication.mdx | 1 + .../research/2022-02-25-rnn-memory-limits.mdx | 2 +- .../2022-05-09-rl-anomaly-detection.mdx | 2 +- .../2022-08-01-extended-self-replication.mdx | 1 + .../2022-12-01-organism-network-emergence.mdx | 1 + .../2023-02-24-voronoi-data-augmentation.mdx | 1 + ...-25-autoencoder-trajectory-compression.mdx | 1 + .../2023-05-01-emergent-social-dynamics.mdx | 1 + .../2023-06-25-primate-subsegment-sorting.mdx | 1 + .../2024-01-13-aquarium-marl-environment.mdx | 1 + .../2024-10-27-mas-emergence-safety.mdx | 2 +- content/template.mdx.tmp | 2 +- lib/blog.ts | 12 +- lib/mdx.ts | 36 ++-- lib/posts.ts | 14 +- lib/publications.ts | 8 +- lib/umami.ts | 2 +- lib/utils.ts | 10 +- next.config.mjs | 12 -- next.config.ts | 9 + package.json | 8 +- pnpm-lock.yaml | 185 ++++++++++++++++++ tailwind.config.ts | 6 +- 89 files changed, 1117 insertions(+), 594 deletions(-) create mode 100644 .dockerignore delete mode 100644 app/blog/[slug]/page.tsx delete mode 100644 app/blog/page.tsx create mode 100644 app/not-found.js delete mode 100644 app/projects/[slug]/page.tsx delete mode 100644 app/projects/page.tsx create mode 100644 app/sitemap.ts create mode 100644 app/status/page.tsx create mode 100644 components/card-research.tsx create mode 100644 components/container-footer.tsx create mode 100644 components/filterable-experience-list.tsx create mode 100644 components/filterable-research-list.tsx delete mode 100644 components/footer.tsx create mode 100644 components/list-item-research.tsx delete mode 100644 components/magicui/shiny-button.tsx create mode 100644 components/types.tsx delete mode 100644 content/blog/2022-01-22-wireguard-ui-nat-vps.mdx delete mode 100644 next.config.mjs diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..5911a689 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,19 @@ +# Git +.git +.gitignore + +# Docker +Dockerfile +.dockerignore + +# Dependencies +node_modules + +# Build output +.next + +# Local environment variables +.env.local + +# PNPM specific +# pnpm-lock.yaml \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md index a25a535a..784923cc 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,7 +4,7 @@ This document provides context for AI agents working on this project. ## Project Overview -This is a personal website and portfolio built with Next.js and Tailwind CSS. The site is statically generated, with content sourced from local `.mdx` files. The primary purpose is to showcase research, projects, and blog posts. +This is a personal website and portfolio built with Next.js and Tailwind CSS. The site is statically generated, with content sourced from local `.mdx` files. The primary purpose is to showcase experience, and publications posts. ## Tech Stack @@ -24,7 +24,7 @@ This is a personal website and portfolio built with Next.js and Tailwind CSS. Th ## Project Structure -- `content/`: Root directory for all MDX content, organized into subdirectories by category (e.g., `research`, `projects`, `blog`). +- `content/`: Root directory for all MDX content, organized into subdirectories by category (e.g., `research`, `experience`). - `src/app/`: Next.js App Router structure. Pages are dynamically generated based on the content in the `content/` directory. - `src/lib/mdx.ts`: Core logic for finding, parsing, and serializing MDX files. It reads the file contents, separates frontmatter using `gray-matter`, and prepares it for rendering. - `src/components/`: Contains all reusable React components. diff --git a/Dockerfile b/Dockerfile index 12da4aa9..ee45b45e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,3 +1,39 @@ -FROM nginx:latest -COPY ./_site/. /usr/share/nginx/html/. -COPY ./nginx_default.conf /etc/nginx/conf.d/default.conf \ No newline at end of file +# Stage 1: Builder +FROM node:20-alpine AS builder + +# Enable pnpm +RUN corepack enable + +WORKDIR /app + +# Copy dependency files and install dependencies +COPY package.json pnpm-lock.yaml ./ +RUN pnpm install --frozen-lockfile + +# Copy the rest of the application source code +COPY . . + +# Run the build command +RUN pnpm run build + +# --- + +# Stage 2: Runner +FROM node:20-alpine AS runner +WORKDIR /app + +# Create a non-root user for security +RUN addgroup --system --gid 1001 nodejs +RUN adduser --system --uid 1001 nextjs +USER nextjs + +# Copy the standalone output from the builder stage +COPY --from=builder /app/public ./public +COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ +COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static + +EXPOSE 3000 +ENV PORT 3000 + +# The standalone output creates a server.js file that is the entrypoint +CMD ["node", "server.js"] \ No newline at end of file diff --git a/README.md b/README.md index d25feabe..4e65c769 100644 --- a/README.md +++ b/README.md @@ -9,8 +9,7 @@ Built with next.js, [shadcn/ui](https://ui.shadcn.com/), and [magic ui](https:// # Features - Setup only takes a few minutes by editing the [single config file](./src/data/resume.tsx) -- Built using Next.js 14, React, Typescript, Shadcn/UI, TailwindCSS, Framer Motion, Magic UI -- Includes a blog +- Built using Next.js 15, React, Typescript, Shadcn/UI, TailwindCSS, React/Motion, Magic UI - Responsive for different devices - Optimized for Next.js and Vercel diff --git a/app/blog/[slug]/page.tsx b/app/blog/[slug]/page.tsx deleted file mode 100644 index 7d278272..00000000 --- a/app/blog/[slug]/page.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import { getPostBySlug, getPostSlugs } from '@/lib/mdx'; -import { CitationProvider } from '@/components/context-citation'; -import { ReferencesContainer } from '@/components/container-references'; -import { notFound } from 'next/navigation'; -import Image from 'next/image'; -import { DATA } from '@/app/resume'; -import { getPublicationsData } from '@/lib/publications'; -import { CustomMDX } from '@/components/mdx-custom'; -import Link from 'next/link'; - -export async function generateStaticParams() { - const slugs = getPostSlugs('blog'); - return slugs.map((slug) => ({ slug })); -} - -export async function generateMetadata({ params: { slug } }: { params: { slug: string } }) { - const post = await getPostBySlug('blog', slug); - if (!post) { - return {}; - } - return { - title: post.frontmatter.title, - description: post.frontmatter.description || DATA.description, - }; - } - -export default async function BlogPage({ params: { slug } }: { params: { slug: string } }) { - const post = await getPostBySlug('blog', slug); - const publications = getPublicationsData(); - - if (!post) { - return notFound(); - } - - return ( - -
-
-
-

- {post.frontmatter.title} -

- {post.frontmatter.excerpt && ( -

{post.frontmatter.excerpt}

- )} - {post.frontmatter.icon && ( -
- {`${post.frontmatter.title} -
- )} -
- - - - {post.frontmatter.tags && ( -
- {post.frontmatter.tags.map((tag: string) => ( - - {tag} - - ))} -
- )} - - -
-
-
- ); -} diff --git a/app/blog/page.tsx b/app/blog/page.tsx deleted file mode 100644 index 86f2da22..00000000 --- a/app/blog/page.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import { getSortedPostsData } from "@/lib/posts"; -import { ProjectCard } from "@/components/project-card"; -import { BlurFade } from "@/components/magicui/blur-fade"; - -const BLUR_FADE_DELAY = 0.04; - -export default function BlogPage() { - const posts = getSortedPostsData("blog"); - - return ( -
-
-
-
-

- Blog -

-

- Musings on technology, research, and life. -

-
- -
- {posts - .filter((post) => post.title) - .map((post, id) => ( - - - - ))} -
-
-
-
- ); -} diff --git a/app/connect/page.tsx b/app/connect/page.tsx index 89ba453c..9cf4a568 100644 --- a/app/connect/page.tsx +++ b/app/connect/page.tsx @@ -1,13 +1,11 @@ import { DATA } from "@/app/resume"; import Image from "next/image"; -import Link from "next/link"; import { Button } from "@/components/ui/button"; -import { CenteredImage } from "@/components/centered-image"; import { BlurFade } from "@/components/magicui/blur-fade"; import { TrackedLink } from "@/components/util-tracked-link"; -const BLUR_FADE_DELAY = 0.05; +const BLUR_FADE_DELAY = 0.01; export default function ConnectPage() { const featuredSocials = ["Email", "LinkedIn", "GoogleScholar", "arXiv", "ResearchGate", "Gitea"]; @@ -16,10 +14,9 @@ export default function ConnectPage() { return (
- - -
+
{socialLinks.map(([name, social]) => ( - +
-
-
- -
-
-
+ -
); diff --git a/app/experience/[slug]/page.tsx b/app/experience/[slug]/page.tsx index 164cf5eb..a41a62e2 100644 --- a/app/experience/[slug]/page.tsx +++ b/app/experience/[slug]/page.tsx @@ -1,30 +1,43 @@ -import { getPostBySlug, getPostSlugs } from '@/lib/mdx'; +import { getPostBySlug, getPostSlugs, Post } from '@/lib/mdx'; import { notFound } from 'next/navigation'; import { DATA } from '@/app/resume'; import { getPublicationsData } from '@/lib/publications'; import { Article } from '@/components/page-article'; +type Props = { + params: Promise<{ slug: string }>; + searchParams: Promise<{ [key: string]: string | string[] | undefined }>; +}; + export async function generateStaticParams() { const slugs = getPostSlugs('experience'); return slugs.map((slug) => ({ slug })); } -export async function generateMetadata({ params }: { params: { slug: string } }) { - // FIX: Await params to get slug for Next.js 15 +export async function generateMetadata({ params }: Props) { const { slug } = await params; + if (!slug) { + return {}; + } + const post = await getPostBySlug('experience', slug); - if (!post) { return {}; } + if (!post) { + return {}; + } return { title: post.frontmatter.title, description: post.frontmatter.teaser || DATA.description, }; } -export default async function ExperiencePage({ params }: { params: { slug: string } }) { - // FIX: Await params to get slug for Next.js 15 +export default async function ExperiencePage({ params }: Props) { const { slug } = await params; + if (!slug) { + notFound(); + } + const post = await getPostBySlug('experience', slug); const publications = getPublicationsData(); @@ -37,11 +50,13 @@ export default async function ExperiencePage({ params }: { params: { slug: strin const currentIndex = allSlugs.findIndex((s) => s === slug); const prevSlug = currentIndex > 0 ? allSlugs[currentIndex - 1] : null; const nextSlug = currentIndex < allSlugs.length - 1 ? allSlugs[currentIndex + 1] : null; - const prevPost = prevSlug ? await getPostBySlug('experience', prevSlug) : null; - const nextPost = nextSlug ? await getPostBySlug('experience', nextSlug) : null; + + const prevPost: Post | null = prevSlug ? await getPostBySlug('experience', prevSlug) : null; + const nextPost: Post | null = nextSlug ? await getPostBySlug('experience', nextSlug) : null; + const navigation = { - prev: prevPost ? { slug: prevSlug, title: prevPost.frontmatter.title } : null, - next: nextPost ? { slug: nextSlug, title: nextPost.frontmatter.title } : null, + prev: prevPost ? { slug: prevSlug!, title: prevPost.frontmatter.title } : null, + next: nextPost ? { slug: nextSlug!, title: nextPost.frontmatter.title } : null, }; return
; diff --git a/app/experience/page.tsx b/app/experience/page.tsx index 2232b6f5..f775e95c 100644 --- a/app/experience/page.tsx +++ b/app/experience/page.tsx @@ -1,18 +1,13 @@ // src/pages/experience.tsx -import { getSortedPostsData } from "@/lib/posts"; -import { ProjectCard } from "@/components/project-card"; -import { ExperienceCard } from "@/components/list-item"; // Import the new component -import { BlurFade } from "@/components/magicui/blur-fade"; - -const BLUR_FADE_DELAY = 0.04; +import { getAllTags, getSortedPostsData } from "@/lib/posts"; +import { FilterableExperienceGrid } from "@/components/filterable-experience-list"; export default function ExperiencePage() { const posts = getSortedPostsData("experience"); - // Filter out posts that might not be suitable for a list item if needed, - // or ensure your getSortedPostsData provides necessary fields for both. - const experiencePosts = posts.filter((post) => post.title); + const allPosts = posts.filter((post) => post.title); + const allTags = getAllTags(5, "experience").filter((tag) => (tag.name === "project" || tag.name === "teaching")); return (
@@ -23,30 +18,12 @@ export default function ExperiencePage() { Experience

- My professional experience encompasses both hands-on systems engineering and academic instruction. I've worked at the intersection of machine learning and complex systems, with projects ranging from exploring emergent behavior in AI to managing cluster infrastructure. In my role at LMU Munich, I further developed this experience by mentoring students, contributing to lectures, and leading practical seminars.
+ My professional experience encompasses both hands-on systems engineering and academic instruction. I've worked at the intersection of machine learning and complex systems, with projects ranging from exploring emergent behavior in AI to managing cluster infrastructure. In my role at LMU Munich, I further developed this experience by mentoring students, contributing to lectures, and leading practical seminars.


-
- {experiencePosts.map((post, id) => ( - - - - ))} -
+ +
diff --git a/app/layout.tsx b/app/layout.tsx index a81b812b..33065d0a 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -6,7 +6,7 @@ import type { Metadata } from "next"; import { Inter as FontSans } from "next/font/google"; import "./globals.css"; import { Providers } from "@/components/providers"; -import { Footer } from "@/components/footer"; +import { Footer } from "@/components/container-footer"; const fontSans = FontSans({ subsets: ["latin"], diff --git a/app/not-found.js b/app/not-found.js new file mode 100644 index 00000000..4c53d43f --- /dev/null +++ b/app/not-found.js @@ -0,0 +1,55 @@ +import Image from "next/image"; +import Link from "next/link"; + +import { Button } from "@/components/ui/button"; +import { BlurFade } from "@/components/magicui/blur-fade"; + +const BLUR_FADE_DELAY = 0.01; + +export default function NotFoundPage() { + return ( +
+
+ + + A lost robot in a desert, indicating a 404 page not found error. + + + +

+ Lost in the Digital Sands +

+
+ + +

+ It seems the page you are looking for does not exist, has been moved, + or is currently unavailable. +

+
+ + +
+ + + + + + + + + +
+
+
+
+ ); +} \ No newline at end of file diff --git a/app/page.tsx b/app/page.tsx index 97c3e675..db612ff4 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -5,13 +5,11 @@ import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import { Badge } from "@/components/ui/badge"; import { DATA } from "@/app/resume"; import Markdown from "react-markdown"; -//import {ContactCard} from "@/components/contact-card"; -import Link from "next/link"; import { TextAnimate } from "@/components/magicui/text-animate"; import { BlurFade } from "@/components/magicui/blur-fade"; import { TrackedLink } from "@/components/util-tracked-link"; -const BLUR_FADE_DELAY = 0.04; +const BLUR_FADE_DELAY = 0.01; export default function Page() { const posts = getSortedPostsData().slice(0, 6); @@ -68,8 +66,7 @@ export default function Page() { Check out my latest work

- I've worked on a variety of projects, from scientific research to managing projects. Here are a few of my - latest. + I've worked on a variety of projects, from scientific research to managing projects. Here are a few of my latest.

diff --git a/app/projects/[slug]/page.tsx b/app/projects/[slug]/page.tsx deleted file mode 100644 index 6836327d..00000000 --- a/app/projects/[slug]/page.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import { getPostBySlug, getPostSlugs } from '@/lib/mdx'; -import { notFound } from 'next/navigation'; - -import { getPublicationsData } from '@/lib/publications'; -import { Article } from '@/components/page-article'; -import { DATA } from '@/app/resume'; - -export async function generateStaticParams() { - const slugs = getPostSlugs('projects'); - return slugs.map((slug) => ({ slug })); -} - -export async function generateMetadata({ params }: { params: { slug: string } }) { - // FIX: Await params to get slug for Next.js 15 - const { slug } = await params; - - const post = await getPostBySlug('projects', slug); - if (!post) { return {}; } - return { - title: post.frontmatter.title, - description: post.frontmatter.teaser || DATA.description, - }; -} - -export default async function ProjectPage({ params }: { params: { slug: string } }) { - // FIX: Await params to get slug for Next.js 15 - const { slug } = await params; - - const post = await getPostBySlug('projects', slug); - const publications = getPublicationsData(); - - if (!post) { - notFound(); - } - - // --- Navigation Logic --- - const allSlugs = getPostSlugs('projects'); - const currentIndex = allSlugs.findIndex((s) => s === slug); - const prevSlug = currentIndex > 0 ? allSlugs[currentIndex - 1] : null; - const nextSlug = currentIndex < allSlugs.length - 1 ? allSlugs[currentIndex + 1] : null; - const prevPost = prevSlug ? await getPostBySlug('projects', prevSlug) : null; - const nextPost = nextSlug ? await getPostBySlug('projects', nextSlug) : null; - const navigation = { - prev: prevPost ? { slug: prevSlug, title: prevPost.frontmatter.title } : null, - next: nextPost ? { slug: nextSlug, title: nextPost.frontmatter.title } : null, - }; - - return
; -} \ No newline at end of file diff --git a/app/projects/page.tsx b/app/projects/page.tsx deleted file mode 100644 index 10fd179b..00000000 --- a/app/projects/page.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { getSortedPostsData } from "@/lib/posts"; -import { BlurFade } from "@/components/magicui/blur-fade"; -import { ExperienceCard } from "@/components/list-item"; - -const BLUR_FADE_DELAY = 0.04; - -export default function ProjectsPage() { - const posts = getSortedPostsData("projects"); - - return ( -
-
-
-
-

- Projects -

-

- My work sits at the intersection of machine learning and systems engineering. I have experience in everything from exploring emergent behavior in AI agents to managing robust, automated infrastructure with Kubernetes. Here are some highlights. -

-
-
-
{/* Use flex-col for list items */} - {posts.map((post, id) => ( - - - - ))} -
-
-
-
- ); -} \ No newline at end of file diff --git a/app/publications/page.tsx b/app/publications/page.tsx index 95bfb105..45389182 100644 --- a/app/publications/page.tsx +++ b/app/publications/page.tsx @@ -1,7 +1,6 @@ import { getPublicationsData } from "@/lib/publications"; import { PublicationCard } from "@/components/publication-card"; import { DATA } from "@/app/resume"; -import Link from "next/link"; import { Badge } from "@/components/ui/badge"; import fs from "fs"; import path from "path"; @@ -42,9 +41,10 @@ export default function PublicationsPage() {
{Object.entries(DATA.contact.social) - .filter(([_, social]) => social.pub) + //ts- + .filter(([, social]) => social.pub) .map(([name, social]) => ( - + {name} diff --git a/app/research/[slug]/page.tsx b/app/research/[slug]/page.tsx index abac59a8..cac96391 100644 --- a/app/research/[slug]/page.tsx +++ b/app/research/[slug]/page.tsx @@ -5,13 +5,14 @@ import { notFound } from 'next/navigation'; import { getPublicationsData } from '@/lib/publications'; import { Article } from '@/components/page-article'; import { DATA } from '@/app/resume'; +import { Props } from '@/components/types'; export async function generateStaticParams() { const slugs = getPostSlugs('research'); return slugs.map((slug) => ({ slug })); } -export async function generateMetadata({ params }: { params: { slug: string } }) { +export async function generateMetadata({ params }: Props ) { const { slug } = await params; const post = await getPostBySlug('research', slug); @@ -22,7 +23,7 @@ export async function generateMetadata({ params }: { params: { slug: string } }) }; } -export default async function ResearchPage({ params }: { params: { slug: string } }) { +export default async function ResearchPage({ params }: Props ) { const { slug } = await params; const post = await getPostBySlug('research', slug); @@ -42,8 +43,8 @@ export default async function ResearchPage({ params }: { params: { slug: string const nextPost = nextSlug ? await getPostBySlug('research', nextSlug) : null; const navigation = { - prev: prevPost ? { slug: prevSlug, title: prevPost.frontmatter.title } : null, - next: nextPost ? { slug: nextSlug, title: nextPost.frontmatter.title } : null, + prev: prevPost ? { slug: prevSlug!, title: prevPost.frontmatter.title } : null, + next: nextPost ? { slug: nextSlug!, title: nextPost.frontmatter.title } : null, }; return
; diff --git a/app/research/page.tsx b/app/research/page.tsx index 0188e747..9b87e13f 100644 --- a/app/research/page.tsx +++ b/app/research/page.tsx @@ -1,51 +1,33 @@ -import { getSortedPostsData } from "@/lib/posts"; -import { ProjectCard } from "@/components/project-card"; -import { BlurFade } from "@/components/magicui/blur-fade"; +// NO "use client" here. This is a Server Component. -const BLUR_FADE_DELAY = 0.04; +import { getSortedPostsData, getAllTags } from "@/lib/posts"; +import { FilterableResearchGrid } from "@/components/filterable-research-list"; // Import the new client component export default function ResearchPage() { - const posts = getSortedPostsData("research"); + // These functions run safely on the server because this is a Server Component. + const allPosts = getSortedPostsData("research"); + const allTags = getAllTags(5, "research"); return (
-
+

Research

-

+

This section details my scientific publications, primarily focused - on advancing machine learning and deep neural networks. - My involvement has spanned, from conceptualizing the ideas - and developing machine learning models, to providing support - to my colleagues. + on advancing machine learning and deep neural networks. My + involvement has spanned, from conceptualizing the ideas and + developing machine learning models, to providing support to my + colleagues.


-
- {posts - .filter((post) => post.title) - .map((post, id) => ( - - - - ))} -
+ + +
diff --git a/app/resume.tsx b/app/resume.tsx index 3e14c686..6a9d4d35 100644 --- a/app/resume.tsx +++ b/app/resume.tsx @@ -1,15 +1,8 @@ import { Icons } from "@/components/icons"; -import { url } from "inspector"; import { HomeIcon, - NotebookIcon, FlaskConicalIcon, PaperclipIcon, - BookUserIcon, - BriefcaseIcon, - GraduationCapIcon, - LayersIcon, - GridIcon, ClipboardListIcon, } from "lucide-react"; @@ -73,7 +66,7 @@ export const DATA = { ], contact: { - email: "contact@steffenillium.de", + email: "steffen.illium@ifi.lmu.de", tel: "", social: { diff --git a/app/sitemap.ts b/app/sitemap.ts new file mode 100644 index 00000000..7b35d92f --- /dev/null +++ b/app/sitemap.ts @@ -0,0 +1,61 @@ +// app/sitemap.ts +import { getAllTags, getSortedPostsData } from '@/lib/posts'; +import { MetadataRoute } from 'next'; + +export default function sitemap(): MetadataRoute.Sitemap { + const baseUrl = 'https://steffenillium.de'; + + // Improvement 1: Fetch all posts with a single, more efficient call + const allPosts = getSortedPostsData(); + const postUrls: MetadataRoute.Sitemap = allPosts.map((post) => ({ + url: `${baseUrl}/${post.href}`, + lastModified: new Date(post.date), + changeFrequency: 'yearly', + priority: 0.7, + })); + + + const allTags = getAllTags(2); + const tagUrls: MetadataRoute.Sitemap = allTags.map((tag) => ({ + url: `${baseUrl}/tags/${tag.name}`, + lastModified: new Date(), + changeFrequency: 'monthly', + priority: 0.5, + })); + + // The types for changeFrequency and lastModified will now be correctly inferred + const staticRoutes: MetadataRoute.Sitemap = [ + { + url: baseUrl, + lastModified: new Date(), + priority: 1, + changeFrequency: 'monthly', + }, + { + url: `${baseUrl}/experience`, + lastModified: new Date(), + priority: 0.8, + changeFrequency: 'monthly', + }, + { + url: `${baseUrl}/research`, + lastModified: new Date(), + priority: 0.9, + changeFrequency: 'yearly', + }, + { + url: `${baseUrl}/publications`, + lastModified: new Date(), + priority: 0.9, + changeFrequency: 'yearly', + }, + { + url: `${baseUrl}/tags`, + lastModified: new Date(), + priority: 0.7, + changeFrequency: 'monthly', + }, + ]; + + return [...staticRoutes, ...postUrls, ...tagUrls]; +} \ No newline at end of file diff --git a/app/status/page.tsx b/app/status/page.tsx new file mode 100644 index 00000000..33a949bf --- /dev/null +++ b/app/status/page.tsx @@ -0,0 +1,33 @@ +import { BlurFade } from "@/components/magicui/blur-fade"; + +const BLUR_FADE_DELAY = 0.01; + +export default function StatusPage() { + + return ( +
+
+
+
+

+ Status +

+ +
+
+ +
+ + + +
+
+
+
+ ); +} \ No newline at end of file diff --git a/app/tags/[tag]/page.tsx b/app/tags/[tag]/page.tsx index bab19fe5..0ccb2a0e 100644 --- a/app/tags/[tag]/page.tsx +++ b/app/tags/[tag]/page.tsx @@ -3,16 +3,17 @@ import { ProjectCard } from "@/components/project-card"; import { BlurFade } from "@/components/magicui/blur-fade"; import { notFound } from "next/navigation"; import { Breadcrumbs } from "@/components/element-breadcrumbs"; +import { TagProps } from "@/components/types"; -const BLUR_FADE_DELAY = 0.04; +const BLUR_FADE_DELAY = 0.01; export async function generateStaticParams() { const tags = getAllTags(); return tags.map((tag) => ({ tag: tag.name })); } -export async function generateMetadata({ params }: { params: { tag: string } }) { +export async function generateMetadata({ params }: TagProps ) { const { tag } = await params; const tagData = getTagData(tag); @@ -22,7 +23,7 @@ export async function generateMetadata({ params }: { params: { tag: string } }) }; } -export default async function TagPage({ params }: { params: { tag: string } }) { +export default async function TagPage({ params }: TagProps) { const { tag } = await params; const posts = getPostsByTag(tag); const tagData = getTagData(tag); diff --git a/app/tags/page.tsx b/app/tags/page.tsx index 7f8c3697..34a5b06a 100644 --- a/app/tags/page.tsx +++ b/app/tags/page.tsx @@ -2,7 +2,7 @@ import { getAllTags } from "@/lib/posts"; import Link from "next/link"; import { BlurFade } from "@/components/magicui/blur-fade"; -const BLUR_FADE_DELAY = 0.04; +const BLUR_FADE_DELAY = 0.01; export default function TagsPage() { const tags = getAllTags(2); diff --git a/components/card-research.tsx b/components/card-research.tsx new file mode 100644 index 00000000..528fb450 --- /dev/null +++ b/components/card-research.tsx @@ -0,0 +1,80 @@ +"use client"; + +import { + Card, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { cn } from "@/lib/utils"; +import { ChevronRightIcon } from "lucide-react"; +import Image from "next/image"; +import Link from "next/link"; +import Markdown from "react-markdown"; + +// Interface is identical to ProjectCard but adds the 'venue' +interface Props { + title: string; + href: string; // Made href non-optional as research should always link somewhere + description: string; + dates: string; + venue?: string; // The new field for publication venue + tags: readonly string[]; + image?: string; + video?: string; + className?: string; +} + +export function ResearchCard({ + title, + href, + description, + dates, + venue, + image, + video, + className, +}: Props) { + return ( + + + {video && ( + + + ); +} \ No newline at end of file diff --git a/components/container-footer.tsx b/components/container-footer.tsx new file mode 100644 index 00000000..cea82f9b --- /dev/null +++ b/components/container-footer.tsx @@ -0,0 +1,38 @@ +// ./components/footer.tsx +"use client"; + +import Link from "next/link"; +import Image from "next/image"; +import { usePathname } from "next/navigation"; + +const StatusBadge = () => ( + System Status +); + +export function Footer() { + const pathname = usePathname(); + + if (pathname !== "/connect") return; + + return ( + +
+ + + +
+ ); +} \ No newline at end of file diff --git a/components/container-header.tsx b/components/container-header.tsx index 09fca3c6..ec63c3fd 100644 --- a/components/container-header.tsx +++ b/components/container-header.tsx @@ -6,54 +6,55 @@ import Link from "next/link"; import { usePathname } from "next/navigation"; export function Header() { - const [isVisible, setIsVisible] = useState(false); - const pathname = usePathname(); - if (pathname === "/connect") return null; - const isMainPage = pathname === "/"; + const [isVisible, setIsVisible] = useState(false); + const pathname = usePathname(); + const isMainPage = pathname === "/"; - useEffect(() => { - const handleScroll = () => { - if (window.scrollY > 100) { - setIsVisible(true); - } else { - setIsVisible(false); - } - }; + useEffect(() => { + if (pathname === "/connect") return; - if (isMainPage) { - window.addEventListener("scroll", handleScroll); - handleScroll(); - } else { - setIsVisible(true); - } + const handleScroll = () => { + if (window.scrollY > 100) { + setIsVisible(true); + } else { + setIsVisible(false); + } + }; - return () => { - if (isMainPage) { - window.removeEventListener("scroll", handleScroll); - } - }; - }, [isMainPage]); + if (isMainPage) { + window.addEventListener("scroll", handleScroll); + handleScroll(); + } else { + setIsVisible(true); + } - if (!isVisible && isMainPage) { - return null; - } + return () => { + if (isMainPage) { + window.removeEventListener("scroll", handleScroll); + } + }; + }, [isMainPage, pathname]); - return ( -
-
-
- - portfolio - - User's headshot -
-
-
- ); -} + if (pathname === "/connect" || (!isVisible && isMainPage)) { + return null; + } + + return ( +
+
+
+ + portfolio + + User's headshot +
+
+
+ ); +} \ No newline at end of file diff --git a/components/container-references.tsx b/components/container-references.tsx index 96768f08..b5309258 100644 --- a/components/container-references.tsx +++ b/components/container-references.tsx @@ -40,7 +40,7 @@ export function ReferencesContainer() { pdfUrl={pub.pdfUrl} bibtex={pub.bibtex} pdfAvailable={pub.pdfAvailable} - className="cards" + className="cards cursor-pointer" /> ); })} diff --git a/components/filterable-experience-list.tsx b/components/filterable-experience-list.tsx new file mode 100644 index 00000000..1f49416b --- /dev/null +++ b/components/filterable-experience-list.tsx @@ -0,0 +1,132 @@ +"use client"; + +import { useState } from "react"; + +import { BlurFade } from "@/components/magicui/blur-fade"; +import { Badge } from "@/components/ui/badge"; +import { ExperienceCard } from "./list-item"; + + +const BLUR_FADE_DELAY = 0.01; + +// Define the shape of your post data for type safety +type Post = { + id: string; + href: string; + title?: string; + excerpt?: string; + date: string; + venue?: string; + tags: readonly string[]; + image?: string; + video?: string; +}; + +type Tag = { + name: string; + count: number; +}; + +interface Props { + posts: Post[]; + tags: Tag[]; +} + +// IDs of your most important papers to feature. +const FEATURED_Experience_IDS = [ + "FIKS", + "water-networks" +]; + +export function FilterableExperienceGrid({ posts, tags }: Props) { + const [activeTag, setActiveTag] = useState(null); + + const featuredPosts = posts.filter((post) => + FEATURED_Experience_IDS.includes(post.id) + ); + const regularPosts = posts.filter( + (post) => !FEATURED_Experience_IDS.includes(post.id) + ); + + const displayedPosts = activeTag + ? regularPosts.filter((post) => post.tags.includes(activeTag)) + : regularPosts; + + const handleTagClick = (tag: string) => { + setActiveTag(activeTag === tag ? null : tag); + }; + + return ( + <> + {/* SECTION 1: Featured Experience */} +
+

Featured Projects

+
+ {featuredPosts.map((post, id) => ( + + + + ))} +
+
+ +
+ + {/* SECTION 2: All Experience with Filtering */} +
+

Other

+
+

Filter by Topic:

+ {tags.map((tag) => ( + handleTagClick(tag.name)} + className="cursor-pointer transition-transform hover:scale-105" + > + {tag.name} ({tag.count}) + + ))} + {activeTag && ( + setActiveTag(null)} + className="cursor-pointer" + > + Clear Filter × + + )} +
+ +
+ {displayedPosts.map((post, id) => ( + + + + ))} +
+ {displayedPosts.length === 0 && activeTag && ( +

+ No publications found with the tag "{activeTag}". +

+ )} +
+ + ); +} \ No newline at end of file diff --git a/components/filterable-research-list.tsx b/components/filterable-research-list.tsx new file mode 100644 index 00000000..0da58945 --- /dev/null +++ b/components/filterable-research-list.tsx @@ -0,0 +1,135 @@ +"use client"; + +import { useState } from "react"; +import { ResearchCard } from "@/components/card-research"; +import { BlurFade } from "@/components/magicui/blur-fade"; +import { Badge } from "@/components/ui/badge"; +import { ResearchListItem } from "./list-item-research"; + +const BLUR_FADE_DELAY = 0.01; + +// Define the shape of your post data for type safety +type Post = { + id: string; + href: string; + title?: string; + excerpt?: string; + date: string; + venue?: string; + tags: readonly string[]; + image?: string; + video?: string; +}; + +type Tag = { + name: string; + count: number; +}; + +interface Props { + posts: Post[]; + tags: Tag[]; +} + +// IDs of your most important papers to feature. +const FEATURED_RESEARCH_IDS = [ + "mas-emergence-safety", + "audio-vision-transformer", + "voronoi-data-augmentation", + "rnn-memory-limits" +]; + +export function FilterableResearchGrid({ posts, tags }: Props) { + const [activeTag, setActiveTag] = useState(null); + + const featuredPosts = posts.filter((post) => + FEATURED_RESEARCH_IDS.includes(post.id) + ); + const regularPosts = posts.filter( + (post) => !FEATURED_RESEARCH_IDS.includes(post.id) + ); + + const displayedPosts = activeTag + ? regularPosts.filter((post) => post.tags.includes(activeTag)) + : regularPosts; + + const handleTagClick = (tag: string) => { + setActiveTag(activeTag === tag ? null : tag); + }; + + return ( + <> + {/* SECTION 1: Featured Research */} +
+

Featured Publications

+
+ {featuredPosts.map((post, id) => ( + + + + ))} +
+
+ +
+ + {/* SECTION 2: All Research with Filtering */} +
+

Other

+
+

Filter by Topic:

+ {tags.map((tag) => ( + handleTagClick(tag.name)} + className="cursor-pointer transition-transform hover:scale-105" + > + {tag.name} ({tag.count}) + + ))} + {activeTag && ( + setActiveTag(null)} + className="cursor-pointer" + > + Clear Filter × + + )} +
+ +
+ {displayedPosts.map((post, id) => ( + + + + ))} +
+ {displayedPosts.length === 0 && activeTag && ( +

+ No publications found with the tag "{activeTag}". +

+ )} +
+ + ); +} \ No newline at end of file diff --git a/components/footer.tsx b/components/footer.tsx deleted file mode 100644 index c45c6e0d..00000000 --- a/components/footer.tsx +++ /dev/null @@ -1,9 +0,0 @@ -"use client"; - -export function Footer() { - return ( -
- -
- ); -} diff --git a/components/list-item-research.tsx b/components/list-item-research.tsx new file mode 100644 index 00000000..79812524 --- /dev/null +++ b/components/list-item-research.tsx @@ -0,0 +1,82 @@ +"use client"; + +import { Card } from "@/components/ui/card"; +import { cn } from "@/lib/utils"; +import Image from "next/image"; +import Link from "next/link"; +import Markdown from "react-markdown"; + +// Using the same interface as ResearchCard for data consistency +interface Props { + title: string; + href: string; + description: string; + dates: string; + venue?: string; + tags: readonly string[]; + image?: string; + video?: string; + className?: string; +} + +export function ResearchListItem({ + title, + href, + description, + dates, + venue, + image, + video, // Video is less common in this layout but supported + className, +}: Props) { + return ( + + + {/* Image Container (Fixed Width) */} + {(image || video) && ( +
+ {video ? ( +
+ )} + + {/* Text Content Container (Flexible Width) */} +
+

{title}

+
+ {venue && {venue}} + {venue && } + +
+ +
+ {description} +
+ {/* + +
+ {tags.map((tag) => ( + + {tag} + + ))} +
+
*/} +
+
+ + ); +} \ No newline at end of file diff --git a/components/list-item.tsx b/components/list-item.tsx index 4b2d1a55..c109215b 100644 --- a/components/list-item.tsx +++ b/components/list-item.tsx @@ -2,7 +2,6 @@ import { Badge } from "@/components/ui/badge"; import { cn } from "@/lib/utils"; -import Image from "next/image"; import Link from "next/link"; import Markdown from "react-markdown"; import React from "react"; @@ -32,17 +31,17 @@ export function ExperienceCard({ href, description, dates, - tags, - link, image, - video, links, className, }: Props) { return ( {/* 3. The Card component now has its conflicting shadow removed. */} diff --git a/components/magicui/shiny-button.tsx b/components/magicui/shiny-button.tsx deleted file mode 100644 index ea2dacde..00000000 --- a/components/magicui/shiny-button.tsx +++ /dev/null @@ -1,72 +0,0 @@ -"use client"; - -import { cn } from "@/lib/utils"; -import { motion, MotionProps, type AnimationProps } from "motion/react"; -import React from "react"; - -const animationProps = { - initial: { "--x": "100%", scale: 0.8 }, - animate: { "--x": "-100%", scale: 1 }, - whileTap: { scale: 0.95 }, - transition: { - repeat: Infinity, - repeatType: "loop", - repeatDelay: 1, - type: "spring", - stiffness: 20, - damping: 15, - mass: 2, - scale: { - type: "spring", - stiffness: 200, - damping: 5, - mass: 0.5, - }, - }, -} as AnimationProps; - -interface ShinyButtonProps - extends Omit, keyof MotionProps>, - MotionProps { - children: React.ReactNode; - className?: string; -} - -export const ShinyButton = React.forwardRef< - HTMLButtonElement, - ShinyButtonProps ->(({ children, className, ...props }, ref) => { - return ( - - - {children} - - - - ); -}); - -ShinyButton.displayName = "ShinyButton"; diff --git a/components/mdx.tsx b/components/mdx.tsx index 0afaae1d..792c4c56 100644 --- a/components/mdx.tsx +++ b/components/mdx.tsx @@ -1,8 +1,8 @@ "use client"; -import Image from "next/image"; +import Image, { ImageProps } from "next/image"; import Link from "next/link"; -import React, { Children, isValidElement } from "react"; +import React, { AnchorHTMLAttributes, Children, isValidElement } from "react"; import { InfoBox } from "./infobox"; import { Cite } from "./cite"; import { FloatingImage } from "./floating-image"; @@ -10,26 +10,39 @@ import { CenteredImage } from "./centered-image"; import { FaGithub, FaPython, FaBook, FaFileAlt } from 'react-icons/fa'; -function CustomLink(props: any) { - let href = props.href; +type CustomLinkProps = AnchorHTMLAttributes & { + href: string; // We ensure href is a required string +}; + +function CustomLink({ href, children, ...rest }: CustomLinkProps) { if (href.startsWith("/")) { return ( - - {props.children} + + {children} ); } if (href.startsWith("#")) { - return ; + // The same pattern applies to regular tags. + return {children}; } - return ; + // We explicitly add href and children, and spread the rest. + return ( + + {children} + + ); } -function RoundedImage(props: any) { - return {props.alt}; +function RoundedImage(props: ImageProps) { + {props.alt} } // Helper to extract plain text content from React children for slugification. @@ -39,10 +52,6 @@ function getPlainTextFromChildren(nodes: React.ReactNode): string { if (typeof node === 'string') { return node; } - // Recursively extract text from valid React elements - if (isValidElement(node) && node.props && node.props.children) { - return getPlainTextFromChildren(node.props.children); - } return ''; // Ignore other types of nodes (null, undefined, numbers etc.) }) .join(''); @@ -61,10 +70,10 @@ function slugify(str: string) { } function createHeading(level: number) { - const Heading = ({ children, ...props }: { children?: React.ReactNode, [key: string]: any }) => { + const Heading = ({ children, ...props }: { children?: React.ReactNode, [key: string]: unknown }) => { // Extract plain text from children for reliable slug generation const plainText = getPlainTextFromChildren(children); - let slug = slugify(plainText); + const slug = slugify(plainText); // Process children to handle HTML entities for text nodes and ensure unique keys const processedChildren = Children.map(children, (child, index) => { @@ -88,7 +97,7 @@ function createHeading(level: number) { href: `#${slug}`, key: `link-${slug}`, className: "anchor", - }), ...processedChildren + }), ...(processedChildren || []) ] ); }; diff --git a/components/navbar.tsx b/components/navbar.tsx index f785c3fc..91fbc811 100644 --- a/components/navbar.tsx +++ b/components/navbar.tsx @@ -8,7 +8,6 @@ import { Tooltip, TooltipContent, TooltipTrigger, - TooltipProvider, } from "@/components/ui/tooltip"; import { DATA } from "@/app/resume"; import { cn } from "@/lib/utils"; diff --git a/components/project-card.tsx b/components/project-card.tsx index 526ca9a7..99e06acb 100644 --- a/components/project-card.tsx +++ b/components/project-card.tsx @@ -36,7 +36,6 @@ export function ProjectCard({ href, description, dates, - tags, link, image, video, diff --git a/components/publication-card.tsx b/components/publication-card.tsx index 973ae104..c3b24042 100644 --- a/components/publication-card.tsx +++ b/components/publication-card.tsx @@ -136,8 +136,8 @@ export function PublicationCard({ return (
{children}; diff --git a/components/types.tsx b/components/types.tsx new file mode 100644 index 00000000..e17591bd --- /dev/null +++ b/components/types.tsx @@ -0,0 +1,9 @@ + +export type Props = { + params: Promise<{ slug: string }>; + searchParams: Promise<{ [key: string]: string | string[] | undefined }>; +}; + +export type TagProps = { + params: Promise<{ tag: string }>; +}; diff --git a/content/blog/2022-01-22-wireguard-ui-nat-vps.mdx b/content/blog/2022-01-22-wireguard-ui-nat-vps.mdx deleted file mode 100644 index 8601ef7b..00000000 --- a/content/blog/2022-01-22-wireguard-ui-nat-vps.mdx +++ /dev/null @@ -1,9 +0,0 @@ ---- -layout: single -title: "Welcome to Jekyll!" -categories: blog -excerpt: "A unique line of text to describe this post that will display in an archive listing and meta description with SEO benefits." - ---- - -W. I. P. \ No newline at end of file diff --git a/content/experience/2018-02-01-innomi.mdx b/content/experience/2018-02-01-innomi.mdx index d0ed0fef..109a7519 100644 --- a/content/experience/2018-02-01-innomi.mdx +++ b/content/experience/2018-02-01-innomi.mdx @@ -1,6 +1,6 @@ --- title: "InnoMi Project" -tags: [mobile-internet, technology-transfer, bavaria, industry] +tags: [project, mobile-internet, technology-transfer, bavaria, industry] excerpt: "Early-stage mobile/distributed tech transfer between academia and industry." teaser: /images/projects/innomi.png icon: /images/projects/innomi.png diff --git a/content/experience/2018-02-15-computer_achitecture.mdx b/content/experience/2018-02-15-computer_achitecture.mdx index 667b0f27..7d489797 100644 --- a/content/experience/2018-02-15-computer_achitecture.mdx +++ b/content/experience/2018-02-15-computer_achitecture.mdx @@ -1,6 +1,6 @@ --- title: "Computer Architecture TA" -tags: [computer architecture, coordination] +tags: [teaching, computer architecture, coordination] excerpt: "Served as a Teaching Assistant and Tutorial Coordinator for the LMU Computer Architecture course, managing tutors and curriculum for over 600 students." teaser: "/images/teaching/computer_gear.png" icon: "/images/teaching/computer_gear.png" diff --git a/content/experience/2018-10-15-IOT.mdx b/content/experience/2018-10-15-IOT.mdx index 08a06e1b..848127ab 100644 --- a/content/experience/2018-10-15-IOT.mdx +++ b/content/experience/2018-10-15-IOT.mdx @@ -1,6 +1,6 @@ --- title: "IoT Practical Exercise" -tags: [iot, mqtt, python, influxdb, distributed-systems, practical-course] +tags: [teaching, iot, mqtt, python, influxdb, distributed-systems, practical-course] excerpt: "Designed and taught an IoT practical exercise using MQTT and Python for approximately 200 students." teaser: "/images/teaching/server.png" icon: "/images/teaching/server.png" diff --git a/content/experience/2018-10-30-Python.mdx b/content/experience/2018-10-30-Python.mdx index 92f9311f..b9febd3f 100644 --- a/content/experience/2018-10-30-Python.mdx +++ b/content/experience/2018-10-30-Python.mdx @@ -1,6 +1,6 @@ --- title: "Python 101 Course" -tags: [python, programming, introductory-course, curriculum-development] +tags: [teaching, python, programming, introductory-course, curriculum-development] excerpt: "Co-developed/taught intensive introductory Python course for 200 students." teaser: /images/teaching/py.png icon: /images/teaching/py.png diff --git a/content/experience/2019-02-24-dw.mdx b/content/experience/2019-02-24-dw.mdx index 216d3ab8..0fec4ce1 100644 --- a/content/experience/2019-02-24-dw.mdx +++ b/content/experience/2019-02-24-dw.mdx @@ -1,6 +1,6 @@ --- title: "DW Editorial Lead" -tags: [editorial, content management, digital strategy, magazine, workflow optimization, website relaunch] +tags: [project, editorial, content management, digital strategy, magazine, workflow optimization, website relaunch] excerpt: "Led online editorial team for DIGITALE WELT Magazin (2018-2023)." teaser: "/images/projects/dw.png" icon: "/images/projects/dw.png" diff --git a/content/experience/2019-02-24-water-networks.mdx b/content/experience/2019-02-24-water-networks.mdx index d98843db..8b9d8457 100644 --- a/content/experience/2019-02-24-water-networks.mdx +++ b/content/experience/2019-02-24-water-networks.mdx @@ -1,7 +1,7 @@ --- title: "ErLoWa Leak Detection" excerpt: "Deep learning detects acoustic water leaks with SWM." -tags: [acoustic, anomaly-detection, deep-learning, real-world-data, signal-processing, water-management, sensors] +tags: [project, acoustic, anomaly-detection, deep-learning, real-world-data, signal-processing, water-management, sensors] teaser: "/images/projects/pipe_leak.png" icon: "/images/projects/pipe_leak.png" --- diff --git a/content/experience/2019-05-01-seminar-TIMS.mdx b/content/experience/2019-05-01-seminar-TIMS.mdx index 116fba35..78528f60 100644 --- a/content/experience/2019-05-01-seminar-TIMS.mdx +++ b/content/experience/2019-05-01-seminar-TIMS.mdx @@ -1,6 +1,6 @@ --- title: "TIMS Seminar Supervision" -tags: [supervision, mentoring, academic writing] +tags: [teaching, supervision, mentoring, academic writing] excerpt: "Supervised student research, writing, and presentation skills in Mobile/Distributed Systems, ML, and Quantum Computing." teaser: "/images/teaching/thesis.png" icon: "/images/teaching/thesis.png" diff --git a/content/experience/2019-05-01-seminar-VTIMS.mdx b/content/experience/2019-05-01-seminar-VTIMS.mdx index 6f58631f..f9879a70 100644 --- a/content/experience/2019-05-01-seminar-VTIMS.mdx +++ b/content/experience/2019-05-01-seminar-VTIMS.mdx @@ -1,6 +1,6 @@ --- title: "VTIMS Advanced Seminar" -tags: [machine learning, distributed systems, quantum computing, research mentoring, scientific writing, presentation coaching, critical analysis, academic assessment] +tags: [teaching, machine learning, distributed systems, quantum computing, research mentoring, scientific writing, presentation coaching, critical analysis, academic assessment] excerpt: "Supervised Master's advanced research/analysis in Mobile/Distributed Systems, ML, QC." teaser: "/images/teaching/thesis_master.png" icon: "/images/teaching/thesis_master.png" diff --git a/content/experience/2019-10-15-OpenMunich.mdx b/content/experience/2019-10-15-OpenMunich.mdx index 6a1f4de1..9a0325ce 100644 --- a/content/experience/2019-10-15-OpenMunich.mdx +++ b/content/experience/2019-10-15-OpenMunich.mdx @@ -1,6 +1,6 @@ --- title: "OpenMunich Conference Organization" -tags: [community-engagement, event-management, conference, open-source, industry, technology] +tags: [project, community-engagement, event-management, conference, open-source, industry, technology] excerpt: "Led the OpenMunich conference series (2018-19), connecting academia, industry, and students on open-source topics." teaser: "/images/projects/openmunich.png" icon: "/images/projects/openmunich.png" diff --git a/content/experience/2019-10-15-operating_systems.mdx b/content/experience/2019-10-15-operating_systems.mdx index 4d6b91a5..98e7418c 100644 --- a/content/experience/2019-10-15-operating_systems.mdx +++ b/content/experience/2019-10-15-operating_systems.mdx @@ -1,6 +1,6 @@ --- title: "Operating Systems TA" -tags: [system programming, java, lmu munich] +tags: [teaching, system programming, java, lmu munich] excerpt: "TA & Coordinator for the Operating Systems lecture, focusing on system programming concepts and concurrent programming in Java for over 350 students." teaser: /images/teaching/computer_os.png icon: /images/teaching/computer_os.png diff --git a/content/experience/2020-04-15-IOS.mdx b/content/experience/2020-04-15-IOS.mdx index cf5231e9..20b73bae 100644 --- a/content/experience/2020-04-15-IOS.mdx +++ b/content/experience/2020-04-15-IOS.mdx @@ -1,6 +1,6 @@ --- title: "iOS App Development" -tags: [mobile-development, app-development, agile, teamwork] +tags: [teaching, mobile-development, app-development, agile, teamwork] excerpt: "Supervised iOS Praktikum: student teams built Swift apps using agile." teaser: /images/teaching/ios.png icon: /images/teaching/ios.png diff --git a/content/experience/2020-05-01-FIKS.mdx b/content/experience/2020-05-01-FIKS.mdx index 5c3cd4d9..df52b1dc 100644 --- a/content/experience/2020-05-01-FIKS.mdx +++ b/content/experience/2020-05-01-FIKS.mdx @@ -1,6 +1,6 @@ --- title: "AI-Fusion Safety" -tags: [MARL, reinforcement-learning, AI-safety, emergence, simulation, python, unity, software-engineering, industry-collaboration] +tags: [teaching, MARL, reinforcement-learning, AI-safety, emergence, simulation, python, unity, software-engineering, industry-collaboration] excerpt: "Studied MARL emergence and safety, built simulations with Fraunhofer." teaser: /images/projects/robot.png icon: /images/projects/robot.png diff --git a/content/experience/2022-10-15-android.mdx b/content/experience/2022-10-15-android.mdx index 29b4af38..c31ea3eb 100644 --- a/content/experience/2022-10-15-android.mdx +++ b/content/experience/2022-10-15-android.mdx @@ -1,6 +1,6 @@ --- title: "MSP Android Course" -tags: [java, kotlin, mobile-development, app-development, agile, teamwork] +tags: [teaching, java, kotlin, mobile-development, app-development, agile, teamwork] excerpt: "Supervised MSP: teams built Android apps (Java/Kotlin) using agile." teaser: "/images/teaching/android.png" icon: "/images/teaching/android.png" diff --git a/content/experience/2023-10-15-server-administration.mdx b/content/experience/2023-10-15-server-administration.mdx index d92fb0f1..b43795a6 100644 --- a/content/experience/2023-10-15-server-administration.mdx +++ b/content/experience/2023-10-15-server-administration.mdx @@ -1,6 +1,6 @@ --- title: "Chair DevOps Admin" -tags: [devops, kubernetes, server-administration, infrastructure] +tags: [project, devops, kubernetes, server-administration, infrastructure] excerpt: "Managed LMU chair IT: Kubernetes, CI/CD, and infrastructure automation from 2018 to 2023." teaser: "/images/projects/arch.png" icon: "/images/projects/arch.png" diff --git a/content/research/2018-11-01-learned-trajectory-annotation.mdx b/content/research/2018-11-01-learned-trajectory-annotation.mdx index edfe77e7..8d587273 100644 --- a/content/research/2018-11-01-learned-trajectory-annotation.mdx +++ b/content/research/2018-11-01-learned-trajectory-annotation.mdx @@ -2,6 +2,7 @@ title: "Learned Trajectory Annotation" tags: [geoinformatics, machine-learning, unsupervised-learning, human-robot-interaction, autoencoder, clustering, trajectory, perception, spatial-context, representation-learning] excerpt: "Unsupervised autoencoder learns spatial context from trajectory data for annotation." +venue: "26th ACM SIGSPATIAL" teaser: "/figures/0_trajectory_reconstruction_teaser.png" --- diff --git a/content/research/2019-07-01-neural-self-replication.mdx b/content/research/2019-07-01-neural-self-replication.mdx index 428054f9..0ab0d84c 100644 --- a/content/research/2019-07-01-neural-self-replication.mdx +++ b/content/research/2019-07-01-neural-self-replication.mdx @@ -3,6 +3,7 @@ title: "Neural Self-Replication" tags: [neural-networks, artificial-life, complex-systems, self-organization, machine-learning, evolution, replication] excerpt: "Neural networks replicating weights, inspired by biology and artificial life." teaser: "/figures/1_self_replication_pca_space.jpg" +venue: "ALIFE 2019" --- Drawing inspiration from the fundamental process of self-replication in biological systems, this research explores the potential for implementing analogous mechanisms within neural networks. The objective is to develop computational models capable of autonomously reproducing their own structure (specifically, their connection weights), potentially leading to the emergence of complex, adaptive behaviors . diff --git a/content/research/2019-07-05-deep-audio-baselines.mdx b/content/research/2019-07-05-deep-audio-baselines.mdx index f24f9a54..ff4fd561 100644 --- a/content/research/2019-07-05-deep-audio-baselines.mdx +++ b/content/research/2019-07-05-deep-audio-baselines.mdx @@ -11,6 +11,7 @@ tags: [ end-to-end-learning, cnn ] +venue: "Interspeech 2019" excerpt: "Deep learning audio baseline for Interspeech 2019 ComParE challenge." teaser: "/figures/3_deep_neural_baselines_teaser.jpg" --- diff --git a/content/research/2020-03-01-soccer-team-vectors.mdx b/content/research/2020-03-01-soccer-team-vectors.mdx index 016482e7..be38de4d 100644 --- a/content/research/2020-03-01-soccer-team-vectors.mdx +++ b/content/research/2020-03-01-soccer-team-vectors.mdx @@ -2,6 +2,7 @@ title: "Soccer Team Vectors" tags: [machine-learning, representation-learning, sports-analytics, similarity-search, soccer, embeddings, team-performance, prediction] excerpt: "STEVE learns soccer team embeddings from match data for analysis." +venue: "ECML PKDD 2019" teaser: "/figures/2_steve_algo.jpg" --- diff --git a/content/research/2020-05-01-3d-primitive-segmentation.mdx b/content/research/2020-05-01-3d-primitive-segmentation.mdx index e2f79bfb..ac0f0a76 100644 --- a/content/research/2020-05-01-3d-primitive-segmentation.mdx +++ b/content/research/2020-05-01-3d-primitive-segmentation.mdx @@ -3,6 +3,7 @@ title: "3D Primitive Segmentation" tags: [computer-vision, 3d-processing, point-clouds, segmentation, deep-learning, genetic-algorithms] excerpt: "Hybrid method segments/fits primitives in large 3D point clouds." teaser: "/figures/4_point_cloud_segmentation_teaser.jpg" +venue: "VISIGRAPP 2020" --- diff --git a/content/research/2020-06-01-peoc-ood-detection.mdx b/content/research/2020-06-01-peoc-ood-detection.mdx index 05803893..d0d5c42f 100644 --- a/content/research/2020-06-01-peoc-ood-detection.mdx +++ b/content/research/2020-06-01-peoc-ood-detection.mdx @@ -3,6 +3,7 @@ title: "PEOC OOD Detection" tags: [deep-reinforcement-learning, out-of-distribution-detection, safety, anomaly-detection, policy-entropy, machine-learning-safety] excerpt: "PEOC uses policy entropy for OOD detection in deep RL." teaser: "/figures/6_ood_pipeline.jpg" +venue: "ICANN 2020" --- Ensuring the safety and reliability of deep reinforcement learning (RL) agents deployed in real-world environments necessitates the ability to detect when the agent encounters states significantly different from those seen during training (i.e., out-of-distribution or OOD states). This research introduces **PEOC (Policy Entropy-based OOD Classifier)**, a novel and computationally efficient method designed for this purpose. diff --git a/content/research/2020-07-15-av-meantime-coverage.mdx b/content/research/2020-07-15-av-meantime-coverage.mdx index ae157d52..76a19755 100644 --- a/content/research/2020-07-15-av-meantime-coverage.mdx +++ b/content/research/2020-07-15-av-meantime-coverage.mdx @@ -3,6 +3,7 @@ title: "AV Meantime Coverage" tags: [autonomous-vehicles, shared-mobility, transportation-systems, urban-computing, geoinformatics, mobility-as-a-service, resource-optimization] excerpt: "Analyzing service coverage of parked AVs during downtime ('meantime')." teaser: "/figures/5_meantime_coverage.jpg" +venue: "AGILE-GISS 2020" --- diff --git a/content/research/2020-10-25-surgical-mask-detection.mdx b/content/research/2020-10-25-surgical-mask-detection.mdx index 94e64554..baf2adb4 100644 --- a/content/research/2020-10-25-surgical-mask-detection.mdx +++ b/content/research/2020-10-25-surgical-mask-detection.mdx @@ -3,6 +3,7 @@ title: "Surgical-Mask Detection" tags: [audio-classification, deep-learning, data-augmentation, computer-vision, paralinguistics, speech-processing, audio-analysis, signal-processing, spectrograms] excerpt: "CNN mask detection in speech using augmented spectrograms." teaser: "/figures/7_mask_models.jpg" +venue: "Interspeech 2020" --- diff --git a/content/research/2021-03-01-anomalous-sound-features.mdx b/content/research/2021-03-01-anomalous-sound-features.mdx index 676cc9ca..c9d23450 100644 --- a/content/research/2021-03-01-anomalous-sound-features.mdx +++ b/content/research/2021-03-01-anomalous-sound-features.mdx @@ -3,6 +3,7 @@ title: "Anomalous Sound Features" tags: [anomaly-detection, audio-classification, deep-learning, transfer-learning, feature-extraction, machine-learning, predictive-maintenance, dcase, sound-analysis, unsupervised-learning] excerpt: "Pretrained networks extract features for anomalous industrial sound detection." teaser: "/figures/8_anomalous_sound_teaser.jpg" +venue: "IJCNN 2021" --- Detecting anomalous sounds, particularly in industrial settings, is crucial for predictive maintenance and safety. This often involves unsupervised or semi-supervised approaches where models learn a representation of 'normal' sounds. This research explores the effectiveness of leveraging **transfer learning** for this task by using **pretrained deep neural networks** as fixed feature extractors. diff --git a/content/research/2021-03-02-sound-anomaly-transfer.mdx b/content/research/2021-03-02-sound-anomaly-transfer.mdx index f085e868..9057f899 100644 --- a/content/research/2021-03-02-sound-anomaly-transfer.mdx +++ b/content/research/2021-03-02-sound-anomaly-transfer.mdx @@ -3,7 +3,7 @@ title: "Sound Anomaly Transfer" tags: [anomaly-detection, audio-classification, deep-learning, transfer-learning, feature-extraction, computer-vision, industrial-monitoring, machine-learning] excerpt: "Image nets detect acoustic anomalies in machinery via spectrograms." teaser: "/figures/9_image_transfer_sound_teaser.jpg" -icon: "/figures/9_image_transfer_sound_workflow.jpg" +venue: "ICAART 2021" --- diff --git a/content/research/2021-03-03-acoustic-leak-detection.mdx b/content/research/2021-03-03-acoustic-leak-detection.mdx index f435c644..7027127c 100644 --- a/content/research/2021-03-03-acoustic-leak-detection.mdx +++ b/content/research/2021-03-03-acoustic-leak-detection.mdx @@ -3,6 +3,7 @@ title: "Acoustic Leak Detection" tags: [anomaly-detection, audio-processing, deep-learning, signal-processing, real-world-application, water-networks, infrastructure-monitoring] excerpt: "Anomaly detection models for acoustic leak detection in water networks." teaser: "/figures/10_water_networks_teaser.jpg" +venue: "ICAART 2021" --- Detecting leaks in vast municipal water distribution networks is critical for resource conservation and infrastructure maintenance. This study introduces and evaluates an **anomaly detection approach for acoustic leak identification**, specifically designed with **energy efficiency** and **ease of deployment** as key considerations. diff --git a/content/research/2021-03-04-primate-vocalization-classification.mdx b/content/research/2021-03-04-primate-vocalization-classification.mdx index 3c28212f..1f40a40e 100644 --- a/content/research/2021-03-04-primate-vocalization-classification.mdx +++ b/content/research/2021-03-04-primate-vocalization-classification.mdx @@ -3,6 +3,7 @@ title: "Primate Vocalization Classification" tags: [deep-learning, audio-classification, bioacoustics, conservation-technology, recurrent-neural-networks, machine-learning, wildlife-monitoring, pytorch, animal-conservation, bayesian-optimization] excerpt: "Deep BiLSTM classifies primate vocalizations for acoustic wildlife monitoring." teaser: /figures/11_recurrent_primate_workflow.jpg +venue: "Interspeech 2021" --- # Primate Vocalization Classification diff --git a/content/research/2021-03-05-audio-vision-transformer.mdx b/content/research/2021-03-05-audio-vision-transformer.mdx index e9808b33..5107e9ea 100644 --- a/content/research/2021-03-05-audio-vision-transformer.mdx +++ b/content/research/2021-03-05-audio-vision-transformer.mdx @@ -3,7 +3,7 @@ title: "Audio Vision Transformer" tags: [deep-learning, audio-classification, computer-vision, attention-mechanisms, transformers, mel-spectrograms, ComParE-2021] excerpt: "Vision Transformer on spectrograms for audio classification, with data augmentation." teaser: /figures/12_vision_transformer_teaser.jpg - +venue: "Interspeech 2021" --- This research explores the application of the **Vision Transformer (ViT)** architecture, originally designed for image processing, to the domain of audio classification by operating on **mel-spectrogram representations**. diff --git a/content/research/2021-03-06-tasked-self-replication.mdx b/content/research/2021-03-06-tasked-self-replication.mdx index 21d7eb3c..01d303a3 100644 --- a/content/research/2021-03-06-tasked-self-replication.mdx +++ b/content/research/2021-03-06-tasked-self-replication.mdx @@ -3,6 +3,7 @@ title: "Tasked Self-Replication" tags: [artificial-life, complex-systems, neural-networks, self-organization, multi-task-learning, self-replication, artificial-chemistry, evolution, computational-systems, guided-evolution, artificial-intelligence] excerpt: "Self-replicating networks perform tasks, exploring stabilization in artificial chemistry." teaser: "/figures/13_sr_teaser.jpg" +venue: "ALIFE 2021" --- diff --git a/content/research/2022-02-25-rnn-memory-limits.mdx b/content/research/2022-02-25-rnn-memory-limits.mdx index ca427732..4cbf1e0a 100644 --- a/content/research/2022-02-25-rnn-memory-limits.mdx +++ b/content/research/2022-02-25-rnn-memory-limits.mdx @@ -3,7 +3,7 @@ title: "RNN Memory Limits" tags: [deep-learning, recurrent-neural-networks, sequence-modeling, theoretical-ml, machine-learning, memory-systems] excerpt: "Investigated memory limits of RNNs in recalling uncorrelated sequences." teaser: "/figures/22_rnn_limits.png" - +venue: "ICAART 2022" --- Recurrent Neural Networks (RNNs), including variants like Long Short-Term Memory (LSTM) and Gated Recurrent Units (GRU), are designed with the intent to capture temporal dependencies within sequential data. Their internal mechanisms allow information from previous time steps to influence current processing. diff --git a/content/research/2022-05-09-rl-anomaly-detection.mdx b/content/research/2022-05-09-rl-anomaly-detection.mdx index 4d1c2432..f8c140e1 100644 --- a/content/research/2022-05-09-rl-anomaly-detection.mdx +++ b/content/research/2022-05-09-rl-anomaly-detection.mdx @@ -3,7 +3,7 @@ title: "RL Anomaly Detection" tags: [reinforcement-learning, anomaly-detection, safety, lifelong-learning, generalization] excerpt: "Perspective on anomaly detection challenges and future in reinforcement learning." teaser: "/figures/14_ad_rl_teaser.jpg" - +venue: "AAMAS 22" --- Anomaly Detection (AD) is crucial for the safe deployment of Reinforcement Learning (RL) agents, especially in safety-critical applications where encountering unexpected or out-of-distribution situations can lead to catastrophic failures. This work provides a perspective on the state and future directions of AD research specifically tailored for the complexities inherent in RL. diff --git a/content/research/2022-08-01-extended-self-replication.mdx b/content/research/2022-08-01-extended-self-replication.mdx index 20b9b9c3..5d7f9a16 100644 --- a/content/research/2022-08-01-extended-self-replication.mdx +++ b/content/research/2022-08-01-extended-self-replication.mdx @@ -3,6 +3,7 @@ title: "Extended Self-Replication" tags: [artificial-life, complex-systems, neural-networks, self-organization, dynamical-systems, self-replication, emergent-behavior, robustness, replication-fidelity] excerpt: "Journal extension: self-replication, noise robustness, emergence, dynamical system analysis." teaser: "/figures/15_sr_journal_teaser.jpg" +venue: "Artificial Life 28 (2022)" --- diff --git a/content/research/2022-12-01-organism-network-emergence.mdx b/content/research/2022-12-01-organism-network-emergence.mdx index 6b569f3a..f1c91ab2 100644 --- a/content/research/2022-12-01-organism-network-emergence.mdx +++ b/content/research/2022-12-01-organism-network-emergence.mdx @@ -3,6 +3,7 @@ title: "Organism Network Emergence" tags: [artificial-life, complex-systems, neural-networks, self-organization, emergent-computation, artificial-intelligence, collective-intelligence, evolutionary-computation] excerpt: "Self-replicating networks collaborate forming higher-level Organism Networks with emergent functionalities." teaser: "/figures/16_on_teaser.jpg" +venue: "SSCI 2022" --- This research investigates the transition from simple self-replication to higher levels of organization by exploring how populations of basic, self-replicating neural network units can form **"Organism Networks" (ONs)** through **collaboration and emergent differentiation**. Moving beyond the replication of individual networks, the focus shifts to the collective dynamics and functional capabilities that arise when these units interact within a shared environment (akin to an "artificial chemistry"). diff --git a/content/research/2023-02-24-voronoi-data-augmentation.mdx b/content/research/2023-02-24-voronoi-data-augmentation.mdx index e4745f7b..061ee3fb 100644 --- a/content/research/2023-02-24-voronoi-data-augmentation.mdx +++ b/content/research/2023-02-24-voronoi-data-augmentation.mdx @@ -3,6 +3,7 @@ title: "Voronoi Data Augmentation" tags: [data-augmentation, computer-vision, deep-learning, convolutional-neural-networks, voronoi, machine-learning, image-processing] excerpt: "VoronoiPatches improves CNN robustness via non-linear recombination augmentation." teaser: "/figures/17_vp_teaser.jpg" +venue: "ICAART 2023" --- Data augmentation is essential for improving the performance and generalization of Convolutional Neural Networks (CNNs), especially when training data is limited. This research introduces **VoronoiPatches (VP)**, a novel data augmentation algorithm based on the principle of **non-linear recombination** of image information. diff --git a/content/research/2023-02-25-autoencoder-trajectory-compression.mdx b/content/research/2023-02-25-autoencoder-trajectory-compression.mdx index 03c1df63..5c8a034f 100644 --- a/content/research/2023-02-25-autoencoder-trajectory-compression.mdx +++ b/content/research/2023-02-25-autoencoder-trajectory-compression.mdx @@ -3,6 +3,7 @@ title: "Autoencoder Trajectory Compression" tags: [deep-learning, recurrent-neural-networks, trajectory-analysis, data-compression, geoinformatics, autoencoders, LSTM, GPS] excerpt: "LSTM autoencoder better DP for trajectory compression (Fréchet/DTW)." teaser: /figures/23_trajectory_model.png +venue: "ICAART 2023" --- The proliferation of location-aware mobile devices generates vast amounts of GPS trajectory data, necessitating efficient storage solutions. While various compression techniques aim to reduce data volume, preserving essential spatio-temporal information remains crucial. diff --git a/content/research/2023-05-01-emergent-social-dynamics.mdx b/content/research/2023-05-01-emergent-social-dynamics.mdx index 4f0db613..0212e2be 100644 --- a/content/research/2023-05-01-emergent-social-dynamics.mdx +++ b/content/research/2023-05-01-emergent-social-dynamics.mdx @@ -3,6 +3,7 @@ title: "Emergent Social Dynamics" tags: [artificial-life, complex-systems, neural-networks, self-organization, emergent-behavior, predictive-coding, artificial-chemistry, social-interaction] excerpt: "Artificial chemistry networks develop predictive models via surprise minimization." teaser: "/figures/18_surprised_soup_teaser.jpg" +venue: "ALIFE 2023" --- This research extends the study of **artificial chemistry** systems populated by neural network "particles" , focusing on the emergence of complex behaviors driven by **social interaction** rather than explicit programming. Building on systems where particles may exhibit self-replication, we introduce interactions based on principles of **predictive processing and surprise minimization** (akin to the Free Energy Principle). diff --git a/content/research/2023-06-25-primate-subsegment-sorting.mdx b/content/research/2023-06-25-primate-subsegment-sorting.mdx index 6d3454c7..20ba0cb3 100644 --- a/content/research/2023-06-25-primate-subsegment-sorting.mdx +++ b/content/research/2023-06-25-primate-subsegment-sorting.mdx @@ -3,6 +3,7 @@ title: "Primate Subsegment Sorting" tags: [bioacoustics, audio-classification, deep-learning, data-labeling, signal-processing, primate-vocalizations, wildlife-monitoring, machine-learning, spectrograms, cnn] excerpt: "Binary subsegment presorting improves noisy primate sound classification." teaser: /figures/19_binary_primates_teaser.jpg +venue: "ICAART 2023" --- { - let slug = path.basename(file, path.extname(file)); - let { metadata, source } = await getPost(slug); + const slug = path.basename(file, path.extname(file)); + const { metadata, source } = await getPost(slug); console.log(file); console.log(slug); console.log(metadata); @@ -79,8 +79,8 @@ async function getAllPosts(dir: string, category?: string) { return posts; } -export async function getBlogPosts(category?: string) { - let postPath = path.join(process.cwd(), "content"); +export async function getPosts(category?: string) { + const postPath = path.join(process.cwd(), "content"); console.log(postPath); return getAllPosts(postPath, category); } \ No newline at end of file diff --git a/lib/mdx.ts b/lib/mdx.ts index cf4da707..7d691ded 100644 --- a/lib/mdx.ts +++ b/lib/mdx.ts @@ -1,11 +1,9 @@ import fs from 'fs'; import path from 'path'; -import matter from 'gray-matter'; import { bundleMDX } from 'mdx-bundler'; import remarkGfm from 'remark-gfm'; import rehypeSlug from 'rehype-slug'; - export interface Frontmatter { title: string; date: string; @@ -17,21 +15,28 @@ export interface Frontmatter { video?: string; } +export interface Post { + slug: string; + frontmatter: Frontmatter; + code: string; +} + const contentDirectory = path.join(process.cwd(), 'content'); const datePrefixRegex = /^\d{4}-\d{2}-\d{2}-/; -export function getPostSlugs(type: 'projects' | 'research' | 'teaching' | 'experience' ) { +export function getPostSlugs(type: 'projects' | 'research' | 'teaching' | 'experience') { const typeDirectory = path.join(contentDirectory, type); if (!fs.existsSync(typeDirectory)) return []; - return fs.readdirSync(typeDirectory).map(file => + return fs.readdirSync(typeDirectory).map(file => file.replace(datePrefixRegex, '').replace(/\.mdx$/, '') ); } -export async function getPostBySlug(type: 'projects' | 'research' | 'teaching' | 'experience', slug: string) { +export async function getPostBySlug(type: 'projects' | 'research' | 'teaching' | 'experience', slug: string): Promise { const typeDirectory = path.join(contentDirectory, type); + const allFiles = fs.readdirSync(typeDirectory); - const fileName = allFiles.find(file => + const fileName = allFiles.find(file => file.replace(datePrefixRegex, '').replace(/\.mdx$/, '') === slug ); @@ -42,10 +47,10 @@ export async function getPostBySlug(type: 'projects' | 'research' | 'teaching' | const fullPath = path.join(typeDirectory, fileName); const fileContents = fs.readFileSync(fullPath, 'utf8'); - + const dateMatch = fileName.match(/^\d{4}-\d{2}-\d{2}/); const dateFromFilename = dateMatch ? dateMatch[0] : new Date().toISOString(); - + const { code, frontmatter } = await bundleMDX({ source: fileContents, mdxOptions(options) { @@ -61,18 +66,3 @@ export async function getPostBySlug(type: 'projects' | 'research' | 'teaching' | code, }; } - -export function getSortedPostsData(type: 'projects' | 'research' | 'teaching' | 'experience') { - const postsDirectory = path.join(contentDirectory, type); - if (!fs.existsSync(postsDirectory)) return []; - const fileNames = fs.readdirSync(postsDirectory); - const allPostsData = fileNames.map((fileName) => { - const slug = fileName.replace(datePrefixRegex, "").replace(/\.mdx$/, ""); - const fullPath = path.join(postsDirectory, fileName); - const fileContents = fs.readFileSync(fullPath, "utf8"); - const { data } = matter(fileContents); - return { slug, href: `/${type}/${slug}`, ...data, }; - }); - // @ts-ignore - return allPostsData.sort((a, b) => new Date(b.date) - new Date(a.date)); -} \ No newline at end of file diff --git a/lib/posts.ts b/lib/posts.ts index a54ab42d..923c9bf3 100644 --- a/lib/posts.ts +++ b/lib/posts.ts @@ -1,4 +1,3 @@ - import fs from "fs"; import path from "path"; import matter from "gray-matter"; @@ -9,13 +8,14 @@ const tagsFilePath = path.join(postsDirectory, "tags.json"); type PostData = { id: string; href: string; + venue: string; date: string; tags: string[]; - image: string | null; + image: string | undefined; title?: string; excerpt?: string; video?: string; - [key: string]: any; + [key: string]: string | string[] | null | undefined; }; function getPostFilePaths(dir: string, fileList: string[] = []) { @@ -55,12 +55,14 @@ export function getSortedPostsData(category?: string): PostData[] { ? matterResult.data.tags : matterResult.data.tags.split(/, ?/) : []; + const venue = matterResult.data.venue ? matterResult.data.venue: "arxive"; - let image = matterResult.data.teaser || matterResult.data.image || null; + const image = matterResult.data.teaser || matterResult.data.image || null; return { id, href, + venue, date: matterResult.data.date ?? dateFromFileName, ...matterResult.data, tags, @@ -77,8 +79,8 @@ export function getSortedPostsData(category?: string): PostData[] { }); } -export function getAllTags(minCount: number = 1) { - const allPosts = getSortedPostsData(); +export function getAllTags(minCount: number = 1, category?: "experience" | "research") { + const allPosts = getSortedPostsData(category); const tags: { [tag: string]: number } = {}; allPosts.forEach((post) => { diff --git a/lib/publications.ts b/lib/publications.ts index a015200e..db2b6819 100644 --- a/lib/publications.ts +++ b/lib/publications.ts @@ -2,10 +2,8 @@ import { parse } from "@retorquere/bibtex-parser"; import fs from "fs"; import path from "path"; -// Your existing path is correct. const bibliographyPath = path.join(process.cwd(), "content", "_bibliography.bib"); -// --- FIX: Add `pdfAvailable` to the Publication interface --- export interface Publication { key: string; title: string; @@ -14,8 +12,8 @@ export interface Publication { year: string; bibtex: string; pdfUrl: string; - url?: string; // Also make url optional to be safe - pdfAvailable?: boolean; // This will hold the result of our check + url?: string; + pdfAvailable?: boolean; } export function getPublicationsData(): Publication[] { @@ -59,7 +57,7 @@ export function getPublicationsData(): Publication[] { url: Array.isArray(entry.fields.url) ? entry.fields.url.join(" ") : entry.fields.url, bibtex: bibtexEntryString, pdfUrl: `/publications/${entry.key}.pdf`, - pdfAvailable: pdfExists, // <-- Add the result here + pdfAvailable: pdfExists, }; }); } \ No newline at end of file diff --git a/lib/umami.ts b/lib/umami.ts index e299975d..88ac5ae0 100644 --- a/lib/umami.ts +++ b/lib/umami.ts @@ -2,7 +2,7 @@ declare global { interface Window { umami?: { - track: (eventName: string, eventData?: Record) => void; + track: (eventName: string, eventData?: Record) => void; }; } } diff --git a/lib/utils.ts b/lib/utils.ts index eb28ba1e..da930956 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -6,15 +6,15 @@ export function cn(...inputs: ClassValue[]) { } export function formatDate(date: string) { - let currentDate = new Date().getTime(); + const currentDate = new Date().getTime(); if (!date.includes("T")) { date = `${date}T00:00:00`; } - let targetDate = new Date(date).getTime(); - let timeDifference = Math.abs(currentDate - targetDate); - let daysAgo = Math.floor(timeDifference / (1000 * 60 * 60 * 24)); + const targetDate = new Date(date).getTime(); + const timeDifference = Math.abs(currentDate - targetDate); + const daysAgo = Math.floor(timeDifference / (1000 * 60 * 60 * 24)); - let fullDate = new Date(date).toLocaleString("en-us", { + const fullDate = new Date(date).toLocaleString("en-us", { month: "long", day: "numeric", year: "numeric", diff --git a/next.config.mjs b/next.config.mjs deleted file mode 100644 index 32e3e8cb..00000000 --- a/next.config.mjs +++ /dev/null @@ -1,12 +0,0 @@ -/** @type {import('next').NextConfig} */ -const nextConfig = { - reactStrictMode: true, - experimental: { - serverComponentsExternalPackages: [ - 'next-mdx-remote', - '@retorquere/bibtex-parser', - ], - }, -}; - -export default nextConfig; diff --git a/next.config.ts b/next.config.ts index 500fca9a..7de0dc61 100644 --- a/next.config.ts +++ b/next.config.ts @@ -2,6 +2,15 @@ import type { NextConfig } from "next"; const nextConfig: NextConfig = { /* your other config options here */ + output: 'standalone', + images: { + remotePatterns: [ + { + protocol: "https", + hostname: "*.steffenillium.de", + }, + ], + }, }; export default nextConfig; diff --git a/package.json b/package.json index 976f5e60..d6ee6768 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "@radix-ui/react-slot": "^1.2.3", "@radix-ui/react-tooltip": "^1.2.8", "@retorquere/bibtex-parser": "^9.0.21", + "@tailwindcss/typography": "^0.5.16", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "gray-matter": "^4.0.3", @@ -27,13 +28,18 @@ "react-dom": "19.1.1", "react-icons": "^5.5.0", "react-markdown": "^10.1.0", + "rehype-pretty-code": "^0.14.1", "rehype-slug": "^6.0.0", + "rehype-stringify": "^10.0.1", "remark-gfm": "^4.0.1", "remark-html": "^16.0.1", "remark-parse": "^11.0.0", "remark-rehype": "^11.1.2", + "rough-notation": "^0.5.1", "shiki": "^3.12.2", - "tailwind-merge": "^3.3.1" + "tailwind-merge": "^3.3.1", + "tailwindcss-animate": "^1.0.7", + "unified": "^11.0.5" }, "devDependencies": { "@eslint/eslintrc": "^3.3.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 39483802..6d1aa0a5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -26,6 +26,9 @@ importers: '@retorquere/bibtex-parser': specifier: ^9.0.21 version: 9.0.21 + '@tailwindcss/typography': + specifier: ^0.5.16 + version: 0.5.16(tailwindcss@4.1.13) class-variance-authority: specifier: ^0.7.1 version: 0.7.1 @@ -62,9 +65,15 @@ importers: react-markdown: specifier: ^10.1.0 version: 10.1.0(@types/react@19.1.12)(react@19.1.1) + rehype-pretty-code: + specifier: ^0.14.1 + version: 0.14.1(shiki@3.12.2) rehype-slug: specifier: ^6.0.0 version: 6.0.0 + rehype-stringify: + specifier: ^10.0.1 + version: 10.0.1 remark-gfm: specifier: ^4.0.1 version: 4.0.1 @@ -77,12 +86,21 @@ importers: remark-rehype: specifier: ^11.1.2 version: 11.1.2 + rough-notation: + specifier: ^0.5.1 + version: 0.5.1 shiki: specifier: ^3.12.2 version: 3.12.2 tailwind-merge: specifier: ^3.3.1 version: 3.3.1 + tailwindcss-animate: + specifier: ^1.0.7 + version: 1.0.7(tailwindcss@4.1.13) + unified: + specifier: ^11.0.5 + version: 11.0.5 devDependencies: '@eslint/eslintrc': specifier: ^3.3.1 @@ -960,6 +978,11 @@ packages: '@tailwindcss/postcss@4.1.13': resolution: {integrity: sha512-HLgx6YSFKJT7rJqh9oJs/TkBFhxuMOfUKSBEPYwV+t78POOBsdQ7crhZLzwcH3T0UyUuOzU/GK5pk5eKr3wCiQ==} + '@tailwindcss/typography@0.5.16': + resolution: {integrity: sha512-0wDLwCVF5V3x3b1SGXPCDcdsbDHMBe+lkFzBRaHeLvNi+nrrnZ1lA18u+OTWO8iSWU2GxUOCvlXtDuqftc1oiA==} + peerDependencies: + tailwindcss: '>=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1' + '@tybys/wasm-util@0.10.0': resolution: {integrity: sha512-VyyPYFlOMNylG45GoAe0xDoLwWuowvf92F9kySqzYh8vmYm7D2u4iUJKa1tOUpS70Ku13ASrOkS4ScXFsTaCNQ==} @@ -1371,6 +1394,11 @@ packages: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} + cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} @@ -1449,6 +1477,10 @@ packages: resolution: {integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==} engines: {node: '>=10.13.0'} + entities@6.0.1: + resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} + engines: {node: '>=0.12'} + es-abstract@1.24.0: resolution: {integrity: sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==} engines: {node: '>= 0.4'} @@ -1810,9 +1842,18 @@ packages: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} + hast-util-from-html@2.0.3: + resolution: {integrity: sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==} + + hast-util-from-parse5@8.0.3: + resolution: {integrity: sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==} + hast-util-heading-rank@3.0.0: resolution: {integrity: sha512-EJKb8oMUXVHcWZTDepnr+WNbfnXKFNf9duMesmr4S8SXTJBJ9M4Yok08pu9vxdJwdlGRhVumk9mEhkEvKGifwA==} + hast-util-parse-selector@4.0.0: + resolution: {integrity: sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==} + hast-util-sanitize@5.0.2: resolution: {integrity: sha512-3yTWghByc50aGS7JlGhk61SPenfE/p1oaFeNwkOOyrscaOkMGrcW9+Cy/QAIOBpZxP1yqDIzFMR0+Np0i0+usg==} @@ -1831,6 +1872,9 @@ packages: hast-util-whitespace@3.0.0: resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} + hastscript@9.0.1: + resolution: {integrity: sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==} + html-url-attributes@3.0.1: resolution: {integrity: sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==} @@ -2122,6 +2166,12 @@ packages: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} + lodash.castarray@4.4.0: + resolution: {integrity: sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==} + + lodash.isplainobject@4.0.6: + resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} + lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} @@ -2478,6 +2528,12 @@ packages: parse-entities@4.0.2: resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==} + parse-numeric-range@1.3.0: + resolution: {integrity: sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ==} + + parse5@7.3.0: + resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} + path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -2504,6 +2560,10 @@ packages: resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} engines: {node: '>= 0.4'} + postcss-selector-parser@6.0.10: + resolution: {integrity: sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==} + engines: {node: '>=4'} + postcss@8.4.31: resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} engines: {node: ^10 || ^12 || >=14} @@ -2590,12 +2650,24 @@ packages: resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} engines: {node: '>= 0.4'} + rehype-parse@9.0.1: + resolution: {integrity: sha512-ksCzCD0Fgfh7trPDxr2rSylbwq9iYDkSn8TCDmEJ49ljEUBxDVCzCHv7QNzZOfODanX4+bWQ4WZqLCRWYLfhag==} + + rehype-pretty-code@0.14.1: + resolution: {integrity: sha512-IpG4OL0iYlbx78muVldsK86hdfNoht0z63AP7sekQNW2QOTmjxB7RbTO+rhIYNGRljgHxgVZoPwUl6bIC9SbjA==} + engines: {node: '>=18'} + peerDependencies: + shiki: ^1.0.0 || ^2.0.0 || ^3.0.0 + rehype-recma@1.0.0: resolution: {integrity: sha512-lqA4rGUf1JmacCNWWZx0Wv1dHqMwxzsDWYMTowuplHF3xH0N/MmrZ/G3BDZnzAkRmxDadujCjaKM2hqYdCBOGw==} rehype-slug@6.0.0: resolution: {integrity: sha512-lWyvf/jwu+oS5+hL5eClVd3hNdmwM1kAC0BUvEGD19pajQMIzcNUd/k9GsfQ+FfECvX+JE+e9/btsKH0EjJT6A==} + rehype-stringify@10.0.1: + resolution: {integrity: sha512-k9ecfXHmIPuFVI61B9DeLPN0qFHfawM6RsuX48hoqlaKSF61RskNjSm1lI8PhBEM0MRdLxVVm4WmTqJQccH9mA==} + remark-frontmatter@5.0.0: resolution: {integrity: sha512-XTFYvNASMe5iPN0719nPrdItC9aU0ssC4v14mH1BCi1u0n1gAocqcujWUrByftZTbLhRtiKRyjYTSIOcr69UVQ==} @@ -2644,6 +2716,9 @@ packages: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + rough-notation@0.5.1: + resolution: {integrity: sha512-ITHofTzm13cWFVfoGsh/4c/k2Mg8geKgBCwex71UZLnNuw403tCRjYPQ68jSAd37DMbZIePXPjDgY0XdZi9HPw==} + run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} @@ -2810,6 +2885,11 @@ packages: tailwind-merge@3.3.1: resolution: {integrity: sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g==} + tailwindcss-animate@1.0.7: + resolution: {integrity: sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==} + peerDependencies: + tailwindcss: '>=3.0.0 || insiders' + tailwindcss@4.1.13: resolution: {integrity: sha512-i+zidfmTqtwquj4hMEwdjshYYgMbOrPzb9a0M3ZgNa0JMoZeFC6bxZvO8yr8ozS6ix2SDz0+mvryPeBs2TFE+w==} @@ -2926,10 +3006,16 @@ packages: peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + uuid@9.0.1: resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} hasBin: true + vfile-location@5.0.3: + resolution: {integrity: sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==} + vfile-message@3.1.4: resolution: {integrity: sha512-fa0Z6P8HUrQN4BZaX05SIVXic+7kE3b05PWAtPuYP9QLHsLKYR7/AlLW3NtOrpXRLeawpDLMsVkmk5DG0NXgWw==} @@ -2942,6 +3028,9 @@ packages: vfile@6.0.3: resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} + web-namespaces@2.0.1: + resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==} + which-boxed-primitive@1.1.1: resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} engines: {node: '>= 0.4'} @@ -3711,6 +3800,14 @@ snapshots: postcss: 8.5.6 tailwindcss: 4.1.13 + '@tailwindcss/typography@0.5.16(tailwindcss@4.1.13)': + dependencies: + lodash.castarray: 4.4.0 + lodash.isplainobject: 4.0.6 + lodash.merge: 4.6.2 + postcss-selector-parser: 6.0.10 + tailwindcss: 4.1.13 + '@tybys/wasm-util@0.10.0': dependencies: tslib: 2.8.1 @@ -4157,6 +4254,8 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 + cssesc@3.0.0: {} + csstype@3.1.3: {} damerau-levenshtein@1.0.8: {} @@ -4232,6 +4331,8 @@ snapshots: graceful-fs: 4.2.11 tapable: 2.2.3 + entities@6.0.1: {} + es-abstract@1.24.0: dependencies: array-buffer-byte-length: 1.0.2 @@ -4784,10 +4885,34 @@ snapshots: dependencies: function-bind: 1.1.2 + hast-util-from-html@2.0.3: + dependencies: + '@types/hast': 3.0.4 + devlop: 1.1.0 + hast-util-from-parse5: 8.0.3 + parse5: 7.3.0 + vfile: 6.0.3 + vfile-message: 4.0.3 + + hast-util-from-parse5@8.0.3: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + devlop: 1.1.0 + hastscript: 9.0.1 + property-information: 7.1.0 + vfile: 6.0.3 + vfile-location: 5.0.3 + web-namespaces: 2.0.1 + hast-util-heading-rank@3.0.0: dependencies: '@types/hast': 3.0.4 + hast-util-parse-selector@4.0.0: + dependencies: + '@types/hast': 3.0.4 + hast-util-sanitize@5.0.2: dependencies: '@types/hast': 3.0.4 @@ -4857,6 +4982,14 @@ snapshots: dependencies: '@types/hast': 3.0.4 + hastscript@9.0.1: + dependencies: + '@types/hast': 3.0.4 + comma-separated-tokens: 2.0.3 + hast-util-parse-selector: 4.0.0 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + html-url-attributes@3.0.1: {} html-void-elements@3.0.0: {} @@ -5122,6 +5255,10 @@ snapshots: dependencies: p-locate: 5.0.0 + lodash.castarray@4.4.0: {} + + lodash.isplainobject@4.0.6: {} + lodash.merge@4.6.2: {} longest-streak@3.1.0: {} @@ -5775,6 +5912,12 @@ snapshots: is-decimal: 2.0.1 is-hexadecimal: 2.0.1 + parse-numeric-range@1.3.0: {} + + parse5@7.3.0: + dependencies: + entities: 6.0.1 + path-exists@4.0.0: {} path-key@3.1.1: {} @@ -5789,6 +5932,11 @@ snapshots: possible-typed-array-names@1.1.0: {} + postcss-selector-parser@6.0.10: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + postcss@8.4.31: dependencies: nanoid: 3.3.11 @@ -5912,6 +6060,22 @@ snapshots: gopd: 1.2.0 set-function-name: 2.0.2 + rehype-parse@9.0.1: + dependencies: + '@types/hast': 3.0.4 + hast-util-from-html: 2.0.3 + unified: 11.0.5 + + rehype-pretty-code@0.14.1(shiki@3.12.2): + dependencies: + '@types/hast': 3.0.4 + hast-util-to-string: 3.0.1 + parse-numeric-range: 1.3.0 + rehype-parse: 9.0.1 + shiki: 3.12.2 + unified: 11.0.5 + unist-util-visit: 5.0.0 + rehype-recma@1.0.0: dependencies: '@types/estree': 1.0.8 @@ -5928,6 +6092,12 @@ snapshots: hast-util-to-string: 3.0.1 unist-util-visit: 5.0.0 + rehype-stringify@10.0.1: + dependencies: + '@types/hast': 3.0.4 + hast-util-to-html: 9.0.5 + unified: 11.0.5 + remark-frontmatter@5.0.0: dependencies: '@types/mdast': 4.0.4 @@ -6015,6 +6185,8 @@ snapshots: reusify@1.1.0: {} + rough-notation@0.5.1: {} + run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 @@ -6248,6 +6420,10 @@ snapshots: tailwind-merge@3.3.1: {} + tailwindcss-animate@1.0.7(tailwindcss@4.1.13): + dependencies: + tailwindcss: 4.1.13 + tailwindcss@4.1.13: {} tapable@2.2.3: {} @@ -6424,8 +6600,15 @@ snapshots: dependencies: react: 19.1.1 + util-deprecate@1.0.2: {} + uuid@9.0.1: {} + vfile-location@5.0.3: + dependencies: + '@types/unist': 3.0.3 + vfile: 6.0.3 + vfile-message@3.1.4: dependencies: '@types/unist': 2.0.11 @@ -6448,6 +6631,8 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 + web-namespaces@2.0.1: {} + which-boxed-primitive@1.1.1: dependencies: is-bigint: 1.1.0 diff --git a/tailwind.config.ts b/tailwind.config.ts index 81b0d60e..d87fabd1 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -1,8 +1,8 @@ import type { Config } from "tailwindcss"; -import { fontFamily } from "tailwindcss/defaultTheme"; +import defaultTheme from "tailwindcss/defaultTheme"; const config = { - darkMode: ["class"], + darkMode: "class", content: [ "./pages/**/*.{ts,tsx}", "./components/**/*.{ts,tsx}", @@ -23,7 +23,7 @@ const config = { '18': '4.5rem', }, fontFamily: { - sans: ["var(--font-sans)", ...fontFamily.sans], + sans: ["var(--font-sans)", ...defaultTheme.fontFamily.sans], }, colors: { border: "hsl(var(--border))",