working on Publications, Article base page
This commit is contained in:
253
Gemfile.lock
253
Gemfile.lock
@@ -1,253 +0,0 @@
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
activesupport (8.0.2)
|
||||
base64
|
||||
benchmark (>= 0.3)
|
||||
bigdecimal
|
||||
concurrent-ruby (~> 1.0, >= 1.3.1)
|
||||
connection_pool (>= 2.2.5)
|
||||
drb
|
||||
i18n (>= 1.6, < 2)
|
||||
logger (>= 1.4.2)
|
||||
minitest (>= 5.1)
|
||||
securerandom (>= 0.3)
|
||||
tzinfo (~> 2.0, >= 2.0.5)
|
||||
uri (>= 0.13.1)
|
||||
addressable (2.8.7)
|
||||
public_suffix (>= 2.0.2, < 7.0)
|
||||
base64 (0.2.0)
|
||||
benchmark (0.4.0)
|
||||
bibtex-ruby (6.1.0)
|
||||
latex-decode (~> 0.0)
|
||||
racc (~> 1.7)
|
||||
bigdecimal (3.1.9)
|
||||
citeproc (1.1.0)
|
||||
date
|
||||
forwardable
|
||||
json
|
||||
namae (~> 1.0)
|
||||
observer (< 1.0)
|
||||
open-uri (< 1.0)
|
||||
citeproc-ruby (1.1.14)
|
||||
citeproc (~> 1.0, >= 1.0.9)
|
||||
csl (~> 1.6)
|
||||
colorator (1.1.0)
|
||||
concurrent-ruby (1.3.5)
|
||||
connection_pool (2.5.0)
|
||||
csl (1.6.0)
|
||||
namae (~> 1.0)
|
||||
rexml
|
||||
csl-styles (1.0.1.11)
|
||||
csl (~> 1.0)
|
||||
csv (3.3.3)
|
||||
date (3.4.1)
|
||||
drb (2.2.1)
|
||||
em-websocket (0.5.3)
|
||||
eventmachine (>= 0.12.9)
|
||||
http_parser.rb (~> 0)
|
||||
eventmachine (1.2.7)
|
||||
faraday (2.12.2)
|
||||
faraday-net_http (>= 2.0, < 3.5)
|
||||
json
|
||||
logger
|
||||
faraday-net_http (3.4.0)
|
||||
net-http (>= 0.5.0)
|
||||
ffi (1.17.1-aarch64-linux-gnu)
|
||||
ffi (1.17.1-aarch64-linux-musl)
|
||||
ffi (1.17.1-arm-linux-gnu)
|
||||
ffi (1.17.1-arm-linux-musl)
|
||||
ffi (1.17.1-arm64-darwin)
|
||||
ffi (1.17.1-x86_64-darwin)
|
||||
ffi (1.17.1-x86_64-linux-gnu)
|
||||
ffi (1.17.1-x86_64-linux-musl)
|
||||
forwardable (1.3.3)
|
||||
forwardable-extended (2.6.0)
|
||||
gemoji (4.1.0)
|
||||
google-protobuf (4.30.1)
|
||||
bigdecimal
|
||||
rake (>= 13)
|
||||
google-protobuf (4.30.1-aarch64-linux)
|
||||
bigdecimal
|
||||
rake (>= 13)
|
||||
google-protobuf (4.30.1-arm64-darwin)
|
||||
bigdecimal
|
||||
rake (>= 13)
|
||||
google-protobuf (4.30.1-x86_64-darwin)
|
||||
bigdecimal
|
||||
rake (>= 13)
|
||||
google-protobuf (4.30.1-x86_64-linux)
|
||||
bigdecimal
|
||||
rake (>= 13)
|
||||
html-pipeline (2.14.3)
|
||||
activesupport (>= 2)
|
||||
nokogiri (>= 1.4)
|
||||
http_parser.rb (0.8.0)
|
||||
i18n (1.14.7)
|
||||
concurrent-ruby (~> 1.0)
|
||||
jekyll (4.4.1)
|
||||
addressable (~> 2.4)
|
||||
base64 (~> 0.2)
|
||||
colorator (~> 1.0)
|
||||
csv (~> 3.0)
|
||||
em-websocket (~> 0.5)
|
||||
i18n (~> 1.0)
|
||||
jekyll-sass-converter (>= 2.0, < 4.0)
|
||||
jekyll-watch (~> 2.0)
|
||||
json (~> 2.6)
|
||||
kramdown (~> 2.3, >= 2.3.1)
|
||||
kramdown-parser-gfm (~> 1.0)
|
||||
liquid (~> 4.0)
|
||||
mercenary (~> 0.3, >= 0.3.6)
|
||||
pathutil (~> 0.9)
|
||||
rouge (>= 3.0, < 5.0)
|
||||
safe_yaml (~> 1.0)
|
||||
terminal-table (>= 1.8, < 4.0)
|
||||
webrick (~> 1.7)
|
||||
jekyll-archives (2.3.0)
|
||||
jekyll (>= 3.6, < 5.0)
|
||||
jekyll-data (1.1.1)
|
||||
jekyll (>= 3.3, < 5.0.0)
|
||||
jekyll-feed (0.17.0)
|
||||
jekyll (>= 3.7, < 5.0)
|
||||
jekyll-gist (1.5.0)
|
||||
octokit (~> 4.2)
|
||||
jekyll-include-cache (0.2.1)
|
||||
jekyll (>= 3.7, < 5.0)
|
||||
jekyll-paginate (1.1.0)
|
||||
jekyll-sass-converter (3.1.0)
|
||||
sass-embedded (~> 1.75)
|
||||
jekyll-scholar (7.1.3)
|
||||
bibtex-ruby (~> 6.0)
|
||||
citeproc-ruby (~> 1.0)
|
||||
csl-styles (~> 1.0)
|
||||
jekyll (~> 4.0)
|
||||
jekyll-sitemap (1.4.0)
|
||||
jekyll (>= 3.7, < 5.0)
|
||||
jekyll-watch (2.2.1)
|
||||
listen (~> 3.0)
|
||||
jekyll-webp (1.0.0)
|
||||
jemoji (0.13.0)
|
||||
gemoji (>= 3, < 5)
|
||||
html-pipeline (~> 2.2)
|
||||
jekyll (>= 3.0, < 5.0)
|
||||
json (2.10.2)
|
||||
kramdown (2.5.1)
|
||||
rexml (>= 3.3.9)
|
||||
kramdown-parser-gfm (1.1.0)
|
||||
kramdown (~> 2.0)
|
||||
latex-decode (0.4.0)
|
||||
liquid (4.0.4)
|
||||
listen (3.9.0)
|
||||
rb-fsevent (~> 0.10, >= 0.10.3)
|
||||
rb-inotify (~> 0.9, >= 0.9.10)
|
||||
logger (1.6.6)
|
||||
mercenary (0.4.0)
|
||||
minimal-mistakes-jekyll (4.26.2)
|
||||
jekyll (>= 3.7, < 5.0)
|
||||
jekyll-feed (~> 0.1)
|
||||
jekyll-gist (~> 1.5)
|
||||
jekyll-include-cache (~> 0.1)
|
||||
jekyll-paginate (~> 1.1)
|
||||
jekyll-sitemap (~> 1.3)
|
||||
minitest (5.25.5)
|
||||
namae (1.2.0)
|
||||
racc (~> 1.7)
|
||||
net-http (0.6.0)
|
||||
uri
|
||||
nokogiri (1.18.5-aarch64-linux-gnu)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.18.5-aarch64-linux-musl)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.18.5-arm-linux-gnu)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.18.5-arm-linux-musl)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.18.5-arm64-darwin)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.18.5-x86_64-darwin)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.18.5-x86_64-linux-gnu)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.18.5-x86_64-linux-musl)
|
||||
racc (~> 1.4)
|
||||
observer (0.1.2)
|
||||
octokit (4.25.1)
|
||||
faraday (>= 1, < 3)
|
||||
sawyer (~> 0.9)
|
||||
open-uri (0.5.0)
|
||||
stringio
|
||||
time
|
||||
uri
|
||||
pathutil (0.16.2)
|
||||
forwardable-extended (~> 2.6)
|
||||
public_suffix (6.0.1)
|
||||
racc (1.8.1)
|
||||
rake (13.2.1)
|
||||
rb-fsevent (0.11.2)
|
||||
rb-inotify (0.11.1)
|
||||
ffi (~> 1.0)
|
||||
rexml (3.4.1)
|
||||
rouge (4.5.1)
|
||||
safe_yaml (1.0.5)
|
||||
sass-embedded (1.86.0-aarch64-linux-gnu)
|
||||
google-protobuf (~> 4.30)
|
||||
sass-embedded (1.86.0-aarch64-linux-musl)
|
||||
google-protobuf (~> 4.30)
|
||||
sass-embedded (1.86.0-arm-linux-gnueabihf)
|
||||
google-protobuf (~> 4.30)
|
||||
sass-embedded (1.86.0-arm-linux-musleabihf)
|
||||
google-protobuf (~> 4.30)
|
||||
sass-embedded (1.86.0-arm64-darwin)
|
||||
google-protobuf (~> 4.30)
|
||||
sass-embedded (1.86.0-x86_64-darwin)
|
||||
google-protobuf (~> 4.30)
|
||||
sass-embedded (1.86.0-x86_64-linux-gnu)
|
||||
google-protobuf (~> 4.30)
|
||||
sass-embedded (1.86.0-x86_64-linux-musl)
|
||||
google-protobuf (~> 4.30)
|
||||
sawyer (0.9.2)
|
||||
addressable (>= 2.3.5)
|
||||
faraday (>= 0.17.3, < 3)
|
||||
securerandom (0.4.1)
|
||||
stringio (3.1.5)
|
||||
terminal-table (3.0.2)
|
||||
unicode-display_width (>= 1.1.1, < 3)
|
||||
time (0.4.1)
|
||||
date
|
||||
tzinfo (2.0.6)
|
||||
concurrent-ruby (~> 1.0)
|
||||
unicode-display_width (2.6.0)
|
||||
uri (1.0.3)
|
||||
webrick (1.9.1)
|
||||
|
||||
PLATFORMS
|
||||
aarch64-linux
|
||||
aarch64-linux-gnu
|
||||
aarch64-linux-musl
|
||||
arm-linux-gnu
|
||||
arm-linux-gnueabihf
|
||||
arm-linux-musl
|
||||
arm-linux-musleabihf
|
||||
arm64-darwin
|
||||
x86_64-darwin
|
||||
x86_64-linux-gnu
|
||||
x86_64-linux-musl
|
||||
|
||||
DEPENDENCIES
|
||||
http_parser.rb (~> 0.6.0)
|
||||
jekyll
|
||||
jekyll-archives
|
||||
jekyll-data
|
||||
jekyll-include-cache
|
||||
jekyll-scholar
|
||||
jekyll-webp
|
||||
jemoji
|
||||
json
|
||||
minimal-mistakes-jekyll
|
||||
tzinfo (>= 1, < 3)
|
||||
tzinfo-data
|
||||
wdm (~> 0.1.1)
|
||||
|
||||
BUNDLED WITH
|
||||
2.6.6
|
||||
@@ -1,12 +1,27 @@
|
||||
---
|
||||
title: "Aquarium MARL Environment"
|
||||
tags: [multi-agent-reinforcement-learning, MARL, simulation, emergence, complex-systems]
|
||||
tags: [MARL, simulation, emergence, complex-systems, environment]
|
||||
excerpt: "Aquarium: Open-source MARL environment for predator-prey studies."
|
||||
teaser: /figures/20_aquarium.png
|
||||
|
||||
---
|
||||
|
||||
<FloatingImage
|
||||
src="/figures/20_aquarium.png"
|
||||
alt="The Multi-Agent Reinforcement Learning Cycle. Plot showing how Agent receive individual rewards from the environment."
|
||||
width={450}
|
||||
height={350}
|
||||
caption="The Multi-Agent Reinforcement Learning Cycle."
|
||||
/>
|
||||
The study of complex interactions using Multi-Agent Reinforcement Learning (MARL), particularly **predator-prey dynamics**, often requires specialized simulation environments. To streamline research and avoid redundant development efforts, we introduce **Aquarium**: a versatile, open-source MARL environment specifically designed for investigating predator-prey scenarios and related **emergent behaviors**.
|
||||
<CenteredImage
|
||||
src="/figures/20_observation_vector.png"
|
||||
alt="Diagram detailing the construction of the observation vector for an agent"
|
||||
width={450}
|
||||
height={350}
|
||||
maxWidth="75%"
|
||||
caption="Construction details of the agent observation vector."
|
||||
/>
|
||||
|
||||
Key Features of Aquarium:
|
||||
|
||||
@@ -17,15 +32,13 @@ Key Features of Aquarium:
|
||||
* **Environmental Parameters:** Key dynamics like agent speeds, prey reproduction rates, predator starvation mechanisms, sensor ranges, and more are fully adjustable.
|
||||
* **Visualization & Recording:** Includes a resource-efficient visualizer and supports video recording of simulation runs, facilitating qualitative analysis and understanding of agent behaviors.
|
||||
|
||||
<div className="my-6 grid grid-cols-1 sm:grid-cols-2 gap-6 items-start">
|
||||
<div className="text-center">
|
||||
<Image src="/figures/20_observation_vector.png" alt="Diagram detailing the construction of the observation vector for an agent" width={450} height={350} />
|
||||
<figcaption className="text-sm text-muted-foreground mt-2">Construction details of the agent observation vector.</figcaption>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<Image src="/figures/20_capture_statistics.png" alt="Graphs showing average captures or rewards per prey agent under different training regimes" width={450} height={350} />
|
||||
<figcaption className="text-sm text-muted-foreground mt-2">Performance metrics (e.g., average captures/rewards) comparing training strategies.</figcaption>
|
||||
</div>
|
||||
</div>
|
||||
<CenteredImage
|
||||
src="/figures/20_capture_statistics.png"
|
||||
alt="Graphs showing average captures or rewards per prey agent under different training regimes"
|
||||
width={450}
|
||||
height={350}
|
||||
maxWidth="75%"
|
||||
caption="Performance metrics (e.g., average captures/rewards) comparing training strategies."
|
||||
/>
|
||||
|
||||
To demonstrate its capabilities, we conducted preliminary studies using **Proximal Policy Optimization (PPO)** to train multiple prey agents learning to evade a predator within Aquarium. Consistent with findings in existing MARL literature, our results showed that training agents with **individual policies led to suboptimal performance**, whereas utilizing **parameter sharing** among prey agents significantly improved coordination, sample efficiency, and overall evasion success. <Cite bibtexKey="kolle2024aquarium" />
|
||||
To demonstrate its capabilities, we conducted preliminary studies using **Proximal Policy Optimization (PPO)** to train multiple prey agents learning to evade a predator within Aquarium. Consistent with findings in existing MARL literature, our results showed that training agents with **individual policies led to suboptimal performance**, whereas utilizing **parameter sharing** among prey agents significantly improved coordination, sample efficiency, and overall evasion success. <Cite bibtexKey="kolle2024aquarium" />
|
||||
|
||||
71
src/app/connect/page.tsx
Normal file
71
src/app/connect/page.tsx
Normal file
@@ -0,0 +1,71 @@
|
||||
import { DATA } from "@/data/resume";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import BlurFade from "@/components/magicui/blur-fade";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { CenteredImage } from "@/components/centered-image";
|
||||
|
||||
const BLUR_FADE_DELAY = 0.05;
|
||||
|
||||
export default function ConnectPage() {
|
||||
const featuredSocials = ["Email", "LinkedIn", "GoogleScholar", "arXiv", "ResearchGate", "Gitea"];
|
||||
const socialLinks = Object.entries(DATA.contact.social)
|
||||
.filter(([name]) => featuredSocials.includes(name));
|
||||
|
||||
return (
|
||||
<main
|
||||
className="fixed inset-0 flex flex-col items-center justify-center bg-background"
|
||||
>
|
||||
<div className="flex flex-col items-center space-y-8 text-center max-w-sm w-full p-6">
|
||||
|
||||
<BlurFade delay={BLUR_FADE_DELAY * 1}>
|
||||
<Image
|
||||
src="/images/newshot_2.jpg"
|
||||
alt="Dr. Steffen Illium's headshot"
|
||||
width={128}
|
||||
height={128}
|
||||
className="rounded-full border shadow-sm"
|
||||
/>
|
||||
</BlurFade>
|
||||
|
||||
<BlurFade delay={BLUR_FADE_DELAY * 2}>
|
||||
<h1 className="text-4xl font-bold tracking-tight">
|
||||
Dr. Steffen Illium
|
||||
</h1>
|
||||
</BlurFade>
|
||||
|
||||
<BlurFade delay={BLUR_FADE_DELAY * 3}>
|
||||
<div className="grid grid-cols-2 sm:grid-cols-3 gap-2 w-full">
|
||||
{socialLinks.map(([name, social]) => (
|
||||
<Link href={social.url} key={name} target="_blank">
|
||||
<Button variant="outline" className="w-full">
|
||||
<social.icon className="size-4 mr-2" />
|
||||
{name}
|
||||
</Button>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</BlurFade>
|
||||
|
||||
<div className="flex w-full flex-col items-center space-y-4 pb-8">
|
||||
<BlurFade delay={BLUR_FADE_DELAY * 4} className="w-full px-8">
|
||||
<hr className="w-full" />
|
||||
</BlurFade>
|
||||
|
||||
<BlurFade delay={BLUR_FADE_DELAY * 5}>
|
||||
<a href="/images/qr.png" download="SteffenIllium-QRCode.png">
|
||||
<Image
|
||||
src="/images/qr.png"
|
||||
alt="QR Code to connect"
|
||||
width={256}
|
||||
height={256}
|
||||
className="rounded-xl shadow-lg hover:opacity-80 transition-opacity"
|
||||
/>
|
||||
</a>
|
||||
</BlurFade>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { DATA } from "@/data/resume";
|
||||
import Markdown from "react-markdown";
|
||||
import {ContactCard} from "@/components/contact-card";
|
||||
//import {ContactCard} from "@/components/contact-card";
|
||||
import {Button} from "@/components/ui/button";
|
||||
import Link from "next/link";
|
||||
|
||||
@@ -170,34 +170,34 @@ export default function Page() {
|
||||
</div>
|
||||
</section>
|
||||
<section id="contact">
|
||||
<div className="grid items-center justify-center gap-4 px-4 text-center md:px-6 w-full py-12">
|
||||
<BlurFade delay={BLUR_FADE_DELAY * 16}>
|
||||
<div className="space-y-3">
|
||||
<div className="inline-block rounded-lg bg-foreground text-background px-3 py-1 text-sm">
|
||||
Contact
|
||||
</div>
|
||||
<h2 className="text-3xl font-bold tracking-tighter sm:text-5xl">Get in Touch</h2>
|
||||
<p className="mx-auto max-w-[800px] text-muted-foreground md:text-xl/relaxed lg:text-base/relaxed xl:text-xl/relaxed">
|
||||
Want to collaborate or have a question?<br/>I'd love to hear from you.
|
||||
</p>
|
||||
<div className="grid items-center justify-center gap-4 px-4 text-center md:px-6 w-full py-12">
|
||||
<BlurFade delay={BLUR_FADE_DELAY * 16}>
|
||||
<div className="space-y-3">
|
||||
<div className="inline-block rounded-lg bg-foreground text-background px-3 py-1 text-sm">
|
||||
Contact
|
||||
</div>
|
||||
</BlurFade>
|
||||
<BlurFade delay={BLUR_FADE_DELAY * 18}>
|
||||
<div className="flex flex-wrap gap-2 justify-center">
|
||||
{Object.entries(DATA.contact.social)
|
||||
.filter(([_, social]) => social.navbar)
|
||||
.map(([name, social]) => (
|
||||
<Link href={social.url} key={name} target="_blank">
|
||||
<Badge className="flex gap-2 px-3 py-1 text-sm">
|
||||
<social.icon className="size-4" />
|
||||
{name}
|
||||
</Badge>
|
||||
</Link>
|
||||
))}
|
||||
<h2 className="text-3xl font-bold tracking-tighter sm:text-5xl">Get in Touch</h2>
|
||||
<p className="mx-auto max-w-[800px] text-muted-foreground md:text-xl/relaxed lg:text-base/relaxed xl:text-xl/relaxed">
|
||||
Want to collaborate or have a question?<br/>I'd love to hear from you.
|
||||
</p>
|
||||
</div>
|
||||
</BlurFade>
|
||||
</BlurFade>
|
||||
<BlurFade delay={BLUR_FADE_DELAY * 18}>
|
||||
<div className="flex flex-wrap gap-2 justify-center">
|
||||
{Object.entries(DATA.contact.social)
|
||||
.filter(([_, social]) => social.navbar)
|
||||
.map(([name, social]) => (
|
||||
<Link href={social.url} key={name} target="_blank">
|
||||
<Badge className="flex gap-2 px-3 py-1 text-sm">
|
||||
<social.icon className="size-4" />
|
||||
{name}
|
||||
</Badge>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
</BlurFade>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,10 +3,11 @@
|
||||
import { CitationProvider } from '@/context/citation-context';
|
||||
import { ReferencesContainer } from '@/components/references-container';
|
||||
import { CustomMDX } from '@/components/custom-mdx';
|
||||
import { Breadcrumbs } from "./breadcrumbs";
|
||||
import Link from 'next/link';
|
||||
import { Publication } from '@/lib/publications';
|
||||
import { ProjectNavigation } from './project-navigation';
|
||||
// --- FIX: Import the Image component ---
|
||||
import { Tag } from 'lucide-react';
|
||||
import Image from 'next/image';
|
||||
|
||||
interface Post {
|
||||
@@ -16,8 +17,8 @@ interface Post {
|
||||
excerpt?: string;
|
||||
teaser?: string;
|
||||
tags?: string[];
|
||||
// --- FIX: Add the optional 'icon' property to the interface ---
|
||||
icon?: string;
|
||||
date?: string;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -32,45 +33,70 @@ interface ArticleProps {
|
||||
export function Article({ post, publications, navigation, basePath }: ArticleProps) {
|
||||
return (
|
||||
<CitationProvider publications={publications}>
|
||||
<main className="flex flex-col min-h-[100dvh] py-8">
|
||||
<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.icon && (
|
||||
<div className="float-left mr-4 mb-0">
|
||||
<Image
|
||||
src={post.frontmatter.icon}
|
||||
alt={`${post.frontmatter.title} icon`}
|
||||
width={64}
|
||||
height={64}
|
||||
className=""
|
||||
<main className="flex flex-col min-h-[100dvh] space-y-10">
|
||||
<section id="article">
|
||||
<div className="mx-auto w-full max-w-4xl space-y-8">
|
||||
<div className="flex flex-col gap-y-2 sm:flex-row sm:justify-between sm:items-center">
|
||||
<Breadcrumbs
|
||||
basePath={basePath}
|
||||
baseLabel={basePath.charAt(0).toUpperCase() + basePath.slice(1)}
|
||||
/>
|
||||
{/* MOVED: The date is now on the right side of the container */}
|
||||
{post.frontmatter.date && (
|
||||
<time className="text-sm text-muted-foreground" dateTime={post.frontmatter.date}>
|
||||
{new Date(post.frontmatter.date).toLocaleDateString("en-US", {
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
})}
|
||||
</time>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<div className="space-y-2">
|
||||
<h1 className="text-3xl font-bold tracking-tighter sm:text-5xl xl:text-6xl/none">
|
||||
{post.frontmatter.title}
|
||||
</h1>
|
||||
{post.frontmatter.excerpt && (
|
||||
<p className="text-muted-foreground">
|
||||
{post.frontmatter.excerpt}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<article className="prose prose-stone dark:prose-invert max-w-none">
|
||||
{post.frontmatter.icon && (
|
||||
<div className="float-left mr-4 mb-2">
|
||||
<Image
|
||||
src={post.frontmatter.icon}
|
||||
alt={`${post.frontmatter.title} icon`}
|
||||
width={64}
|
||||
height={64}
|
||||
className="rounded-full"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<CustomMDX code={post.code} />
|
||||
</article>
|
||||
|
||||
<div className="mt-8">
|
||||
<CustomMDX code={post.code} />
|
||||
{/* Tags, References, and Navigation now live outside the `prose` scope for better control */}
|
||||
{post.frontmatter.tags && (
|
||||
<div className="flex flex-wrap items-center gap-x-2 gap-y-1">
|
||||
<Tag className="size-4 text-muted-foreground" />
|
||||
{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 transition-colors hover:bg-primary hover:text-primary-foreground">
|
||||
{tag}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<ReferencesContainer />
|
||||
|
||||
{navigation && (
|
||||
<ProjectNavigation prev={navigation.prev} next={navigation.next} basePath={basePath} />
|
||||
)}
|
||||
</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 transition-colors hover:bg-primary hover:text-primary-foreground">
|
||||
{tag}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<ReferencesContainer />
|
||||
|
||||
{navigation && (
|
||||
<ProjectNavigation prev={navigation.prev} next={navigation.next} basePath={basePath} />
|
||||
)}
|
||||
</article>
|
||||
</section>
|
||||
</main>
|
||||
</CitationProvider>
|
||||
);
|
||||
|
||||
21
src/components/breadcrumbs.tsx
Normal file
21
src/components/breadcrumbs.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import { ChevronRight, HomeIcon } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
|
||||
interface BreadcrumbsProps {
|
||||
basePath: string;
|
||||
baseLabel: string;
|
||||
}
|
||||
|
||||
export function Breadcrumbs({ basePath, baseLabel }: BreadcrumbsProps) {
|
||||
return (
|
||||
<nav className="flex items-center space-x-2 text-sm text-muted-foreground">
|
||||
<Link href="/" className="hover:text-primary transition-colors">
|
||||
<HomeIcon className="size-4" />
|
||||
</Link>
|
||||
<ChevronRight className="size-4" />
|
||||
<Link href={`/${basePath}`} className="hover:text-primary transition-colors">
|
||||
{baseLabel}
|
||||
</Link>
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
51
src/components/centered-image.tsx
Normal file
51
src/components/centered-image.tsx
Normal file
@@ -0,0 +1,51 @@
|
||||
"use client";
|
||||
|
||||
import Image from "next/image";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface CenteredImageProps {
|
||||
src: string;
|
||||
alt: string;
|
||||
width: number;
|
||||
height: number;
|
||||
caption?: string;
|
||||
// New prop to control the max width as a percentage
|
||||
maxWidth?: "50%" | "75%" | "100%";
|
||||
}
|
||||
|
||||
export function CenteredImage({
|
||||
src,
|
||||
alt,
|
||||
width,
|
||||
height,
|
||||
caption,
|
||||
maxWidth = "100%", // Default to 100% of the column width
|
||||
}: CenteredImageProps) {
|
||||
// Map the user-friendly prop to actual TailwindCSS classes
|
||||
const widthClasses = {
|
||||
"50%": "sm:max-w-[50%]",
|
||||
"75%": "sm:max-w-[75%]",
|
||||
"100%": "max-w-full", // `max-w-full` is the default for responsive images
|
||||
};
|
||||
|
||||
return (
|
||||
// Use <figure> for the image and its caption
|
||||
// `not-prose` escapes Tailwind Typography styles
|
||||
// `mx-auto` is the key to centering the block
|
||||
<figure className={cn("not-prose mx-auto my-6", widthClasses[maxWidth])}>
|
||||
<Image
|
||||
src={src}
|
||||
alt={alt}
|
||||
width={width}
|
||||
height={height}
|
||||
// `mx-auto` on the image itself helps in some flex contexts
|
||||
className="mx-auto rounded-lg"
|
||||
/>
|
||||
{caption && (
|
||||
<figcaption className="mt-2 text-sm text-center italic text-muted-foreground">
|
||||
{caption}
|
||||
</figcaption>
|
||||
)}
|
||||
</figure>
|
||||
);
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import { usePathname } from "next/navigation";
|
||||
export function Header() {
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
const pathname = usePathname();
|
||||
if (pathname === "/connect") return null;
|
||||
const isMainPage = pathname === "/";
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -6,6 +6,7 @@ import React from "react";
|
||||
import { InfoBox } from "./infobox";
|
||||
import { Cite } from "./cite";
|
||||
import { FloatingImage } from "./floating-image";
|
||||
import { CenteredImage } from "./centered-image";
|
||||
|
||||
|
||||
function CustomLink(props: any) {
|
||||
@@ -77,4 +78,5 @@ export const mdxComponents = {
|
||||
InfoBox,
|
||||
Cite,
|
||||
FloatingImage: FloatingImage,
|
||||
CenteredImage: CenteredImage,
|
||||
};
|
||||
|
||||
@@ -40,7 +40,7 @@ export function ReferencesContainer() {
|
||||
year={pub.year}
|
||||
pdfUrl={pub.pdfUrl}
|
||||
bibtex={pub.bibtex}
|
||||
// The pdfAvailable prop is now safely omitted
|
||||
pdfAvailable={pub.pdfAvailable}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -40,13 +40,16 @@ 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,
|
||||
});
|
||||
|
||||
return {
|
||||
slug,
|
||||
frontmatter: frontmatter as Frontmatter,
|
||||
frontmatter: { ...frontmatter, date: dateFromFilename } as Frontmatter,
|
||||
code,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2,8 +2,10 @@ 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(), "_bibliography.bib");
|
||||
|
||||
// --- FIX: Add `pdfAvailable` to the Publication interface ---
|
||||
export interface Publication {
|
||||
key: string;
|
||||
title: string;
|
||||
@@ -12,6 +14,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
|
||||
}
|
||||
|
||||
export function getPublicationsData(): Publication[] {
|
||||
@@ -25,7 +29,6 @@ export function getPublicationsData(): Publication[] {
|
||||
)
|
||||
: [];
|
||||
|
||||
// A simple way to reconstruct the bibtex string for a single entry
|
||||
let bibtexEntryString = `@${entry.type}{${entry.key},\n`;
|
||||
for (const [key, value] of Object.entries(entry.fields)) {
|
||||
if (key === 'author') {
|
||||
@@ -42,8 +45,10 @@ export function getPublicationsData(): Publication[] {
|
||||
}
|
||||
bibtexEntryString += `}`
|
||||
|
||||
|
||||
const journalField = entry.fields.booktitle || entry.fields.journal;
|
||||
|
||||
const pdfPath = path.join(process.cwd(), "public", "publications", `${entry.key}.pdf`);
|
||||
const pdfExists = fs.existsSync(pdfPath);
|
||||
|
||||
return {
|
||||
key: entry.key,
|
||||
@@ -54,6 +59,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
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user