Skip to content

Commit e7df0cf

Browse files
authored
feat: Add Vibenet build page (#12)
1 parent d6a64ed commit e7df0cf

12 files changed

Lines changed: 685 additions & 38 deletions

File tree

app/components/AppShell.tsx

Lines changed: 1 addition & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -197,12 +197,6 @@ function NavGlyph({ name }: NavGlyphProps) {
197197
);
198198
case 'vibenet':
199199
return <VibenetIcon size={common.width} className="nav-vibenet-icon" />;
200-
case 'tips':
201-
return (
202-
<svg {...common}>
203-
<path d="M13 2L4.5 14H12L11 22L19.5 10H12L13 2Z" />
204-
</svg>
205-
);
206200
default:
207201
return null;
208202
}
@@ -269,7 +263,7 @@ function SidebarContent({ onNavigate, hideBrand, layoutScope = 'desktop' }: { on
269263
)}
270264

271265
<nav style={styles.nav}>
272-
{NAV_ITEMS.filter((item) => item.icon !== 'tips').map((item) => {
266+
{NAV_ITEMS.filter((item) => item.icon).map((item) => {
273267
let active: boolean;
274268
if (item.href === '/') {
275269
active = pathname === '/';
@@ -293,21 +287,6 @@ function SidebarContent({ onNavigate, hideBrand, layoutScope = 'desktop' }: { on
293287
/>
294288
);
295289
})}
296-
{NAV_ITEMS.filter((item) => item.icon === 'tips').map((item) => {
297-
const active = pathname.startsWith(item.href);
298-
return (
299-
<NavRow
300-
key={item.href}
301-
icon={item.icon}
302-
label={item.label}
303-
href={item.href}
304-
active={active}
305-
enabled={item.enabled}
306-
onNavigate={onNavigate}
307-
layoutScope={layoutScope}
308-
/>
309-
);
310-
})}
311290
</nav>
312291

313292
<div style={styles.sidebarFooter}>

app/globals.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ html,
188188
body {
189189
margin: 0;
190190
padding: 0;
191-
scrollbar-gutter: stable;
191+
scrollbar-gutter: auto;
192192
}
193193

194194
body {

app/layout.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,8 @@ const baseSans = localFont({
5050
});
5151

5252
export const metadata = {
53-
title: 'Base Labs',
54-
description: 'Dashboards and stats for the Base network, in one place.',
53+
title: 'Base Chain',
54+
description: 'Dashboards and tools for Base Chain, in one place.',
5555
};
5656

5757
export default function RootLayout({ children }: PropsWithChildren) {

app/navigation.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export type NavIcon = 'home' | 'snapshots' | 'upgrades' | 'changelog' | 'vibenet' | 'tips';
1+
export type NavIcon = 'home' | 'snapshots' | 'upgrades' | 'changelog' | 'vibenet';
22

33
export type NavItem = {
44
label: string;
@@ -15,7 +15,6 @@ export const NAV_ITEMS: NavItem[] = [
1515
{ label: 'Upgrades', href: '/upgrades', icon: 'upgrades', enabled: true },
1616
{ label: 'Changelog', href: '/upgrades/changelog', icon: 'changelog', enabled: true },
1717
{ label: 'Vibenet', href: '/vibenet', icon: 'vibenet', enabled: true },
18-
{ label: 'TIPS', href: '/tips', icon: 'tips', enabled: false },
1918
];
2019

2120
export function titleForPath(pathname: string): string {

app/page.tsx

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,6 @@ const SURFACES: Surface[] = [
3232
description: 'Ephemeral Base devnet explorer and faucet for trying out in-flight features.',
3333
enabled: true,
3434
},
35-
{
36-
label: 'TIPS',
37-
href: '/tips',
38-
description: 'Transaction inclusion pool bundle metrics and audit history.',
39-
enabled: false,
40-
},
4135
];
4236

4337
function SurfaceCard({ surface }: SurfaceCardProps) {
@@ -77,18 +71,18 @@ export default function Home() {
7771
<header className="flex flex-col gap-8 border-b border-bds-gray-10 pb-12">
7872
<div className="max-w-3xl">
7973
<Text variant="caption" className="mb-4 text-base-blue">
80-
Base Labs
74+
Base Chain
8175
</Text>
8276
<Text variant="display" className="text-balance">
83-
Dashboards and stats for the Base network, in one place.
77+
Dashboards and tools for Base Chain, in one place.
8478
</Text>
8579
</div>
8680
</header>
8781

8882
<section>
8983
<SectionHeading
9084
title="Surfaces"
91-
description="Jump into the Base network dashboards and tools."
85+
description="Jump into the Base Chain dashboards and tools."
9286
className="mb-8"
9387
/>
9488
<div className="grid grid-cols-[repeat(auto-fit,minmax(240px,1fr))] gap-4">

app/vibenet/components/FeatureCard.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ export function FeatureCard({ feature }: FeatureCardProps) {
3535
const hasHighlights = highlights.length > 0;
3636
const links = feature.links ?? [];
3737
const showStatus = feature.status !== 'live';
38-
const hasActions = Boolean(feature.cta) || showStatus || links.length > 0;
38+
const hasActions =
39+
Boolean(feature.cta) || Boolean(feature.secondaryCta) || showStatus || links.length > 0;
3940

4041
return (
4142
<Card
@@ -66,6 +67,7 @@ export function FeatureCard({ feature }: FeatureCardProps) {
6667
{hasActions ? (
6768
<div className="mt-5 flex flex-wrap items-center gap-3">
6869
{feature.cta ? <LinkButton link={feature.cta} primary /> : null}
70+
{feature.secondaryCta ? <LinkButton link={feature.secondaryCta} /> : null}
6971
{showStatus ? (
7072
<span className="inline-flex items-center rounded-full border border-bds-gray-10 px-4 py-2 text-[14px] text-bds-gray-60 dark:border-white/10 dark:text-bds-gray-40">
7173
{STATUS_LABEL[feature.status]}

app/vibenet/data/features.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,10 @@ export const FEATURES: VibenetFeature[] = [
2828
{ title: 'Batch everything', detail: 'atomic multicall with top-level metadata' },
2929
],
3030
// The account demo has shipped, so the card links straight to it and shows
31-
// as `live` (which hides the "coming-soon" badge).
31+
// as `live` (which hides the "coming-soon" badge). "Build your own" points at
32+
// the EIP-8130 build guide.
3233
cta: { label: 'Open the demo', href: '/vibenet/demos/account' },
34+
secondaryCta: { label: 'Build your own', href: '/vibenet/demos/account/build' },
3335
links: [{ label: 'EIP-8130', href: 'https://eip.tools/eip/8130', external: true }],
3436
},
3537
];
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
'use client';
2+
3+
import { useCallback, useState } from 'react';
4+
5+
import { cn } from '../../../../components/ui/cn';
6+
7+
type CodeBlockProps = {
8+
/** The full (multi-line) code shown and copied to the clipboard. */
9+
code: string;
10+
/** Uppercase label shown in the header, e.g. a language or filename. */
11+
label?: string;
12+
className?: string;
13+
};
14+
15+
// Multi-line, monospaced code block with a copy-to-clipboard button. Mirrors
16+
// the single-line `CommandBox` styling but preserves whitespace and wraps long
17+
// snippets in a horizontal scroll area — used by the EIP-8130 build guide.
18+
export function CodeBlock({ code, label = 'Code', className }: CodeBlockProps) {
19+
const [copied, setCopied] = useState(false);
20+
21+
const handleCopy = useCallback(() => {
22+
async function copy() {
23+
try {
24+
await navigator.clipboard.writeText(code);
25+
setCopied(true);
26+
setTimeout(() => setCopied(false), 2000);
27+
} catch {
28+
// ignore clipboard failures (e.g. non-secure context)
29+
}
30+
}
31+
void copy();
32+
}, [code]);
33+
34+
return (
35+
<div
36+
className={cn(
37+
'overflow-hidden rounded-[10px] border border-bds-gray-10 dark:border-white/10',
38+
className,
39+
)}
40+
>
41+
<div className="flex items-center justify-between border-b border-bds-gray-10 bg-bds-gray-5 px-3 py-2 dark:border-white/10 dark:bg-white/5">
42+
<span className="font-mono text-[11px] uppercase tracking-[0.8px] text-bds-gray-60 dark:text-bds-gray-40">
43+
{label}
44+
</span>
45+
<button
46+
type="button"
47+
onClick={handleCopy}
48+
className="rounded-md border border-bds-gray-10 bg-white px-2.5 py-[3px] text-[12px] text-bds-gray-60 transition-colors hover:bg-bds-gray-5 dark:border-white/10 dark:bg-white/5 dark:text-bds-gray-40 dark:hover:bg-white/10"
49+
>
50+
{copied ? 'Copied' : 'Copy'}
51+
</button>
52+
</div>
53+
<div className="overflow-x-auto px-3.5 py-3">
54+
<pre className="font-mono text-[13px] leading-relaxed text-black dark:text-white">
55+
<code>{code}</code>
56+
</pre>
57+
</div>
58+
</div>
59+
);
60+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
'use client';
2+
3+
import { useState } from 'react';
4+
5+
import { Button } from '../../../../components/ui/Button';
6+
7+
type SkillActionsProps = {
8+
/** Raw SKILL.md content, read at build time and passed in from the page. */
9+
content: string;
10+
/** Suggested filename for the download. */
11+
filename: string;
12+
};
13+
14+
// Copy / download the `build-with-viem-eip8130` skill straight from the UI, so
15+
// devs can drop it into their AI coding agent without cloning the repo. The
16+
// content is baked in at build time (see page.tsx), so these are pure
17+
// client-side clipboard/blob actions.
18+
export function SkillActions({ content, filename }: SkillActionsProps) {
19+
const [copied, setCopied] = useState(false);
20+
const disabled = !content;
21+
22+
const copy = async () => {
23+
try {
24+
await navigator.clipboard.writeText(content);
25+
setCopied(true);
26+
setTimeout(() => setCopied(false), 1500);
27+
} catch {
28+
// clipboard unavailable (non-secure context) — use Download instead
29+
}
30+
};
31+
32+
const download = () => {
33+
const blob = new Blob([content], { type: 'text/markdown;charset=utf-8' });
34+
const url = URL.createObjectURL(blob);
35+
const a = document.createElement('a');
36+
a.href = url;
37+
a.download = filename;
38+
document.body.appendChild(a);
39+
a.click();
40+
a.remove();
41+
URL.revokeObjectURL(url);
42+
};
43+
44+
return (
45+
<div className="flex flex-wrap items-center gap-3">
46+
<Button variant="secondary" size="sm" onClick={download} disabled={disabled}>
47+
Grab the skill
48+
</Button>
49+
<Button variant="outline" size="sm" onClick={copy} disabled={disabled}>
50+
{copied ? 'Copied' : 'Copy'}
51+
</Button>
52+
</div>
53+
);
54+
}

0 commit comments

Comments
 (0)