feat: Redirect /presentations to slides.jeffemmett.com
slides.jeffemmett.com is now the canonical home for presentations. Server-side redirect via next.config.mjs + client-side fallback preserving hash fragments. Hero button links directly to new domain. Also: clean up v0/Vercel references from README and package.json. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
c2147fd3dc
commit
24197d859a
29
README.md
29
README.md
|
|
@ -1,30 +1 @@
|
|||
# Personal website redesign
|
||||
|
||||
*Automatically synced with your [v0.app](https://v0.app) deployments*
|
||||
|
||||
[](https://vercel.com/jeff-emmetts-projects/v0-personal-website-redesign)
|
||||
[](https://v0.app/chat/c8jGR8t3eSX)
|
||||
|
||||
## Overview
|
||||
|
||||
This repository will stay in sync with your deployed chats on [v0.app](https://v0.app).
|
||||
Any changes you make to your deployed app will be automatically pushed to this repository from [v0.app](https://v0.app).
|
||||
|
||||
## Deployment
|
||||
|
||||
Your project is live at:
|
||||
|
||||
**[https://vercel.com/jeff-emmetts-projects/v0-personal-website-redesign](https://vercel.com/jeff-emmetts-projects/v0-personal-website-redesign)**
|
||||
|
||||
## Build your app
|
||||
|
||||
Continue building your app on:
|
||||
|
||||
**[https://v0.app/chat/c8jGR8t3eSX](https://v0.app/chat/c8jGR8t3eSX)**
|
||||
|
||||
## How It Works
|
||||
|
||||
1. Create and modify your project using [v0.app](https://v0.app)
|
||||
2. Deploy your chats from the v0 interface
|
||||
3. Changes are automatically pushed to this repository
|
||||
4. Vercel deploys the latest version from this repository
|
||||
|
|
|
|||
|
|
@ -1,261 +1,15 @@
|
|||
"use client"
|
||||
|
||||
import { useEffect } from "react"
|
||||
import { Link2, ExternalLink, ArrowLeft } from "lucide-react"
|
||||
import { Card } from "@/components/ui/card"
|
||||
import { CursorEffect } from "@/components/cursor-effect"
|
||||
import { toast } from "sonner"
|
||||
|
||||
const presentations = [
|
||||
{
|
||||
id: "osmotic-governance",
|
||||
title: "Osmotic Governance",
|
||||
description: "Exploring the intersection of mycelium and emancipatory technologies",
|
||||
embedUrl: "https://slides.jeffemmett.com/osmotic-governance",
|
||||
venue: "Team Human with Douglas Rushkoff",
|
||||
videoUrl: "https://www.teamhuman.fm/episodes/238-jeff-emmett",
|
||||
videoLabel: "Listen to the full episode",
|
||||
tags: ["Mycelium", "Governance", "Emancipatory Tech"],
|
||||
},
|
||||
{
|
||||
id: "exploring-mycofi",
|
||||
title: "Exploring MycoFi",
|
||||
description: "Mycelial design patterns for Web3 and beyond",
|
||||
embedUrl: "https://slides.jeffemmett.com/exploring-mycofi",
|
||||
venue: "DevCon 7 in Bangkok",
|
||||
videoUrl: "https://www.youtube.com/watch?v=0A4jXL5eBaI",
|
||||
videoLabel: "Watch the full talk",
|
||||
tags: ["MycoFi", "Web3", "Biomimicry"],
|
||||
},
|
||||
{
|
||||
id: "mycofi-cofi-gathering",
|
||||
title: "MycoFi talk at CoFi Gathering",
|
||||
description: "Mycelial design patterns for Web3 and beyond",
|
||||
embedUrl: "https://slides.jeffemmett.com/mycofi-cofi-gathering",
|
||||
venue: "Greenpill Network",
|
||||
videoUrl: "https://www.youtube.com/watch?v=AFJFDajuCSg",
|
||||
videoLabel: "Watch the full talk",
|
||||
tags: ["MycoFi", "Greenpill", "CoFi"],
|
||||
},
|
||||
{
|
||||
id: "myco-mutualism",
|
||||
title: "Myco-Mutualism",
|
||||
description: "Exploring mutualistic relationships in mycelial networks and their applications to human systems",
|
||||
embedUrl: "https://slides.jeffemmett.com/myco-mutualism",
|
||||
venue: "The Mutualist Society",
|
||||
tags: ["Mutualism", "Mycelium", "Systems Design"],
|
||||
},
|
||||
{
|
||||
id: "psilocybernetics",
|
||||
title: "Psilocybernetics: The Emergence of Institutional Neuroplasticity",
|
||||
description: "Exploring the intersection of mycelium and cybernetic institutional design",
|
||||
embedUrl: "https://slides.jeffemmett.com/psilocybernetics",
|
||||
venue: "General Forum for Ethereum Localism",
|
||||
tags: ["Cybernetics", "Institutions", "Neuroplasticity"],
|
||||
},
|
||||
{
|
||||
id: "move-slow-fix-things",
|
||||
title: "Move Slow & Fix Things: The Commons Stack Design Pattern",
|
||||
description: "Design patterns for sustainable commons infrastructure",
|
||||
embedUrl: "https://slides.jeffemmett.com/move-slow-fix-things",
|
||||
venue: "ReFi Unconf @ the Commons Hub Austria",
|
||||
videoUrl: "https://www.youtube.com/live/i8qcg7FfpLM?si=onLcl8q5rz7cMViO&t=1362",
|
||||
videoLabel: "Watch the full talk",
|
||||
tags: ["Commons Stack", "ReFi", "Design Patterns"],
|
||||
},
|
||||
{
|
||||
id: "commons-stack-launch",
|
||||
title: "Commons Stack Launch & Open Sourcing cadCAD",
|
||||
description: "The launch of Commons Stack and the open sourcing of cadCAD for token engineering",
|
||||
embedUrl: "https://slides.jeffemmett.com/commons-stack-launch",
|
||||
venue: "Token Engineering Global Gathering (TEGG)",
|
||||
videoUrl: "https://youtu.be/qjdjX2m_p0Q?si=r2AXVnVyzCIxIOSc&t=20",
|
||||
videoLabel: "Watch the full talk",
|
||||
tags: ["Commons Stack", "cadCAD", "Token Engineering"],
|
||||
},
|
||||
{
|
||||
id: "conviction-voting",
|
||||
title: "New Tools for Dynamic Collective Intelligence: Conviction Voting & Variations",
|
||||
description: "Exploring innovative voting mechanisms for collective decision-making in decentralized systems",
|
||||
embedUrl: "https://slides.jeffemmett.com/conviction-voting",
|
||||
venue: "Conviction Voting Presentation",
|
||||
tags: ["Conviction Voting", "Governance", "Collective Intelligence"],
|
||||
},
|
||||
{
|
||||
id: "polycentric-governance",
|
||||
title: "Exploring Polycentric Governance in Web3 Ecosystems",
|
||||
description: "Understanding multi-level governance structures in decentralized networks",
|
||||
embedUrl: "https://slides.jeffemmett.com/polycentric-governance",
|
||||
venue: "OpenWeb Hackathon",
|
||||
videoUrl: "https://youtu.be/ZIWskNogafg?si=DmUbOQJaSRE1rdzq",
|
||||
videoLabel: "Watch the full talk",
|
||||
tags: ["Polycentric Governance", "Web3", "Ostrom"],
|
||||
},
|
||||
{
|
||||
id: "mycofi-myco-munnities",
|
||||
title: "MycoFi for Myco-munnities",
|
||||
description: "Exploring mycelial financial systems for community-based organizations",
|
||||
embedUrl: "https://slides.jeffemmett.com/mycofi-myco-munnities",
|
||||
venue: "CoFi Gathering in Liege",
|
||||
tags: ["MycoFi", "Community Finance", "CoFi"],
|
||||
},
|
||||
{
|
||||
id: "community-resilience",
|
||||
title: "Building Community Resilience in an Age of Crisis",
|
||||
description: "Internet outages during crises can disrupt communication, education, and access to vital information. Preparing for such disruptions is essential for communities operating in challenging environments.",
|
||||
embedUrl: "https://slides.jeffemmett.com/community-resilience",
|
||||
venue: "re:publica conference, May 2025",
|
||||
videoUrl: "https://www.youtube.com/watch?v=rTOLk7k9Ad8",
|
||||
videoLabel: "Watch the full talk",
|
||||
tags: ["Resilience", "Crisis Response", "Community Networks"],
|
||||
},
|
||||
]
|
||||
|
||||
function copyLink(id: string) {
|
||||
const url = `${window.location.origin}/presentations#${id}`
|
||||
navigator.clipboard.writeText(url).then(() => {
|
||||
toast.success("Link copied to clipboard")
|
||||
})
|
||||
}
|
||||
|
||||
export default function PresentationsPage() {
|
||||
export default function PresentationsRedirect() {
|
||||
useEffect(() => {
|
||||
if (window.location.hash) {
|
||||
const id = window.location.hash.slice(1)
|
||||
const el = document.getElementById(id)
|
||||
if (el) {
|
||||
setTimeout(() => {
|
||||
el.scrollIntoView({ behavior: "smooth", block: "start" })
|
||||
}, 100)
|
||||
}
|
||||
}
|
||||
window.location.replace(`https://slides.jeffemmett.com/${window.location.hash}`)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<>
|
||||
<CursorEffect />
|
||||
<main className="relative min-h-screen bg-background text-foreground">
|
||||
{/* Animated background blobs */}
|
||||
<div className="fixed inset-0 overflow-hidden opacity-15 pointer-events-none">
|
||||
<div className="absolute top-1/4 left-1/3 h-[500px] w-[500px] rounded-full bg-primary blur-[150px] animate-float" />
|
||||
<div
|
||||
className="absolute bottom-1/3 right-1/4 h-[400px] w-[400px] rounded-full bg-accent blur-[150px]"
|
||||
style={{ animationDelay: "3s" }}
|
||||
/>
|
||||
<div className="flex items-center justify-center min-h-screen bg-background text-muted-foreground">
|
||||
<p>Redirecting to slides.jeffemmett.com...</p>
|
||||
</div>
|
||||
|
||||
{/* Header */}
|
||||
<div className="relative z-10 border-b border-border/50 bg-background/80 backdrop-blur-sm">
|
||||
<div className="max-w-5xl mx-auto px-6 py-4 flex items-center justify-between">
|
||||
<a
|
||||
href="/"
|
||||
className="inline-flex items-center gap-2 text-sm text-muted-foreground hover:text-primary transition-colors"
|
||||
>
|
||||
<ArrowLeft className="h-4 w-4" />
|
||||
Jeff Emmett
|
||||
</a>
|
||||
<span className="text-sm font-mono text-muted-foreground border border-border px-3 py-1 rounded-full">
|
||||
{"{ Presentations }"}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Hero */}
|
||||
<div className="relative z-10 max-w-5xl mx-auto px-6 pt-20 pb-16">
|
||||
<h1 className="text-5xl md:text-6xl font-bold mb-6">Presentations</h1>
|
||||
<p className="text-xl text-muted-foreground max-w-2xl text-balance leading-relaxed">
|
||||
Research into the intersection of mycelium networks, emancipatory technologies,
|
||||
and convivial tooling. Designing systems that support collective action and
|
||||
community self-organization.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Presentations grid */}
|
||||
<div className="relative z-10 max-w-5xl mx-auto px-6 pb-32">
|
||||
<div className="space-y-10">
|
||||
{presentations.map((p) => (
|
||||
<Card
|
||||
key={p.id}
|
||||
id={p.id}
|
||||
className="group/card overflow-hidden bg-card/80 backdrop-blur-sm border-border hover:border-primary/50 transition-all duration-300 scroll-mt-24"
|
||||
>
|
||||
<div className="p-6 pb-4">
|
||||
<h2 className="text-2xl font-semibold group-hover/card:text-primary transition-colors">
|
||||
<a
|
||||
href={`#${p.id}`}
|
||||
onClick={(e) => {
|
||||
e.preventDefault()
|
||||
window.history.replaceState(null, "", `#${p.id}`)
|
||||
document.getElementById(p.id)?.scrollIntoView({ behavior: "smooth", block: "start" })
|
||||
copyLink(p.id)
|
||||
}}
|
||||
className="group/link inline-flex items-start gap-2"
|
||||
>
|
||||
<span>{p.title}</span>
|
||||
<Link2 className="h-5 w-5 mt-1 opacity-0 group-hover/link:opacity-100 text-muted-foreground transition-opacity shrink-0" />
|
||||
</a>
|
||||
</h2>
|
||||
<p className="text-muted-foreground mt-2 leading-relaxed">{p.description}</p>
|
||||
{p.tags && (
|
||||
<div className="flex flex-wrap gap-2 mt-3">
|
||||
{p.tags.map((tag, i) => (
|
||||
<span
|
||||
key={i}
|
||||
className="text-xs font-mono px-3 py-1 bg-secondary text-secondary-foreground rounded-full"
|
||||
>
|
||||
{tag}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="px-6 pb-4">
|
||||
<div className="rounded-lg overflow-hidden border border-border/50">
|
||||
<div style={{ position: "relative", paddingTop: "max(60%, 324px)", width: "100%", height: 0 }}>
|
||||
<iframe
|
||||
style={{ position: "absolute", border: "none", width: "100%", height: "100%", left: 0, top: 0 }}
|
||||
src={p.embedUrl}
|
||||
seamless
|
||||
scrolling="no"
|
||||
frameBorder="0"
|
||||
allowTransparency
|
||||
allowFullScreen
|
||||
title={p.title}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="px-6 pb-6 flex flex-wrap items-center justify-between gap-3 border-t border-border/30 pt-4 mx-6">
|
||||
<span className="text-sm text-muted-foreground italic font-mono">{p.venue}</span>
|
||||
{p.videoUrl ? (
|
||||
<a
|
||||
href={p.videoUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="group/ext inline-flex items-center gap-1.5 text-sm font-medium px-4 py-2 bg-secondary text-secondary-foreground rounded-lg transition-all hover:scale-105 hover:bg-primary hover:text-primary-foreground"
|
||||
>
|
||||
{p.videoLabel} <ExternalLink className="h-3.5 w-3.5" />
|
||||
</a>
|
||||
) : (
|
||||
<span className="text-sm text-muted-foreground">Video coming soon</span>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Footer */}
|
||||
<div className="relative z-10 border-t border-border py-8 bg-card/30">
|
||||
<div className="max-w-5xl mx-auto px-6 text-center">
|
||||
<p className="text-muted-foreground font-mono text-sm">
|
||||
© 2025 Jeff Emmett. Built with Next.js & TailwindCSS
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ export function HeroSection() {
|
|||
<div className="absolute inset-0 bg-accent opacity-0 group-hover:opacity-100 transition-opacity" />
|
||||
</a>
|
||||
<a
|
||||
href="/presentations"
|
||||
href="https://slides.jeffemmett.com"
|
||||
className="px-8 py-4 bg-secondary text-secondary-foreground rounded-lg font-medium transition-all hover:scale-105 hover:bg-secondary/80"
|
||||
>
|
||||
Presentations
|
||||
|
|
|
|||
|
|
@ -7,6 +7,20 @@ const nextConfig = {
|
|||
images: {
|
||||
unoptimized: true,
|
||||
},
|
||||
async redirects() {
|
||||
return [
|
||||
{
|
||||
source: '/presentations',
|
||||
destination: 'https://slides.jeffemmett.com',
|
||||
permanent: true,
|
||||
},
|
||||
{
|
||||
source: '/presentations/:path*',
|
||||
destination: 'https://slides.jeffemmett.com/:path*',
|
||||
permanent: true,
|
||||
},
|
||||
]
|
||||
},
|
||||
}
|
||||
|
||||
export default nextConfig
|
||||
|
|
|
|||
|
|
@ -37,7 +37,6 @@
|
|||
"@radix-ui/react-toggle": "1.1.1",
|
||||
"@radix-ui/react-toggle-group": "1.1.1",
|
||||
"@radix-ui/react-tooltip": "1.1.6",
|
||||
"@vercel/analytics": "latest",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
|
|
|
|||
Loading…
Reference in New Issue