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"
|
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."
|
excerpt: "Aquarium: Open-source MARL environment for predator-prey studies."
|
||||||
teaser: /figures/20_aquarium.png
|
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**.
|
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:
|
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.
|
* **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.
|
* **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">
|
<CenteredImage
|
||||||
<div className="text-center">
|
src="/figures/20_capture_statistics.png"
|
||||||
<Image src="/figures/20_observation_vector.png" alt="Diagram detailing the construction of the observation vector for an agent" width={450} height={350} />
|
alt="Graphs showing average captures or rewards per prey agent under different training regimes"
|
||||||
<figcaption className="text-sm text-muted-foreground mt-2">Construction details of the agent observation vector.</figcaption>
|
width={450}
|
||||||
</div>
|
height={350}
|
||||||
<div className="text-center">
|
maxWidth="75%"
|
||||||
<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} />
|
caption="Performance metrics (e.g., average captures/rewards) comparing training strategies."
|
||||||
<figcaption className="text-sm text-muted-foreground mt-2">Performance metrics (e.g., average captures/rewards) comparing training strategies.</figcaption>
|
/>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
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 { Badge } from "@/components/ui/badge";
|
||||||
import { DATA } from "@/data/resume";
|
import { DATA } from "@/data/resume";
|
||||||
import Markdown from "react-markdown";
|
import Markdown from "react-markdown";
|
||||||
import {ContactCard} from "@/components/contact-card";
|
//import {ContactCard} from "@/components/contact-card";
|
||||||
import {Button} from "@/components/ui/button";
|
import {Button} from "@/components/ui/button";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
|
||||||
@@ -170,34 +170,34 @@ export default function Page() {
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<section id="contact">
|
<section id="contact">
|
||||||
<div className="grid items-center justify-center gap-4 px-4 text-center md:px-6 w-full py-12">
|
<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}>
|
<BlurFade delay={BLUR_FADE_DELAY * 16}>
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<div className="inline-block rounded-lg bg-foreground text-background px-3 py-1 text-sm">
|
<div className="inline-block rounded-lg bg-foreground text-background px-3 py-1 text-sm">
|
||||||
Contact
|
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>
|
</div>
|
||||||
</BlurFade>
|
<h2 className="text-3xl font-bold tracking-tighter sm:text-5xl">Get in Touch</h2>
|
||||||
<BlurFade delay={BLUR_FADE_DELAY * 18}>
|
<p className="mx-auto max-w-[800px] text-muted-foreground md:text-xl/relaxed lg:text-base/relaxed xl:text-xl/relaxed">
|
||||||
<div className="flex flex-wrap gap-2 justify-center">
|
Want to collaborate or have a question?<br/>I'd love to hear from you.
|
||||||
{Object.entries(DATA.contact.social)
|
</p>
|
||||||
.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>
|
</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>
|
</div>
|
||||||
</section>
|
</BlurFade>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
</main>
|
</main>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,10 +3,11 @@
|
|||||||
import { CitationProvider } from '@/context/citation-context';
|
import { CitationProvider } from '@/context/citation-context';
|
||||||
import { ReferencesContainer } from '@/components/references-container';
|
import { ReferencesContainer } from '@/components/references-container';
|
||||||
import { CustomMDX } from '@/components/custom-mdx';
|
import { CustomMDX } from '@/components/custom-mdx';
|
||||||
|
import { Breadcrumbs } from "./breadcrumbs";
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { Publication } from '@/lib/publications';
|
import { Publication } from '@/lib/publications';
|
||||||
import { ProjectNavigation } from './project-navigation';
|
import { ProjectNavigation } from './project-navigation';
|
||||||
// --- FIX: Import the Image component ---
|
import { Tag } from 'lucide-react';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
|
|
||||||
interface Post {
|
interface Post {
|
||||||
@@ -16,8 +17,8 @@ interface Post {
|
|||||||
excerpt?: string;
|
excerpt?: string;
|
||||||
teaser?: string;
|
teaser?: string;
|
||||||
tags?: string[];
|
tags?: string[];
|
||||||
// --- FIX: Add the optional 'icon' property to the interface ---
|
|
||||||
icon?: string;
|
icon?: string;
|
||||||
|
date?: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,45 +33,70 @@ interface ArticleProps {
|
|||||||
export function Article({ post, publications, navigation, basePath }: ArticleProps) {
|
export function Article({ post, publications, navigation, basePath }: ArticleProps) {
|
||||||
return (
|
return (
|
||||||
<CitationProvider publications={publications}>
|
<CitationProvider publications={publications}>
|
||||||
<main className="flex flex-col min-h-[100dvh] py-8">
|
<main className="flex flex-col min-h-[100dvh] space-y-10">
|
||||||
<article className="prose prose-stone dark:prose-invert max-w-none">
|
<section id="article">
|
||||||
<header className="mb-8">
|
<div className="mx-auto w-full max-w-4xl space-y-8">
|
||||||
<h1 className="text-4xl font-bold tracking-tighter sm:text-5xl mb-2">{post.frontmatter.title}</h1>
|
<div className="flex flex-col gap-y-2 sm:flex-row sm:justify-between sm:items-center">
|
||||||
{post.frontmatter.excerpt && (<p className="text-lg text-muted-foreground mt-1">{post.frontmatter.excerpt}</p>)}
|
<Breadcrumbs
|
||||||
</header>
|
basePath={basePath}
|
||||||
|
baseLabel={basePath.charAt(0).toUpperCase() + basePath.slice(1)}
|
||||||
{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=""
|
|
||||||
/>
|
/>
|
||||||
|
{/* 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>
|
||||||
)}
|
<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">
|
{/* Tags, References, and Navigation now live outside the `prose` scope for better control */}
|
||||||
<CustomMDX code={post.code} />
|
{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>
|
</div>
|
||||||
|
</section>
|
||||||
{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>
|
|
||||||
</main>
|
</main>
|
||||||
</CitationProvider>
|
</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() {
|
export function Header() {
|
||||||
const [isVisible, setIsVisible] = useState(false);
|
const [isVisible, setIsVisible] = useState(false);
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
|
if (pathname === "/connect") return null;
|
||||||
const isMainPage = pathname === "/";
|
const isMainPage = pathname === "/";
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import React from "react";
|
|||||||
import { InfoBox } from "./infobox";
|
import { InfoBox } from "./infobox";
|
||||||
import { Cite } from "./cite";
|
import { Cite } from "./cite";
|
||||||
import { FloatingImage } from "./floating-image";
|
import { FloatingImage } from "./floating-image";
|
||||||
|
import { CenteredImage } from "./centered-image";
|
||||||
|
|
||||||
|
|
||||||
function CustomLink(props: any) {
|
function CustomLink(props: any) {
|
||||||
@@ -77,4 +78,5 @@ export const mdxComponents = {
|
|||||||
InfoBox,
|
InfoBox,
|
||||||
Cite,
|
Cite,
|
||||||
FloatingImage: FloatingImage,
|
FloatingImage: FloatingImage,
|
||||||
|
CenteredImage: CenteredImage,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ export function ReferencesContainer() {
|
|||||||
year={pub.year}
|
year={pub.year}
|
||||||
pdfUrl={pub.pdfUrl}
|
pdfUrl={pub.pdfUrl}
|
||||||
bibtex={pub.bibtex}
|
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 fullPath = path.join(typeDirectory, fileName);
|
||||||
const fileContents = fs.readFileSync(fullPath, 'utf8');
|
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({
|
const { code, frontmatter } = await bundleMDX({
|
||||||
source: fileContents,
|
source: fileContents,
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
slug,
|
slug,
|
||||||
frontmatter: frontmatter as Frontmatter,
|
frontmatter: { ...frontmatter, date: dateFromFilename } as Frontmatter,
|
||||||
code,
|
code,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,10 @@ import { parse } from "@retorquere/bibtex-parser";
|
|||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
|
|
||||||
|
// Your existing path is correct.
|
||||||
const bibliographyPath = path.join(process.cwd(), "_bibliography.bib");
|
const bibliographyPath = path.join(process.cwd(), "_bibliography.bib");
|
||||||
|
|
||||||
|
// --- FIX: Add `pdfAvailable` to the Publication interface ---
|
||||||
export interface Publication {
|
export interface Publication {
|
||||||
key: string;
|
key: string;
|
||||||
title: string;
|
title: string;
|
||||||
@@ -12,6 +14,8 @@ export interface Publication {
|
|||||||
year: string;
|
year: string;
|
||||||
bibtex: string;
|
bibtex: string;
|
||||||
pdfUrl: 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[] {
|
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`;
|
let bibtexEntryString = `@${entry.type}{${entry.key},\n`;
|
||||||
for (const [key, value] of Object.entries(entry.fields)) {
|
for (const [key, value] of Object.entries(entry.fields)) {
|
||||||
if (key === 'author') {
|
if (key === 'author') {
|
||||||
@@ -42,8 +45,10 @@ export function getPublicationsData(): Publication[] {
|
|||||||
}
|
}
|
||||||
bibtexEntryString += `}`
|
bibtexEntryString += `}`
|
||||||
|
|
||||||
|
|
||||||
const journalField = entry.fields.booktitle || entry.fields.journal;
|
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 {
|
return {
|
||||||
key: entry.key,
|
key: entry.key,
|
||||||
@@ -54,6 +59,7 @@ export function getPublicationsData(): Publication[] {
|
|||||||
url: Array.isArray(entry.fields.url) ? entry.fields.url.join(" ") : entry.fields.url,
|
url: Array.isArray(entry.fields.url) ? entry.fields.url.join(" ") : entry.fields.url,
|
||||||
bibtex: bibtexEntryString,
|
bibtex: bibtexEntryString,
|
||||||
pdfUrl: `/publications/${entry.key}.pdf`,
|
pdfUrl: `/publications/${entry.key}.pdf`,
|
||||||
|
pdfAvailable: pdfExists, // <-- Add the result here
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user