Rebuild website from scratch on Tailwind v4 + shadcn/ui

- Fresh Astro 5 project with Tailwind v4 and shadcn/ui olive preset
- All shadcn components installed (Card, Button, Badge, Separator, etc.)
- Homepage with hero, terminal demo, workflows, agents, sources, compute
- Full docs system with 24 markdown pages across 5 sections
- Sidebar navigation with active state highlighting
- Prose styles for markdown content using shadcn color tokens
- Dark/light theme toggle with localStorage persistence
- Shiki everforest syntax themes for code blocks
- 404 page with VT323 font
- /docs redirect to installation page
- GitHub star count fetch
- Earthy green/cream oklch color palette matching TUI theme

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Advait Paliwal
2026-03-24 15:57:03 -07:00
parent 7d3fbc3f6b
commit 8f8cf2a4a9
61 changed files with 9369 additions and 2633 deletions

View File

@@ -1,21 +0,0 @@
---
interface Props {
class?: string;
size?: 'nav' | 'hero';
}
const { class: className = '', size = 'hero' } = Astro.props;
const sizeClasses = size === 'nav'
? 'text-2xl'
: 'text-6xl sm:text-7xl md:text-8xl';
---
<span
class:list={[
"font-['VT323'] text-accent inline-block tracking-tighter",
sizeClasses,
className,
]}
aria-label="Feynman"
>feynman</span>

View File

@@ -1,9 +0,0 @@
<footer class="py-8 mt-16">
<div class="max-w-6xl mx-auto px-6 flex flex-col sm:flex-row items-center justify-between gap-4">
<span class="text-sm text-text-dim">&copy; 2026 Companion Inc.</span>
<div class="flex gap-6">
<a href="https://github.com/getcompanion-ai/feynman" target="_blank" rel="noopener" class="text-sm text-text-dim hover:text-text-primary transition-colors">GitHub</a>
<a href="/docs/getting-started/installation" class="text-sm text-text-dim hover:text-text-primary transition-colors">Docs</a>
</div>
</div>
</footer>

View File

@@ -1,29 +0,0 @@
---
import ThemeToggle from './ThemeToggle.astro';
import AsciiLogo from './AsciiLogo.astro';
interface Props {
active?: 'home' | 'docs';
}
const { active = 'home' } = Astro.props;
---
<nav class="sticky top-0 z-50 bg-bg">
<div class="max-w-6xl mx-auto px-6 h-14 flex items-center justify-between">
<a href="/" class="hover:opacity-80 transition-opacity" aria-label="Feynman">
<AsciiLogo size="nav" />
</a>
<div class="flex items-center gap-6">
<a href="/docs/getting-started/installation"
class:list={["text-sm transition-colors", active === 'docs' ? 'text-text-primary' : 'text-text-muted hover:text-text-primary']}>
Docs
</a>
<a href="https://github.com/getcompanion-ai/feynman" target="_blank" rel="noopener"
class="text-sm text-text-muted hover:text-text-primary transition-colors">
GitHub
</a>
<ThemeToggle />
</div>
</div>
</nav>

View File

