Skip to content

Commit cb9a9b4

Browse files
committed
feat(design): replace prose with workbench — calm claude-code aesthetic
Pivots the quote generator from the distracting Prose aesthetic (aurora gradients, electric lime) to Workbench: a calm, modern dev-tool aesthetic inspired by Claude Code and Codex. Ground is #0F0F10 warm near-black with a barely-visible film grain and a soft terracotta vignette at the top. Single muted accent: #C4643A (warm clay) used sparingly on the status dot, CTA hover, focus ring, and accent notch under the header rule. Typography: Fraunces regular (no italic, opsz 72) for display paired with Instrument Sans for body and JetBrains Mono for micro-labels. No drifting animations, only a single quick fade-up reveal. Rewrites QuoteGeneratorApp.jsx layout: preview now occupies the larger left column (lg-col-span-7 / xl-col-span-8) with sticky top-20, and controls the right (lg-col-span-5 / xl-col-span-4). Adds a preview meta row above the canvas (dimensions, word and char counts) and a download row underneath with an 'export as' label. Rewrites QuoteGeneratorPage.jsx: replaces the huge hero + muse ticker with a slim sticky top bar (48px) containing back link, breadcrumb, local clock and version; a single compact header (one-line Fraunces title, short subtitle, three meta pills) with a hairline rule carrying a 40px accent notch on the left edge. Main area hosts the app directly — no wrapper card competing with the app's own layout. Footer is a one-line colophon. Updates the /design card from Prose to Workbench with matching aesthetic (warm black, terracotta notch, Fraunces regular wordmark, 'open workbench' CTA).
1 parent 1edc1b4 commit cb9a9b4

3 files changed

Lines changed: 342 additions & 432 deletions

File tree

src/app/QuoteGenerator/QuoteGeneratorApp.jsx

