-
-
Something went wrong
-
+
+
+
+
Application error
+
Something went wrong
+
The request could not be completed. Try again or return to Fulling.
{error.digest ? (
-
Reference: {error.digest}
+
Reference: {error.digest}
) : null}
diff --git a/app/globals.css b/app/globals.css
index bf61c61..4445c9b 100644
--- a/app/globals.css
+++ b/app/globals.css
@@ -2,26 +2,140 @@
@import "tw-animate-css";
:root {
- --background: #f7f8f6;
- --foreground: #171a1f;
- --card: #ffffff;
- --card-foreground: #171a1f;
- --popover: #ffffff;
- --popover-foreground: #171a1f;
- --primary: #171a1f;
- --primary-foreground: #ffffff;
- --secondary: #eef1ef;
- --secondary-foreground: #30373e;
- --muted: #f0f2f1;
- --muted-foreground: #5e6974;
- --accent: #edf3ef;
- --accent-foreground: #245f49;
- --destructive: #c43d3d;
- --destructive-foreground: #ffffff;
- --border: #dce1e4;
- --input: #d4dade;
- --ring: #287355;
- --radius: 0.375rem;
+ /* Fulling design tokens — the single executable source of truth. */
+ --color-fulling-ink: #303055;
+ --color-code-plum: #8844ae;
+ --color-code-cobalt: #3b61b0;
+ --color-code-teal: #096e72;
+ --color-code-rust: #984e4d;
+ --color-slate: #403f53;
+ --color-fog: #767682;
+ --color-mist: #a8a8b0;
+ --color-obsidian: #111111;
+ --color-lavender-mist: #e8e8f2;
+ --color-paper: #ffffff;
+ --color-secondary-ink: rgba(26, 26, 46, 0.6);
+ --color-quiet-ink: rgba(26, 26, 46, 0.38);
+ --color-announcement-blue: #5196b3;
+ --color-status-success: #35654d;
+ --color-status-warning: #755c16;
+ --color-status-error: #a13d3b;
+ --color-status-info: #405f88;
+
+ --font-rubik-variable: "Rubik Variable", ui-sans-serif, system-ui, -apple-system,
+ BlinkMacSystemFont, "Segoe UI", sans-serif;
+ --font-ibm-plex-mono: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Monaco,
+ Consolas, monospace;
+
+ --text-micro: 9px;
+ --leading-micro: 1.78;
+ --text-caption: 12px;
+ --leading-caption: 1.5;
+ --text-metadata: 13px;
+ --leading-metadata: 1.2;
+ --text-body: 14px;
+ --leading-body: 1.8;
+ --text-body-lg: 16px;
+ --leading-body-lg: 1.8;
+ --text-heading-sm: 18px;
+ --leading-heading-sm: 1.5;
+ --text-heading: 20px;
+ --leading-heading: 1.5;
+ --text-display: 48px;
+ --leading-display: 1.1;
+ --tracking-display: -1.01px;
+ --font-weight-regular: 400;
+ --font-weight-medium: 500;
+ --font-weight-semibold: 600;
+
+ /* Tailwind type aliases derive every used utility from the Fulling scale. */
+ --text-xs: var(--text-caption);
+ --text-xs--line-height: var(--leading-caption);
+ --text-sm: var(--text-body);
+ --text-sm--line-height: var(--leading-body);
+ --text-base: var(--text-body-lg);
+ --text-base--line-height: var(--leading-body-lg);
+ --text-lg: var(--text-heading-sm);
+ --text-lg--line-height: var(--leading-heading-sm);
+ --text-xl: var(--text-heading);
+ --text-xl--line-height: var(--leading-heading);
+
+ --spacing-unit: 4px;
+ --spacing: var(--spacing-unit);
+ --spacing-4: 4px;
+ --spacing-8: 8px;
+ --spacing-12: 12px;
+ --spacing-16: 16px;
+ --spacing-24: 24px;
+ --spacing-32: 32px;
+ --spacing-40: 40px;
+ --spacing-48: 48px;
+ --spacing-64: 64px;
+ --spacing-72: 72px;
+
+ --page-max-width: 1200px;
+ --landing-hero-width: 857px;
+ --landing-code-width: 408px;
+ --landing-copy-width: 400px;
+ --landing-column-gap: 49px;
+ --landing-header-height: 64px;
+ --landing-header-height-compact: 56px;
+ --landing-footer-height: 48px;
+ --header-utility-height: 36px;
+ --section-gap: 64px;
+ --card-padding: 16px;
+ --element-gap-min: 10px;
+ --element-gap-max: 16px;
+
+ --radius-sm: 1px;
+ --radius-md: 4px;
+ --radius-lg: 8px;
+ --radius-tags: var(--radius-md);
+ --radius-inputs: var(--radius-md);
+ --radius-buttons: var(--radius-md);
+ --radius-cards: var(--radius-lg);
+ --radius-code-blocks: var(--radius-lg);
+
+ --surface-paper: var(--color-paper);
+ --surface-lavender-mist: var(--color-lavender-mist);
+ --surface-header: rgba(255, 255, 255, 0.8);
+ --surface-code-top: #fcfcfd;
+ --surface-code-bottom: #f8f8fb;
+ --surface-code-depth-top: #efeff6;
+ --surface-code-depth-bottom: #e5e5f0;
+ --surface-destructive-subtle: rgba(161, 61, 59, 0.08);
+ --surface-dialog-overlay: rgba(26, 26, 46, 0.58);
+ --color-code-caption: rgba(26, 26, 46, 0.42);
+ --color-code-dot: #d2d2df;
+ --color-border-hover: #cfcfdd;
+ --shadow-code-block:
+ rgba(199, 199, 199, 0.04) 0 1px 1.5px,
+ rgba(199, 199, 199, 0.08) 0.1px 12px 18px,
+ rgba(199, 199, 199, 0.08) 0.3px 26px 39px,
+ rgba(199, 199, 199, 0.1) 0.6px 55px 82.5px,
+ rgba(255, 255, 255, 0.9) 0 0 2px 1px inset;
+ --shadow-dialog: 0 16px 48px rgba(26, 26, 46, 0.14);
+
+ /* Semantic aliases consumed by shared UI primitives and Tailwind. */
+ --background: var(--color-paper);
+ --foreground: var(--color-fulling-ink);
+ --card: var(--color-paper);
+ --card-foreground: var(--color-fulling-ink);
+ --popover: var(--color-paper);
+ --popover-foreground: var(--color-fulling-ink);
+ --primary: var(--color-fulling-ink);
+ --primary-foreground: var(--color-paper);
+ --secondary: var(--color-lavender-mist);
+ --secondary-foreground: var(--color-fulling-ink);
+ --muted: var(--color-lavender-mist);
+ --muted-foreground: var(--color-fog);
+ --accent: var(--color-lavender-mist);
+ --accent-foreground: var(--color-fulling-ink);
+ --destructive: var(--color-status-error);
+ --destructive-foreground: var(--color-paper);
+ --border: var(--color-lavender-mist);
+ --input: var(--color-lavender-mist);
+ --ring: var(--color-fulling-ink);
}
@theme inline {
@@ -44,7 +158,9 @@
--color-border: var(--border);
--color-input: var(--input);
--color-ring: var(--ring);
- --font-sans: var(--font-body);
+ --color-destructive-subtle: var(--surface-destructive-subtle);
+ --font-sans: var(--font-rubik-variable);
+ --font-mono: var(--font-ibm-plex-mono);
}
* {
@@ -60,16 +176,31 @@ body {
min-height: 100vh;
background: var(--background);
color: var(--foreground);
- font-family: var(--font-body), sans-serif;
+ font-family: var(--font-rubik-variable);
+ font-size: var(--text-body);
+ font-weight: var(--font-weight-regular);
+ line-height: var(--leading-body);
}
button,
a,
input,
-textarea {
+textarea,
+select {
-webkit-tap-highlight-color: transparent;
}
+:where(a, button, input, textarea, select, [tabindex]):focus-visible {
+ border-radius: var(--radius-md);
+ outline: 2px solid var(--color-fulling-ink);
+ outline-offset: 3px;
+}
+
+::selection {
+ background: var(--color-lavender-mist);
+ color: var(--color-fulling-ink);
+}
+
@media (prefers-reduced-motion: reduce) {
*,
*::before,
diff --git a/app/layout.tsx b/app/layout.tsx
index f641f22..a76633d 100644
--- a/app/layout.tsx
+++ b/app/layout.tsx
@@ -1,18 +1,18 @@
import type { Metadata } from 'next';
-import { Noto_Sans, Space_Grotesk } from 'next/font/google';
+import { IBM_Plex_Mono, Rubik } from 'next/font/google';
import './globals.css';
-const spaceGrotesk = Space_Grotesk({
+const rubik = Rubik({
subsets: ['latin'],
- variable: '--font-heading',
- weight: ['500', '600', '700'],
+ variable: '--font-rubik-variable',
+ weight: ['400', '500', '600'],
});
-const notoSans = Noto_Sans({
+const ibmPlexMono = IBM_Plex_Mono({
subsets: ['latin'],
- variable: '--font-body',
- weight: ['400', '500'],
+ variable: '--font-ibm-plex-mono',
+ weight: ['400', '500', '600'],
});
export const metadata: Metadata = {
@@ -27,7 +27,9 @@ export default function RootLayout({
}>) {
return (
-
+
{children}
diff --git a/app/not-found.tsx b/app/not-found.tsx
index b603e52..40732d5 100644
--- a/app/not-found.tsx
+++ b/app/not-found.tsx
@@ -5,12 +5,12 @@ import { Button } from '@/components/ui/button'
export default function NotFound() {
return (
-
-
-
-
404
-
Page not found
-
+
+
+
+
404
+
Page not found
+
The page does not exist or has moved.
diff --git a/components/fulling-brand.tsx b/components/fulling-brand.tsx
new file mode 100644
index 0000000..e5d62c4
--- /dev/null
+++ b/components/fulling-brand.tsx
@@ -0,0 +1,34 @@
+import Image from 'next/image'
+import Link from 'next/link'
+
+import { cn } from '@/lib/utils'
+
+type FullingBrandProps = {
+ className?: string
+ href?: string
+}
+
+export function FullingBrand({ className, href = '/' }: FullingBrandProps) {
+ return (
+
+
+
+
+
Fulling
+
+ )
+}
diff --git a/components/ui/alert-dialog.tsx b/components/ui/alert-dialog.tsx
index 0ebd23b..5417e9f 100644
--- a/components/ui/alert-dialog.tsx
+++ b/components/ui/alert-dialog.tsx
@@ -36,7 +36,7 @@ function AlertDialogOverlay({
{
@@ -63,7 +63,7 @@ function AlertDialogContent({
data-slot="alert-dialog-content"
data-size={size}
className={cn(
- "bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 group/alert-dialog-content fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-2xl duration-200 data-[size=sm]:max-w-xs data-[size=default]:sm:max-w-lg",
+ "bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 group/alert-dialog-content fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-[var(--radius-cards)] border p-6 shadow-[var(--shadow-dialog)] duration-200 data-[size=sm]:max-w-xs data-[size=default]:sm:max-w-lg",
className
)}
{...props}
@@ -141,7 +141,7 @@ function AlertDialogMedia({
svg]:px-3",
- xs: "h-6 gap-1 rounded-md px-2 text-xs has-[>svg]:px-1.5 [&_svg:not([class*='size-'])]:size-3",
- sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
- lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
+ default: "h-9 px-4 has-[>svg]:px-3",
+ xs: "h-6 gap-1 px-2 text-xs has-[>svg]:px-1.5 [&_svg:not([class*='size-'])]:size-3",
+ sm: "h-8 gap-1.5 px-3 has-[>svg]:px-2.5",
+ lg: "h-10 px-5 has-[>svg]:px-4",
icon: "size-9",
- "icon-xs": "size-6 rounded-md [&_svg:not([class*='size-'])]:size-3",
+ "icon-xs": "size-6 [&_svg:not([class*='size-'])]:size-3",
"icon-sm": "size-8",
"icon-lg": "size-10",
},
diff --git a/components/ui/textarea.tsx b/components/ui/textarea.tsx
index 7f21b5e..5fa20a4 100644
--- a/components/ui/textarea.tsx
+++ b/components/ui/textarea.tsx
@@ -7,7 +7,7 @@ function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {