Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ MIT License
Copyright (c) 2026 Base

NOTE: Some files in this repository are excluded from this license. See NOTICE
for the exceptions, which include the Base Sans brand typefaces under
for the exceptions, which include the Google Sans Flex typeface under
public/fonts/, and for third-party attribution.

Permission is hereby granted, free of charge, to any person obtaining a copy
Expand Down
19 changes: 8 additions & 11 deletions NOTICE
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,16 @@ exceptions listed below. Where this file and LICENSE disagree, this file
governs for the files it names.


Base Sans and Base Sans Mono (brand typefaces) — NOT MIT licensed
-----------------------------------------------------------------
Google Sans Flex (UI typeface) — NOT MIT licensed
-------------------------------------------------

public/fonts/BaseSans/*
public/fonts/BaseSansMono/*
public/fonts/GoogleSansFlex/*

These typefaces are Base's proprietary brand assets. Copyright (c) Base. All
rights reserved.

They are included in this repository solely so that this application renders
with correct branding. They are NOT covered by the MIT license in LICENSE, and
no license to them is granted by it. You may not copy, redistribute, embed,
modify, sublicense, or otherwise use these font files outside of this project.
This typeface is a proprietary Google font, included here solely so that this
application renders with the same UI sans as the Base dashboard. It is NOT
covered by the MIT license in LICENSE, and no license to it is granted by it.
You may not copy, redistribute, embed, modify, sublicense, or otherwise use
these font files outside of this project.

If you fork this repository or reuse its code, remove these files and
substitute your own typeface. Note that they are loaded via next/font/local in
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ outside this repo at the org/platform level.
- `vibenet/` — devnet explorer and faucet
- `components/` — shared shell and UI primitives
- `theme.ts` / `spectrum.ts` / `globals.css` — BDS design tokens
- `public/` — Base Sans fonts and images
- `public/` — Google Sans Flex, Doto, and images

## Environment

Expand Down
44 changes: 24 additions & 20 deletions app/components/AppShell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { AnimatedBaseLogo, BaseMark } from './ui/AnimatedBaseLogo';
import { Breadcrumb } from './ui/Breadcrumb';
import { cn } from './ui/cn';
import { AnimatedArrowIcon, CloseIcon } from './ui/icons';
import { Text } from './ui/Text';
import { Text, textVariantClasses } from './ui/Text';

const SIDEBAR_WIDTH = 248;
/** Matches base.org's nav logo size, which the animation timings are tuned for. */
Expand Down Expand Up @@ -84,7 +84,6 @@ const styles: Record<string, CSSProperties> = {
gap: 10,
padding: '9px 10px',
borderRadius: 8,
fontSize: 14,
},
navIcon: { display: 'inline-flex', width: 20, height: 20 },
soon: {
Expand Down Expand Up @@ -172,6 +171,13 @@ const styles: Record<string, CSSProperties> = {
contentInner: { width: '100%', maxWidth: 1280, margin: '0 auto', padding: '24px 28px 80px', flexGrow: 1, flexShrink: 0, display: 'flex', flexDirection: 'column' as const, minWidth: 0 },
};

const TEXT_TRIM = '[text-box-trim:trim-both] [text-box-edge:cap_alphabetic]';

const NAV_LABEL = `${textVariantClasses['label.medium']} box-content min-h-[1lh]`;

const NAV_FOCUS_RING =
'outline-none focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-blue';

function NavGlyph({ name }: NavGlyphProps) {
const common = {
width: 20,
Expand Down Expand Up @@ -293,7 +299,7 @@ function NavRow({ icon, label, href, active, enabled, hasChildren, onNavigate }:
<NavGlyph name={icon} />
</span>
)}
<Text as="span" variant="label.medium" tone="inherit">{label}</Text>
<span className={TEXT_TRIM}>{label}</span>
{!enabled && <span style={styles.soon}>Soon</span>}
{hasChildren && (
<span style={{ marginLeft: 'auto', color: 'var(--bds-gray-50)' }}>
Expand All @@ -307,18 +313,17 @@ function NavRow({ icon, label, href, active, enabled, hasChildren, onNavigate }:
...styles.navRow,
...styles.navLink,
color,
fontWeight: active ? 500 : 400,
cursor: enabled ? 'pointer' : 'default',
};

if (!enabled) {
return <div style={rowStyle}>{content}</div>;
return <div className={NAV_LABEL} style={rowStyle}>{content}</div>;
}

return (
<Link
href={href}
className={`${hasChildren ? 'group ' : ''}nav-row-hover${active ? ' nav-row-active' : ''} ${NAV_FOCUS_RING}`}
className={`${hasChildren ? 'group ' : ''}nav-row-hover${active ? ' nav-row-active' : ''} ${NAV_LABEL} ${NAV_FOCUS_RING}`}
style={rowStyle}
onClick={(event) => {
if (opensInNewTab(event)) return;
Expand Down Expand Up @@ -380,14 +385,11 @@ const APP_BANNER_HEIGHT = '2.25rem';
const PENDING_PATH_TIMEOUT_MS = 5000;

const SIDEBAR_FOOTER_LINK =
'group outline-none text-bds-gray-50 transition-colors duration-150 hover:text-bds-gray-80';
`group outline-none ${NAV_LABEL} text-bds-gray-50 transition-colors duration-150 hover:text-bds-gray-80`;

const SIDEBAR_FOOTER_LINK_LABEL =
'inline-flex items-center gap-2.5 rounded-lg group-focus-visible:outline group-focus-visible:outline-2 group-focus-visible:outline-offset-2 group-focus-visible:outline-brand-blue';

const NAV_FOCUS_RING =
'outline-none focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-blue';

// Track and thumb. Shared with the `:not(...)` list in disableAnimation so the
// page-wide no-transition stamp cannot override the 180ms slide.
const THEME_SWITCH_ANIM = 'theme-switch-anim';
Expand Down Expand Up @@ -509,7 +511,7 @@ function SidebarContent({ dark, onToggleTheme, onNavigate, hideBrand }: SidebarC
>
<Link
href="/"
className={`nav-header-hover group ${NAV_FOCUS_RING}`}
className={`nav-header-hover group ${NAV_LABEL} ${NAV_FOCUS_RING}`}
style={{ ...styles.navLink, display: 'flex', alignItems: 'center', padding: '9px 6px 9px 2px', marginBottom: 2, color: 'var(--bds-gray-50)' }}
onClick={(event) => {
if (opensInNewTab(event)) return;
Expand Down Expand Up @@ -537,7 +539,9 @@ function SidebarContent({ dark, onToggleTheme, onNavigate, hideBrand }: SidebarC
className="transition-[stroke-dashoffset] duration-200 ease-out group-hover:[stroke-dashoffset:0]"
/>
</svg>
<Text as="span" variant="label.medium" style={{ flex: 1, textAlign: 'center', paddingRight: 16 }}>{activeParent.label}</Text>
<span className="flex-1 text-center pr-4">
<span className={TEXT_TRIM}>{activeParent.label}</span>
</span>
</Link>
<nav style={styles.nav}>
{activeParent.children!.map((child) => {
Expand Down Expand Up @@ -584,7 +588,7 @@ function SidebarContent({ dark, onToggleTheme, onNavigate, hideBrand }: SidebarC
<path d="M2.25 12h4.5l2.25-6 4.5 12 2.25-6h6.75" />
</svg>
</span>
<Text as="span" variant="label.medium" tone="inherit">Status</Text>
<span className={TEXT_TRIM}>Status</span>
</span>
</a>
<a href="https://base.org/discord" target="_blank" rel="noreferrer" className={SIDEBAR_FOOTER_LINK} style={styles.footerLink}>
Expand All @@ -594,7 +598,7 @@ function SidebarContent({ dark, onToggleTheme, onNavigate, hideBrand }: SidebarC
<path d="M216.856 16.597C200.285 8.843 182.566 3.208 164.042 0c-2.275 4.113-4.933 9.645-6.766 14.046-19.692-2.961-39.203-2.961-58.533 0-1.832-4.401-4.55-9.933-6.846-14.046C73.353 3.208 55.613 8.864 39.042 16.638 5.618 67.147-3.443 116.401 1.087 164.956c22.169 16.555 43.653 26.612 64.775 33.193 5.215-7.177 9.866-14.807 13.873-22.848-7.631-2.9-14.94-6.478-21.846-10.632 1.832-1.357 3.624-2.776 5.356-4.237 42.122 19.702 87.89 19.702 129.51 0 1.751 1.46 3.543 2.88 5.355 4.237-6.926 4.174-14.255 7.753-21.886 10.653 4.006 8.02 8.638 15.67 13.873 22.848 21.142-6.58 42.646-16.637 64.815-33.213 5.316-56.288-9.08-105.09-38.056-148.36ZM85.474 135.095c-12.645 0-23.015-11.805-23.015-26.18s10.149-26.2 23.015-26.2c12.867 0 23.236 11.804 23.015 26.2.02 14.375-10.148 26.18-23.015 26.18Zm85.051 0c-12.645 0-23.014-11.805-23.014-26.18s10.148-26.2 23.014-26.2c12.867 0 23.236 11.804 23.015 26.2 0 14.375-10.148 26.18-23.015 26.18Z" />
</svg>
</span>
<Text as="span" variant="label.medium" tone="inherit">Support</Text>
<span className={TEXT_TRIM}>Support</span>
</span>
</a>
<a href="https://docs.base.org" target="_blank" rel="noreferrer" className={SIDEBAR_FOOTER_LINK} style={styles.footerLink}>
Expand All @@ -604,7 +608,7 @@ function SidebarContent({ dark, onToggleTheme, onNavigate, hideBrand }: SidebarC
<path d="M15 15H19.5M15 25H19.5M15 20H16M24 15L25 15M24 25H25M21 20H25M13 31H27C29.2091 31 31 29.2091 31 27V13C31 10.7909 29.2091 9 27 9H13C10.7909 9 9 10.7909 9 13V27C9 29.2091 10.7909 31 13 31Z" />
</svg>
</span>
<Text as="span" variant="label.medium" tone="inherit">Docs</Text>
<span className={TEXT_TRIM}>Docs</span>
</span>
</a>
<div style={styles.footerLastRow}>
Expand All @@ -615,7 +619,7 @@ function SidebarContent({ dark, onToggleTheme, onNavigate, hideBrand }: SidebarC
<path d="M12.75 19.5V18.75C12.75 16.76 11.96 14.85 10.55 13.45C9.15 12.04 7.24 11.25 5.25 11.25H4.5M4.5 4.5H5.25C13.12 4.5 19.5 10.88 19.5 18.75V19.5M6 18.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0Z" />
</svg>
</span>
<Text as="span" variant="label.medium" tone="inherit">Blog</Text>
<span className={TEXT_TRIM}>Blog</span>
</span>
</a>
{/* `role="switch"` rather than a plain button: the control reports a state
Expand Down Expand Up @@ -679,8 +683,8 @@ function GlobalBanner({ dismissed, onDismiss, className, height }: GlobalBannerP
>
<div className="mx-auto flex w-full max-w-6xl flex-col items-center justify-center min-[860px]:flex-row min-[860px]:gap-5">
<div className="flex min-w-0 flex-wrap items-center justify-center gap-x-2 gap-y-1 min-[860px]:flex-nowrap">
<Text as="span" variant="label.medium" className="whitespace-nowrap">New!</Text>
<Text as="span" variant="label.medium" className="whitespace-nowrap">EIP-8130: Accounts</Text>
<Text as="span" variant="label.medium" className="whitespace-nowrap text-[12.5px] leading-[16px] font-[500] md:text-[12.5px] md:leading-[16px] md:font-[500] [text-box-trim:trim-both] [text-box-edge:cap_alphabetic]">New!</Text>
<Text as="span" variant="label.medium" className="whitespace-nowrap text-[12.5px] leading-[16px] font-[500] md:text-[12.5px] md:leading-[16px] md:font-[500] [text-box-trim:trim-both] [text-box-edge:cap_alphabetic]">EIP-8130: Accounts</Text>
<span className="inline-block h-3.5 w-px shrink-0 bg-bds-gray-20"></span>
<Link
href="/vibenet/demos/account"
Expand All @@ -690,7 +694,7 @@ function GlobalBanner({ dismissed, onDismiss, className, height }: GlobalBannerP
onDismiss();
}}
>
<Text as="span" variant="label.medium" className="text-base-blue">Test on Vibenet</Text>
<Text as="span" variant="label.medium" className="text-[12.5px] leading-[16px] font-[500] md:text-[12.5px] md:leading-[16px] md:font-[500] [text-box-trim:trim-both] [text-box-edge:cap_alphabetic] text-base-blue">Test on Vibenet</Text>
<AnimatedArrowIcon size={14} strokeWidth={2} className="text-base-blue transition-transform duration-200 ease-out group-hover:translate-x-[3px]" />
</Link>
</div>
Expand Down Expand Up @@ -892,7 +896,7 @@ export function AppShell({ children }: PropsWithChildren) {
</main>
</div>
</div>
<Toaster position="top-center" style={{ '--width': '300px' } as React.CSSProperties} toastOptions={{ className: 'text-[13px] font-base tracking-[0px]' }} />
<Toaster position="top-center" style={{ '--width': '300px' } as React.CSSProperties} toastOptions={{ className: 'text-[13px] tracking-[0px]' }} />
</div>
);
}
4 changes: 2 additions & 2 deletions app/components/ui/AnimatedBaseLogo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { CSSProperties, SVGProps, useCallback, useRef } from 'react';
/**
* The animated Base logo from base.org: hovering the square splits it into four
* squares, springs up an SVG "b" glyph, spells `base` in dot-matrix Doto, then
* resolves each character into Base Sans via a 5x5 pixel-grid shuffle.
* resolves each character into the UI sans via a 5x5 pixel-grid shuffle.
*
* Ported from the base.org web app:
* apps/web/app/(current)/components/illustration/AnimatedLogo.tsx
Expand Down Expand Up @@ -610,7 +610,7 @@ function Logo({ size, color }: { size: number; color?: string }) {

{/* normal */}
<div
className="pointer-events-none absolute left-0 flex h-full w-full select-none items-center font-base font-medium leading-[70%] tracking-[-0.01em] text-base-blue transition-colors duration-150 dark:text-white"
className="pointer-events-none absolute left-0 flex h-full w-full select-none items-center font-medium leading-[70%] tracking-[-0.01em] text-base-blue transition-colors duration-150 dark:text-white"
style={color ? { ...normalTextStyle, color } : normalTextStyle}
>
<span className="char block opacity-0">b</span>
Expand Down
7 changes: 5 additions & 2 deletions app/components/ui/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,18 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button
) {
let sizeClasses: string;
if (size === 'sm') {
sizeClasses = 'h-[34px] px-3 gap-1 pb-px';
sizeClasses = 'h-[34px] px-3 gap-1';
} else if (arrow) {
sizeClasses = 'h-10 pl-4 pr-3 gap-1';
} else {
sizeClasses = 'h-10 px-4 gap-2';
}

const classes = cn(
'group flex whitespace-nowrap w-fit active:scale-[0.96] transition-[background-color,border-color,color,transform] duration-150 ease-out max-w-full items-center justify-center rounded-full font-sans sm:w-auto',
'group flex whitespace-nowrap w-fit active:scale-[0.96] transition-[background-color,border-color,color,transform] duration-150 ease-out max-w-full items-center justify-center rounded-full sm:w-auto',
// Trim half-leading to cap/alphabetic so Google Sans Flex sits optically
// centered in the fixed-height pill. Type styles live on this same node.
'[text-box-trim:trim-both] [text-box-edge:cap_alphabetic]',
sizeClasses,
size === 'sm' ? textVariantClasses.label : textVariantClasses.button,
'md:leading-[20px]',
Expand Down
7 changes: 4 additions & 3 deletions app/components/ui/Tabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,9 @@ export function Tabs({
onClick={() => !item.disabled && onChange(item.value)}
disabled={item.disabled}
className={cn(
'relative flex shrink-0 select-none items-center gap-1.5 rounded-full font-sans whitespace-nowrap transition-colors',
size === 'sm' ? 'px-2.5 py-1 text-[12px]' : 'px-3 py-1.5 text-[14px]',
'relative flex shrink-0 select-none items-center gap-1.5 rounded-full whitespace-nowrap transition-colors font-[500]',
// Height is on the control so cap-trim cannot collapse the pill.
size === 'sm' ? 'h-7 px-2.5 py-1 text-[12px]' : 'h-8 px-3 py-1.5 text-[14px]',
item.disabled
? 'cursor-not-allowed text-bds-gray-40'
: active
Expand All @@ -77,7 +78,7 @@ export function Tabs({
/>
) : null}
{item.icon ? <span className="relative z-[1] flex shrink-0">{item.icon}</span> : null}
<span className="relative z-[1]">{item.label}</span>
<span className="relative z-[1] [text-box-trim:trim-both] [text-box-edge:cap_alphabetic]">{item.label}</span>
</button>
);
})}
Expand Down
26 changes: 13 additions & 13 deletions app/components/ui/Text.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import type { ComponentPropsWithoutRef } from 'react';

import { cn } from './cn';

// Typographic scale ported from the Base Design System (BDS) so migrated Upgrades markup keeps
// its variants in both color modes.
// Sizes stay on this site's scale. Weights match the dashboard type tokens so
// Google Sans Flex (a variable face) renders the same as there.
type TextVariant =
| 'display'
| 'stats'
Expand Down Expand Up @@ -32,29 +32,29 @@ type TextProps = ComponentPropsWithoutRef<'p'> & {

export const textVariantClasses: Record<TextVariant, string> = {
display:
'text-[36px] sm:text-[40px] md:text-[56px] leading-[40px] sm:leading-[48px] md:leading-[64px] font-base font-[400] tracking-[-0.04em]',
'text-[36px] sm:text-[40px] md:text-[56px] leading-[40px] sm:leading-[48px] md:leading-[64px] font-[550] tracking-[-0.04em]',
stats:
'text-[24px] sm:text-[32px] md:text-[40px] leading-[28px] sm:leading-[36px] md:leading-[48px] font-base font-[400] tracking-[0px]',
'text-[24px] sm:text-[32px] md:text-[40px] leading-[28px] sm:leading-[36px] md:leading-[48px] font-[550] tracking-[0px]',
title1:
'text-[28px] md:text-[36px] leading-[34px] md:leading-[44px] font-base font-[400] tracking-tight',
'text-[28px] md:text-[36px] leading-[34px] md:leading-[44px] font-[528] tracking-tight',
title2:
'text-[20px] md:text-[24px] leading-[28px] md:leading-[32px] font-base font-[400] tracking-tight',
'text-[20px] md:text-[24px] leading-[28px] md:leading-[32px] font-[500] tracking-tight',
title3:
'text-[18px] md:text-[20px] leading-[26px] md:leading-[28px] font-base font-[400] tracking-tight',
'text-[18px] md:text-[20px] leading-[26px] md:leading-[28px] font-[500] tracking-tight',
headline:
'text-[16px] md:text-[18px] leading-[24px] md:leading-[26px] font-base font-[400] tracking-tight',
body: 'text-[15px] md:text-[16px] leading-[140%] font-base-text font-[400] tracking-[0px]',
'text-[16px] md:text-[18px] leading-[24px] md:leading-[26px] font-[500] tracking-[0px]',
body: 'text-[15px] md:text-[16px] leading-[140%] font-[425] tracking-[0px]',
label:
'text-[13px] md:text-[14px] leading-[18px] md:leading-[20px] font-base font-[400] tracking-[-0.01em]',
'text-[13px] md:text-[14px] leading-[18px] md:leading-[20px] font-[500] tracking-[-0.01em]',
'label.medium':
'text-[13px] md:text-[14px] leading-[18px] md:leading-[20px] font-base font-[400] tracking-[0px]',
'text-[13px] md:text-[14px] leading-[18px] md:leading-[20px] font-[500] tracking-[0px]',
'label.regular':
'text-[13px] md:text-[14px] leading-[20px] font-base-text font-[400] tracking-[0px]',
'text-[13px] md:text-[14px] leading-[20px] font-[400] tracking-[0px]',
'label.mono':
'text-[13px] md:text-[14px] leading-[18px] md:leading-[20px] font-mono font-[400] tracking-[0px]',
caption:
'text-[11px] md:text-[12px] leading-[14px] md:leading-[16px] font-[500] tracking-[0px] uppercase',
button: 'text-[14px] leading-[20px] font-base font-[400] tracking-[-0.01em]',
button: 'text-[14px] leading-[20px] font-[500] tracking-[-0.01em]',
footnote: 'text-[11px] md:text-[12px] leading-[14px] md:leading-[16px] font-[400] tracking-[0px]',
};

Expand Down
12 changes: 11 additions & 1 deletion app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -388,20 +388,30 @@ body {

body {
font-family:
var(--font-base-sans),
var(--font-google-sans-flex),
-apple-system,
BlinkMacSystemFont,
'Segoe UI',
Roboto,
Helvetica,
Arial,
sans-serif;
/* Same axes as the dashboard UI sans so Google Sans Flex matches there. */
font-variation-settings: 'wdth' 101, 'GRAD' 0, 'ROND' 30, 'slnt' 0;
color: var(--bds-gray-100);
background: var(--bds-gray-0);
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
}

.font-mono,
code,
pre,
kbd,
samp {
font-variation-settings: normal;
}

a {
color: inherit;
}
Expand Down
Loading
Loading