Lines changed: 71 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,12 @@ import { DownloadSimpleIcon } from '@phosphor-icons/react';
77
const QuoteGeneratorApp = () => {
88
const { addToast } = useToast();
99

10-
// State
1110
const [state, setState] = useState({
1211
text: 'The only way to deal with an unfree world is to become so absolutely free that your very existence is an act of rebellion.',
1312
author: 'Albert Camus',
1413
width: 1080,
15-
height: 1080, // Square by default, maybe customizable later
16-
backgroundType: 'solid', // 'solid', 'linear', 'radial'
14+
height: 1080,
15+
backgroundType: 'solid',
1716
backgroundColor: '#ffffff',
1817
gradientColor1: '#ff0000',
1918
gradientColor2: '#0000ff',
@@ -28,7 +27,7 @@ const QuoteGeneratorApp = () => {
2827
backgroundImage: null,
2928
overlayOpacity: 0,
3029
overlayColor: '#000000',
31-
themeType: 'standard', // 'standard', 'wordbox', 'typewriter'
30+
themeType: 'standard',
3231
});
3332

3433
const [triggerDownload, setTriggerDownload] = useState(null);
@@ -37,7 +36,6 @@ const QuoteGeneratorApp = () => {
3736
setState((prev) => ({ ...prev, ...newState }));
3837
};
3938

40-
// Font Loading
4139
useEffect(() => {
4240
const fonts = [
4341
'Inter:wght@400;700;900',
@@ -70,50 +68,82 @@ const QuoteGeneratorApp = () => {
7068

7169
addToast({
7270
title: 'Quote Downloaded',
73-
message: `Your quote has been saved successfully as ${format?.toUpperCase() || 'PNG'}.`,
71+
message: `Saved as ${format?.toUpperCase() || 'PNG'}.`,
7472
type: 'success',
7573
});
7674
};
7775

76+
const charCount = state.text.length;
77+
const wordCount = state.text.trim().split(/\s+/).filter(Boolean).length;
78+
7879
return (
79-
<div className="grid grid-cols-1 lg:grid-cols-12 gap-8">
80-
{/* Controls */}
81-
<div className="lg:col-span-4 h-fit lg:sticky lg:top-8">
82-
<ControlPanel state={state} updateState={updateState} />
83-
</div>
80+
<div className="grid grid-cols-1 lg:grid-cols-12 gap-6 lg:gap-8">
81+
{/* ── PREVIEW · sticky on lg, larger column ── */}
82+
<div className="lg:col-span-7 xl:col-span-8 order-1 lg:order-1">
83+
<div className="lg:sticky lg:top-20 space-y-4">
84+
{/* meta row above canvas */}
85+
<div className="flex items-center justify-between text-[11px]">
86+
<div className="flex items-center gap-4 qg-mono" style={{ letterSpacing: '0.08em' }}>
87+
<span className="qg-label">preview</span>
88+
<span className="qg-dim">·</span>
89+
<span className="qg-dim">{state.width} × {state.height}</span>
90+
</div>
91+
<div className="flex items-center gap-3 qg-mono" style={{ letterSpacing: '0.08em' }}>
92+
<span className="qg-dim">{wordCount} words</span>
93+
<span className="qg-dim">·</span>
94+
<span className="qg-dim">{charCount} chars</span>
95+
</div>
96+
</div>
97+
98+
<CanvasPreview
99+
{...state}
100+
onDownload={handleDownload}
101+
triggerDownload={triggerDownload}
102+
/>
84103

85-
{/* Preview */}
86-
<div className="lg:col-span-8 space-y-4">
87-
<CanvasPreview
88-
{...state}
89-
onDownload={handleDownload}
90-
triggerDownload={triggerDownload}
91-
/>
92-
93-
<div className="flex justify-end gap-2">
94-
<button
95-
onClick={() => setTriggerDownload('png')}
96-
className="flex items-center gap-2 px-4 py-2 bg-white/10 text-white hover:bg-white hover:text-black transition-all font-mono uppercase tracking-widest text-xs font-bold rounded-sm border border-white/20"
97-
>
98-
<DownloadSimpleIcon weight="bold" size={16} />
99-
<span>PNG</span>
100-
</button>
101-
<button
102-
onClick={() => setTriggerDownload('jpeg')}
103-
className="flex items-center gap-2 px-4 py-2 bg-white/10 text-white hover:bg-white hover:text-black transition-all font-mono uppercase tracking-widest text-xs font-bold rounded-sm border border-white/20"
104-
>
105-
<DownloadSimpleIcon weight="bold" size={16} />
106-
<span>JPEG</span>
107-
</button>
108-
<button
109-
onClick={() => setTriggerDownload('webp')}
110-
className="flex items-center gap-2 px-4 py-2 bg-white/10 text-white hover:bg-white hover:text-black transition-all font-mono uppercase tracking-widest text-xs font-bold rounded-sm border border-white/20"
111-
>
112-
<DownloadSimpleIcon weight="bold" size={16} />
113-
<span>WebP</span>
114-
</button>
104+
{/* download row */}
105+
<div className="flex flex-wrap items-center justify-between gap-3">
106+
<div className="qg-mono text-[10px] qg-dim" style={{ letterSpacing: '0.1em' }}>
107+
export as
108+
</div>
109+
<div className="flex items-center gap-2">
110+
<button
111+
onClick={() => setTriggerDownload('png')}
112+
className="flex items-center gap-2 px-4 py-2 border text-xs font-mono uppercase tracking-widest font-bold rounded-sm bg-white/10 text-white border-white/20 hover:bg-white hover:text-black transition-all"
113+
>
114+
<DownloadSimpleIcon weight="bold" size={14} />
115+
<span>PNG</span>
116+
</button>
117+
<button
118+
onClick={() => setTriggerDownload('jpeg')}
119+
className="flex items-center gap-2 px-4 py-2 border text-xs font-mono uppercase tracking-widest font-bold rounded-sm bg-white/10 text-white border-white/20 hover:bg-white hover:text-black transition-all"
120+
>
121+
<DownloadSimpleIcon weight="bold" size={14} />
122+
<span>JPEG</span>
123+
</button>
124+
<button
125+
onClick={() => setTriggerDownload('webp')}
126+
className="flex items-center gap-2 px-4 py-2 border text-xs font-mono uppercase tracking-widest font-bold rounded-sm bg-white/10 text-white border-white/20 hover:bg-white hover:text-black transition-all"
127+
>
128+
<DownloadSimpleIcon weight="bold" size={14} />
129+
<span>WebP</span>
130+
</button>
131+
</div>
132+
</div>
115133
</div>
116134
</div>
135+
136+
{/* ── CONTROLS · scrollable right column ── */}
137+
<div className="lg:col-span-5 xl:col-span-4 order-2 lg:order-2">
138+
<ControlPanel state={state} updateState={updateState} />
139+
</div>
140+
141+
{/* Host-page can restyle labels via these classes */}
142+
<style>{`
143+
.qg-mono { font-family: 'JetBrains Mono', 'Space Mono', monospace; }
144+
.qg-label { color: currentColor; text-transform: lowercase; }
145+
.qg-dim { color: currentColor; opacity: 0.55; text-transform: lowercase; }
146+
`}</style>
117147
</div>
118148
);
119149
};

src/pages/DesignSelectionPage.jsx

Lines changed: 39 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {
1313
ScrollIcon,
1414
TerminalWindowIcon,
1515
PlantIcon,
16-
SparkleIcon,
16+
TerminalIcon,
1717
} from '@phosphor-icons/react';
1818
import Seo from '../components/Seo';
1919
import GenerativeArt from '../components/GenerativeArt';
@@ -592,119 +592,107 @@ const DesignSelectionPage = () => {
592592
</motion.div>
593593
</Link>
594594

595-
{/* ── PROSE ── */}
595+
{/* ── WORKBENCH ── */}
596596
<Link to="/apps/quote-generator" className="group block relative">
597597
<motion.div
598598
whileHover={{ y: -10 }}
599599
className="h-full p-12 flex flex-col justify-between overflow-hidden transition-all duration-500 relative"
600600
style={{
601-
background: '#0A0A0B',
602-
borderRadius: 20,
601+
background: '#0F0F10',
602+
borderRadius: 14,
603603
boxShadow:
604-
'inset 0 0 0 1px rgba(255,255,255,0.08), 0 30px 60px -30px rgba(0,0,0,0.7)',
604+
'inset 0 0 0 1px rgba(239,236,228,0.08), 0 30px 60px -30px rgba(0,0,0,0.6)',
605605
}}
606606
>
607-
{/* aurora mesh */}
607+
{/* warm top vignette */}
608608
<div
609609
className="absolute inset-0 pointer-events-none"
610610
style={{
611611
background:
612-
'radial-gradient(45% 35% at 20% 15%, rgba(197,242,75,0.18), transparent 70%), radial-gradient(40% 30% at 80% 85%, rgba(236,72,153,0.12), transparent 70%), radial-gradient(35% 25% at 85% 15%, rgba(56,189,248,0.1), transparent 70%)',
613-
filter: 'blur(30px)',
612+
'radial-gradient(60% 40% at 50% 0%, rgba(196,100,58,0.08), transparent 65%)',
614613
}}
615614
/>
616-
{/* dotted grid */}
615+
{/* subtle film grain */}
617616
<div
618617
className="absolute inset-0 pointer-events-none"
619618
style={{
620619
backgroundImage:
621-
'radial-gradient(rgba(255,255,255,0.06) 1px, transparent 1px)',
622-
backgroundSize: '22px 22px',
623-
maskImage:
624-
'radial-gradient(ellipse at center, black 40%, transparent 85%)',
625-
}}
626-
/>
627-
{/* gradient hairline */}
628-
<div
629-
className="absolute inset-0 pointer-events-none"
630-
style={{
631-
padding: 1,
632-
borderRadius: 20,
633-
background:
634-
'linear-gradient(140deg, rgba(255,255,255,0.14), rgba(255,255,255,0.04) 40%, rgba(197,242,75,0.25))',
635-
WebkitMask:
636-
'linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0)',
637-
WebkitMaskComposite: 'xor',
638-
maskComposite: 'exclude',
620+
'radial-gradient(circle at 30% 30%, rgba(239,236,228,0.02) 1px, transparent 1px)',
621+
backgroundSize: '3px 3px',
622+
opacity: 0.6,
639623
}}
640624
/>
641625

642626
<div className="space-y-8 relative z-10">
643627
<div
644-
className="w-16 h-16 flex items-center justify-center transition-all duration-500"
628+
className="w-14 h-14 flex items-center justify-center"
645629
style={{
646-
background: 'rgba(197,242,75,0.08)',
647-
color: '#C5F24B',
648-
borderRadius: 14,
649-
border: '1px solid rgba(197,242,75,0.28)',
630+
background: 'rgba(196,100,58,0.12)',
631+
color: '#C4643A',
632+
borderRadius: 10,
633+
border: '1px solid rgba(196,100,58,0.3)',
650634
}}
651635
>
652-
<SparkleIcon size={30} weight="regular" />
636+
<TerminalIcon size={26} weight="regular" />
653637
</div>
654638
<div className="space-y-4">
655639
<div
656-
className="text-[10px] uppercase tracking-[0.28em] flex items-center gap-2"
640+
className="text-[10px] flex items-center gap-2"
657641
style={{
658-
color: '#A1A1AA',
642+
color: '#A8A49B',
659643
fontFamily: "'JetBrains Mono', monospace",
644+
letterSpacing: '0.08em',
660645
}}
661646
>
662647
<span
663648
className="h-1.5 w-1.5 rounded-full"
664-
style={{ background: '#C5F24B', boxShadow: '0 0 8px rgba(197,242,75,0.5)' }}
649+
style={{ background: '#C4643A' }}
665650
/>
666-
Composer · v2026.1
651+
<span>composer · v2026.1</span>
667652
</div>
668653
<h3
669-
className="text-6xl italic leading-none tracking-[-0.02em]"
654+
className="leading-none tracking-[-0.01em]"
670655
style={{
671656
fontFamily: "'Fraunces', serif",
672-
fontVariationSettings: "'opsz' 144, 'SOFT' 20, 'WONK' 0",
673-
color: '#F5F5F4',
657+
fontVariationSettings: "'opsz' 72, 'SOFT' 0, 'WONK' 0",
658+
color: '#EFECE4',
659+
fontSize: 56,
660+
fontWeight: 400,
674661
}}
675662
>
676-
Prose<span style={{ color: '#C5F24B' }}>.</span>
663+
Workbench
677664
</h3>
678665
<p
679666
className="text-sm leading-relaxed"
680667
style={{
681668
fontFamily: "'Instrument Sans', sans-serif",
682-
color: '#A1A1AA',
669+
color: '#A8A49B',
683670
}}
684671
>
685-
2026 writing-tool aesthetic. Off-black canvas, a single
686-
electric-lime accent, drifting aurora gradient, Fraunces
687-
italic where the type does the talking.
672+
Calm 2026 dev-tool aesthetic — Claude Code, Codex. Warm
673+
near-black canvas, a single muted terracotta accent,
674+
Fraunces and Instrument Sans. No noise.
688675
</p>
689676
</div>
690677
</div>
691678
<div
692679
className="mt-12 pt-8 flex justify-between items-center relative z-10"
693-
style={{ borderTop: '1px solid rgba(255,255,255,0.08)' }}
680+
style={{ borderTop: '1px solid rgba(239,236,228,0.08)' }}
694681
>
695682
<span
696-
className="text-[10px] uppercase tracking-[0.28em] flex items-center gap-2"
683+
className="text-[10px] flex items-center gap-2"
697684
style={{
698-
color: '#C5F24B',
685+
color: '#C4643A',
699686
fontFamily: "'JetBrains Mono', monospace",
687+
letterSpacing: '0.1em',
700688
}}
701689
>
702-
start composing
690+
open workbench
703691
</span>
704692
<ArrowRightIcon
705693
className="group-hover:translate-x-2 transition-all"
706-
size={24}
707-
style={{ color: '#C5F24B' }}
694+
size={22}
695+
style={{ color: '#C4643A' }}
708696
/>
709697
</div>
710698
</motion.div>

0 commit comments

Comments
 (0)