@@ -1,80 +0,0 @@
---
interface Props {
currentSlug: string;
}
const { currentSlug } = Astro.props;
const sections = [
{
title: 'Getting Started',
items: [
{ label: 'Installation', slug: 'getting-started/installation' },
{ label: 'Quick Start', slug: 'getting-started/quickstart' },
{ label: 'Setup', slug: 'getting-started/setup' },
{ label: 'Configuration', slug: 'getting-started/configuration' },
],
},
{
title: 'Workflows',
items: [
{ label: 'Deep Research', slug: 'workflows/deep-research' },
{ label: 'Literature Review', slug: 'workflows/literature-review' },
{ label: 'Peer Review', slug: 'workflows/review' },
{ label: 'Code Audit', slug: 'workflows/audit' },
{ label: 'Replication', slug: 'workflows/replication' },
{ label: 'Source Comparison', slug: 'workflows/compare' },
{ label: 'Draft Writing', slug: 'workflows/draft' },
{ label: 'Autoresearch', slug: 'workflows/autoresearch' },
{ label: 'Watch', slug: 'workflows/watch' },
],
},
{
title: 'Agents',
items: [
{ label: 'Researcher', slug: 'agents/researcher' },
{ label: 'Reviewer', slug: 'agents/reviewer' },
{ label: 'Writer', slug: 'agents/writer' },
{ label: 'Verifier', slug: 'agents/verifier' },
],
},
{
title: 'Tools',
items: [
{ label: 'AlphaXiv', slug: 'tools/alphaxiv' },
{ label: 'Web Search', slug: 'tools/web-search' },
{ label: 'Session Search', slug: 'tools/session-search' },
{ label: 'Preview', slug: 'tools/preview' },
],
},
{
title: 'Reference',
items: [
{ label: 'CLI Commands', slug: 'reference/cli-commands' },
{ label: 'Slash Commands', slug: 'reference/slash-commands' },
{ label: 'Package Stack', slug: 'reference/package-stack' },
],
},
];
---
<aside id="sidebar" class="w-64 shrink-0 h-[calc(100vh-3.5rem)] sticky top-14 overflow-y-auto py-6 pr-4 hidden lg:block border-r border-border">
{sections.map((section) => (
<div class="mb-6">
<div class="text-xs font-semibold text-accent uppercase tracking-wider px-3 mb-2">{section.title}</div>
{section.items.map((item) => (
<a
href={`/docs/${item.slug}`}
class:list={[
'block px-3 py-1.5 text-sm border-l-[2px] transition-colors',
currentSlug === item.slug
? 'border-accent text-text-primary'
: 'border-transparent text-text-muted hover:text-text-primary',
]}
>
{item.label}
</a>
))}
</div>
))}
</aside>

View File

@@ -1,53 +0,0 @@
<button id="theme-toggle" class="p-1.5 rounded-md text-text-muted hover:text-text-primary hover:bg-surface transition-colors" aria-label="Toggle theme">
<svg id="sun-icon" class="hidden w-[18px] h-[18px]" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<circle cx="12" cy="12" r="5" />
<path d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42" />
</svg>
<svg id="moon-icon" class="hidden w-[18px] h-[18px]" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" />
</svg>
</button>
<script is:inline>
(function() {
var stored = localStorage.getItem('theme');
var prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
var dark = stored === 'dark' || (!stored && prefersDark);
if (dark) {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
function updateIcons() {
var isDark = document.documentElement.classList.contains('dark');
var sun = document.getElementById('sun-icon');
var moon = document.getElementById('moon-icon');
if (sun) sun.style.display = isDark ? 'block' : 'none';
if (moon) moon.style.display = isDark ? 'none' : 'block';
}
function bindToggle() {
var btn = document.getElementById('theme-toggle');
if (btn && !btn._bound) {
btn._bound = true;
btn.addEventListener('click', function() {
document.documentElement.classList.toggle('dark');
var isDark = document.documentElement.classList.contains('dark');
localStorage.setItem('theme', isDark ? 'dark' : 'light');
updateIcons();
});
}
}
updateIcons();
document.addEventListener('DOMContentLoaded', function() {
updateIcons();
bindToggle();
});
document.addEventListener('astro:after-swap', function() {
updateIcons();
bindToggle();
});
})();
</script>

View File

@@ -0,0 +1,49 @@
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { Slot } from "radix-ui"
import { cn } from "@/lib/utils"
const badgeVariants = cva(
"group/badge inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-4xl border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3!",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80",
secondary:
"bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80",
destructive:
"bg-destructive/10 text-destructive focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 [a]:hover:bg-destructive/20",
outline:
"border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground",
ghost:
"hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50",
link: "text-primary underline-offset-4 hover:underline",
},
},
defaultVariants: {
variant: "default",
},
}
)
function Badge({
className,
variant = "default",
asChild = false,
...props
}: React.ComponentProps<"span"> &
VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
const Comp = asChild ? Slot.Root : "span"
return (
<Comp
data-slot="badge"
data-variant={variant}
className={cn(badgeVariants({ variant }), className)}
{...props}
/>
)
}
export { Badge, badgeVariants }

