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:
@@ -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>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user