feat(ui): apply Justine ink & parchment design system

- Map Justine tokens to shadcn CSS variables (--vibn-* aliases)
- Switch fonts to Inter + Lora via next/font (IBM Plex Mono for code)
- Base typography: body Inter, h1–h3 Lora; marketing hero + wordmark serif
- Project shell and global chrome use semantic colors
- Replace Outfit/Newsreader references across TSX inline styles

Made-with: Cursor
This commit is contained in:
2026-04-01 21:03:40 -07:00
parent 06238f958a
commit bada63452f
33 changed files with 300 additions and 286 deletions

View File

@@ -361,7 +361,7 @@ const LIBRARY_STYLE_OPTIONS: Record<string, LibraryStyleOptions> = {
function ConfigRow({ label, children }: { label: string; children: React.ReactNode }) {
return (
<div style={{ display: "flex", flexDirection: "column", gap: 7, paddingBottom: 12, borderBottom: "1px solid #f0ece4" }}>
<span style={{ fontSize: "0.62rem", fontWeight: 700, color: "#a09a90", textTransform: "uppercase", letterSpacing: "0.1em", fontFamily: "Outfit" }}>{label}</span>
<span style={{ fontSize: "0.62rem", fontWeight: 700, color: "#a09a90", textTransform: "uppercase", letterSpacing: "0.1em", fontFamily: "var(--font-inter), ui-sans-serif, sans-serif" }}>{label}</span>
<div style={{ display: "flex", gap: 6, flexWrap: "wrap" }}>
{children}
</div>
@@ -383,7 +383,7 @@ function OptionChip({
display: "flex", alignItems: "center", gap: 5,
padding: multi ? "4px 9px" : "4px 11px",
borderRadius: 5, border: "1px solid",
fontSize: "0.72rem", fontFamily: "Outfit", cursor: "pointer",
fontSize: "0.72rem", fontFamily: "var(--font-inter), ui-sans-serif, sans-serif", cursor: "pointer",
transition: "all 0.1s",
borderColor: active ? "#1a1a1a" : "#e0dcd4",
background: active ? "#1a1a1a" : "#fff",
@@ -411,7 +411,7 @@ function ModeToggle({ value, onChange }: { value: string; onChange: (v: "dark" |
key={m}
onClick={() => onChange(id)}
style={{
padding: "4px 14px", border: "none", fontSize: "0.72rem", fontFamily: "Outfit",
padding: "4px 14px", border: "none", fontSize: "0.72rem", fontFamily: "var(--font-inter), ui-sans-serif, sans-serif",
cursor: "pointer", fontWeight: active ? 600 : 400,
background: active ? "#1a1a1a" : "transparent",
color: active ? "#fff" : "#8a8478",
@@ -622,7 +622,7 @@ function SurfaceSection({
{ScaffoldComponent
? <ScaffoldComponent themeColor={activeColorTheme ?? undefined} config={designConfig} />
: (
<div style={{ height: "100%", display: "flex", alignItems: "center", justifyContent: "center", color: "#b5b0a6", fontSize: "0.82rem", fontFamily: "Outfit" }}>
<div style={{ height: "100%", display: "flex", alignItems: "center", justifyContent: "center", color: "#b5b0a6", fontSize: "0.82rem", fontFamily: "var(--font-inter), ui-sans-serif, sans-serif" }}>
Select a library below to preview
</div>
)
@@ -647,7 +647,7 @@ function SurfaceSection({
style={{
flex: 1, padding: "7px 14px", borderRadius: 7, border: "1px solid #e0dcd4",
background: "#fff", color: "#1a1a1a", fontSize: "0.76rem", fontWeight: 600,
fontFamily: "Outfit", cursor: "pointer", transition: "opacity 0.15s",
fontFamily: "var(--font-inter), ui-sans-serif, sans-serif", cursor: "pointer", transition: "opacity 0.15s",
}}
onMouseEnter={e => (e.currentTarget.style.opacity = "0.7")}
onMouseLeave={e => (e.currentTarget.style.opacity = "1")}
@@ -660,7 +660,7 @@ function SurfaceSection({
flex: 1, padding: "7px 14px", borderRadius: 7, border: `1px solid ${previewId && !saving ? "#1a1a1a" : "#e0dcd4"}`,
background: previewId && !saving ? "#1a1a1a" : "#e0dcd4",
color: previewId && !saving ? "#fff" : "#b5b0a6",
fontSize: "0.76rem", fontWeight: 600, fontFamily: "Outfit",
fontSize: "0.76rem", fontWeight: 600, fontFamily: "var(--font-inter), ui-sans-serif, sans-serif",
cursor: !previewId || saving ? "not-allowed" : "pointer",
transition: "opacity 0.15s",
}}
@@ -670,7 +670,7 @@ function SurfaceSection({
)}
{activeTheme && (
<a href={activeTheme.url} target="_blank" rel="noopener noreferrer"
style={{ fontSize: "0.72rem", color: "#b5b0a6", textDecoration: "none", fontFamily: "Outfit", flexShrink: 0 }}
style={{ fontSize: "0.72rem", color: "#b5b0a6", textDecoration: "none", fontFamily: "var(--font-inter), ui-sans-serif, sans-serif", flexShrink: 0 }}
onMouseEnter={e => (e.currentTarget.style.color = "#1a1a1a")}
onMouseLeave={e => (e.currentTarget.style.color = "#b5b0a6")}
>Docs </a>
@@ -679,7 +679,7 @@ function SurfaceSection({
{/* 2. Library */}
<div style={{ display: "flex", flexDirection: "column", gap: 7, paddingBottom: 12, borderBottom: "1px solid #f0ece4" }}>
<span style={{ fontSize: "0.62rem", fontWeight: 700, color: "#a09a90", textTransform: "uppercase", letterSpacing: "0.1em", fontFamily: "Outfit" }}>Library</span>
<span style={{ fontSize: "0.62rem", fontWeight: 700, color: "#a09a90", textTransform: "uppercase", letterSpacing: "0.1em", fontFamily: "var(--font-inter), ui-sans-serif, sans-serif" }}>Library</span>
<div style={{ display: "flex", gap: 6, flexWrap: "wrap" }}>
{surface.themes.map(theme => {
const isActive = theme.id === previewId;
@@ -693,7 +693,7 @@ function SurfaceSection({
style={{
display: "flex", alignItems: "center", gap: 4,
padding: "4px 11px", borderRadius: 5, border: "1px solid",
fontSize: "0.72rem", fontFamily: "Outfit", cursor: dimmed ? "not-allowed" : "pointer",
fontSize: "0.72rem", fontFamily: "var(--font-inter), ui-sans-serif, sans-serif", cursor: dimmed ? "not-allowed" : "pointer",
transition: "all 0.1s", opacity: dimmed ? 0.35 : 1,
borderColor: isActive ? "#1a1a1a" : "#e0dcd4",
background: isActive ? "#1a1a1a" : "#fff",
@@ -720,7 +720,7 @@ function SurfaceSection({
{/* 4. Colour */}
{availableColorThemes.length > 0 && (
<div style={{ display: "flex", flexDirection: "column", gap: 7, paddingBottom: 12, borderBottom: "1px solid #f0ece4" }}>
<span style={{ fontSize: "0.62rem", fontWeight: 700, color: "#a09a90", textTransform: "uppercase", letterSpacing: "0.1em", fontFamily: "Outfit" }}>Colour</span>
<span style={{ fontSize: "0.62rem", fontWeight: 700, color: "#a09a90", textTransform: "uppercase", letterSpacing: "0.1em", fontFamily: "var(--font-inter), ui-sans-serif, sans-serif" }}>Colour</span>
<div style={{ display: "flex", gap: 7, flexWrap: "wrap", alignItems: "center" }}>
{availableColorThemes.map(ct => (
<button
@@ -740,7 +740,7 @@ function SurfaceSection({
))}
</div>
{activeColorTheme && (
<span style={{ fontSize: "0.72rem", color: "#a09a90", fontFamily: "Outfit" }}>{activeColorTheme.label}</span>
<span style={{ fontSize: "0.72rem", color: "#a09a90", fontFamily: "var(--font-inter), ui-sans-serif, sans-serif" }}>{activeColorTheme.label}</span>
)}
</div>
)}
@@ -801,7 +801,7 @@ function SurfaceSection({
{/* Colour swatches when locked (read-only) */}
{isLocked && availableColorThemes.length > 0 && (
<div style={{ display: "flex", flexDirection: "column", gap: 7 }}>
<span style={{ fontSize: "0.62rem", fontWeight: 700, color: "#a09a90", textTransform: "uppercase", letterSpacing: "0.1em", fontFamily: "Outfit" }}>Colour</span>
<span style={{ fontSize: "0.62rem", fontWeight: 700, color: "#a09a90", textTransform: "uppercase", letterSpacing: "0.1em", fontFamily: "var(--font-inter), ui-sans-serif, sans-serif" }}>Colour</span>
<div style={{ display: "flex", gap: 7, flexWrap: "wrap" }}>
{availableColorThemes.map(ct => (
<button key={ct.id} title={ct.label} disabled
@@ -863,8 +863,8 @@ function SurfacePicker({
};
return (
<div style={{ padding: "28px 32px", fontFamily: "Outfit, sans-serif" }}>
<h3 style={{ fontFamily: "Newsreader, serif", fontSize: "1.2rem", fontWeight: 400, color: "#1a1a1a", marginBottom: 4 }}>
<div style={{ padding: "28px 32px", fontFamily: "var(--font-inter), ui-sans-serif, sans-serif" }}>
<h3 style={{ fontFamily: "var(--font-lora), ui-serif, serif", fontSize: "1.2rem", fontWeight: 400, color: "#1a1a1a", marginBottom: 4 }}>
Design surfaces
</h3>
<p style={{ fontSize: "0.8rem", color: "#a09a90", marginBottom: aiSuggested.length > 0 ? 10 : 24 }}>
@@ -898,7 +898,7 @@ function SurfacePicker({
border: `1px solid ${isSelected ? "#1a1a1a" : "#e8e4dc"}`,
background: isSelected ? "#1a1a1a08" : "#fff",
boxShadow: isSelected ? "0 0 0 1px #1a1a1a" : "0 1px 2px #1a1a1a05",
cursor: "pointer", transition: "all 0.12s", fontFamily: "Outfit",
cursor: "pointer", transition: "all 0.12s", fontFamily: "var(--font-inter), ui-sans-serif, sans-serif",
position: "relative",
}}
onMouseEnter={e => { if (!isSelected) (e.currentTarget.style.borderColor = "#d0ccc4"); }}
@@ -937,7 +937,7 @@ function SurfacePicker({
padding: "9px 20px", borderRadius: 7, border: "none",
background: selected.size === 0 || saving ? "#e0dcd4" : "#1a1a1a",
color: selected.size === 0 || saving ? "#b5b0a6" : "#fff",
fontSize: "0.82rem", fontWeight: 600, fontFamily: "Outfit",
fontSize: "0.82rem", fontWeight: 600, fontFamily: "var(--font-inter), ui-sans-serif, sans-serif",
cursor: selected.size === 0 || saving ? "not-allowed" : "pointer",
transition: "opacity 0.15s",
}}
@@ -947,7 +947,7 @@ function SurfacePicker({
{saving ? "Saving…" : `Confirm surfaces (${selected.size})`}
</button>
{selected.size === 0 && (
<p style={{ display: "inline-block", marginLeft: 12, fontSize: "0.74rem", color: "#b5b0a6", fontFamily: "Outfit" }}>
<p style={{ display: "inline-block", marginLeft: 12, fontSize: "0.74rem", color: "#b5b0a6", fontFamily: "var(--font-inter), ui-sans-serif, sans-serif" }}>
Select at least one surface to continue
</p>
)}
@@ -1056,7 +1056,7 @@ function DesignPageInner({ projectId }: { projectId: string }) {
if (loading) {
return (
<div style={{ display: "flex", alignItems: "center", justifyContent: "center", height: "100%", fontFamily: "Outfit" }}>
<div style={{ display: "flex", alignItems: "center", justifyContent: "center", height: "100%", fontFamily: "var(--font-inter), ui-sans-serif, sans-serif" }}>
<div style={{ width: 18, height: 18, borderRadius: "50%", border: "2px solid #e8e4dc", borderTopColor: "#1a1a1a", animation: "spin 0.8s linear infinite" }} />
<style>{`@keyframes spin { to { transform: rotate(360deg); } }`}</style>
</div>
@@ -1072,7 +1072,7 @@ function DesignPageInner({ projectId }: { projectId: string }) {
const lockedCount = Object.keys(surfaceThemes).length;
return (
<div style={{ display: "flex", height: "100%", fontFamily: "Outfit, sans-serif" }}>
<div style={{ display: "flex", height: "100%", fontFamily: "var(--font-inter), ui-sans-serif, sans-serif" }}>
{/* Left nav */}
<div style={{ width: 180, flexShrink: 0, borderRight: "1px solid #e8e4dc", display: "flex", flexDirection: "column", background: "#fff" }}>
@@ -1095,7 +1095,7 @@ function DesignPageInner({ projectId }: { projectId: string }) {
color: isActive ? "#1a1a1a" : "#6b6560",
fontSize: "0.8rem", fontWeight: isActive ? 600 : 450,
cursor: "pointer", transition: "all 0.12s", position: "relative",
fontFamily: "Outfit",
fontFamily: "var(--font-inter), ui-sans-serif, sans-serif",
}}
onMouseEnter={e => { if (!isActive) (e.currentTarget.style.background = "#f6f4f0"); }}
onMouseLeave={e => { if (!isActive) (e.currentTarget.style.background = "transparent"); }}
@@ -1113,11 +1113,11 @@ function DesignPageInner({ projectId }: { projectId: string }) {
<div style={{ padding: "12px 18px", borderTop: "1px solid #f0ece4" }}>
{lockedCount === activeSurfaces.length && lockedCount > 0 && (
<p style={{ fontSize: "0.68rem", color: "#2e7d32", fontFamily: "Outfit", marginBottom: 6 }}> All locked</p>
<p style={{ fontSize: "0.68rem", color: "#2e7d32", fontFamily: "var(--font-inter), ui-sans-serif, sans-serif", marginBottom: 6 }}> All locked</p>
)}
<button
onClick={() => setSurfaces([])}
style={{ fontSize: "0.72rem", color: "#a09a90", background: "none", border: "none", cursor: "pointer", fontFamily: "Outfit", padding: 0 }}
style={{ fontSize: "0.72rem", color: "#a09a90", background: "none", border: "none", cursor: "pointer", fontFamily: "var(--font-inter), ui-sans-serif, sans-serif", padding: 0 }}
onMouseEnter={e => (e.currentTarget.style.color = "#1a1a1a")}
onMouseLeave={e => (e.currentTarget.style.color = "#a09a90")}
>
@@ -1147,7 +1147,7 @@ function DesignPageInner({ projectId }: { projectId: string }) {
export default function DesignPage({ params }: { params: Promise<{ workspace: string; projectId: string }> }) {
const { projectId } = use(params);
return (
<Suspense fallback={<div style={{ display: "flex", height: "100%", alignItems: "center", justifyContent: "center", color: "#a09a90", fontFamily: "Outfit, sans-serif", fontSize: "0.85rem" }}>Loading</div>}>
<Suspense fallback={<div style={{ display: "flex", height: "100%", alignItems: "center", justifyContent: "center", color: "#a09a90", fontFamily: "var(--font-inter), ui-sans-serif, sans-serif", fontSize: "0.85rem" }}>Loading</div>}>
<DesignPageInner projectId={projectId} />
</Suspense>
);