View File

@@ -0,0 +1,67 @@
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { Slot } from "radix-ui"
import { cn } from "@/lib/utils"
const buttonVariants = cva(
"group/button inline-flex shrink-0 items-center justify-center rounded-md border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/80",
outline:
"border-border bg-background shadow-xs hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50",
secondary:
"bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
ghost:
"hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50",
destructive:
"bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default:
"h-9 gap-1.5 px-2.5 in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
xs: "h-6 gap-1 rounded-[min(var(--radius-md),8px)] px-2 text-xs in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
sm: "h-8 gap-1 rounded-[min(var(--radius-md),10px)] px-2.5 in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5",
lg: "h-10 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-3 has-data-[icon=inline-start]:pl-3",
icon: "size-9",
"icon-xs":
"size-6 rounded-[min(var(--radius-md),8px)] in-data-[slot=button-group]:rounded-md [&_svg:not([class*='size-'])]:size-3",
"icon-sm":
"size-8 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-md",
"icon-lg": "size-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
function Button({
className,
variant = "default",
size = "default",
asChild = false,
...props
}: React.ComponentProps<"button"> &
VariantProps<typeof buttonVariants> & {
asChild?: boolean
}) {
const Comp = asChild ? Slot.Root : "button"
return (
<Comp
data-slot="button"
data-variant={variant}
data-size={size}
className={cn(buttonVariants({ variant, size, className }))}
{...props}
/>
)
}
export { Button, buttonVariants }

View File

@@ -0,0 +1,103 @@
import * as React from "react"
import { cn } from "@/lib/utils"
function Card({
className,
size = "default",
...props
}: React.ComponentProps<"div"> & { size?: "default" | "sm" }) {
return (
<div
data-slot="card"
data-size={size}
className={cn(
"group/card flex flex-col gap-6 overflow-hidden rounded-xl bg-card py-6 text-sm text-card-foreground shadow-xs ring-1 ring-foreground/10 has-[>img:first-child]:pt-0 data-[size=sm]:gap-4 data-[size=sm]:py-4 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl",
className
)}
{...props}
/>
)
}
function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-header"
className={cn(
"group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-t-xl px-6 group-data-[size=sm]/card:px-4 has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-6 group-data-[size=sm]/card:[.border-b]:pb-4",
className
)}
{...props}
/>
)
}
function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-title"
className={cn(
"font-heading text-base leading-normal font-medium group-data-[size=sm]/card:text-sm",
className
)}
{...props}
/>
)
}
function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-description"
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
)
}
function CardAction({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-action"
className={cn(
"col-start-2 row-span-2 row-start-1 self-start justify-self-end",
className
)}
{...props}
/>
)
}
function CardContent({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-content"
className={cn("px-6 group-data-[size=sm]/card:px-4", className)}
{...props}
/>
)
}
function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-footer"
className={cn(
"flex items-center rounded-b-xl px-6 group-data-[size=sm]/card:px-4 [.border-t]:pt-6 group-data-[size=sm]/card:[.border-t]:pt-4",
className
)}
{...props}
/>
)
}
export {
Card,
CardHeader,
CardFooter,
CardTitle,
CardAction,
CardDescription,
CardContent,
}

View File

@@ -0,0 +1,26 @@
import * as React from "react"
import { Separator as SeparatorPrimitive } from "radix-ui"
import { cn } from "@/lib/utils"
function Separator({
className,
orientation = "horizontal",
decorative = true,
...props
}: React.ComponentProps<typeof SeparatorPrimitive.Root>) {
return (
<SeparatorPrimitive.Root
data-slot="separator"
decorative={decorative}
orientation={orientation}
className={cn(
"shrink-0 bg-border data-horizontal:h-px data-horizontal:w-full data-vertical:w-px data-vertical:self-stretch",
className
)}
{...props}
/>
)
}
export { Separator }