smaller adjustments and server client seperation for projects

This commit is contained in:
2025-08-30 14:38:52 +02:00
parent dda268930c
commit 452ca1770c
12 changed files with 356 additions and 162 deletions

View File

@@ -8,35 +8,36 @@ role: Researcher, Software Developer
skills: Multi-Agent Reinforcement Learning (MARL), Emergence Analysis, AI Safety, Simulation Environment Design, Python, Gymnasium API, Software Engineering, Unity (Visualization), Industry Collaboration
---
<div className="container">
<InfoBox title="Project Resources">
{/* The InfoBox now contains ALL metadata, creating a clean sidebar. */}
<InfoBox title="Project Details">
{/* Section 2: Project Info */}
<h4>Overview</h4>
<p style={{ lineHeight: '1.5', margin: 0 }}>
<strong>Project:</strong> AI-Fusion<br/>
<strong>Partner:</strong> <a href="https://www.iks.fraunhofer.de/" target="_blank" rel="noopener noreferrer">Fraunhofer IKS</a><br/>
<strong>Duration:</strong> 2022 - 2023
</p>
<Image src="/images/projects/full_domain.png" alt="logo" width="400" height="200" />
<br />
<hr />
<br />
{/* Section 1: Resources */}
<h4 style={{marginTop: 0, marginRight: 0}}>Resources</h4>
<ul style={{ listStyle: 'none', paddingLeft: '0' }}>
<li><a href="https://github.com/illiumst/marl-factory-grid/" target="_blank" rel="noopener noreferrer"><i className="fab fa-fw fa-github" aria-hidden="true"></i> GitHub Repo</a></li>
<li><a href="https://pypi.org/project/Marl-Factory-Grid/" target="_blank" rel="noopener noreferrer"><i className="fab fa-fw fa-python" aria-hidden="true"></i> Install via PyPI</a></li>
<li><a href="https://marl-factory-grid.readthedocs.io/en/latest/" target="_blank" rel="noopener noreferrer"><i className="fas fa-fw fa-book" aria-hidden="true"></i> ReadTheDocs</a></li>
<li><i className="fas fa-fw fa-file-alt" aria-hidden="true"></i> <Cite bibtexKey="altmann2024emergence" /></li>
</ul>
<Image src="/images/projects/full_domain.png" alt="logo" style={{ margin: '0em', padding: '0em', width: '15em' }} />
</InfoBox>
<div className="main-content" style={{ float: 'left', width: '75%' }}>
In collaboration with Fraunhofer IKS, the AI-Fusion project addressed the critical challenge of understanding and ensuring safety in multi-agent reinforcement learning (MARL) systems. Emergence, defined as the arising of complex, often unpredictable, system-level dynamics from local interactions between agents and their environment, was a central focus due to its implications for system safety and reliability.
<hr />
</InfoBox>
**Project:** AI-Fusion<br/>
**Partner:** [Fraunhofer Institute for Cognitive Systems (IKS)](https://www.iks.fraunhofer.de/)<br/>
**Duration:** 2022 - 2023<br/>
**Objective:** To investigate the detection and mitigation of potentially unsafe emergent behaviors in complex systems composed of multiple interacting AI agents, particularly in scenarios involving heterogeneous agents (e.g., mixed-vendor autonomous systems).
</div>
</div>
---
{/* All the main content now flows naturally in a single column. */}
In collaboration with Fraunhofer IKS, the AI-Fusion project addressed the critical challenge of understanding and ensuring safety in multi-agent reinforcement learning (MARL) systems. Emergence, defined as the arising of complex, often unpredictable, system-level dynamics from local interactions between agents and their environment, was a central focus due to its implications for system safety and reliability. The project's objective was to investigate the detection and mitigation of potentially unsafe emergent behaviors in complex systems composed of multiple interacting AI agents, particularly in scenarios involving heterogeneous agents (e.g., mixed-vendor autonomous systems).
To facilitate research into these phenomena, key contributions included the development of specialized simulation tools:
**1. High-Performance MARL Simulation Environment:**
* A flexible and efficient simulation environment was developed in Python, adhering to the [Gymnasium (formerly Gym) API specification](https://gymnasium.farama.org/main/).
* **Purpose:** Designed specifically for training and evaluating reinforcement learning algorithms in multi-agent contexts prone to emergent behaviors.
* **Features:**
@@ -45,16 +46,13 @@ To facilitate research into these phenomena, key contributions included the deve
* **Performance:** Optimized for efficient simulation runs, enabling extensive experimentation.
**2. Unity-Based Demonstrator Unit:**
* A complementary visualization tool was created using the Unity engine.
* **Purpose:** Allows for the replay, inspection, and detailed analysis of specific simulation scenarios and agent interactions.
* **Utility:** Aids researchers in identifying and understanding the mechanisms behind observed emergent dynamics.
* [View Demonstrator on GitHub](https://github.com/illiumst/F-IKS_demonstrator)
<div style={{ clear: 'both' }}></div>
<div className="text-center">
<Image src="/images/projects/rel_emergence.png" alt="Diagram illustrating the concept of emergence from interactions between agents and environment" style={{ padding: '0.1em', width: '80%' }} />
<Image src="/images/projects/rel_emergence.png" alt="Diagram illustrating the concept of emergence from interactions between agents and environment" width="800" height="300"/>
<figcaption>Conceptual relationship defining emergence in multi-agent systems.</figcaption>
</div>

View File

@@ -141,4 +141,14 @@ code[data-line-numbers-max-digits="2"] > [data-line]::before {
code[data-line-numbers-max-digits="3"] > [data-line]::before {
width: 3rem;
}
.prose a {
/* Use the CSS variable we defined in the component */
color: var(--accent-color);
}
.prose a:hover {
/* Optional: slightly darken or lighten the color on hover for better feedback */
opacity: 0.8;
}

View File

@@ -7,6 +7,7 @@ import { cn } from "@/lib/utils";
import type { Metadata } from "next";
import { Inter as FontSans } from "next/font/google";
import "./globals.css";
import { AccentColorProvider } from "@/context/accent-color-context";
const fontSans = FontSans({
subsets: ["latin"],
@@ -64,11 +65,13 @@ export default function RootLayout({
>
<script defer src="https://umami.steffenillium.de/script.js" data-website-id="170441c3-f9ca-4dea-9f44-ba0573b0f9e5"></script>
<ThemeProvider attribute="class" defaultTheme="light">
<TooltipProvider delayDuration={0}>
<Header />
{children}
<Navbar />
</TooltipProvider>
<AccentColorProvider>
<TooltipProvider delayDuration={0}>
<Header />
{children}
<Navbar />
</TooltipProvider>
</AccentColorProvider>
</ThemeProvider>
</body>
</html>

View File

@@ -1,12 +1,12 @@
import { getPostBySlug, getPostSlugs } from '@/lib/mdx';
import { CitationProvider } from '@/context/citation-context';
import { ReferencesContainer } from '@/components/references-container';
import { getPostBySlug, getPostSlugs, getPostFrontmatter } from '@/lib/mdx'; // Assume getPostFrontmatter exists
import { notFound } from 'next/navigation';
import Image from 'next/image';
import { DATA } from '@/data/resume';
import { getPublicationsData } from '@/lib/publications';
import { CustomMDX } from '@/components/custom-mdx';
import Link from 'next/link';
import { ProjectArticle } from '@/components/project-article';
// Helper function to get frontmatter for a slug (you may need to create this in lib/mdx.ts)
// It's a lighter version of getPostBySlug that only parses frontmatter.
// If you don't have it, we can just use getPostBySlug, it's just slightly less performant.
export async function generateStaticParams() {
const slugs = getPostSlugs('projects');
@@ -15,9 +15,7 @@ export async function generateStaticParams() {
export async function generateMetadata({ params }: { params: { slug: string } }) {
const post = await getPostBySlug('projects', params.slug);
if (!post) {
return {};
}
if (!post) { return {}; }
return {
title: post.frontmatter.title,
description: post.frontmatter.description || DATA.description,
@@ -25,76 +23,28 @@ export async function generateMetadata({ params }: { params: { slug: string } })
}
export default async function ProjectPage({ params }: { params: { slug: string } }) {
const post = await getPostBySlug('projects', params.slug);
const { slug } = params;
const post = await getPostBySlug('projects', slug);
const publications = getPublicationsData();
if (!post) {
return notFound();
notFound();
}
return (
<CitationProvider publications={publications}>
<main className="flex flex-col min-h-[100dvh]">
<article className="prose prose-stone dark:prose-invert max-w-none">
<header className="mb-8">
<h1 className="text-4xl font-bold tracking-tighter sm:text-5xl">
{post.frontmatter.title}
</h1>
{post.frontmatter.excerpt && (
<p className="text-lg text-muted-foreground mt-1">{post.frontmatter.excerpt}</p>
)}
</header>
// --- Logic for Previous/Next Navigation ---
const allSlugs = getPostSlugs('projects'); // Or get them from your DATA file if they are ordered there
const currentIndex = allSlugs.findIndex((s) => s === slug);
{post.frontmatter.show_teaser && post.frontmatter.teaser && (
<div className="my-6">
<Image
src={post.frontmatter.teaser}
alt={`${post.frontmatter.title} teaser image`}
width={800}
height={400}
className="rounded-lg"
/>
</div>
)}
const prevSlug = currentIndex > 0 ? allSlugs[currentIndex - 1] : null;
const nextSlug = currentIndex < allSlugs.length - 1 ? allSlugs[currentIndex + 1] : null;
{post.frontmatter.tags && (
<div className="flex flex-wrap gap-2 mt-8">
{post.frontmatter.tags.map((tag: string) => (
<Link
key={tag}
href={`/tags/${tag}`}
className="px-3 py-1 text-sm font-medium bg-secondary text-secondary-foreground rounded-full hover:bg-primary hover:text-primary-foreground transition-colors"
>
{tag}
</Link>
))}
</div>
)}
<div>
{post.frontmatter.icon ? (
// If there is an icon, render it and then the content.
// The parent div with `flow-root` contains the float correctly.
<div className="flow-root">
<Image
src={post.frontmatter.icon}
alt={`${post.frontmatter.title} icon`}
width={80}
height={80}
className="float-left mr-4 mb-2 rounded-full"
/>
<CustomMDX {...post.source} />
</div>
) : (
// If there is NO icon, wrap the content in a div for the drop-cap effect.
<div className="first-letter:float-left first-letter:mr-3 first-letter:text-5xl first-letter:font-bold first-letter:leading-none first-letter:text-primary first-letter:pr-1">
<CustomMDX {...post.source} />
</div>
)}
</div>
<ReferencesContainer />
</article>
</main>
</CitationProvider>
);
}
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 <ProjectArticle post={post} publications={publications} navigation={navigation} />;
}

View File

@@ -50,13 +50,15 @@ export default function PublicationsPage() {
</Link>
))}
</div>
<div className="space-y-4">
<div className="space-y-8"> {/* Increased spacing between year sections */}
{years.map((year) => (
<div key={year} className="relative">
<h2 className="sticky top-18 z-10 -ml-4 pl-4 pr-4 py-2 bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60 font-bold text-xl">
{year}
</h2>
<div className="space-y-4 pt-4">
<div key={year} className="flex flex-col md:flex-row md:space-x-8">
<div className="md:w-16 flex-shrink-0">
<h2 className="sticky top-20 font-bold text-xl text-muted-foreground md:text-right">
{year}
</h2>
</div>
<div className="flex-grow space-y-4 pt-2 md:pt-0"> {/* Main content column */}
{publicationsByYear[year].map((pub) => (
<PublicationCard
key={pub.key}

View File

@@ -9,13 +9,14 @@ export function InfoBox({ title, children, className, ...props }: InfoBoxProps)
return (
<aside
className={cn(
"relative mt-0 md:mt-2 md:float-right md:w-64 md:ml-4 md:mb-4 p-2 border rounded-lg shadow-sm bg-card text-card-foreground",
// The `not-prose` class will now work correctly.
"not-prose relative mt-4 mb-8 md:float-right md:w-64 md:ml-4 md:mb-4 p-4 border rounded-lg shadow-sm bg-card text-card-foreground",
className
)}
{...props}
>
<h3 className="font-bold mt-0 mb-0 text-lg">{title}</h3>
<div className="text-sm space-y-0 mb-0">{children}</div>
<h3 className="font-bold mt-0 mb-2 text-lg">{title}</h3>
<div className="text-sm space-y-2">{children}</div>
</aside>
);
}
}

View File

@@ -74,3 +74,15 @@ export const mdxComponents = {
InfoBox,
Cite,
};
const serverSafeComponents = {
// KEEP components that DO NOT use hooks
h1: mdxComponents.h1,
h2: mdxComponents.h2,
h3: mdxComponents.h3,
h4: mdxComponents.h4,
h5: mdxComponents.h5,
h6: mdxComponents.h6,
Image: mdxComponents.Image,
a: mdxComponents.a,
};

View File

@@ -0,0 +1,110 @@
"use client";
import { CitationProvider } from '@/context/citation-context';
import { ReferencesContainer } from '@/components/references-container';
import Image from 'next/image';
import { CustomMDX } from '@/components/custom-mdx';
import Link from 'next/link';
import { Publication } from '@/lib/publications';
import { useAccentColor } from '@/context/accent-color-context';
import { ProjectNavigation } from './project-navigation';
// Define the types for our props
interface Post {
source: any;
frontmatter: {
title: string;
excerpt?: string;
show_teaser?: boolean;
teaser?: string;
tags?: string[];
icon?: string;
};
}
interface NavigationLink {
slug: string;
title: string;
}
interface ProjectArticleProps {
post: Post;
publications: Publication[];
navigation: {
prev: NavigationLink | null;
next: NavigationLink | null;
};
basePath: string;
}
export function ProjectArticle({ post, publications, navigation, basePath }: ProjectArticleProps) {
const accentColor = useAccentColor();
return (
<CitationProvider publications={publications}>
<main className="flex flex-col min-h-[100dvh] py-8" style={{ '--accent-color': accentColor } as React.CSSProperties}>
<article className="prose prose-stone dark:prose-invert max-w-none">
<header className="mb-8">
<h1 className="text-4xl font-bold tracking-tighter sm:text-5xl mb-2">
{post.frontmatter.title}
</h1>
{post.frontmatter.excerpt && (
<p className="text-lg text-muted-foreground mt-1">{post.frontmatter.excerpt}</p>
)}
</header>
{post.frontmatter.show_teaser && post.frontmatter.teaser && (
<div className="my-6">
<Image
src={post.frontmatter.teaser}
alt={`${post.frontmatter.title} teaser image`}
width={800}
height={400}
className="rounded-lg"
/>
</div>
)}
{post.frontmatter.tags && (
<div className="flex flex-wrap gap-2 mt-8">
{post.frontmatter.tags.map((tag: string) => (
<Link
key={tag}
href={`/tags/${tag}`}
className="px-3 py-1 text-sm font-medium bg-secondary text-secondary-foreground hover:text-primary-foreground transition-colors"
onMouseOver={(e) => e.currentTarget.style.backgroundColor = accentColor}
onMouseOut={(e) => e.currentTarget.style.backgroundColor = ''}
>
{tag}
</Link>
))}
</div>
)}
<div className="mt-8">
{post.frontmatter.icon ? (
<div className="flow-root">
<Image
src={post.frontmatter.icon}
alt={`${post.frontmatter.title} icon`}
width={80}
height={80}
className="float-left mr-4 mb-2"
/>
<CustomMDX {...post.source} />
</div>
) : (
<div className="first-letter:float-left first-letter:mr-3 first-letter:text-5xl first-letter:font-bold first-letter:leading-none first-letter:text-primary first-letter:pr-1">
<CustomMDX {...post.source} />
</div>
)}
</div>
<ReferencesContainer />
<ProjectNavigation prev={navigation.prev} next={navigation.next} basePath={basePath} />
</article>
</main>
</CitationProvider>
);
}

View File

@@ -0,0 +1,57 @@
"use client";
import Link from 'next/link';
import { ArrowLeft, ArrowRight } from 'lucide-react';
import { useAccentColor } from '@/context/accent-color-context';
interface NavLink {
slug: string;
title: string;
}
interface ProjectNavigationProps {
prev?: NavLink | null;
next?: NavLink | null;
// --- FIX #1: Added basePath prop for reusability ---
basePath: string;
}
export function ProjectNavigation({ prev, next, basePath }: ProjectNavigationProps) {
const accentColor = useAccentColor();
if (!prev && !next) return null;
return (
// --- FIX #2: Added 'not-prose' to remove unwanted link styles ---
<div className="not-prose grid grid-cols-1 md:grid-cols-2 gap-4 mt-12 pt-8 border-t">
<div>
{prev && (
// Use the dynamic basePath
<Link href={`/${basePath}/${prev.slug}`} className="block p-4 border rounded-lg hover:border-primary transition-colors h-full">
<div className="text-sm text-muted-foreground flex items-center gap-2">
<ArrowLeft size={16} />
Previous
</div>
<div className="font-semibold truncate" style={{ color: accentColor }}>
{prev.title}
</div>
</Link>
)}
</div>
<div className="md:text-right">
{next && (
// Use the dynamic basePath
<Link href={`/${basePath}/${next.slug}`} className="block p-4 border rounded-lg hover:border-primary transition-colors h-full">
<div className="text-sm text-muted-foreground flex items-center gap-2 md:justify-end">
Next
<ArrowRight size={16} />
</div>
<div className="font-semibold truncate" style={{ color: accentColor }}>
{next.title}
</div>
</Link>
)}
</div>
</div>
);
}

View File

@@ -1,11 +1,11 @@
"use client";
import { Card, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { FileText, Copy, Download, Paperclip } from "lucide-react";
import { FileText, Copy, Download, Paperclip, BookOpen, Check, X } from "lucide-react";
import { useState, useEffect } from "react";
import { cn } from "@/lib/utils";
import Image from "next/image";
import Link from "next/link"; // Import the Link component
import Link from "next/link";
import { CardTitle } from "@/components/ui/card";
interface Props {
bibtexKey: string;
@@ -17,7 +17,8 @@ interface Props {
pdfUrl?: string;
bibtex: string;
className?: string;
pdfAvailable: boolean;
// --- FIX IS HERE: Made pdfAvailable optional ---
pdfAvailable?: boolean;
}
export function PublicationCard({
@@ -30,9 +31,10 @@ export function PublicationCard({
pdfUrl,
bibtex,
className,
pdfAvailable,
// --- FIX IS HERE: Default pdfAvailable to false if not provided ---
pdfAvailable = false,
}: Props) {
const [copied, setCopied] = useState(false);
const [copyStatus, setCopyStatus] = useState<"idle" | "success" | "error">("idle");
const [imageError, setImageError] = useState(false);
const [downloadUrl, setDownloadUrl] = useState<string | null>(null);
const [isClient, setIsClient] = useState(false);
@@ -44,9 +46,16 @@ export function PublicationCard({
e.stopPropagation();
if (isClient && navigator.clipboard?.writeText) {
navigator.clipboard.writeText(bibtex).then(() => {
setCopied(true);
setTimeout(() => setCopied(false), 2000);
}).catch(err => console.error("Failed to copy BibTeX:", err));
setCopyStatus("success");
setTimeout(() => setCopyStatus("idle"), 2000);
}).catch(err => {
console.error("Failed to copy BibTeX:", err);
setCopyStatus("error");
setTimeout(() => setCopyStatus("idle"), 2000);
});
} else {
setCopyStatus("error");
setTimeout(() => setCopyStatus("idle"), 2000);
}
};
@@ -69,7 +78,7 @@ export function PublicationCard({
const ImageContent = (
<>
{!imageError ? (
<Image src={imageUrl} alt={`Preview for ${title}`} fill className="rounded-md object-cover" onError={() => setImageError(true)} sizes="(max-width: 768px) 100vw, 33vw" />
<Image src={imageUrl} alt={`Preview for ${title}`} fill className="rounded-md object-cover" onError={() => setImageError(true)} sizes="96px" />
) : (
<div className="flex items-center justify-center w-full h-full bg-muted/20 rounded-md">
<Paperclip className="h-6 w-6 text-muted-foreground" />
@@ -79,42 +88,47 @@ export function PublicationCard({
);
return (
<div className={cn("flex items-center space-x-4 p-4 border rounded-lg shadow-sm hover:shadow-lg transition-all duration-300 ease-out", className)} id={bibtexKey}>
{url ? (
<Link href={url} target="_blank" rel="noopener noreferrer" className="flex-shrink-0 w-24 h-24 relative">
{ImageContent}
</Link>
) : (
<div className="flex-shrink-0 w-24 h-24 relative">{ImageContent}</div>
)}
<div className="flex-grow flex flex-col md:flex-row items-start justify-between space-y-2 md:space-y-0 md:space-x-4">
<div className="flex-grow space-y-1">
<CardTitle className="text-base font-medium leading-snug">
{url ? (
<Link href={url} target="_blank" rel="noopener noreferrer" className="hover:underline">
{title}
</Link>
) : (
title
)}
</CardTitle>
<p className="text-sm text-muted-foreground">
{formattedAuthors}. <em>{journal}</em>. {year}
</p>
</div>
<div className="flex flex-col space-y-1 items-end self-start md:self-auto">
<Button variant="ghost" size="sm" className="px-2 h-7 w-7" onClick={handleDownload} title="Download BibTeX"><Download className="h-4 w-4" /></Button>
<Button variant="ghost" size="sm" className="px-2 h-7 w-7 relative" onClick={handleCopy} title="Copy BibTeX">
<Copy className="h-4 w-4" />
{copied && <span className="absolute -right-full top-1/2 -translate-y-1/2 ml-2 bg-green-500 text-white text-xs px-2 py-1 rounded whitespace-nowrap animate-fade-in">Copied!</span>}
</Button>
{pdfUrl && pdfAvailable && (
<Button variant="ghost" size="sm" className="px-2 h-7 w-7" asChild onClick={(e) => e.stopPropagation()}>
<a href={pdfUrl} target="_blank" rel="noopener noreferrer" title="Open PDF"><FileText className="h-4 w-4" /></a>
</Button>
<div className={cn("flex items-start space-x-4 p-4 border rounded-lg shadow-sm hover:shadow-lg transition-all duration-300 ease-out", className)} id={bibtexKey}>
<div className="flex-shrink-0 w-24 h-24 relative">
{url ? (
<Link href={url} target="_blank" rel="noopener noreferrer" className="block w-full h-full">
{ImageContent}
</Link>
) : (
ImageContent
)}
</div>
<div className="flex-grow space-y-1">
<CardTitle className="text-base font-medium leading-snug">
{url ? (
<Link href={url} target="_blank" rel="noopener noreferrer" className="hover:underline">
{title}
</Link>
) : (
title
)}
</div>
</CardTitle>
<p className="text-sm text-muted-foreground">
{formattedAuthors}. <em>{journal}</em>. {year}
</p>
</div>
<div className="flex flex-col space-y-1 flex-shrink-0">
<Button variant="ghost" size="sm" className="px-2 h-7 w-7" onClick={handleDownload} title="Download BibTeX">
<FileText className="h-4 w-4" />
</Button>
<Button variant="ghost" size="sm" className="px-2 h-7 w-7 relative" onClick={handleCopy} title="Copy BibTeX">
{copyStatus === 'idle' && <Copy className="h-4 w-4" />}
{copyStatus === 'success' && <Check className="h-4 w-4 text-green-500" />}
{copyStatus === 'error' && <X className="h-4 w-4 text-red-500" />}
</Button>
{pdfUrl && pdfAvailable && (
<Button variant="ghost" size="sm" className="px-2 h-7 w-7" asChild onClick={(e) => e.stopPropagation()}>
<a href={pdfUrl} target="_blank" rel="noopener noreferrer" title="Open PDF">
<BookOpen className="h-4 w-4" />
</a>
</Button>
)}
</div>
</div>
);
}
}

View File

@@ -2,11 +2,11 @@
import React, { useEffect, useState } from 'react';
import { useCitations } from '@/context/citation-context';
import { PublicationCard } from './publication-card'; // Changed import
import { PublicationCard } from './publication-card';
import { BookOpen } from 'lucide-react';
export function ReferencesContainer() {
const { citedKeys, getPublicationByKey } = useCitations(); // Added getPublicationByKey
const { citedKeys, getPublicationByKey } = useCitations();
const [isClient, setIsClient] = useState(false);
useEffect(() => {
@@ -20,12 +20,13 @@ export function ReferencesContainer() {
const sortedKeys = Array.from(citedKeys).sort();
return (
<div className="mt-8 pt-4 border-t"> {/* Adjusted whitespace */}
<div className="mt-8 pt-4 border-t">
<h2 className="text-2xl font-bold mb-2 flex items-center gap-2">
<BookOpen className="h-6 w-6" />
References
</h2>
<div className="space-y-4">
{/* --- FIX IS IN THE LINE BELOW: Added not-prose --- */}
<div className="not-prose space-y-4">
{sortedKeys.map(key => {
const pub = getPublicationByKey(key);
if (!pub) return null;
@@ -39,10 +40,11 @@ export function ReferencesContainer() {
year={pub.year}
pdfUrl={pub.pdfUrl}
bibtex={pub.bibtex}
// The pdfAvailable prop is now safely omitted
/>
);
})}
</div>
</div>
);
}
}

View File

@@ -0,0 +1,35 @@
"use client";
import React, { createContext, useContext, useMemo } from 'react';
// A curated palette of visually appealing accent colors from Tailwind's palette
const ACCENT_COLORS = [
'#f43f5e', // rose-500
'#3b82f6', // blue-500
'#16a34a', // green-600
'#8b5cf6', // violet-500
'#f97316', // orange-500
'#06b6d4', // cyan-500
'#d946ef', // fuchsia-500
];
const AccentColorContext = createContext<string>(ACCENT_COLORS[0]);
export function AccentColorProvider({ children }: { children: React.ReactNode }) {
const accentColor = useMemo(() => {
// This calculation ensures the color is stable for the duration
const interval = 180 * 60 * 1000; // 180 minutes in milliseconds
const timeBucket = Math.floor(Date.now() / interval);
return ACCENT_COLORS[timeBucket % ACCENT_COLORS.length];
}, []); // Empty dependency array means this runs only once on mount
return (
<AccentColorContext.Provider value={accentColor}>
{children}
</AccentColorContext.Provider>
);
}
export function useAccentColor() {
return useContext(AccentColorContext);
}